The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/6492
This e-mail was sent by the LXC bot, direct replies will not reach the author unless they happen to be subscribed to this list. === Description (from pull-request) ===
From 2da6f44a5f9c5c1d5aaedf7554c83e4680044998 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Thu, 21 Nov 2019 14:03:25 +0000 Subject: [PATCH 01/13] lxd/container: Removes instanceCompareSnapshots Moved to instance pkg. Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/container.go | 63 ------------------------------------------------ 1 file changed, 63 deletions(-) diff --git a/lxd/container.go b/lxd/container.go index 5a4a5274d3..e1753215f8 100644 --- a/lxd/container.go +++ b/lxd/container.go @@ -1369,69 +1369,6 @@ func instanceLoad(s *state.State, args db.InstanceArgs, profiles []api.Profile) return inst, nil } -// instanceCompareSnapshots returns a list of snapshots to sync to the target and a list of -// snapshots to remove from the target. A snapshot will be marked as "to sync" if it either doesn't -// exist in the target or its creation date is different to the source. A snapshot will be marked -// as "to delete" if it doesn't exist in the source or creation date is different to the source. -func instanceCompareSnapshots(source instance.Instance, target instance.Instance) ([]instance.Instance, []instance.Instance, error) { - // Get the source snapshots. - sourceSnapshots, err := source.Snapshots() - if err != nil { - return nil, nil, err - } - - // Get the target snapshots. - targetSnapshots, err := target.Snapshots() - if err != nil { - return nil, nil, err - } - - // Compare source and target. - sourceSnapshotsTime := map[string]time.Time{} - targetSnapshotsTime := map[string]time.Time{} - - toDelete := []instance.Instance{} - toSync := []instance.Instance{} - - // Generate a list of source snapshot creation dates. - for _, snap := range sourceSnapshots { - _, snapName, _ := shared.ContainerGetParentAndSnapshotName(snap.Name()) - - sourceSnapshotsTime[snapName] = snap.CreationDate() - } - - // Generate a list of target snapshot creation times, if the source doesn't contain the - // the snapshot or the creation time is different on the source then add the target snapshot - // to the "to delete" list. - for _, snap := range targetSnapshots { - _, snapName, _ := shared.ContainerGetParentAndSnapshotName(snap.Name()) - - targetSnapshotsTime[snapName] = snap.CreationDate() - existDate, exists := sourceSnapshotsTime[snapName] - if !exists { - // Snapshot doesn't exist in source, mark it for deletion on target. - toDelete = append(toDelete, snap) - } else if existDate != snap.CreationDate() { - // Snapshot creation date is different in source, mark it for deletion on - // target. - toDelete = append(toDelete, snap) - } - } - - // For each of the source snapshots, decide whether it needs to be synced or not based on - // whether it already exists in the target and whether the creation dates match. - for _, snap := range sourceSnapshots { - _, snapName, _ := shared.ContainerGetParentAndSnapshotName(snap.Name()) - - existDate, exists := targetSnapshotsTime[snapName] - if !exists || existDate != snap.CreationDate() { - toSync = append(toSync, snap) - } - } - - return toSync, toDelete, nil -} - func autoCreateContainerSnapshotsTask(d *Daemon) (task.Func, task.Schedule) { f := func(ctx context.Context) { // Load all local instances From 81d090982385c061824c282fce5fc5f82e44c727 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Thu, 21 Nov 2019 14:03:49 +0000 Subject: [PATCH 02/13] lxd/instance/instance/utils: Adds CompareSnapshots function Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/instance/instance_utils.go | 70 ++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 lxd/instance/instance_utils.go diff --git a/lxd/instance/instance_utils.go b/lxd/instance/instance_utils.go new file mode 100644 index 0000000000..0cacf16868 --- /dev/null +++ b/lxd/instance/instance_utils.go @@ -0,0 +1,70 @@ +package instance + +import ( + "time" + + "github.com/lxc/lxd/shared" +) + +// CompareSnapshots returns a list of snapshots to sync to the target and a list of +// snapshots to remove from the target. A snapshot will be marked as "to sync" if it either doesn't +// exist in the target or its creation date is different to the source. A snapshot will be marked +// as "to delete" if it doesn't exist in the source or creation date is different to the source. +func CompareSnapshots(source Instance, target Instance) ([]Instance, []Instance, error) { + // Get the source snapshots. + sourceSnapshots, err := source.Snapshots() + if err != nil { + return nil, nil, err + } + + // Get the target snapshots. + targetSnapshots, err := target.Snapshots() + if err != nil { + return nil, nil, err + } + + // Compare source and target. + sourceSnapshotsTime := map[string]time.Time{} + targetSnapshotsTime := map[string]time.Time{} + + toDelete := []Instance{} + toSync := []Instance{} + + // Generate a list of source snapshot creation dates. + for _, snap := range sourceSnapshots { + _, snapName, _ := shared.ContainerGetParentAndSnapshotName(snap.Name()) + + sourceSnapshotsTime[snapName] = snap.CreationDate() + } + + // Generate a list of target snapshot creation times, if the source doesn't contain the + // the snapshot or the creation time is different on the source then add the target snapshot + // to the "to delete" list. + for _, snap := range targetSnapshots { + _, snapName, _ := shared.ContainerGetParentAndSnapshotName(snap.Name()) + + targetSnapshotsTime[snapName] = snap.CreationDate() + existDate, exists := sourceSnapshotsTime[snapName] + if !exists { + // Snapshot doesn't exist in source, mark it for deletion on target. + toDelete = append(toDelete, snap) + } else if existDate != snap.CreationDate() { + // Snapshot creation date is different in source, mark it for deletion on + // target. + toDelete = append(toDelete, snap) + } + } + + // For each of the source snapshots, decide whether it needs to be synced or not based on + // whether it already exists in the target and whether the creation dates match. + for _, snap := range sourceSnapshots { + _, snapName, _ := shared.ContainerGetParentAndSnapshotName(snap.Name()) + + existDate, exists := targetSnapshotsTime[snapName] + if !exists || existDate != snap.CreationDate() { + toSync = append(toSync, snap) + } + } + + return toSync, toDelete, nil +} From 055b3c90be7fc2ab518dd276ad04ac18cf19af9c Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Thu, 21 Nov 2019 14:04:37 +0000 Subject: [PATCH 03/13] lxd/container: Updates instance.CompareSnapshots usage Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/container.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lxd/container.go b/lxd/container.go index e1753215f8..712dedf7c9 100644 --- a/lxd/container.go +++ b/lxd/container.go @@ -629,7 +629,7 @@ func instanceCreateAsCopy(s *state.State, args db.InstanceArgs, sourceInst insta if !instanceOnly { if refresh { // Compare snapshots. - syncSnapshots, deleteSnapshots, err := instanceCompareSnapshots(sourceInst, inst) + syncSnapshots, deleteSnapshots, err := instance.CompareSnapshots(sourceInst, inst) if err != nil { return nil, err } From f5313ccdd4f84b3d2a7a39a7a9d1700d1c77b36a Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Thu, 21 Nov 2019 14:24:07 +0000 Subject: [PATCH 04/13] lxd/container: Links instanceCreateAsCopy refresh instance to new storage pkg Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/container.go | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/lxd/container.go b/lxd/container.go index 712dedf7c9..f109e30d86 100644 --- a/lxd/container.go +++ b/lxd/container.go @@ -704,39 +704,41 @@ func instanceCreateAsCopy(s *state.State, args db.InstanceArgs, sourceInst insta } } - // Now clone or refresh the storage. - if refresh { - if inst.Type() != instancetype.Container { - return nil, fmt.Errorf("Instance type must be container") - } - - ct := inst.(*containerLXC) - err = ct.Storage().ContainerRefresh(inst, sourceInst, snapshots) + // Check if we can load new storage layer for both target and source pool driver types. + pool, err := storagePools.GetPoolByInstance(s, inst) + _, srcPoolErr := storagePools.GetPoolByInstance(s, sourceInst) + if err != storageDrivers.ErrUnknownDriver && err != storageDrivers.ErrNotImplemented && srcPoolErr != storageDrivers.ErrUnknownDriver && srcPoolErr != storageDrivers.ErrNotImplemented { if err != nil { - return nil, err + return nil, errors.Wrap(err, "Load instance storage pool") } - } else { - // Check if we can load new storage layer for both target and source pool driver types. - pool, err := storagePools.GetPoolByInstance(s, inst) - _, srcPoolErr := storagePools.GetPoolByInstance(s, sourceInst) - if err != storageDrivers.ErrUnknownDriver && err != storageDrivers.ErrNotImplemented && srcPoolErr != storageDrivers.ErrUnknownDriver && srcPoolErr != storageDrivers.ErrNotImplemented { + + if refresh { + err = pool.RefreshInstance(inst, sourceInst, snapshots, op) if err != nil { - return nil, errors.Wrap(err, "Load instance storage pool") + return nil, errors.Wrap(err, "Refresh instance") } - + } else { err = pool.CreateInstanceFromCopy(inst, sourceInst, !instanceOnly, op) if err != nil { return nil, errors.Wrap(err, "Create instance from copy") } - } else if inst.Type() == instancetype.Container { - ct := inst.(*containerLXC) - err = ct.Storage().ContainerCopy(inst, sourceInst, instanceOnly) + } + } else if inst.Type() == instancetype.Container { + ct := inst.(*containerLXC) + + if refresh { + err = ct.Storage().ContainerRefresh(inst, sourceInst, snapshots) if err != nil { return nil, err } } else { - return nil, fmt.Errorf("Instance type not supported") + err = ct.Storage().ContainerCopy(inst, sourceInst, instanceOnly) + if err != nil { + return nil, err + } } + } else { + return nil, fmt.Errorf("Instance type not supported") } // Apply any post-storage configuration. From 334ab9721992f36688b35f71ef26648d2267aeba Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Fri, 22 Nov 2019 15:53:09 +0000 Subject: [PATCH 05/13] lxd/storage/interfaces: RefreshInstance signature Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/storage/interfaces.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lxd/storage/interfaces.go b/lxd/storage/interfaces.go index 7632620464..27ed6bf7de 100644 --- a/lxd/storage/interfaces.go +++ b/lxd/storage/interfaces.go @@ -4,6 +4,7 @@ import ( "io" deviceConfig "github.com/lxc/lxd/lxd/device/config" + "github.com/lxc/lxd/lxd/instance" "github.com/lxc/lxd/lxd/instance/instancetype" "github.com/lxc/lxd/lxd/migration" "github.com/lxc/lxd/lxd/operations" @@ -50,7 +51,7 @@ type Pool interface { DeleteInstance(inst Instance, op *operations.Operation) error MigrateInstance(inst Instance, conn io.ReadWriteCloser, args migration.VolumeSourceArgs, op *operations.Operation) error - RefreshInstance(inst Instance, src Instance, snapshots bool, op *operations.Operation) error + RefreshInstance(inst instance.Instance, src instance.Instance, srcSnapshots []instance.Instance, op *operations.Operation) error BackupInstance(inst Instance, targetPath string, optimized bool, snapshots bool, op *operations.Operation) error GetInstanceUsage(inst Instance) (int64, error) From a90163f55942aff1ace13328a33d09aeadc67107 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Thu, 21 Nov 2019 15:04:21 +0000 Subject: [PATCH 06/13] lxd/storage/backend/lxd: Implements RefreshInstance Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/storage/backend_lxd.go | 140 +++++++++++++++++++++++++++++++++++-- 1 file changed, 136 insertions(+), 4 deletions(-) diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go index 27c3a02911..5edd4025f5 100644 --- a/lxd/storage/backend_lxd.go +++ b/lxd/storage/backend_lxd.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/lxc/lxd/lxd/db" + "github.com/lxc/lxd/lxd/instance" "github.com/lxc/lxd/lxd/instance/instancetype" "github.com/lxc/lxd/lxd/migration" "github.com/lxc/lxd/lxd/operations" @@ -468,6 +469,141 @@ func (b *lxdBackend) CreateInstanceFromCopy(inst Instance, src Instance, snapsho return nil } +// RefreshInstance synchronises one instance's volume (and optionally snapshots) over another. +// Snapshots that are not present in the source but are in the destination are removed from the +// destination if snapshots are included in the synchronisation. +func (b *lxdBackend) RefreshInstance(inst instance.Instance, src instance.Instance, srcSnapshots []instance.Instance, op *operations.Operation) error { + logger := logging.AddContext(b.logger, log.Ctx{"project": inst.Project(), "instance": inst.Name(), "src": src.Name(), "srcSnapshots": len(srcSnapshots)}) + logger.Debug("RefreshInstance started") + defer logger.Debug("RefreshInstance finished") + + if inst.Type() != src.Type() { + return fmt.Errorf("Instance types must match") + } + + volType, err := InstanceTypeToVolumeType(inst.Type()) + if err != nil { + return err + } + + contentType := drivers.ContentTypeFS + if inst.Type() == instancetype.VM { + contentType = drivers.ContentTypeBlock + } + + // Get the root disk device config. + _, rootDiskConf, err := shared.GetRootDiskDevice(inst.ExpandedDevices().CloneNative()) + if err != nil { + return err + } + + // Initialise a new volume containing the root disk config supplied in the new instance. + vol := b.newVolume(volType, contentType, project.Prefix(inst.Project(), inst.Name()), rootDiskConf) + + // We don't need to use the source instance's root disk config, so set to nil. + srcVol := b.newVolume(volType, contentType, project.Prefix(src.Project(), src.Name()), nil) + + srcSnapVols := []drivers.Volume{} + for _, snapInst := range srcSnapshots { + // Initialise a new volume containing the root disk config supplied in the + // new instance. We don't need to use the source instance's snapshot root + // disk config, so set to nil. This is because snapshots are immutable yet + // the instance and its snapshots can be transferred between pools, so using + // the data from the snapshot is incorrect. + srcSnapVol := b.newVolume(volType, contentType, project.Prefix(snapInst.Project(), snapInst.Name()), nil) + srcSnapVols = append(srcSnapVols, srcSnapVol) + } + + srcPool, err := GetPoolByInstance(b.state, src) + if err != nil { + return err + } + + if b.Name() == srcPool.Name() { + logger.Debug("RefreshInstance same-pool mode detected") + err = b.driver.RefreshVolume(vol, srcVol, srcSnapVols, op) + if err != nil { + return err + } + } else { + // We are copying volumes between storage pools so use migration system as it will + // be able to negotiate a common transfer method between pool types. + logger.Debug("RefreshInstance cross-pool mode detected") + + // Retrieve a list of snapshots we are copying. + snapshotNames := []string{} + for _, srcSnapVol := range srcSnapVols { + _, snapShotName, _ := shared.ContainerGetParentAndSnapshotName(srcSnapVol.Name()) + snapshotNames = append(snapshotNames, snapShotName) + } + + // Use in-memory pipe pair to simulate a connection between the sender and receiver. + aEnd, bEnd := memorypipe.NewPipePair() + + // Negotiate the migration type to use. + offeredTypes := srcPool.MigrationTypes(contentType) + offerHeader := migration.TypesToHeader(offeredTypes...) + migrationType, err := migration.MatchTypes(offerHeader, migration.MigrationFSType_RSYNC, b.MigrationTypes(contentType)) + if err != nil { + return fmt.Errorf("Failed to negotiate copy migration type: %v", err) + } + + // Run sender and receiver in separate go routines to prevent deadlocks. + aEndErrCh := make(chan error, 1) + bEndErrCh := make(chan error, 1) + go func() { + err := srcPool.MigrateInstance(src, aEnd, migration.VolumeSourceArgs{ + Name: src.Name(), + Snapshots: snapshotNames, + MigrationType: migrationType, + TrackProgress: true, // Do use a progress tracker on sender. + }, op) + + aEndErrCh <- err + }() + + go func() { + err := b.CreateInstanceFromMigration(inst, bEnd, migration.VolumeTargetArgs{ + Name: inst.Name(), + Snapshots: snapshotNames, + MigrationType: migrationType, + Refresh: true, // Indicate to receiver volume should exist. + TrackProgress: false, // Do not a progress tracker on receiver. + }, op) + + bEndErrCh <- err + }() + + // Capture errors from the sender and receiver from their result channels. + errs := []error{} + aEndErr := <-aEndErrCh + if aEndErr != nil { + errs = append(errs, aEndErr) + } + + bEndErr := <-bEndErrCh + if bEndErr != nil { + errs = append(errs, bEndErr) + } + + if len(errs) > 0 { + return fmt.Errorf("Create instance volume from copy failed: %v", errs) + } + } + + err = b.ensureInstanceSymlink(inst.Type(), inst.Project(), inst.Name(), vol.MountPath()) + if err != nil { + return err + } + + err = inst.DeferTemplateApply("copy") + if err != nil { + return err + } + + return nil +} + // imageFiller returns a function that can be used as a filler function with CreateVolume(). // The function returned will unpack the specified image archive into the specified mount path // provided, and for VM images, a raw root block path is required to unpack the qcow2 image into. @@ -797,10 +933,6 @@ func (b *lxdBackend) MigrateInstance(inst Instance, conn io.ReadWriteCloser, arg return nil } -func (b *lxdBackend) RefreshInstance(inst Instance, src Instance, snapshots bool, op *operations.Operation) error { - return ErrNotImplemented -} - func (b *lxdBackend) BackupInstance(inst Instance, targetPath string, optimized bool, snapshots bool, op *operations.Operation) error { return ErrNotImplemented } From 4001a9942eb1d9386c0d1e3d4b628787a19c8cbf Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Fri, 22 Nov 2019 15:54:06 +0000 Subject: [PATCH 07/13] lxd/storage/backend/mock: RefreshInstance placeholder Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/storage/backend_mock.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lxd/storage/backend_mock.go b/lxd/storage/backend_mock.go index f87beec2ee..0bc13b66b7 100644 --- a/lxd/storage/backend_mock.go +++ b/lxd/storage/backend_mock.go @@ -3,6 +3,7 @@ package storage import ( "io" + "github.com/lxc/lxd/lxd/instance" "github.com/lxc/lxd/lxd/migration" "github.com/lxc/lxd/lxd/operations" "github.com/lxc/lxd/lxd/state" @@ -90,7 +91,7 @@ func (b *mockBackend) MigrateInstance(inst Instance, conn io.ReadWriteCloser, ar return nil } -func (b *mockBackend) RefreshInstance(i Instance, src Instance, snapshots bool, op *operations.Operation) error { +func (b *mockBackend) RefreshInstance(i instance.Instance, src instance.Instance, srcSnapshots []instance.Instance, op *operations.Operation) error { return nil } From 6989f4ab191b8ba5704393d22354542815c2098f Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Thu, 21 Nov 2019 15:26:58 +0000 Subject: [PATCH 08/13] lxd/storage/drivers/interface: Adds RefreshVolume Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/storage/drivers/interface.go | 1 + 1 file changed, 1 insertion(+) diff --git a/lxd/storage/drivers/interface.go b/lxd/storage/drivers/interface.go index 75360761c2..064a707936 100644 --- a/lxd/storage/drivers/interface.go +++ b/lxd/storage/drivers/interface.go @@ -35,6 +35,7 @@ type Driver interface { ValidateVolume(vol Volume, removeUnknownKeys bool) error CreateVolume(vol Volume, filler func(mountPath, rootBlockPath string) error, op *operations.Operation) error CreateVolumeFromCopy(vol Volume, srcVol Volume, copySnapshots bool, op *operations.Operation) error + RefreshVolume(vol Volume, srcVol Volume, srcSnapshots []Volume, op *operations.Operation) error DeleteVolume(volType VolumeType, volName string, op *operations.Operation) error RenameVolume(volType VolumeType, volName string, newName string, op *operations.Operation) error UpdateVolume(vol Volume, changedConfig map[string]string) error From 2525d4fc778b3c9ebf1e0e8d841ed65b6b459976 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Thu, 21 Nov 2019 15:27:11 +0000 Subject: [PATCH 09/13] lxd/storage/drivers/driver/cephfs: Adds RefreshVolume placeholder Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/storage/drivers/driver_cephfs.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lxd/storage/drivers/driver_cephfs.go b/lxd/storage/drivers/driver_cephfs.go index 4ed2b258a5..1d0071ac09 100644 --- a/lxd/storage/drivers/driver_cephfs.go +++ b/lxd/storage/drivers/driver_cephfs.go @@ -431,6 +431,10 @@ func (d *cephfs) CreateVolumeFromCopy(vol Volume, srcVol Volume, copySnapshots b return nil } +func (d *cephfs) RefreshVolume(vol Volume, srcVol Volume, srcSnapshots []Volume, op *operations.Operation) error { + return ErrNotImplemented +} + func (d *cephfs) DeleteVolume(volType VolumeType, volName string, op *operations.Operation) error { if volType != VolumeTypeCustom { return fmt.Errorf("Volume type not supported") From 5e08abc9639af772eab288d4eaeaed7456eb3d59 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Thu, 21 Nov 2019 15:27:34 +0000 Subject: [PATCH 10/13] lxd/migration/migration/volumes: Adds Refresh property to VolumeTargetArgs Indicates this migration target is operating in refresh mode, so allow the target volume to exist already and sync over it. Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/migration/migration_volumes.go | 1 + 1 file changed, 1 insertion(+) diff --git a/lxd/migration/migration_volumes.go b/lxd/migration/migration_volumes.go index 415ecac98d..18b329d1a9 100644 --- a/lxd/migration/migration_volumes.go +++ b/lxd/migration/migration_volumes.go @@ -33,6 +33,7 @@ type VolumeTargetArgs struct { Snapshots []string MigrationType Type TrackProgress bool + Refresh bool } // TypesToHeader converts one or more Types to a MigrationHeader. It uses the first type argument From 3e763e170a3c7e29ca7c6568dbb26a29efec533a Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Thu, 21 Nov 2019 16:16:30 +0000 Subject: [PATCH 11/13] lxd/storage/drivers/driver/dir: RefreshVolume implementation Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/storage/drivers/driver_dir.go | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/lxd/storage/drivers/driver_dir.go b/lxd/storage/drivers/driver_dir.go index 2b8192a72c..38641d5b00 100644 --- a/lxd/storage/drivers/driver_dir.go +++ b/lxd/storage/drivers/driver_dir.go @@ -409,6 +409,27 @@ func (d *dir) CreateVolumeFromMigration(vol Volume, conn io.ReadWriteCloser, vol // CreateVolumeFromCopy provides same-pool volume copying functionality. func (d *dir) CreateVolumeFromCopy(vol Volume, srcVol Volume, copySnapshots bool, op *operations.Operation) error { + var err error + var srcSnapshots []Volume + + if copySnapshots && !srcVol.IsSnapshot() { + // Get the list of snapshots from the source. + srcSnapshots, err = srcVol.Snapshots(op) + if err != nil { + return err + } + } + + return d.copyVolume(vol, srcVol, srcSnapshots, op) +} + +// RefreshVolume provides same-pool volume and specific snapshots syncing functionality. +func (d *dir) RefreshVolume(vol Volume, srcVol Volume, srcSnapshots []Volume, op *operations.Operation) error { + return d.copyVolume(vol, srcVol, srcSnapshots, op) +} + +// copyVolume copies a volume and its specific snapshots. +func (d *dir) copyVolume(vol Volume, srcVol Volume, srcSnapshots []Volume, op *operations.Operation) error { if vol.contentType != ContentTypeFS || srcVol.contentType != ContentTypeFS { return fmt.Errorf("Content type not supported") } @@ -446,13 +467,7 @@ func (d *dir) CreateVolumeFromCopy(vol Volume, srcVol Volume, copySnapshots bool // Ensure the volume is mounted. err = vol.MountTask(func(mountPath string, op *operations.Operation) error { // If copying snapshots is indicated, check the source isn't itself a snapshot. - if copySnapshots && !srcVol.IsSnapshot() { - // Get the list of snapshots from the source. - srcSnapshots, err := srcVol.Snapshots(op) - if err != nil { - return err - } - + if len(srcSnapshots) > 0 && !srcVol.IsSnapshot() { for _, srcSnapshot := range srcSnapshots { _, snapName, _ := shared.ContainerGetParentAndSnapshotName(srcSnapshot.name) From cb6c934756c7cb364cc32645ba76de4dbd03cd74 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Fri, 22 Nov 2019 15:53:23 +0000 Subject: [PATCH 12/13] lxd/storage/drivers/volume: Adds Name() function Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/storage/drivers/volume.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lxd/storage/drivers/volume.go b/lxd/storage/drivers/volume.go index 2543a86304..1ac07aa453 100644 --- a/lxd/storage/drivers/volume.go +++ b/lxd/storage/drivers/volume.go @@ -55,6 +55,11 @@ func NewVolume(driver Driver, poolName string, volType VolumeType, contentType C } } +// Name returns volume's name. +func (v Volume) Name() string { + return v.name +} + // NewSnapshot instantiates a new Volume struct representing a snapshot of the parent volume. func (v Volume) NewSnapshot(snapshotName string) (Volume, error) { if v.IsSnapshot() { From 456508826eb5129d19e29c571fc0dd09818a316a Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Fri, 22 Nov 2019 15:54:55 +0000 Subject: [PATCH 13/13] lxd/storage/backend/lxd: Adds HasVolume checks to CreateInstanceFromMigration and CreateInstanceFromCopy Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/storage/backend_lxd.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go index 5edd4025f5..efa698b4f3 100644 --- a/lxd/storage/backend_lxd.go +++ b/lxd/storage/backend_lxd.go @@ -352,6 +352,10 @@ func (b *lxdBackend) CreateInstanceFromCopy(inst Instance, src Instance, snapsho contentType = drivers.ContentTypeBlock } + if b.driver.HasVolume(volType, project.Prefix(inst.Project(), inst.Name())) { + return fmt.Errorf("Cannot create volume, already exists on target") + } + // Get the root disk device config. _, rootDiskConf, err := shared.GetRootDiskDevice(inst.ExpandedDevices().CloneNative()) if err != nil { @@ -710,6 +714,13 @@ func (b *lxdBackend) CreateInstanceFromMigration(inst Instance, conn io.ReadWrit contentType = drivers.ContentTypeBlock } + volExists := b.driver.HasVolume(volType, project.Prefix(inst.Project(), inst.Name())) + if args.Refresh && !volExists { + return fmt.Errorf("Cannot refresh volume, doesn't exist on target") + } else if !args.Refresh && volExists { + return fmt.Errorf("Cannot create volume, already exists on target") + } + // Find the root device config for instance. _, rootDiskConf, err := shared.GetRootDiskDevice(inst.ExpandedDevices().CloneNative()) if err != nil {
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel