[lxc-devel] [lxd/master] Storage RefreshInstance

tomponline on Github lxc-bot at linuxcontainers.org
Fri Nov 22 15:59:37 UTC 2019


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 301 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20191122/1088efb6/attachment.bin>
-------------- next part --------------
From 2da6f44a5f9c5c1d5aaedf7554c83e4680044998 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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 {


More information about the lxc-devel mailing list