[lxc-devel] [lxd/master] Storage migration source

tomponline on Github lxc-bot at linuxcontainers.org
Wed Dec 4 14:31:47 UTC 2019


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 378 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20191204/2a579cae/attachment-0001.bin>
-------------- next part --------------
From 37fbfa8f7c502c69380a80812dfa90bce529765d Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 3 Dec 2019 15:40:21 +0000
Subject: [PATCH 01/11] lxd/storage/backend/lxd: Comment typos

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/storage/backend_lxd.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go
index 18840f0008..a347fb62e8 100644
--- a/lxd/storage/backend_lxd.go
+++ b/lxd/storage/backend_lxd.go
@@ -842,8 +842,8 @@ func (b *lxdBackend) CreateInstanceFromMigration(inst instance.Instance, conn io
 		}()
 
 		// If the negotiated migration method is rsync and the instance's base image is
-		// already on the host then pre-create the instance's volume using the locla image
-		// to try and speed up the rsync of the incoming volume by avoiding the new to
+		// already on the host then pre-create the instance's volume using the local image
+		// to try and speed up the rsync of the incoming volume by avoiding the need to
 		// transfer the base image files too.
 		if args.MigrationType.FSType == migration.MigrationFSType_RSYNC {
 			fingerprint := inst.ExpandedConfig()["volatile.base_image"]

From 33be0f9b99db85d3ecb081059616d12f8662c217 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 4 Dec 2019 08:56:06 +0000
Subject: [PATCH 02/11] lxd/storage/drivers/drive/dir: Add support for 2-phase
 migration

Adds support for a final rootfs volume sync stage which is sometimes requested by sender.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/storage/drivers/driver_dir.go | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)

diff --git a/lxd/storage/drivers/driver_dir.go b/lxd/storage/drivers/driver_dir.go
index 2a6b83c6b3..7d8a0563bd 100644
--- a/lxd/storage/drivers/driver_dir.go
+++ b/lxd/storage/drivers/driver_dir.go
@@ -431,7 +431,24 @@ func (d *dir) CreateVolumeFromMigration(vol Volume, conn io.ReadWriteCloser, vol
 			wrapper = migration.ProgressTracker(op, "fs_progress", vol.name)
 		}
 
-		return rsync.Recv(path, conn, wrapper, volTargetArgs.MigrationType.Features)
+		err = rsync.Recv(path, conn, wrapper, volTargetArgs.MigrationType.Features)
+		if err != nil {
+			return err
+		}
+
+		// Receive the final main volume sync if needed.
+		if volTargetArgs.Live {
+			if volTargetArgs.TrackProgress {
+				wrapper = migration.ProgressTracker(op, "fs_progress", vol.name)
+			}
+
+			err = rsync.Recv(path, conn, wrapper, volTargetArgs.MigrationType.Features)
+			if err != nil {
+				return err
+			}
+		}
+
+		return nil
 	}, op)
 	if err != nil {
 		return err

From c9f000ad7828f419b7b95fc45e0318be60311795 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 4 Dec 2019 08:57:06 +0000
Subject: [PATCH 03/11] lxd/migration/migration/volumes: Adds Live property to
 VolumeTargetArgs

Allows source node to indicate to target node that a 2-phase volume sync is needed.

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 18b329d1a9..391a0e8353 100644
--- a/lxd/migration/migration_volumes.go
+++ b/lxd/migration/migration_volumes.go
@@ -34,6 +34,7 @@ type VolumeTargetArgs struct {
 	MigrationType Type
 	TrackProgress bool
 	Refresh       bool
+	Live          bool
 }
 
 // TypesToHeader converts one or more Types to a MigrationHeader. It uses the first type argument

From c3661764266e2fb0aa12a9c7c71aaa19c436f3b3 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 4 Dec 2019 09:00:48 +0000
Subject: [PATCH 04/11] lxd/migrate/container: Add support for 2-phase sync in
 migrationSink.Do()

This fixes a bug where the criu property was not being populated in the migration response header.

This field is leveraged in the source to indicate whether the instance is running and whether a 2-phase sync is needed so its important this is sent back even if criu not being used.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/migrate_container.go | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/lxd/migrate_container.go b/lxd/migrate_container.go
index 9c8f5787bf..b33dcd37f0 100644
--- a/lxd/migrate_container.go
+++ b/lxd/migrate_container.go
@@ -893,6 +893,7 @@ func (c *migrationSink) Do(state *state.State, migrateOp *operations.Operation)
 				MigrationType: respType,
 				Refresh:       args.Refresh, // Indicate to receiver volume should exist.
 				TrackProgress: false,        // Do not use a progress tracker on receiver.
+				Live:          args.Live,    // Indicates we will get a final rootfs sync.
 			}
 
 			// At this point we have already figured out the parent container's root
@@ -950,7 +951,6 @@ func (c *migrationSink) Do(state *state.State, migrateOp *operations.Operation)
 
 		respHeader = migration.MigrationHeader{
 			Fs:            &myType,
-			Criu:          criuType,
 			Snapshots:     offerHeader.Snapshots,
 			SnapshotNames: offerHeader.SnapshotNames,
 			Refresh:       &c.refresh,
@@ -985,6 +985,9 @@ func (c *migrationSink) Do(state *state.State, migrateOp *operations.Operation)
 		return fmt.Errorf("Instance type not supported")
 	}
 
+	// Add CRIU info to response.
+	respHeader.Criu = criuType
+
 	if c.refresh {
 		// Get our existing snapshots.
 		targetSnapshots, err := c.src.instance.Snapshots()
@@ -1078,7 +1081,12 @@ func (c *migrationSink) Do(state *state.State, migrateOp *operations.Operation)
 				fsConn = c.src.fsConn
 			}
 
+			// Default to not expecting to receive the final rootfs sync.
 			sendFinalFsDelta := false
+
+			// If we are doing a stateful live transfer or the CRIU type indicates we
+			// are doing a stateless transfer with a running instance then we should
+			// expect the source to send us a final rootfs sync.
 			if live {
 				sendFinalFsDelta = true
 			}

From a5782c43723b934e536bf32cc6baaf84fc46463f Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 4 Dec 2019 13:35:56 +0000
Subject: [PATCH 05/11] lxd/migrate/container: Sends refresh request indicator
 in migration response header

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/migrate_container.go | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lxd/migrate_container.go b/lxd/migrate_container.go
index b33dcd37f0..ec94023b63 100644
--- a/lxd/migrate_container.go
+++ b/lxd/migrate_container.go
@@ -884,6 +884,7 @@ func (c *migrationSink) Do(state *state.State, migrateOp *operations.Operation)
 		respHeader = migration.TypesToHeader(respType)
 		respHeader.SnapshotNames = offerHeader.SnapshotNames
 		respHeader.Snapshots = offerHeader.Snapshots
+		respHeader.Refresh = &c.refresh
 
 		// Translate the legacy MigrationSinkArgs to a VolumeTargetArgs suitable for use
 		// with the new storage layer.
@@ -910,7 +911,7 @@ func (c *migrationSink) Do(state *state.State, migrateOp *operations.Operation)
 			}
 
 			// A zero length Snapshots slice indicates volume only migration in
