[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