|
|
|
@ -2,18 +2,25 @@ package volume
|
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
|
"fmt" |
|
|
|
|
"os" |
|
|
|
|
"path" |
|
|
|
|
"path/filepath" |
|
|
|
|
"strings" |
|
|
|
|
|
|
|
|
|
"git.wegmueller.it/opencloud/opencloud/config" |
|
|
|
|
"git.wegmueller.it/opencloud/opencloud/zfs" |
|
|
|
|
"github.com/opencontainers/go-digest" |
|
|
|
|
uuid "github.com/satori/go.uuid" |
|
|
|
|
"github.com/spf13/viper" |
|
|
|
|
"github.com/ztrue/tracerr" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
const ( |
|
|
|
|
MetadataKeyPrefix = "org.aurora-opencloud.volume" |
|
|
|
|
MetadataKeyPrefix = "org.aurora-opencloud.volume" |
|
|
|
|
ZFSNameProperty = "volume:name" |
|
|
|
|
ZFSMountPointProperty = "volume:destination" |
|
|
|
|
ZFSPersistentProperty = "volume:persistent" |
|
|
|
|
ZFSBackupProperty = "volume:backup" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
type Config struct { |
|
|
|
@ -26,9 +33,23 @@ type Config struct {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type Volume struct { |
|
|
|
|
datasetName string |
|
|
|
|
config Config |
|
|
|
|
ds *zfs.Dataset |
|
|
|
|
config Config |
|
|
|
|
ds *zfs.Dataset |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type List []*Volume |
|
|
|
|
|
|
|
|
|
func (l *List) Add(volume *Volume) { |
|
|
|
|
*l = append(*l, volume) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (l *List) Find(name string) (*Volume, bool) { |
|
|
|
|
for _, vol := range *l { |
|
|
|
|
if vol.config.Name == name { |
|
|
|
|
return vol, true |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return nil, false |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func getOrCreatePersistentContainerVolumeDataset(containerID uuid.UUID) (*zfs.Dataset, error) { |
|
|
|
@ -46,7 +67,7 @@ func getOrCreatePersistentContainerVolumeDataset(containerID uuid.UUID) (*zfs.Da
|
|
|
|
|
return rDs, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func CreateVolume(conf Config, containerDS *zfs.Dataset, containerUUID uuid.UUID) (*Volume, error) { |
|
|
|
|
func CreateVolume(conf Config, parentDS *zfs.Dataset) (*Volume, error) { |
|
|
|
|
if conf.Name == "" { |
|
|
|
|
idx := strings.LastIndex(conf.MountPath, "/") |
|
|
|
|
conf.Name = conf.MountPath[idx+1:] |
|
|
|
@ -56,21 +77,24 @@ func CreateVolume(conf Config, containerDS *zfs.Dataset, containerUUID uuid.UUID
|
|
|
|
|
conf.Properties = zfs.Properties{} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
conf.Properties[zfs.PropertyMountpoint] = conf.MountPath |
|
|
|
|
conf.Properties[zfs.PropertyZoned] = "on" |
|
|
|
|
conf.Properties["container:volume:destination"] = conf.MountPath |
|
|
|
|
conf.Properties["container:volume:persistent"] = boolToString(conf.Persist) |
|
|
|
|
conf.Properties["container:volume:backup"] = boolToString(conf.Backup) |
|
|
|
|
conf.Properties["container:uuid"] = containerUUID.String() |
|
|
|
|
conf.Properties[ZFSNameProperty] = conf.Name |
|
|
|
|
conf.Properties[ZFSMountPointProperty] = conf.MountPath |
|
|
|
|
conf.Properties[ZFSPersistentProperty] = boolToString(conf.Persist) |
|
|
|
|
conf.Properties[ZFSBackupProperty] = boolToString(conf.Backup) |
|
|
|
|
conf.Properties[zfs.PropertyMountpoint] = parentDS.VFSPath(conf.MountPath) |
|
|
|
|
|
|
|
|
|
vol := &Volume{ |
|
|
|
|
config: conf, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if err := os.MkdirAll(parentDS.VFSPath(conf.MountPath), 0755); err != nil { |
|
|
|
|
return nil, fmt.Errorf("could not create volument mount for %s: %w", conf.Name, err) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var err error |
|
|
|
|
vol.ds, err = containerDS.CreateChildDataset(conf.Name, conf.Properties) |
|
|
|
|
vol.ds, err = parentDS.CreateChildDataset(conf.Name, conf.Properties) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, tracerr.Wrap(err) |
|
|
|
|
return nil, fmt.Errorf("could not create dataset %s: %w", path.Join(parentDS.Path, conf.Name), err) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return vol, nil |
|
|
|
@ -84,13 +108,65 @@ func boolToString(b bool) string {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func PersistVolume(containerId uuid.UUID, vol *Volume) error { |
|
|
|
|
func stringToBool(str string) bool { |
|
|
|
|
switch str { |
|
|
|
|
case "on", "true": |
|
|
|
|
return true |
|
|
|
|
default: |
|
|
|
|
return false |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func OpenVolume(dataset *zfs.Dataset) (*Volume, error) { |
|
|
|
|
v := &Volume{ |
|
|
|
|
config: Config{ |
|
|
|
|
Properties: dataset.Properties.ToSimpleProperties(), |
|
|
|
|
}, |
|
|
|
|
ds: dataset, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if val, ok := dataset.Properties[ZFSNameProperty]; ok { |
|
|
|
|
v.config.Name = val.Value |
|
|
|
|
} else { |
|
|
|
|
return nil, fmt.Errorf("dataset is not a volume property %s is not set", ZFSNameProperty) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if val, ok := dataset.Properties[ZFSMountPointProperty]; ok { |
|
|
|
|
v.config.MountPath = val.Value |
|
|
|
|
} else { |
|
|
|
|
return nil, fmt.Errorf("dataset is not a volume property %s is not set", ZFSMountPointProperty) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if val, ok := dataset.Properties[ZFSBackupProperty]; ok { |
|
|
|
|
v.config.Backup = stringToBool(val.Value) |
|
|
|
|
} else { |
|
|
|
|
return nil, fmt.Errorf("dataset is not a volume property %s is not set", ZFSBackupProperty) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if val, ok := dataset.Properties[ZFSPersistentProperty]; ok { |
|
|
|
|
v.config.Persist = stringToBool(val.Value) |
|
|
|
|
} else { |
|
|
|
|
return nil, fmt.Errorf("dataset is not a volume property %s is not set", ZFSPersistentProperty) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return v, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (v *Volume) Seal() error { |
|
|
|
|
if _, err := v.ds.Snapshot(config.SealSnapshotName); err != nil { |
|
|
|
|
return fmt.Errorf("could not seal the volume %s: %w", v.ds.Path, err) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (v *Volume) Persist(containerId uuid.UUID) error { |
|
|
|
|
rootDS, err := getOrCreatePersistentContainerVolumeDataset(containerId) |
|
|
|
|
if err != nil { |
|
|
|
|
return tracerr.Wrap(err) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return vol.ds.Rename(path.Join(rootDS.Path, vol.config.Name), false) |
|
|
|
|
return v.ds.Rename(path.Join(rootDS.Path, v.config.Name), false) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (v *Volume) Destroy() error { |
|
|
|
@ -104,3 +180,64 @@ func (v *Volume) GetDatasetPath() string {
|
|
|
|
|
func (v *Volume) GetDatasetMountPoint() string { |
|
|
|
|
return v.ds.Mountpoint |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (v *Volume) AssignToContainer(containerUUID uuid.UUID) error { |
|
|
|
|
v.config.Properties["container:uuid"] = containerUUID.String() |
|
|
|
|
|
|
|
|
|
if v.ds.IsMounted() { |
|
|
|
|
if err := v.ds.Unmount(); err != nil { |
|
|
|
|
return fmt.Errorf("volume %s could not be unmounted: %w", v.GetName(), err) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if err := v.ds.SetManyProperties(zfs.PropertyKeyValuePair{ |
|
|
|
|
Name: "container:uuid", |
|
|
|
|
Value: containerUUID.String(), |
|
|
|
|
}, zfs.PropertyKeyValuePair{ |
|
|
|
|
Name: zfs.PropertyMountpoint, |
|
|
|
|
Value: v.config.MountPath, |
|
|
|
|
}); err != nil { |
|
|
|
|
return fmt.Errorf("could not assign volume %s to container %s: %w", v.GetName(), containerUUID, err) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (v *Volume) AssignToImage(imageDigest digest.Digest) error { |
|
|
|
|
v.config.Properties["image:digest"] = imageDigest.Encoded() |
|
|
|
|
v.config.Properties["image:algorithm"] = imageDigest.Algorithm().String() |
|
|
|
|
|
|
|
|
|
if err := v.ds.SetProperty("image:digest", imageDigest.Encoded()); err != nil { |
|
|
|
|
return fmt.Errorf("could not assign volume to image %s: %w", imageDigest, err) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if err := v.ds.SetProperty("image:algorithm", imageDigest.Algorithm().String()); err != nil { |
|
|
|
|
return fmt.Errorf("could not assign volume to image %s: %w", imageDigest, err) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (v *Volume) Clone(newParentDS *zfs.Dataset) (*Volume, error) { |
|
|
|
|
snap, err := v.ds.GetSnapshot(config.SealSnapshotName) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, fmt.Errorf("could not find seal snapshot of volume %s: %w", v.ds.Path, err) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
properties := v.ds.Properties.GetAllLocalProperties() |
|
|
|
|
properties[zfs.PropertyMountpoint] = filepath.Join(newParentDS.Mountpoint, v.config.MountPath) |
|
|
|
|
|
|
|
|
|
newVolDs, err := snap.Clone(path.Join(newParentDS.Path, v.config.Name), properties) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, fmt.Errorf("could not clone dataset %s: %w", v.ds.Path, err) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return &Volume{ |
|
|
|
|
config: v.config, |
|
|
|
|
ds: newVolDs, |
|
|
|
|
}, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (v *Volume) GetName() string { |
|
|
|
|
return v.config.Name |
|
|
|
|
} |
|
|
|
|