-			// VolumeTargetArgs. So if VoluneOnly was requested, do not populate them.
+			// VolumeTargetArgs. So if VolumeOnly was requested, do not populate them.
 			if !args.VolumeOnly {
 				volTargetArgs.Snapshots = make([]string, 0, len(args.Snapshots))
 				for _, snap := range args.Snapshots {

From 1c8622a1d035702eadde7d59db2a9a45b5ce00d2 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 4 Dec 2019 13:34:18 +0000
Subject: [PATCH 06/11] lxd/rsync/rsync: Adds more info to error returned in
 sendSetup

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/rsync/rsync.go | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/lxd/rsync/rsync.go b/lxd/rsync/rsync.go
index d5d0ee1320..716a8db525 100644
--- a/lxd/rsync/rsync.go
+++ b/lxd/rsync/rsync.go
@@ -164,15 +164,17 @@ func sendSetup(name string, path string, bwlimit string, execPath string, featur
 	select {
 	case conn = <-chConn:
 		if conn == nil {
+			output, _ := ioutil.ReadAll(stderr)
 			cmd.Process.Kill()
 			cmd.Wait()
-			return nil, nil, nil, fmt.Errorf("Failed to connect to rsync socket")
+			return nil, nil, nil, fmt.Errorf("Failed to connect to rsync socket (%s)", string(output))
 		}
 
 	case <-time.After(10 * time.Second):
+		output, _ := ioutil.ReadAll(stderr)
 		cmd.Process.Kill()
 		cmd.Wait()
-		return nil, nil, nil, fmt.Errorf("rsync failed to spawn after 10s")
+		return nil, nil, nil, fmt.Errorf("rsync failed to spawn after 10s (%s)", string(output))
 	}
 
 	return cmd, *conn, stderr, nil

From 96ddbf92b735bb2a25d654fe107aa2c3bd5a483a Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 3 Dec 2019 14:54:40 +0000
Subject: [PATCH 07/11] lxd/storage/drivers: Adds Config() function to return
 read-only copy of pool config

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/storage/drivers/driver_common.go | 10 ++++++++++
 lxd/storage/drivers/interface.go     |  1 +
 2 files changed, 11 insertions(+)

diff --git a/lxd/storage/drivers/driver_common.go b/lxd/storage/drivers/driver_common.go
index c83225cefb..224f35209d 100644
--- a/lxd/storage/drivers/driver_common.go
+++ b/lxd/storage/drivers/driver_common.go
@@ -93,3 +93,13 @@ func (d *common) MigrationTypes(contentType ContentType) []migration.Type {
 		},
 	}
 }
+
+// Config returns the storage pool config (as a copy, so not modifiable).
+func (d *common) Config() map[string]string {
+	confCopy := make(map[string]string, len(d.config))
+	for k, v := range d.config {
+		confCopy[k] = v
+	}
+
+	return confCopy
+}
diff --git a/lxd/storage/drivers/interface.go b/lxd/storage/drivers/interface.go
index 09082f128f..8297b2d7b9 100644
--- a/lxd/storage/drivers/interface.go
+++ b/lxd/storage/drivers/interface.go
@@ -21,6 +21,7 @@ type driver interface {
 // Driver represents a low-level storage driver.
 type Driver interface {
 	// Internal.
+	Config() map[string]string
 	Info() Info
 	HasVolume(volType VolumeType, volName string) bool
 

From 7c76a4cf07d638d62a329a552a8fb66a3ae15cdc Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 4 Dec 2019 08:58:37 +0000
Subject: [PATCH 08/11] lxd/container/post: Minor cleanup and instance info
 output in containerPost

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/container_post.go | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/lxd/container_post.go b/lxd/container_post.go
index f97254c430..6f8d82343c 100644
--- a/lxd/container_post.go
+++ b/lxd/container_post.go
@@ -84,7 +84,7 @@ func containerPost(d *Daemon, r *http.Request) response.Response {
 				return errors.Wrap(err, "Failed to get address of container's node")
 			}
 			if address == "" {
-				// Local node
+				// Local node.
 				sourceNodeOffline = false
 				return nil
 			}
@@ -129,7 +129,7 @@ func containerPost(d *Daemon, r *http.Request) response.Response {
 	// Cases 1. and 2. are the ones for which the conditional will be true
 	// and we'll either forward the request or load the container.
 	if targetNode == "" || !sourceNodeOffline {
-		// Handle requests targeted to a container on a different node
+		// Handle requests targeted to a container on a different node.
 		resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
 		if err != nil {
 			return response.SmartError(err)
@@ -164,7 +164,7 @@ func containerPost(d *Daemon, r *http.Request) response.Response {
 		return response.BadRequest(err)
 	}
 
-	// Check if stateful (backward compatibility)
+	// Check if stateful (backward compatibility).
 	stateful := true
 	_, err = reqRaw.GetBool("live")
 	if err == nil {
@@ -234,7 +234,7 @@ func containerPost(d *Daemon, r *http.Request) response.Response {
 			return operations.OperationResponse(op)
 		}
 
-		// Pull mode
+		// Pull mode.
 		op, err := operations.OperationCreate(d.State(), project, operations.OperationClassWebsocket, db.OperationContainerMigrate, resources, ws.Metadata(), run, nil, ws.Connect)
 		if err != nil {
 			return response.InternalError(err)
@@ -243,7 +243,7 @@ func containerPost(d *Daemon, r *http.Request) response.Response {
 		return operations.OperationResponse(op)
 	}
 
-	// Check that the name isn't already in use
+	// Check that the name isn't already in use.
 	id, _ := d.cluster.ContainerID(project, req.Name)
 	if id > 0 {
 		return response.Conflict(fmt.Errorf("Name '%s' already in use", req.Name))
@@ -254,7 +254,8 @@ func containerPost(d *Daemon, r *http.Request) response.Response {
 	}
 
 	resources := map[string][]string{}
-	resources["containers"] = []string{name}
+	resources["instances"] = []string{name}
+	resources["containers"] = resources["instances"]
 
 	op, err := operations.OperationCreate(d.State(), project, operations.OperationClassTask, db.OperationContainerRename, resources, nil, run, nil, nil)
 	if err != nil {

From 93869cb9bde0a5d926e89f65f0a8e18c4b61a09e Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 4 Dec 2019 14:27:53 +0000
Subject: [PATCH 09/11] lxd/migrate/container: Links migrationSourceWs.Do to
 new storage pkg

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/migrate_container.go | 158 +++++++++++++++++++++++++++------------
 1 file changed, 112 insertions(+), 46 deletions(-)

diff --git a/lxd/migrate_container.go b/lxd/migrate_container.go
index ec94023b63..7f019c2e29 100644
--- a/lxd/migrate_container.go
+++ b/lxd/migrate_container.go
@@ -341,9 +341,27 @@ func (s *migrationSourceWs) Do(state *state.State, migrateOp *operations.Operati
 	ct := s.instance.(*containerLXC)
 
 	var offerHeader migration.MigrationHeader
+	var poolMigrationTypes []migration.Type
+
+	// Check if we can load new storage layer for pool driver type.
+	pool, err := storagePools.GetPoolByInstance(state, s.instance)
+	if err != storageDrivers.ErrUnknownDriver && err != db.ErrNoSuchObject {
+		if err != nil {
+			return err
+		}
+
+		poolMigrationTypes = pool.MigrationTypes(storagePools.InstanceContentType(s.instance))
+		if len(poolMigrationTypes) < 0 {
+			return fmt.Errorf("No source migration types available")
+		}
+
+		// Convert the pool's migration type options to an offer header to target.
+		// Populate the Fs, ZfsFeatures and RsyncFeatures fields.
+		offerHeader = migration.TypesToHeader(poolMigrationTypes...)
+	} else if s.instance.Type() == instancetype.Container {
+		// Fallback to legacy storage layer and populate the Fs, ZfsFeatures and
+		// RsyncFeatures fields.
 
-	var pool storagePools.Pool // Placeholder for new storage pool.
-	if s.instance.Type() == instancetype.Container {
 		// Storage needs to start unconditionally now, since we need to initialize a new
 		// storage interface.
 		ourStart, err := s.instance.StorageStart()
@@ -424,13 +442,13 @@ func (s *migrationSourceWs) Do(state *state.State, migrateOp *operations.Operati
 	offerHeader.Snapshots = snapshots
 
 	// Add predump info to source header.
-	usePreDumps := false
+	offerUsePreDumps := false
 	maxDumpIterations := 0
 	if s.live {
-		usePreDumps, maxDumpIterations = s.checkForPreDumpSupport()
+		offerUsePreDumps, maxDumpIterations = s.checkForPreDumpSupport()
 	}
 
-	offerHeader.Predump = proto.Bool(usePreDumps)
+	offerHeader.Predump = proto.Bool(offerUsePreDumps)
 
 	// Send offer to target.
 	err = s.send(&offerHeader)
@@ -448,8 +466,9 @@ func (s *migrationSourceWs) Do(state *state.State, migrateOp *operations.Operati
 	}
 
 	var legacyDriver MigrationStorageSourceDriver
-	var abort func(err error) error
-	var bwlimit string
+	var legacyCleanup func()         // Called after migration, to remove any temporary snapshots, etc.
+	var migrationType migration.Type // Negotiated migration type.
+	var rsyncBwlimit string          // Used for CRIU state and legacy storage rsync transfers.
 
 	// Handle rsync options.
 	rsyncFeatures := respHeader.GetRsyncFeaturesSlice()
@@ -459,7 +478,50 @@ func (s *migrationSourceWs) Do(state *state.State, migrateOp *operations.Operati
 		rsyncFeatures = []string{"xattrs", "delete", "compress"}
 	}
 
-	if pool == nil {
+	// All failure paths need to do a few things to correctly handle errors before returning.
+	// Unfortunately, handling errors is not well-suited to defer as the code depends on the
+	// status of driver and the error value. The error value is especially tricky due to the
+	// common case of creating a new err variable (intentional or not) due to scoping and use
+	// of ":=".  Capturing err in a closure for use in defer would be fragile, which defeats
+	// the purpose of using defer. An abort function reduces the odds of mishandling errors
+	// without introducing the fragility of closing on err.
+	abort := func(err error) error {
+		if legacyCleanup != nil {
+			legacyCleanup()
+		}
+
+		go s.sendControl(err)
+		return err
+	}
+
+	if pool != nil {
+		rsyncBwlimit = pool.Driver().Config()["rsync.bwlimit"]
+		migrationType, err = migration.MatchTypes(respHeader, migration.MigrationFSType_RSYNC, poolMigrationTypes)
+		if err != nil {
+			logger.Errorf("Failed to negotiate migration type: %v", err)
+			return abort(err)
+		}
+
+		sendSnapshotNames := snapshotNames
+
+		// If we are in refresh mode, only send the snapshots the target has asked for.
+		if respHeader.GetRefresh() {
+			sendSnapshotNames = respHeader.GetSnapshotNames()
+		}
+
+		volSourceArgs := migration.VolumeSourceArgs{
+			Name:          s.instance.Name(),
+			MigrationType: migrationType,
+			Snapshots:     sendSnapshotNames,
+			TrackProgress: true,
+			FinalSync:     false,
+		}
+
+		err = pool.MigrateInstance(s.instance, &shared.WebsocketIO{Conn: s.fsConn}, volSourceArgs, migrateOp)
+		if err != nil {
+			return abort(err)
+		}
+	} else {
 		// Handle zfs options.
 		zfsFeatures := respHeader.GetZfsFeaturesSlice()
 
@@ -472,12 +534,11 @@ func (s *migrationSourceWs) Do(state *state.State, migrateOp *operations.Operati
 		}
 
 		// Initialize storage driver.
-		var fsErr error
-		legacyDriver, fsErr = ct.Storage().MigrationSource(sourceArgs)
-		if fsErr != nil {
-			s.sendControl(fsErr)
-			return fsErr
+		legacyDriver, err = ct.Storage().MigrationSource(sourceArgs)
+		if err != nil {
+			return abort(err)
 		}
+		legacyCleanup = legacyDriver.Cleanup
 
 		if respHeader.GetRefresh() || *offerHeader.Fs != *respHeader.Fs {
 			myType := migration.MigrationFSType_RSYNC
@@ -492,36 +553,16 @@ func (s *migrationSourceWs) Do(state *state.State, migrateOp *operations.Operati
 			// Check if this storage pool has a rate limit set for rsync.
 			poolwritable := ct.Storage().GetStoragePoolWritable()
 			if poolwritable.Config != nil {
-				bwlimit = poolwritable.Config["rsync.bwlimit"]
+				rsyncBwlimit = poolwritable.Config["rsync.bwlimit"]
 			}
 		}
 
-		// All failure paths need to do a few things to correctly handle errors before
-		// returning. Unfortunately, handling errors is not well-suited to defer as the code
-		// depends on the status of driver and the error value. The error value is
-		// especially tricky due to the common case of creating a new err variable
-		// (intentional or not) due to scoping and use of ":=".  Capturing err in a closure
-		// for use in defer would be fragile, which defeats the purpose of using defer.
-		// An abort function reduces the odds of mishandling errors without introducing the
-		// fragility of closing on err.
-		abort = func(err error) error {
-			legacyDriver.Cleanup()
-			go s.sendControl(err)
-			return err
-		}
-
-		err = legacyDriver.SendWhileRunning(s.fsConn, migrateOp, bwlimit, s.instanceOnly)
+		logger.Debugf("SendWhileRunning starting")
+		err = legacyDriver.SendWhileRunning(s.fsConn, migrateOp, rsyncBwlimit, s.instanceOnly)
 		if err != nil {
 			return abort(err)
 		}
-	}
-
-	// Check if the other side knows about pre-dumping and the associated rsync protocol.
-	usePreDumps = respHeader.GetPredump()
-	if usePreDumps {
-		logger.Debugf("The other side does support pre-copy")
-	} else {
-		logger.Debugf("The other side does not support pre-copy")
+		logger.Debugf("SendWhileRunning finished")
 	}
 
 	restoreSuccess := make(chan bool, 1)
@@ -606,7 +647,11 @@ func (s *migrationSourceWs) Do(state *state.State, migrateOp *operations.Operati
 
 			preDumpCounter := 0
 			preDumpDir := ""
-			if usePreDumps {
+
+			// Check if the other side knows about pre-dumping and the associated
+			// rsync protocol.
+			if respHeader.GetPredump() {
+				logger.Debugf("The other side does support pre-copy")
 				final := false
 				for !final {
 					preDumpCounter++
@@ -618,7 +663,7 @@ func (s *migrationSourceWs) Do(state *state.State, migrateOp *operations.Operati
 					dumpDir := fmt.Sprintf("%03d", preDumpCounter)
 					loopArgs := preDumpLoopArgs{
 						checkpointDir: checkpointDir,
-						bwlimit:       bwlimit,
+						bwlimit:       rsyncBwlimit,
 						preDumpDir:    preDumpDir,
 						dumpDir:       dumpDir,
 						final:         final,
@@ -632,6 +677,8 @@ func (s *migrationSourceWs) Do(state *state.State, migrateOp *operations.Operati
 					preDumpDir = fmt.Sprintf("%03d", preDumpCounter)
 					preDumpCounter++
 				}
+			} else {
+				logger.Debugf("The other side does not support pre-copy")
 			}
 
 			_, err = actionScriptOp.Run()
@@ -684,28 +731,47 @@ func (s *migrationSourceWs) Do(state *state.State, migrateOp *operations.Operati
 			}
 		}
 
-		// We do the transger serially right now, but there's really no reason for us to;
+		// We do the transfer serially right now, but there's really no reason for us to;
 		// since we have separate websockets, we can do it in parallel if we wanted to.
 		// However assuming we're network bound, there's really no reason to do these in.
 		// parallel. In the future when we're using p.haul's protocol, it will make sense
 		// to do these in parallel.
 		ctName, _, _ := shared.InstanceGetParentAndSnapshotName(s.instance.Name())
-		state := s.instance.DaemonState()
-		err = rsync.Send(ctName, shared.AddSlash(checkpointDir), &shared.WebsocketIO{Conn: s.criuConn}, nil, rsyncFeatures, bwlimit, state.OS.ExecPath)
+		err = rsync.Send(ctName, shared.AddSlash(checkpointDir), &shared.WebsocketIO{Conn: s.criuConn}, nil, rsyncFeatures, rsyncBwlimit, state.OS.ExecPath)
 		if err != nil {
 			return abort(err)
 		}
 	}
 
-	if pool == nil {
-		if s.live || (respHeader.Criu != nil && *respHeader.Criu == migration.CRIUType_NONE) {
-			err = legacyDriver.SendAfterCheckpoint(s.fsConn, bwlimit)
+	// If s.live is true or Criu is set to CRIUTYPE_NONE rather than nil, it indicates
+	// that the source container is running and that we should do a two stage transfer
+	// to minimize downtime.
+	if s.live || (respHeader.Criu != nil && *respHeader.Criu == migration.CRIUType_NONE) {
+		if pool != nil {
+			volSourceArgs := migration.VolumeSourceArgs{
+				Name:          s.instance.Name(),
+				MigrationType: migrationType,
+				TrackProgress: true,
+				FinalSync:     true,
+			}
+
+			err = pool.MigrateInstance(s.instance, &shared.WebsocketIO{Conn: s.fsConn}, volSourceArgs, migrateOp)
 			if err != nil {
 				return abort(err)
 			}
+		} else {
+			logger.Debugf("SendAfterCheckpoint starting")
+			err = legacyDriver.SendAfterCheckpoint(s.fsConn, rsyncBwlimit)
+			if err != nil {
+				return abort(err)
+			}
+			logger.Debugf("SendAfterCheckpoint finished")
 		}
+	}
 
-		legacyDriver.Cleanup()
+	// Perform any storage level cleanup, such as removing any temporary snapshots.
+	if legacyCleanup != nil {
+		legacyCleanup()
 	}
 
 	msg := migration.MigrationControl{}

From 6af0d9b05cb026096fac774be281f373ee99821e Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 4 Dec 2019 14:28:16 +0000
Subject: [PATCH 10/11] lxd/migration/migration/volumes: Adds FinalSync bool to
 VolumeSourceArgs

Allows us to indicate to storage layer that this is final sync being performed and to alter behaviour accordingly.

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 391a0e8353..883afc3f86 100644
--- a/lxd/migration/migration_volumes.go
+++ b/lxd/migration/migration_volumes.go
@@ -23,6 +23,7 @@ type VolumeSourceArgs struct {
 	Snapshots     []string
 	MigrationType Type
 	TrackProgress bool
+	FinalSync     bool
 }
 
 // VolumeTargetArgs represents the arguments needed to setup a volume migration sink.

From 9f2fbbc755d6bfb93e783157e31e36c481896ed3 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 4 Dec 2019 14:29:02 +0000
Subject: [PATCH 11/11] lxd/storage/backend/lxd: Adds sanity check to
 MigrateInstance during FinalSync

Should never be transferring snapshots during final sync.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/storage/backend_lxd.go | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go
index a347fb62e8..8173a7a450 100644
--- a/lxd/storage/backend_lxd.go
+++ b/lxd/storage/backend_lxd.go
@@ -1068,6 +1068,10 @@ func (b *lxdBackend) MigrateInstance(inst instance.Instance, conn io.ReadWriteCl
 
 	contentType := InstanceContentType(inst)
 
+	if len(args.Snapshots) > 0 && args.FinalSync {
+		return fmt.Errorf("Snapshots should not be transferred during final sync")
+	}
+
 	// Get the root disk device config.
 	_, rootDiskConf, err := shared.GetRootDiskDevice(inst.ExpandedDevices().CloneNative())
 	if err != nil {


More information about the lxc-devel mailing list