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

tomponline on Github lxc-bot at linuxcontainers.org
Mon Dec 2 13:52:24 UTC 2019


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 381 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20191202/d4d57686/attachment-0001.bin>
-------------- next part --------------
From 93a308b3f4c462ab6ec544d08e57d6b9da045644 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 29 Nov 2019 17:32:54 +0000
Subject: [PATCH 1/4] lxd/storage/backend/lxd: Fixes comments

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

diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go
index b3578033fc..4aade0a6c1 100644
--- a/lxd/storage/backend_lxd.go
+++ b/lxd/storage/backend_lxd.go
@@ -533,7 +533,7 @@ func (b *lxdBackend) CreateInstanceFromCopy(inst instance.Instance, src instance
 				Name:          inst.Name(),
 				Snapshots:     snapshotNames,
 				MigrationType: migrationType,
-				TrackProgress: false, // Do not a progress tracker on receiver.
+				TrackProgress: false, // Do not use a progress tracker on receiver.
 			}, op)
 
 			bEndErrCh <- err
@@ -675,7 +675,7 @@ func (b *lxdBackend) RefreshInstance(inst instance.Instance, src instance.Instan
 				Snapshots:     snapshotNames,
 				MigrationType: migrationType,
 				Refresh:       true,  // Indicate to receiver volume should exist.
-				TrackProgress: false, // Do not a progress tracker on receiver.
+				TrackProgress: false, // Do not use a progress tracker on receiver.
 			}, op)
 
 			bEndErrCh <- err
@@ -1686,7 +1686,7 @@ func (b *lxdBackend) CreateCustomVolumeFromCopy(volName, desc string, config map
 			Config:        config,
 			Snapshots:     snapshotNames,
 			MigrationType: migrationType,
-			TrackProgress: false, // Do not a progress tracker on receiver.
+			TrackProgress: false, // Do not use a progress tracker on receiver.
 
 		}, op)
 

From b48f2271cfba2676bf3cb1b22b0e318d28d25854 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Mon, 2 Dec 2019 13:45:34 +0000
Subject: [PATCH 2/4] lxd/storage/backend/lxd: Adds symlink and revert support
 to CreateInstanceFromMigration

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

diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go
index 4aade0a6c1..2bb15b0fb4 100644
--- a/lxd/storage/backend_lxd.go
+++ b/lxd/storage/backend_lxd.go
@@ -835,12 +835,36 @@ func (b *lxdBackend) CreateInstanceFromMigration(inst instance.Instance, conn io
 	volStorageName := project.Prefix(inst.Project(), args.Name)
 
 	vol := b.newVolume(volType, contentType, volStorageName, args.Config)
+
+	revert := true
+	if !args.Refresh {
+		defer func() {
+			if !revert {
+				return
+			}
+			b.DeleteInstance(inst, op)
+		}()
+	}
+
 	err = b.driver.CreateVolumeFromMigration(vol, conn, args, op)
 	if err != nil {
 		conn.Close()
 		return err
 	}
 
+	err = b.ensureInstanceSymlink(inst.Type(), inst.Project(), inst.Name(), vol.MountPath())
+	if err != nil {
+		return err
+	}
+
+	if len(args.Snapshots) > 0 {
+		err = b.ensureInstanceSnapshotSymlink(inst.Type(), inst.Project(), inst.Name())
+		if err != nil {
+			return err
+		}
+	}
+
+	revert = false
 	return nil
 }
 

From 59d7c3840f47a65629de07655002e9ccb4c1c0b8 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Mon, 2 Dec 2019 13:46:40 +0000
Subject: [PATCH 3/4] lxd/migrate/container: Links migrationSink.Do to new
 storage pkg

- Moves instance snapshot DB record creation into generic migration layer and out of storage layer.

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

diff --git a/lxd/migrate_container.go b/lxd/migrate_container.go
index 6ada2233a0..bd2597999e 100644
--- a/lxd/migrate_container.go
+++ b/lxd/migrate_container.go
@@ -22,6 +22,8 @@ import (
 	"github.com/lxc/lxd/lxd/operations"
 	"github.com/lxc/lxd/lxd/rsync"
 	"github.com/lxc/lxd/lxd/state"
+	storagePools "github.com/lxc/lxd/lxd/storage"
+	storageDrivers "github.com/lxc/lxd/lxd/storage/drivers"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -861,7 +863,82 @@ func (c *migrationSink) Do(state *state.State, migrateOp *operations.Operation)
 	// The migration header to be sent back to source with our target options.
 	var respHeader migration.MigrationHeader
 
-	if c.src.instance.Type() == instancetype.Container {
+	pool, err := storagePools.GetPoolByInstance(state, c.src.instance)
+	if err != storageDrivers.ErrUnknownDriver && err != storageDrivers.ErrNotImplemented {
+		if err != nil {
+			return err
+		}
+
+		// Extract the source's migration type and then match it against our pool's
+		// supported types and features. If a match is found the combined features list
+		// will be sent back to requester.
+		respType, err := migration.MatchTypes(offerHeader, migration.MigrationFSType_RSYNC, pool.MigrationTypes(storagePools.InstanceContentType(c.src.instance)))
+		if err != nil {
+			return err
+		}
+
+		// Convert response type to response header and copy snapshot info into it.
+		respHeader = migration.TypesToHeader(respType)
+		respHeader.SnapshotNames = offerHeader.SnapshotNames
+		respHeader.Snapshots = offerHeader.Snapshots
+
+		// Translate the legacy MigrationSinkArgs to a VolumeTargetArgs suitable for use
+		// with the new storage layer.
+		myTarget = func(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
+			volTargetArgs := migration.VolumeTargetArgs{
+				Name:          args.Instance.Name(),
+				MigrationType: respType,
+				Refresh:       args.Refresh, // Indicate to receiver volume should exist.
+				TrackProgress: false,        // Do not use a progress tracker on receiver.
+			}
+
+			// At this point we have already figured out the parent container's root
+			// disk device so we can simply retrieve it from the expanded devices.
+			parentStoragePool := ""
+			parentExpandedDevices := args.Instance.ExpandedDevices()
+			parentLocalRootDiskDeviceKey, parentLocalRootDiskDevice, _ := shared.GetRootDiskDevice(parentExpandedDevices.CloneNative())
+			if parentLocalRootDiskDeviceKey != "" {
+				parentStoragePool = parentLocalRootDiskDevice["pool"]
+			}
+
+			if parentStoragePool == "" {
+				return fmt.Errorf("Instance's root device is missing the pool property")
+			}
+
+			// A zero length Snapshots slice indicates volume only migration in
+			// VolumeTargetArgs. So if VoluneOnly was requested, do not populate them.
+			if !args.VolumeOnly {
+				volTargetArgs.Snapshots = make([]string, 0, len(args.Snapshots))
+				for _, snap := range args.Snapshots {
+					volTargetArgs.Snapshots = append(volTargetArgs.Snapshots, *snap.Name)
+					snapArgs := snapshotProtobufToInstanceArgs(args.Instance.Project(), args.Instance.Name(), snap)
+
+					// Ensure that snapshot and parent container have the same
+					// storage pool in their local root disk device. If the root
+					// disk device for the snapshot comes from a profile on the
+					// new instance as well we don't need to do anything.
+					if snapArgs.Devices != nil {
+						snapLocalRootDiskDeviceKey, _, _ := shared.GetRootDiskDevice(snapArgs.Devices.CloneNative())
+						if snapLocalRootDiskDeviceKey != "" {
+							snapArgs.Devices[snapLocalRootDiskDeviceKey]["pool"] = parentStoragePool
+						}
+					}
+
+					// Try and a load instance.
+					_, err := instanceLoadByProjectAndName(args.Instance.DaemonState(), args.Instance.Project(), snapArgs.Name)
+					if err != nil {
+						// Create the snapshot as it doesn't seem to exist.
+						_, err := instanceCreateInternal(state, snapArgs)
+						if err != nil {
+							return err
+						}
+					}
+				}
+			}
+
+			return pool.CreateInstanceFromMigration(args.Instance, &shared.WebsocketIO{Conn: conn}, volTargetArgs, op)
+		}
+	} else if c.src.instance.Type() == instancetype.Container {
 		ct := c.src.instance.(*containerLXC)
 		myTarget = ct.Storage().MigrationSink
 		myType := ct.Storage().MigrationType()

From fd76f5853a6dc7d3fd33253f45d0e26ab1644055 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Mon, 2 Dec 2019 13:48:21 +0000
Subject: [PATCH 4/4] lxd/containers/post: Links createFromMigration to new
 storage pkg

- Only creates instance DB records and doesn't create storage volumes (leaves this to the storage layer).

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/containers_post.go | 107 ++++++++++++++++++++++-------------------
 1 file changed, 58 insertions(+), 49 deletions(-)

diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 43bf9ee95c..071cd057b2 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -312,77 +312,87 @@ func createFromMigration(d *Daemon, project string, req *api.InstancesPost) resp
 		}
 	}()
 
+	instanceOnly := req.Source.InstanceOnly || req.Source.ContainerOnly
+
 	if !req.Source.Refresh {
-		/* Only create a container from an image if we're going to
-		 * rsync over the top of it. In the case of a better file
-		 * transfer mechanism, let's just use that.
-		 *
-		 * TODO: we could invent some negotiation here, where if the
-		 * source and sink both have the same image, we can clone from
-		 * it, but we have to know before sending the snapshot that
-		 * we're sending the whole thing or just a delta from the
-		 * image, so one extra negotiation round trip is needed. An
-		 * alternative is to move actual container object to a later
-		 * point and just negotiate it over the migration control
-		 * socket. Anyway, it'll happen later :)
-		 */
-		_, _, err = d.cluster.ImageGet(args.Project, req.Source.BaseImage, false, true)
-		if err != nil {
-			inst, err = instanceCreateAsEmpty(d, args)
+		// Check if we can load new storage layer for pool driver type.
+		_, err := storagePools.GetPoolByName(d.State(), storagePool)
+		if err != storageDrivers.ErrUnknownDriver {
 			if err != nil {
 				return response.InternalError(err)
 			}
-		} else {
-			// Retrieve the future storage pool.
-			tmpInst, err := instanceLoad(d.State(), args, nil)
-			if err != nil {
-				return response.InternalError(err)
+
+			// Get preferred migration type from storage backend.
+			// tomp TODO add optimised migration support from existing image.
+			/*migrationTypes := pool.MigrationTypes(storagePools.InstanceContentType(tmpInst))
+			if len(migrationTypes) > 0 {
+				migrationType = migrationTypes[0].FSType
 			}
+			*/
 
-			_, rootDiskDevice, err := shared.GetRootDiskDevice(tmpInst.ExpandedDevices().CloneNative())
+			// Create the instance record.
+			inst, err = instanceCreateInternal(d.State(), args)
 			if err != nil {
 				return response.InternalError(err)
 			}
-
-			if rootDiskDevice["pool"] == "" {
-				return response.BadRequest(fmt.Errorf("The container's root device is missing the pool property"))
-			}
-
-			storagePool = rootDiskDevice["pool"]
-
-			var migrationType migration.MigrationFSType
-
-			// Check if we can load new storage layer for pool driver type.
-			pool, err := storagePools.GetPoolByName(d.State(), storagePool)
-			if err != storageDrivers.ErrUnknownDriver {
+		} else {
+			/* Only create a container from an image if we're going to
+			 * rsync over the top of it. In the case of a better file
+			 * transfer mechanism, let's just use that.
+			 *
+			 * TODO: we could invent some negotiation here, where if the
+			 * source and sink both have the same image, we can clone from
+			 * it, but we have to know before sending the snapshot that
+			 * we're sending the whole thing or just a delta from the
+			 * image, so one extra negotiation round trip is needed. An
+			 * alternative is to move actual container object to a later
+			 * point and just negotiate it over the migration control
+			 * socket. Anyway, it'll happen later :)
+			 */
+			_, _, err = d.cluster.ImageGet(args.Project, req.Source.BaseImage, false, true)
+			if err != nil {
+				inst, err = instanceCreateAsEmpty(d, args)
 				if err != nil {
 					return response.InternalError(err)
 				}
-
-				// Get preferred migration type from storage backend.
-				migrationTypes := pool.MigrationTypes(storagePools.InstanceContentType(tmpInst))
-				if len(migrationTypes) > 0 {
-					migrationType = migrationTypes[0].FSType
-				}
 			} else {
-				ps, err := storagePoolInit(d.State(), storagePool)
+				// Retrieve the future storage pool.
+				tmpInst, err := instanceLoad(d.State(), args, nil)
 				if err != nil {
 					return response.InternalError(err)
 				}
 
-				migrationType = ps.MigrationType()
-			}
-
-			if migrationType == migration.MigrationFSType_RSYNC {
-				inst, err = instanceCreateFromImage(d, args, req.Source.BaseImage, nil)
+				_, rootDiskDevice, err := shared.GetRootDiskDevice(tmpInst.ExpandedDevices().CloneNative())
 				if err != nil {
 					return response.InternalError(err)
 				}
-			} else {
-				inst, err = instanceCreateAsEmpty(d, args)
+
+				if rootDiskDevice["pool"] == "" {
+					return response.BadRequest(fmt.Errorf("The container's root device is missing the pool property"))
+				}
+
+				storagePool = rootDiskDevice["pool"]
+
+				var migrationType migration.MigrationFSType
+
+				ps, err := storagePoolInit(d.State(), storagePool)
 				if err != nil {
 					return response.InternalError(err)
 				}
+
+				migrationType = ps.MigrationType()
+
+				if migrationType == migration.MigrationFSType_RSYNC {
+					inst, err = instanceCreateFromImage(d, args, req.Source.BaseImage, nil)
+					if err != nil {
+						return response.InternalError(err)
+					}
+				} else {
+					inst, err = instanceCreateAsEmpty(d, args)
+					if err != nil {
+						return response.InternalError(err)
+					}
+				}
 			}
 		}
 	}
@@ -410,7 +420,6 @@ func createFromMigration(d *Daemon, project string, req *api.InstancesPost) resp
 		push = true
 	}
 
-	instanceOnly := req.Source.InstanceOnly || req.Source.ContainerOnly
 	migrationArgs := MigrationSinkArgs{
 		Url: req.Source.Operation,
 		Dialer: websocket.Dialer{


More information about the lxc-devel mailing list