The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/6547
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) === This is a WIP that links the new storage layer for instance migration source.
From 37fbfa8f7c502c69380a80812dfa90bce529765d Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.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 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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.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 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.parr...@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.parr...@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 {
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel