[lxc-devel] [lxd/master] More storage cleanup preparation

monstermunchkin on Github lxc-bot at linuxcontainers.org
Fri Aug 30 18:41:34 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/20190830/95fc281c/attachment-0001.bin>
-------------- next part --------------
From 8b173cae21615d224c960b82741a8615f3bff581 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Mon, 19 Aug 2019 19:36:04 +0200
Subject: [PATCH 1/6] lxd: Move Create{Container,Snapshot}Mountpoint to storage

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/api_internal.go           |  4 +--
 lxd/container_post.go         |  4 +--
 lxd/patches.go                | 10 +++---
 lxd/storage.go                | 55 --------------------------------
 lxd/storage/storage.go        | 60 +++++++++++++++++++++++++++++++++++
 lxd/storage_btrfs.go          | 16 +++++-----
 lxd/storage_ceph.go           |  6 ++--
 lxd/storage_ceph_migration.go |  2 +-
 lxd/storage_ceph_utils.go     |  8 ++---
 lxd/storage_dir.go            | 14 ++++----
 lxd/storage_lvm.go            | 10 +++---
 lxd/storage_lvm_utils.go      |  8 ++---
 lxd/storage_zfs.go            | 14 ++++----
 lxd/storage_zfs_utils.go      |  4 +--
 14 files changed, 110 insertions(+), 105 deletions(-)

diff --git a/lxd/api_internal.go b/lxd/api_internal.go
index ddfffe5ae8..cbe27ad22e 100644
--- a/lxd/api_internal.go
+++ b/lxd/api_internal.go
@@ -923,7 +923,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
 	if backup.Container.Config["security.privileged"] == "" {
 		isPrivileged = true
 	}
-	err = createContainerMountpoint(containerMntPoint, containerPath,
+	err = driver.CreateContainerMountpoint(containerMntPoint, containerPath,
 		isPrivileged)
 	if err != nil {
 		return InternalError(err)
@@ -1029,7 +1029,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
 		sourceName = project.Prefix(projectName, sourceName)
 		snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", backup.Pool.Name, "containers-snapshots", sourceName)
 		snapshotMntPointSymlink := shared.VarPath("snapshots", sourceName)
-		err = createSnapshotMountpoint(snapshotMountPoint, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
+		err = driver.CreateSnapshotMountpoint(snapshotMountPoint, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
 		if err != nil {
 			return InternalError(err)
 		}
diff --git a/lxd/container_post.go b/lxd/container_post.go
index 5883dc69c9..8bd33b2dfa 100644
--- a/lxd/container_post.go
+++ b/lxd/container_post.go
@@ -531,7 +531,7 @@ func containerPostCreateContainerMountPoint(d *Daemon, project, containerName st
 	}
 
 	containerMntPoint := driver.GetContainerMountPoint(c.Project(), poolName, containerName)
-	err = createContainerMountpoint(containerMntPoint, c.Path(), c.IsPrivileged())
+	err = driver.CreateContainerMountpoint(containerMntPoint, c.Path(), c.IsPrivileged())
 	if err != nil {
 		return errors.Wrap(err, "Failed to create container mount point on target node")
 	}
@@ -541,7 +541,7 @@ func containerPostCreateContainerMountPoint(d *Daemon, project, containerName st
 		snapshotsSymlinkTarget := shared.VarPath("storage-pools",
 			poolName, "containers-snapshots", containerName)
 		snapshotMntPointSymlink := shared.VarPath("snapshots", containerName)
-		err := createSnapshotMountpoint(mntPoint, snapshotsSymlinkTarget, snapshotMntPointSymlink)
+		err := driver.CreateSnapshotMountpoint(mntPoint, snapshotsSymlinkTarget, snapshotMntPointSymlink)
 		if err != nil {
 			return errors.Wrap(err, "Failed to create snapshot mount point on target node")
 		}
diff --git a/lxd/patches.go b/lxd/patches.go
index 0ee421fa12..4ba86582f7 100644
--- a/lxd/patches.go
+++ b/lxd/patches.go
@@ -524,7 +524,7 @@ func upgradeFromStorageTypeBtrfs(name string, d *Daemon, defaultPoolName string,
 		// ${LXD_DIR}/containers/<container_name> to
 		// ${LXD_DIR}/storage-pools/<pool>/containers/<container_name>
 		doesntMatter := false
-		err = createContainerMountpoint(newContainerMntPoint, oldContainerMntPoint, doesntMatter)
+		err = driver.CreateContainerMountpoint(newContainerMntPoint, oldContainerMntPoint, doesntMatter)
 		if err != nil {
 			return err
 		}
@@ -808,7 +808,7 @@ func upgradeFromStorageTypeDir(name string, d *Daemon, defaultPoolName string, d
 		}
 
 		doesntMatter := false
-		err = createContainerMountpoint(newContainerMntPoint, oldContainerMntPoint, doesntMatter)
+		err = driver.CreateContainerMountpoint(newContainerMntPoint, oldContainerMntPoint, doesntMatter)
 		if err != nil {
 			return err
 		}
@@ -855,7 +855,7 @@ func upgradeFromStorageTypeDir(name string, d *Daemon, defaultPoolName string, d
 		}
 
 		// Create a symlink for this container.  snapshots.
-		err = createSnapshotMountpoint(newSnapshotMntPoint, newSnapshotMntPoint, oldSnapshotMntPoint)
+		err = driver.CreateSnapshotMountpoint(newSnapshotMntPoint, newSnapshotMntPoint, oldSnapshotMntPoint)
 		if err != nil {
 			return err
 		}
@@ -1191,7 +1191,7 @@ func upgradeFromStorageTypeLvm(name string, d *Daemon, defaultPoolName string, d
 
 		// Create the new container mountpoint.
 		doesntMatter := false
-		err = createContainerMountpoint(newContainerMntPoint, oldContainerMntPoint, doesntMatter)
+		err = driver.CreateContainerMountpoint(newContainerMntPoint, oldContainerMntPoint, doesntMatter)
 		if err != nil {
 			logger.Errorf("Failed to create container mountpoint \"%s\" for LVM logical volume: %s", newContainerMntPoint, err)
 			return err
@@ -1632,7 +1632,7 @@ func upgradeFromStorageTypeZfs(name string, d *Daemon, defaultPoolName string, d
 		// the path but in case it somehow didn't let's do it ourselves.
 		doesntMatter := false
 		newContainerMntPoint := driver.GetContainerMountPoint("default", poolName, ct)
-		err = createContainerMountpoint(newContainerMntPoint, oldContainerMntPoint, doesntMatter)
+		err = driver.CreateContainerMountpoint(newContainerMntPoint, oldContainerMntPoint, doesntMatter)
 		if err != nil {
 			logger.Warnf("Failed to create mountpoint for the container: %s", newContainerMntPoint)
 			failedUpgradeEntities = append(failedUpgradeEntities, fmt.Sprintf("containers/%s: Failed to create container mountpoint: %s", ct, err))
diff --git a/lxd/storage.go b/lxd/storage.go
index 5ee427f4e8..1a03e2fb56 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -607,40 +607,6 @@ func storagePoolVolumeContainerLoadInit(s *state.State, project, containerName s
 	return storagePoolVolumeInit(s, project, poolName, containerName, storagePoolVolumeTypeContainer)
 }
 
-func createContainerMountpoint(mountPoint string, mountPointSymlink string, privileged bool) error {
-	var mode os.FileMode
-	if privileged {
-		mode = 0700
-	} else {
-		mode = 0711
-	}
-
-	mntPointSymlinkExist := shared.PathExists(mountPointSymlink)
-	mntPointSymlinkTargetExist := shared.PathExists(mountPoint)
-
-	var err error
-	if !mntPointSymlinkTargetExist {
-		err = os.MkdirAll(mountPoint, 0711)
-		if err != nil {
-			return err
-		}
-	}
-
-	err = os.Chmod(mountPoint, mode)
-	if err != nil {
-		return err
-	}
-
-	if !mntPointSymlinkExist {
-		err := os.Symlink(mountPoint, mountPointSymlink)
-		if err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
 func deleteContainerMountpoint(mountPoint string, mountPointSymlink string, storageTypeName string) error {
 	if shared.PathExists(mountPointSymlink) {
 		err := os.Remove(mountPointSymlink)
@@ -698,27 +664,6 @@ func renameContainerMountpoint(oldMountPoint string, oldMountPointSymlink string
 	return nil
 }
 
-func createSnapshotMountpoint(snapshotMountpoint string, snapshotsSymlinkTarget string, snapshotsSymlink string) error {
-	snapshotMntPointExists := shared.PathExists(snapshotMountpoint)
-	mntPointSymlinkExist := shared.PathExists(snapshotsSymlink)
-
-	if !snapshotMntPointExists {
-		err := os.MkdirAll(snapshotMountpoint, 0711)
-		if err != nil {
-			return err
-		}
-	}
-
-	if !mntPointSymlinkExist {
-		err := os.Symlink(snapshotsSymlinkTarget, snapshotsSymlink)
-		if err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
 func deleteSnapshotMountpoint(snapshotMountpoint string, snapshotsSymlinkTarget string, snapshotsSymlink string) error {
 	if shared.PathExists(snapshotMountpoint) {
 		err := os.Remove(snapshotMountpoint)
diff --git a/lxd/storage/storage.go b/lxd/storage/storage.go
index f55c9f4ef4..b8d8d42b31 100644
--- a/lxd/storage/storage.go
+++ b/lxd/storage/storage.go
@@ -1,6 +1,8 @@
 package storage
 
 import (
+	"os"
+
 	"github.com/lxc/lxd/lxd/project"
 	"github.com/lxc/lxd/shared"
 )
@@ -49,3 +51,61 @@ func GetStoragePoolVolumeMountPoint(poolName string, volumeName string) string {
 func GetStoragePoolVolumeSnapshotMountPoint(poolName string, snapshotName string) string {
 	return shared.VarPath("storage-pools", poolName, "custom-snapshots", snapshotName)
 }
+
+// CreateContainerMountpoint creates the provided container mountpoint and symlink.
+func CreateContainerMountpoint(mountPoint string, mountPointSymlink string, privileged bool) error {
+	var mode os.FileMode
+	if privileged {
+		mode = 0700
+	} else {
+		mode = 0711
+	}
+
+	mntPointSymlinkExist := shared.PathExists(mountPointSymlink)
+	mntPointSymlinkTargetExist := shared.PathExists(mountPoint)
+
+	var err error
+	if !mntPointSymlinkTargetExist {
+		err = os.MkdirAll(mountPoint, 0711)
+		if err != nil {
+			return err
+		}
+	}
+
+	err = os.Chmod(mountPoint, mode)
+	if err != nil {
+		return err
+	}
+
+	if !mntPointSymlinkExist {
+		err := os.Symlink(mountPoint, mountPointSymlink)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+// CreateSnapshotMountpoint creates the provided container snapshot mountpoint
+// and symlink.
+func CreateSnapshotMountpoint(snapshotMountpoint string, snapshotsSymlinkTarget string, snapshotsSymlink string) error {
+	snapshotMntPointExists := shared.PathExists(snapshotMountpoint)
+	mntPointSymlinkExist := shared.PathExists(snapshotsSymlink)
+
+	if !snapshotMntPointExists {
+		err := os.MkdirAll(snapshotMountpoint, 0711)
+		if err != nil {
+			return err
+		}
+	}
+
+	if !mntPointSymlinkExist {
+		err := os.Symlink(snapshotsSymlinkTarget, snapshotsSymlink)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index b9e133dd16..392fa71726 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -836,7 +836,7 @@ func (s *storageBtrfs) doContainerCreate(projectName, name string, privileged bo
 
 	// Create the mountpoint for the container at:
 	// ${LXD_DIR}/containers/<name>
-	err = createContainerMountpoint(containerSubvolumeName, shared.VarPath("containers", project.Prefix(projectName, name)), privileged)
+	err = driver.CreateContainerMountpoint(containerSubvolumeName, shared.VarPath("containers", project.Prefix(projectName, name)), privileged)
 	if err != nil {
 		return err
 	}
@@ -925,7 +925,7 @@ func (s *storageBtrfs) ContainerCreateFromImage(container container, fingerprint
 
 	// Create the mountpoint for the container at:
 	// ${LXD_DIR}/containers/<name>
-	err = createContainerMountpoint(containerSubvolumeName, container.Path(), container.IsPrivileged())
+	err = driver.CreateContainerMountpoint(containerSubvolumeName, container.Path(), container.IsPrivileged())
 	if err != nil {
 		return errors.Wrap(err, "Failed to create container mountpoint")
 	}
@@ -1006,7 +1006,7 @@ func (s *storageBtrfs) copyContainer(target container, source container) error {
 		return err
 	}
 
-	err = createContainerMountpoint(targetContainerSubvolumeName, target.Path(), target.IsPrivileged())
+	err = driver.CreateContainerMountpoint(targetContainerSubvolumeName, target.Path(), target.IsPrivileged())
 	if err != nil {
 		return err
 	}
@@ -1029,7 +1029,7 @@ func (s *storageBtrfs) copySnapshot(target container, source container) error {
 	containersPath := driver.GetSnapshotMountPoint(target.Project(), s.pool.Name, targetParentName)
 	snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "containers-snapshots", project.Prefix(target.Project(), targetParentName))
 	snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(target.Project(), targetParentName))
-	err := createSnapshotMountpoint(containersPath, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
+	err := driver.CreateSnapshotMountpoint(containersPath, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
 	if err != nil {
 		return err
 	}
@@ -1581,7 +1581,7 @@ func (s *storageBtrfs) ContainerSnapshotCreateEmpty(snapshotContainer container)
 	snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "containers-snapshots", project.Prefix(snapshotContainer.Project(), sourceName))
 	snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(snapshotContainer.Project(), sourceName))
 	if !shared.PathExists(snapshotMntPointSymlink) {
-		err := createContainerMountpoint(snapshotMntPointSymlinkTarget, snapshotMntPointSymlink, snapshotContainer.IsPrivileged())
+		err := driver.CreateContainerMountpoint(snapshotMntPointSymlinkTarget, snapshotMntPointSymlink, snapshotContainer.IsPrivileged())
 		if err != nil {
 			return err
 		}
@@ -1857,7 +1857,7 @@ func (s *storageBtrfs) doContainerBackupLoadOptimized(info backupInfo, data io.R
 		snapshotMntPoint := driver.GetSnapshotMountPoint(info.Project, s.pool.Name, containerName)
 		snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "containers-snapshots", project.Prefix(info.Project, containerName))
 		snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(info.Project, containerName))
-		err = createSnapshotMountpoint(snapshotMntPoint, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
+		err = driver.CreateSnapshotMountpoint(snapshotMntPoint, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
 		if err != nil {
 			feeder.Close()
 			return err
@@ -1900,7 +1900,7 @@ func (s *storageBtrfs) doContainerBackupLoadOptimized(info backupInfo, data io.R
 	}
 
 	// Create mountpoints
-	err = createContainerMountpoint(containerMntPoint, shared.VarPath("containers", project.Prefix(info.Project, info.Name)), info.Privileged)
+	err = driver.CreateContainerMountpoint(containerMntPoint, shared.VarPath("containers", project.Prefix(info.Project, info.Name)), info.Privileged)
 	if err != nil {
 		return err
 	}
@@ -2777,7 +2777,7 @@ func (s *storageBtrfs) MigrationSink(conn *websocket.Conn, op *operation, args M
 
 			snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "containers-snapshots", project.Prefix(args.Container.Project(), containerName))
 			snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(args.Container.Project(), containerName))
-			err = createSnapshotMountpoint(snapshotMntPoint, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
+			err = driver.CreateSnapshotMountpoint(snapshotMntPoint, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
 			if err != nil {
 				return err
 			}
diff --git a/lxd/storage_ceph.go b/lxd/storage_ceph.go
index bd7bbbd4e8..09b7ffc367 100644
--- a/lxd/storage_ceph.go
+++ b/lxd/storage_ceph.go
@@ -878,7 +878,7 @@ func (s *storageCeph) ContainerCreateFromImage(container container, fingerprint
 
 	// Create the mountpoint
 	privileged := container.IsPrivileged()
-	err = createContainerMountpoint(containerPoolVolumeMntPoint,
+	err = driver.CreateContainerMountpoint(containerPoolVolumeMntPoint,
 		containerPath, privileged)
 	if err != nil {
 		logger.Errorf(`Failed to create mountpoint "%s" for container "%s" for RBD storage volume: %s`, containerPoolVolumeMntPoint, containerName, err)
@@ -1135,7 +1135,7 @@ func (s *storageCeph) ContainerCopy(target container, source container,
 			target.Project(),
 			s.pool.Name,
 			targetContainerName)
-		err = createContainerMountpoint(
+		err = driver.CreateContainerMountpoint(
 			targetContainerMountPoint,
 			targetContainerPath,
 			target.IsPrivileged())
@@ -1248,7 +1248,7 @@ func (s *storageCeph) ContainerCopy(target container, source container,
 				"snapshots",
 				project.Prefix(target.Project(), targetContainerName))
 
-			err := createSnapshotMountpoint(
+			err := driver.CreateSnapshotMountpoint(
 				containersPath,
 				snapshotMntPointSymlinkTarget,
 				snapshotMntPointSymlink)
diff --git a/lxd/storage_ceph_migration.go b/lxd/storage_ceph_migration.go
index 70e37a3e82..57e7839ef1 100644
--- a/lxd/storage_ceph_migration.go
+++ b/lxd/storage_ceph_migration.go
@@ -352,7 +352,7 @@ func (s *storageCeph) MigrationSink(conn *websocket.Conn, op *operation, args Mi
 	}
 
 	containerMntPoint := driver.GetContainerMountPoint(args.Container.Project(), s.pool.Name, containerName)
-	err = createContainerMountpoint(
+	err = driver.CreateContainerMountpoint(
 		containerMntPoint,
 		args.Container.Path(),
 		args.Container.IsPrivileged())
diff --git a/lxd/storage_ceph_utils.go b/lxd/storage_ceph_utils.go
index 104b151f4c..1a82a510f6 100644
--- a/lxd/storage_ceph_utils.go
+++ b/lxd/storage_ceph_utils.go
@@ -768,7 +768,7 @@ func (s *storageCeph) copyWithoutSnapshotsFull(target container,
 
 	// Create mountpoint
 	targetContainerMountPoint := driver.GetContainerMountPoint(target.Project(), s.pool.Name, target.Name())
-	err = createContainerMountpoint(targetContainerMountPoint, target.Path(), target.IsPrivileged())
+	err = driver.CreateContainerMountpoint(targetContainerMountPoint, target.Path(), target.IsPrivileged())
 	if err != nil {
 		return err
 	}
@@ -849,7 +849,7 @@ func (s *storageCeph) copyWithoutSnapshotsSparse(target container,
 
 	// Create mountpoint
 	targetContainerMountPoint := driver.GetContainerMountPoint(target.Project(), s.pool.Name, target.Name())
-	err = createContainerMountpoint(targetContainerMountPoint, target.Path(), target.IsPrivileged())
+	err = driver.CreateContainerMountpoint(targetContainerMountPoint, target.Path(), target.IsPrivileged())
 	if err != nil {
 		return err
 	}
@@ -1763,7 +1763,7 @@ func (s *storageCeph) doContainerCreate(projectName, name string, privileged boo
 
 	containerPath := shared.VarPath("containers", project.Prefix(projectName, name))
 	containerMntPoint := driver.GetContainerMountPoint(projectName, s.pool.Name, name)
-	err = createContainerMountpoint(containerMntPoint, containerPath, privileged)
+	err = driver.CreateContainerMountpoint(containerMntPoint, containerPath, privileged)
 	if err != nil {
 		logger.Errorf(`Failed to create mountpoint "%s" for RBD storage volume for container "%s" on storage pool "%s": %s"`, containerMntPoint, name, s.pool.Name, err)
 		return err
@@ -1876,7 +1876,7 @@ func (s *storageCeph) doContainerSnapshotCreate(projectName, targetName string,
 	sourceOnlyName, _, _ := shared.ContainerGetParentAndSnapshotName(sourceName)
 	snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "containers-snapshots", project.Prefix(projectName, sourceOnlyName))
 	snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(projectName, sourceOnlyName))
-	err = createSnapshotMountpoint(targetContainerMntPoint, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
+	err = driver.CreateSnapshotMountpoint(targetContainerMntPoint, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
 	if err != nil {
 		logger.Errorf(`Failed to create mountpoint "%s", snapshot symlink target "%s", snapshot mountpoint symlink"%s" for RBD storage volume "%s" on storage pool "%s": %s`, targetContainerMntPoint, snapshotMntPointSymlinkTarget,
 			snapshotMntPointSymlink, s.volume.Name, s.pool.Name, err)
diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index 21646c6111..3c38cedcfd 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -508,7 +508,7 @@ func (s *storageDir) ContainerCreate(container container) error {
 	}
 
 	containerMntPoint := driver.GetContainerMountPoint(container.Project(), s.pool.Name, container.Name())
-	err = createContainerMountpoint(containerMntPoint, container.Path(), container.IsPrivileged())
+	err = driver.CreateContainerMountpoint(containerMntPoint, container.Path(), container.IsPrivileged())
 	if err != nil {
 		return err
 	}
@@ -552,7 +552,7 @@ func (s *storageDir) ContainerCreateFromImage(container container, imageFingerpr
 	privileged := container.IsPrivileged()
 	containerName := container.Name()
 	containerMntPoint := driver.GetContainerMountPoint(container.Project(), s.pool.Name, containerName)
-	err = createContainerMountpoint(containerMntPoint, container.Path(), privileged)
+	err = driver.CreateContainerMountpoint(containerMntPoint, container.Path(), privileged)
 	if err != nil {
 		return errors.Wrap(err, "Create container mount point")
 	}
@@ -657,7 +657,7 @@ func (s *storageDir) copyContainer(target container, source container) error {
 	}
 	targetContainerMntPoint := driver.GetContainerMountPoint(target.Project(), targetPool, target.Name())
 
-	err := createContainerMountpoint(targetContainerMntPoint, target.Path(), target.IsPrivileged())
+	err := driver.CreateContainerMountpoint(targetContainerMntPoint, target.Path(), target.IsPrivileged())
 	if err != nil {
 		return err
 	}
@@ -691,7 +691,7 @@ func (s *storageDir) copySnapshot(target container, targetPool string, source co
 	containersPath := driver.GetSnapshotMountPoint(target.Project(), targetPool, targetParentName)
 	snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", targetPool, "containers-snapshots", project.Prefix(target.Project(), targetParentName))
 	snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(target.Project(), targetParentName))
-	err := createSnapshotMountpoint(containersPath, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
+	err := driver.CreateSnapshotMountpoint(containersPath, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
 	if err != nil {
 		return err
 	}
@@ -1027,7 +1027,7 @@ func (s *storageDir) ContainerSnapshotCreateEmpty(snapshotContainer container) e
 	snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools",
 		s.pool.Name, "containers-snapshots", project.Prefix(snapshotContainer.Project(), sourceName))
 	snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(snapshotContainer.Project(), sourceName))
-	err = createSnapshotMountpoint(targetContainerMntPoint,
+	err = driver.CreateSnapshotMountpoint(targetContainerMntPoint,
 		snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
 	if err != nil {
 		return err
@@ -1222,7 +1222,7 @@ func (s *storageDir) ContainerBackupLoad(info backupInfo, data io.ReadSeeker, ta
 
 	// Create mountpoints
 	containerMntPoint := driver.GetContainerMountPoint(info.Project, s.pool.Name, info.Name)
-	err = createContainerMountpoint(containerMntPoint, driver.ContainerPath(project.Prefix(info.Project, info.Name), false), info.Privileged)
+	err = driver.CreateContainerMountpoint(containerMntPoint, driver.ContainerPath(project.Prefix(info.Project, info.Name), false), info.Privileged)
 	if err != nil {
 		return errors.Wrap(err, "Create container mount point")
 	}
@@ -1248,7 +1248,7 @@ func (s *storageDir) ContainerBackupLoad(info backupInfo, data io.ReadSeeker, ta
 		snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name,
 			"containers-snapshots", project.Prefix(info.Project, info.Name))
 		snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(info.Project, info.Name))
-		err := createSnapshotMountpoint(snapshotMntPoint, snapshotMntPointSymlinkTarget,
+		err := driver.CreateSnapshotMountpoint(snapshotMntPoint, snapshotMntPointSymlinkTarget,
 			snapshotMntPointSymlink)
 		if err != nil {
 			return err
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index 1a52f99e54..7f2b1f619d 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -965,7 +965,7 @@ func (s *storageLvm) ContainerCreate(container container) error {
 		if err != nil {
 			return err
 		}
-		err = createSnapshotMountpoint(containerMntPoint, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
+		err = driver.CreateSnapshotMountpoint(containerMntPoint, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
 		if err != nil {
 			return err
 		}
@@ -976,7 +976,7 @@ func (s *storageLvm) ContainerCreate(container container) error {
 		if err != nil {
 			return err
 		}
-		err = createContainerMountpoint(containerMntPoint, containerPath, container.IsPrivileged())
+		err = driver.CreateContainerMountpoint(containerMntPoint, containerPath, container.IsPrivileged())
 		if err != nil {
 			return err
 		}
@@ -1019,7 +1019,7 @@ func (s *storageLvm) ContainerCreateFromImage(container container, fingerprint s
 	if err != nil {
 		return errors.Wrapf(err, "Create container mount point directory at %s", containerMntPoint)
 	}
-	err = createContainerMountpoint(containerMntPoint, containerPath, container.IsPrivileged())
+	err = driver.CreateContainerMountpoint(containerMntPoint, containerPath, container.IsPrivileged())
 	if err != nil {
 		return errors.Wrap(err, "Create container mount point")
 	}
@@ -1878,10 +1878,10 @@ func (s *storageLvm) doContainerBackupLoad(projectName, containerName string, pr
 		cname, _, _ := shared.ContainerGetParentAndSnapshotName(containerName)
 		snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(projectName, cname))
 		snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "containers-snapshots", project.Prefix(projectName, cname))
-		err = createSnapshotMountpoint(containerMntPoint, snapshotMntPointSymlinkTarget,
+		err = driver.CreateSnapshotMountpoint(containerMntPoint, snapshotMntPointSymlinkTarget,
 			snapshotMntPointSymlink)
 	} else {
-		err = createContainerMountpoint(containerMntPoint, containerPath, privileged)
+		err = driver.CreateContainerMountpoint(containerMntPoint, containerPath, privileged)
 	}
 	if err != nil {
 		return "", err
diff --git a/lxd/storage_lvm_utils.go b/lxd/storage_lvm_utils.go
index ff92174139..3c88a36888 100644
--- a/lxd/storage_lvm_utils.go
+++ b/lxd/storage_lvm_utils.go
@@ -288,10 +288,10 @@ func (s *storageLvm) createSnapshotContainer(snapshotContainer container, source
 		sourceName, _, _ := shared.ContainerGetParentAndSnapshotName(sourceContainerName)
 		snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "containers-snapshots", project.Prefix(sourceContainer.Project(), sourceName))
 		snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(sourceContainer.Project(), sourceName))
-		err = createSnapshotMountpoint(targetContainerMntPoint, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
+		err = driver.CreateSnapshotMountpoint(targetContainerMntPoint, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
 	} else {
 		targetContainerMntPoint = driver.GetContainerMountPoint(sourceContainer.Project(), targetPool, targetContainerName)
-		err = createContainerMountpoint(targetContainerMntPoint, targetContainerPath, snapshotContainer.IsPrivileged())
+		err = driver.CreateContainerMountpoint(targetContainerMntPoint, targetContainerPath, snapshotContainer.IsPrivileged())
 	}
 	if err != nil {
 		return errors.Wrap(err, "Create mount point")
@@ -351,7 +351,7 @@ func (s *storageLvm) copySnapshot(target container, source container, refresh bo
 	containersPath := driver.GetSnapshotMountPoint(target.Project(), s.pool.Name, targetParentName)
 	snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "containers-snapshots", project.Prefix(target.Project(), targetParentName))
 	snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(target.Project(), targetParentName))
-	err = createSnapshotMountpoint(containersPath, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
+	err = driver.CreateSnapshotMountpoint(containersPath, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
 	if err != nil {
 		return err
 	}
@@ -452,7 +452,7 @@ func (s *storageLvm) copyContainer(target container, source container, refresh b
 	}
 
 	targetContainerMntPoint := driver.GetContainerMountPoint(target.Project(), targetPool, target.Name())
-	err = createContainerMountpoint(targetContainerMntPoint, target.Path(), target.IsPrivileged())
+	err = driver.CreateContainerMountpoint(targetContainerMntPoint, target.Path(), target.IsPrivileged())
 	if err != nil {
 		return err
 	}
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index 97b2901923..d467cfc364 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -900,7 +900,7 @@ func (s *storageZfs) ContainerCreateFromImage(container container, fingerprint s
 	}
 
 	privileged := container.IsPrivileged()
-	err = createContainerMountpoint(containerPoolVolumeMntPoint, containerPath, privileged)
+	err = driver.CreateContainerMountpoint(containerPoolVolumeMntPoint, containerPath, privileged)
 	if err != nil {
 		return err
 	}
@@ -988,7 +988,7 @@ func (s *storageZfs) copyWithoutSnapshotsSparse(target container, source contain
 			defer s.ContainerUmount(target, targetContainerPath)
 		}
 
-		err = createContainerMountpoint(targetContainerMountPoint, targetContainerPath, target.IsPrivileged())
+		err = driver.CreateContainerMountpoint(targetContainerMountPoint, targetContainerPath, target.IsPrivileged())
 		if err != nil {
 			return err
 		}
@@ -1119,7 +1119,7 @@ func (s *storageZfs) copyWithoutSnapshotFull(target container, source container)
 		defer s.ContainerUmount(target, targetContainerMountPoint)
 	}
 
-	err = createContainerMountpoint(targetContainerMountPoint, target.Path(), target.IsPrivileged())
+	err = driver.CreateContainerMountpoint(targetContainerMountPoint, target.Path(), target.IsPrivileged())
 	if err != nil {
 		return err
 	}
@@ -1134,7 +1134,7 @@ func (s *storageZfs) copyWithSnapshots(target container, source container, paren
 	containersPath := driver.GetSnapshotMountPoint(target.Project(), s.pool.Name, targetParentName)
 	snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "containers-snapshots", project.Prefix(target.Project(), targetParentName))
 	snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(target.Project(), targetParentName))
-	err := createSnapshotMountpoint(containersPath, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
+	err := driver.CreateSnapshotMountpoint(containersPath, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
 	if err != nil {
 		return err
 	}
@@ -1291,7 +1291,7 @@ func (s *storageZfs) ContainerCopy(target container, source container, container
 		targetContainerName := target.Name()
 		targetContainerPath := target.Path()
 		targetContainerMountPoint := driver.GetContainerMountPoint(target.Project(), s.pool.Name, targetContainerName)
-		err = createContainerMountpoint(targetContainerMountPoint, targetContainerPath, target.IsPrivileged())
+		err = driver.CreateContainerMountpoint(targetContainerMountPoint, targetContainerPath, target.IsPrivileged())
 		if err != nil {
 			return err
 		}
@@ -2132,7 +2132,7 @@ func (s *storageZfs) ContainerBackupCreate(backup backup, source container) erro
 func (s *storageZfs) doContainerBackupLoadOptimized(info backupInfo, data io.ReadSeeker, tarArgs []string) error {
 	containerName, _, _ := shared.ContainerGetParentAndSnapshotName(info.Name)
 	containerMntPoint := driver.GetContainerMountPoint(info.Project, s.pool.Name, containerName)
-	err := createContainerMountpoint(containerMntPoint, driver.ContainerPath(info.Name, false), info.Privileged)
+	err := driver.CreateContainerMountpoint(containerMntPoint, driver.ContainerPath(info.Name, false), info.Privileged)
 	if err != nil {
 		return err
 	}
@@ -2192,7 +2192,7 @@ func (s *storageZfs) doContainerBackupLoadOptimized(info backupInfo, data io.Rea
 		snapshotMntPoint := driver.GetSnapshotMountPoint(info.Project, s.pool.Name, fmt.Sprintf("%s/%s", containerName, snapshotOnlyName))
 		snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "containers-snapshots", project.Prefix(info.Project, containerName))
 		snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(info.Project, containerName))
-		err = createSnapshotMountpoint(snapshotMntPoint, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
+		err = driver.CreateSnapshotMountpoint(snapshotMntPoint, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
 		if err != nil {
 			// can't use defer because it needs to run before the mount
 			os.RemoveAll(unpackPath)
diff --git a/lxd/storage_zfs_utils.go b/lxd/storage_zfs_utils.go
index 5944c40407..4a2d87091f 100644
--- a/lxd/storage_zfs_utils.go
+++ b/lxd/storage_zfs_utils.go
@@ -670,7 +670,7 @@ func (s *storageZfs) doContainerMount(projectName, name string, privileged bool)
 	// Since we're using mount() directly zfs will not automatically create
 	// the mountpoint for us. So let's check and do it if needed.
 	if !shared.PathExists(containerPoolVolumeMntPoint) {
-		err := createContainerMountpoint(containerPoolVolumeMntPoint, shared.VarPath(fs), privileged)
+		err := driver.CreateContainerMountpoint(containerPoolVolumeMntPoint, shared.VarPath(fs), privileged)
 		if err != nil {
 			return false, err
 		}
@@ -810,7 +810,7 @@ func (s *storageZfs) doContainerCreate(projectName, name string, privileged bool
 		return err
 	}
 
-	err = createContainerMountpoint(containerPoolVolumeMntPoint, containerPath, privileged)
+	err = driver.CreateContainerMountpoint(containerPoolVolumeMntPoint, containerPath, privileged)
 	if err != nil {
 		return err
 	}

From e81185b3a8c511152d68ede3e3adf1533454cf70 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Mon, 19 Aug 2019 19:37:26 +0200
Subject: [PATCH 2/6] lxd: Move ChangeableStoragePoolProperties to storage

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/storage/pools_config.go | 36 ++++++++++++++++++++++++++++++++++++
 lxd/storage_btrfs.go        |  2 +-
 lxd/storage_ceph.go         |  4 ++--
 lxd/storage_cephfs.go       |  2 +-
 lxd/storage_dir.go          |  2 +-
 lxd/storage_lvm.go          |  2 +-
 lxd/storage_pools_config.go | 30 ------------------------------
 lxd/storage_zfs.go          |  2 +-
 8 files changed, 43 insertions(+), 37 deletions(-)
 create mode 100644 lxd/storage/pools_config.go

diff --git a/lxd/storage/pools_config.go b/lxd/storage/pools_config.go
new file mode 100644
index 0000000000..6583acb168
--- /dev/null
+++ b/lxd/storage/pools_config.go
@@ -0,0 +1,36 @@
+package storage
+
+import "fmt"
+
+// ChangeableStoragePoolProperties is a map of storage backends and their
+// changeable storage pool properties.
+var ChangeableStoragePoolProperties = map[string][]string{
+	"btrfs": {
+		"rsync.bwlimit",
+		"btrfs.mount_options"},
+
+	"ceph": {
+		"volume.block.filesystem",
+		"volume.block.mount_options",
+		"volume.size"},
+
+	"cephfs": {
+		"rsync.bwlimit"},
+
+	"dir": {
+		"rsync.bwlimit"},
+
+	"lvm": {
+		"lvm.thinpool_name",
+		"lvm.vg_name",
+		"volume.block.filesystem",
+		"volume.block.mount_options",
+		"volume.size"},
+
+	"zfs": {
+		"rsync_bwlimit",
+		"volume.zfs.remove_snapshots",
+		"volume.zfs.use_refquota",
+		"zfs.clone_copy"},
+}
+
diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index 392fa71726..c5221f95b2 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -511,7 +511,7 @@ func (s *storageBtrfs) StoragePoolUpdate(writable *api.StoragePoolPut,
 	changedConfig []string) error {
 	logger.Infof(`Updating BTRFS storage pool "%s"`, s.pool.Name)
 
-	changeable := changeableStoragePoolProperties["btrfs"]
+	changeable := driver.ChangeableStoragePoolProperties["btrfs"]
 	unchangeable := []string{}
 	for _, change := range changedConfig {
 		if !shared.StringInSlice(change, changeable) {
diff --git a/lxd/storage_ceph.go b/lxd/storage_ceph.go
index 09b7ffc367..ddbc1d7f16 100644
--- a/lxd/storage_ceph.go
+++ b/lxd/storage_ceph.go
@@ -735,7 +735,7 @@ func (s *storageCeph) StoragePoolVolumeRename(newName string) error {
 func (s *storageCeph) StoragePoolUpdate(writable *api.StoragePoolPut, changedConfig []string) error {
 	logger.Infof(`Updating CEPH storage pool "%s"`, s.pool.Name)
 
-	changeable := changeableStoragePoolProperties["ceph"]
+	changeable := driver.ChangeableStoragePoolProperties["ceph"]
 	unchangeable := []string{}
 	for _, change := range changedConfig {
 		if !shared.StringInSlice(change, changeable) {
@@ -744,7 +744,7 @@ func (s *storageCeph) StoragePoolUpdate(writable *api.StoragePoolPut, changedCon
 	}
 
 	if len(unchangeable) > 0 {
-		return updateStoragePoolError(unchangeable, "ceph")
+		return driver.UpdateStoragePoolError(unchangeable, "ceph")
 	}
 
 	// "rsync.bwlimit" requires no on-disk modifications.
diff --git a/lxd/storage_cephfs.go b/lxd/storage_cephfs.go
index 305de37498..b0457fd8cf 100644
--- a/lxd/storage_cephfs.go
+++ b/lxd/storage_cephfs.go
@@ -429,7 +429,7 @@ func (s *storageCephFs) StoragePoolUpdate(writable *api.StoragePoolPut, changedC
 	logger.Infof(`Updating CEPHFS storage pool "%s"`, s.pool.Name)
 
 	// Validate the properties
-	changeable := changeableStoragePoolProperties["cephfs"]
+	changeable := driver.ChangeableStoragePoolProperties["cephfs"]
 	unchangeable := []string{}
 	for _, change := range changedConfig {
 		if !shared.StringInSlice(change, changeable) {
diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index 3c38cedcfd..e9f437aa1c 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -291,7 +291,7 @@ func (s *storageDir) StoragePoolUpdate(writable *api.StoragePoolPut, changedConf
 		return err
 	}
 
-	changeable := changeableStoragePoolProperties["dir"]
+	changeable := driver.ChangeableStoragePoolProperties["dir"]
 	unchangeable := []string{}
 	for _, change := range changedConfig {
 		if !shared.StringInSlice(change, changeable) {
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index 7f2b1f619d..ff97f8027f 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -688,7 +688,7 @@ func (s *storageLvm) GetContainerPoolInfo() (int64, string, string) {
 func (s *storageLvm) StoragePoolUpdate(writable *api.StoragePoolPut, changedConfig []string) error {
 	logger.Infof(`Updating LVM storage pool "%s"`, s.pool.Name)
 
-	changeable := changeableStoragePoolProperties["lvm"]
+	changeable := driver.ChangeableStoragePoolProperties["lvm"]
 	unchangeable := []string{}
 	for _, change := range changedConfig {
 		if !shared.StringInSlice(change, changeable) {
diff --git a/lxd/storage_pools_config.go b/lxd/storage_pools_config.go
index b44f0659df..1f06c0deee 100644
--- a/lxd/storage_pools_config.go
+++ b/lxd/storage_pools_config.go
@@ -16,36 +16,6 @@ func updateStoragePoolError(unchangeable []string, driverName string) error {
 		`storage pools`, unchangeable, driverName)
 }
 
-var changeableStoragePoolProperties = map[string][]string{
-	"btrfs": {
-		"rsync.bwlimit",
-		"btrfs.mount_options"},
-
-	"ceph": {
-		"volume.block.filesystem",
-		"volume.block.mount_options",
-		"volume.size"},
-
-	"cephfs": {
-		"rsync.bwlimit"},
-
-	"dir": {
-		"rsync.bwlimit"},
-
-	"lvm": {
-		"lvm.thinpool_name",
-		"lvm.vg_name",
-		"volume.block.filesystem",
-		"volume.block.mount_options",
-		"volume.size"},
-
-	"zfs": {
-		"rsync_bwlimit",
-		"volume.zfs.remove_snapshots",
-		"volume.zfs.use_refquota",
-		"zfs.clone_copy"},
-}
-
 var storagePoolConfigKeys = map[string]func(value string) error{
 	// valid drivers: btrfs
 	// (Note, that we can't be smart in detecting mount options since a lot
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index d467cfc364..0d98cf2977 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -629,7 +629,7 @@ func (s *storageZfs) GetContainerPoolInfo() (int64, string, string) {
 func (s *storageZfs) StoragePoolUpdate(writable *api.StoragePoolPut, changedConfig []string) error {
 	logger.Infof(`Updating ZFS storage pool "%s"`, s.pool.Name)
 
-	changeable := changeableStoragePoolProperties["zfs"]
+	changeable := driver.ChangeableStoragePoolProperties["zfs"]
 	unchangeable := []string{}
 	for _, change := range changedConfig {
 		if !shared.StringInSlice(change, changeable) {

From cd1f65f6807644d87236bf47b09c7a09724c0c69 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Thu, 29 Aug 2019 20:38:29 +0200
Subject: [PATCH 3/6] lxd: Move UpdateStoragePoolError to storage

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/storage/pools_config.go | 6 ++++++
 lxd/storage_btrfs.go        | 2 +-
 lxd/storage_cephfs.go       | 2 +-
 lxd/storage_dir.go          | 2 +-
 lxd/storage_lvm.go          | 2 +-
 lxd/storage_pools_config.go | 5 -----
 lxd/storage_zfs.go          | 2 +-
 7 files changed, 11 insertions(+), 10 deletions(-)

diff --git a/lxd/storage/pools_config.go b/lxd/storage/pools_config.go
index 6583acb168..a0c1684c53 100644
--- a/lxd/storage/pools_config.go
+++ b/lxd/storage/pools_config.go
@@ -34,3 +34,9 @@ var ChangeableStoragePoolProperties = map[string][]string{
 		"zfs.clone_copy"},
 }
 
+// UpdateStoragePoolError returns an error stating that the provided properties
+// cannot be changed in the given backend.
+func UpdateStoragePoolError(unchangeable []string, driverName string) error {
+	return fmt.Errorf("The %v properties cannot be changed for %q storage pools",
+		unchangeable, driverName)
+}
diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index c5221f95b2..3c10da58fd 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -520,7 +520,7 @@ func (s *storageBtrfs) StoragePoolUpdate(writable *api.StoragePoolPut,
 	}
 
 	if len(unchangeable) > 0 {
-		return updateStoragePoolError(unchangeable, "btrfs")
+		return driver.UpdateStoragePoolError(unchangeable, "btrfs")
 	}
 
 	// "rsync.bwlimit" requires no on-disk modifications.
diff --git a/lxd/storage_cephfs.go b/lxd/storage_cephfs.go
index b0457fd8cf..3fe207b143 100644
--- a/lxd/storage_cephfs.go
+++ b/lxd/storage_cephfs.go
@@ -438,7 +438,7 @@ func (s *storageCephFs) StoragePoolUpdate(writable *api.StoragePoolPut, changedC
 	}
 
 	if len(unchangeable) > 0 {
-		return updateStoragePoolError(unchangeable, "cephfs")
+		return driver.UpdateStoragePoolError(unchangeable, "cephfs")
 	}
 
 	logger.Infof(`Updated CEPHFS storage pool "%s"`, s.pool.Name)
diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index e9f437aa1c..736700304b 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -300,7 +300,7 @@ func (s *storageDir) StoragePoolUpdate(writable *api.StoragePoolPut, changedConf
 	}
 
 	if len(unchangeable) > 0 {
-		return updateStoragePoolError(unchangeable, "dir")
+		return driver.UpdateStoragePoolError(unchangeable, "dir")
 	}
 
 	// "rsync.bwlimit" requires no on-disk modifications.
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index ff97f8027f..ff8e06b703 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -697,7 +697,7 @@ func (s *storageLvm) StoragePoolUpdate(writable *api.StoragePoolPut, changedConf
 	}
 
 	if len(unchangeable) > 0 {
-		return updateStoragePoolError(unchangeable, "lvm")
+		return driver.UpdateStoragePoolError(unchangeable, "lvm")
 	}
 
 	// "volume.block.mount_options" requires no on-disk modifications.
diff --git a/lxd/storage_pools_config.go b/lxd/storage_pools_config.go
index 1f06c0deee..6ecd411ade 100644
--- a/lxd/storage_pools_config.go
+++ b/lxd/storage_pools_config.go
@@ -11,11 +11,6 @@ import (
 	"github.com/lxc/lxd/shared/units"
 )
 
-func updateStoragePoolError(unchangeable []string, driverName string) error {
-	return fmt.Errorf(`The %v properties cannot be changed for "%s" `+
-		`storage pools`, unchangeable, driverName)
-}
-
 var storagePoolConfigKeys = map[string]func(value string) error{
 	// valid drivers: btrfs
 	// (Note, that we can't be smart in detecting mount options since a lot
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index 0d98cf2977..0087bd098c 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -638,7 +638,7 @@ func (s *storageZfs) StoragePoolUpdate(writable *api.StoragePoolPut, changedConf
 	}
 
 	if len(unchangeable) > 0 {
-		return updateStoragePoolError(unchangeable, "zfs")
+		return driver.UpdateStoragePoolError(unchangeable, "zfs")
 	}
 
 	// "rsync.bwlimit" requires no on-disk modifications.

From b3b3bad101ee03bcc15f4768df5a54eb28d3c887 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Fri, 30 Aug 2019 19:51:46 +0200
Subject: [PATCH 4/6] lxd: Move btrfs migration code

The migration code needs to be moved to its own file since
storage_btrfs.go will be removed in the future. The code however needs
to stay in the main package (for now) since it depends on the container
interface.

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/storage_btrfs.go           | 170 ------------------------------
 lxd/storage_migration_btrfs.go | 185 +++++++++++++++++++++++++++++++++
 2 files changed, 185 insertions(+), 170 deletions(-)
 create mode 100644 lxd/storage_migration_btrfs.go

diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index 3c10da58fd..f1cf4cd842 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -2430,170 +2430,6 @@ func btrfsSubVolumesGet(path string) ([]string, error) {
 	return result, nil
 }
 
-type btrfsMigrationSourceDriver struct {
-	container          container
-	snapshots          []container
-	btrfsSnapshotNames []string
-	btrfs              *storageBtrfs
-	runningSnapName    string
-	stoppedSnapName    string
-}
-
-func (s *btrfsMigrationSourceDriver) send(conn *websocket.Conn, btrfsPath string, btrfsParent string, readWrapper func(io.ReadCloser) io.ReadCloser) error {
-	args := []string{"send"}
-	if btrfsParent != "" {
-		args = append(args, "-p", btrfsParent)
-	}
-	args = append(args, btrfsPath)
-
-	cmd := exec.Command("btrfs", args...)
-
-	stdout, err := cmd.StdoutPipe()
-	if err != nil {
-		return err
-	}
-
-	readPipe := io.ReadCloser(stdout)
-	if readWrapper != nil {
-		readPipe = readWrapper(stdout)
-	}
-
-	stderr, err := cmd.StderrPipe()
-	if err != nil {
-		return err
-	}
-
-	err = cmd.Start()
-	if err != nil {
-		return err
-	}
-
-	<-shared.WebsocketSendStream(conn, readPipe, 4*1024*1024)
-
-	output, err := ioutil.ReadAll(stderr)
-	if err != nil {
-		logger.Errorf("Problem reading btrfs send stderr: %s", err)
-	}
-
-	err = cmd.Wait()
-	if err != nil {
-		logger.Errorf("Problem with btrfs send: %s", string(output))
-	}
-
-	return err
-}
-
-func (s *btrfsMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn, op *operation, bwlimit string, containerOnly bool) error {
-	_, containerPool, _ := s.container.Storage().GetContainerPoolInfo()
-	containerName := s.container.Name()
-	containersPath := driver.GetContainerMountPoint("default", containerPool, "")
-	sourceName := containerName
-
-	// Deal with sending a snapshot to create a container on another LXD
-	// instance.
-	if s.container.IsSnapshot() {
-		sourceName, _, _ := shared.ContainerGetParentAndSnapshotName(containerName)
-		snapshotsPath := driver.GetSnapshotMountPoint(s.container.Project(), containerPool, sourceName)
-		tmpContainerMntPoint, err := ioutil.TempDir(snapshotsPath, sourceName)
-		if err != nil {
-			return err
-		}
-		defer os.RemoveAll(tmpContainerMntPoint)
-
-		err = os.Chmod(tmpContainerMntPoint, 0700)
-		if err != nil {
-			return err
-		}
-
-		migrationSendSnapshot := fmt.Sprintf("%s/.migration-send", tmpContainerMntPoint)
-		snapshotMntPoint := driver.GetSnapshotMountPoint(s.container.Project(), containerPool, containerName)
-		err = s.btrfs.btrfsPoolVolumesSnapshot(snapshotMntPoint, migrationSendSnapshot, true, true)
-		if err != nil {
-			return err
-		}
-		defer btrfsSubVolumesDelete(migrationSendSnapshot)
-
-		wrapper := StorageProgressReader(op, "fs_progress", containerName)
-		return s.send(conn, migrationSendSnapshot, "", wrapper)
-	}
-
-	if !containerOnly {
-		for i, snap := range s.snapshots {
-			prev := ""
-			if i > 0 {
-				prev = driver.GetSnapshotMountPoint(snap.Project(), containerPool, s.snapshots[i-1].Name())
-			}
-
-			snapMntPoint := driver.GetSnapshotMountPoint(snap.Project(), containerPool, snap.Name())
-			wrapper := StorageProgressReader(op, "fs_progress", snap.Name())
-			if err := s.send(conn, snapMntPoint, prev, wrapper); err != nil {
-				return err
-			}
-		}
-	}
-
-	tmpContainerMntPoint, err := ioutil.TempDir(containersPath, containerName)
-	if err != nil {
-		return err
-	}
-	defer os.RemoveAll(tmpContainerMntPoint)
-
-	err = os.Chmod(tmpContainerMntPoint, 0700)
-	if err != nil {
-		return err
-	}
-
-	migrationSendSnapshot := fmt.Sprintf("%s/.migration-send", tmpContainerMntPoint)
-	containerMntPoint := driver.GetContainerMountPoint(s.container.Project(), containerPool, sourceName)
-	err = s.btrfs.btrfsPoolVolumesSnapshot(containerMntPoint, migrationSendSnapshot, true, true)
-	if err != nil {
-		return err
-	}
-	defer btrfsSubVolumesDelete(migrationSendSnapshot)
-
-	btrfsParent := ""
-	if len(s.btrfsSnapshotNames) > 0 {
-		btrfsParent = s.btrfsSnapshotNames[len(s.btrfsSnapshotNames)-1]
-	}
-
-	wrapper := StorageProgressReader(op, "fs_progress", containerName)
-	return s.send(conn, migrationSendSnapshot, btrfsParent, wrapper)
-}
-
-func (s *btrfsMigrationSourceDriver) SendAfterCheckpoint(conn *websocket.Conn, bwlimit string) error {
-	tmpPath := driver.GetSnapshotMountPoint(s.container.Project(), s.btrfs.pool.Name,
-		fmt.Sprintf("%s/.migration-send", s.container.Name()))
-	err := os.MkdirAll(tmpPath, 0711)
-	if err != nil {
-		return err
-	}
-
-	err = os.Chmod(tmpPath, 0700)
-	if err != nil {
-		return err
-	}
-
-	s.stoppedSnapName = fmt.Sprintf("%s/.root", tmpPath)
-	parentName, _, _ := shared.ContainerGetParentAndSnapshotName(s.container.Name())
-	containerMntPt := driver.GetContainerMountPoint(s.container.Project(), s.btrfs.pool.Name, parentName)
-	err = s.btrfs.btrfsPoolVolumesSnapshot(containerMntPt, s.stoppedSnapName, true, true)
-	if err != nil {
-		return err
-	}
-
-	return s.send(conn, s.stoppedSnapName, s.runningSnapName, nil)
-}
-
-func (s *btrfsMigrationSourceDriver) Cleanup() {
-	if s.stoppedSnapName != "" {
-		btrfsSubVolumesDelete(s.stoppedSnapName)
-	}
-
-	if s.runningSnapName != "" {
-		btrfsSubVolumesDelete(s.runningSnapName)
-	}
-}
-
 func (s *storageBtrfs) MigrationType() migration.MigrationFSType {
 	if s.s.OS.RunningInUserNS {
 		return migration.MigrationFSType_RSYNC
@@ -3121,12 +2957,6 @@ func (s *storageBtrfs) doCrossPoolVolumeCopy(sourcePool string, sourceName strin
 	return nil
 }
 
-func (s *btrfsMigrationSourceDriver) SendStorageVolume(conn *websocket.Conn, op *operation, bwlimit string, storage storage, volumeOnly bool) error {
-	msg := fmt.Sprintf("Function not implemented")
-	logger.Errorf(msg)
-	return fmt.Errorf(msg)
-}
-
 func (s *storageBtrfs) StorageMigrationSource(args MigrationSourceArgs) (MigrationStorageSourceDriver, error) {
 	return rsyncStorageMigrationSource(args)
 }
diff --git a/lxd/storage_migration_btrfs.go b/lxd/storage_migration_btrfs.go
new file mode 100644
index 0000000000..2f58187467
--- /dev/null
+++ b/lxd/storage_migration_btrfs.go
@@ -0,0 +1,185 @@
+package main
+
+import (
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"os/exec"
+
+	"github.com/gorilla/websocket"
+
+	driver "github.com/lxc/lxd/lxd/storage"
+	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
+)
+
+type btrfsMigrationSourceDriver struct {
+	container          container
+	snapshots          []container
+	btrfsSnapshotNames []string
+	btrfs              *storageBtrfs
+	runningSnapName    string
+	stoppedSnapName    string
+}
+
+func (s *btrfsMigrationSourceDriver) send(conn *websocket.Conn, btrfsPath string, btrfsParent string, readWrapper func(io.ReadCloser) io.ReadCloser) error {
+	args := []string{"send"}
+	if btrfsParent != "" {
+		args = append(args, "-p", btrfsParent)
+	}
+	args = append(args, btrfsPath)
+
+	cmd := exec.Command("btrfs", args...)
+
+	stdout, err := cmd.StdoutPipe()
+	if err != nil {
+		return err
+	}
+
+	readPipe := io.ReadCloser(stdout)
+	if readWrapper != nil {
+		readPipe = readWrapper(stdout)
+	}
+
+	stderr, err := cmd.StderrPipe()
+	if err != nil {
+		return err
+	}
+
+	err = cmd.Start()
+	if err != nil {
+		return err
+	}
+
+	<-shared.WebsocketSendStream(conn, readPipe, 4*1024*1024)
+
+	output, err := ioutil.ReadAll(stderr)
+	if err != nil {
+		logger.Errorf("Problem reading btrfs send stderr: %s", err)
+	}
+
+	err = cmd.Wait()
+	if err != nil {
+		logger.Errorf("Problem with btrfs send: %s", string(output))
+	}
+
+	return err
+}
+
+func (s *btrfsMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn, op *operation, bwlimit string, containerOnly bool) error {
+	_, containerPool, _ := s.container.Storage().GetContainerPoolInfo()
+	containerName := s.container.Name()
+	containersPath := driver.GetContainerMountPoint("default", containerPool, "")
+	sourceName := containerName
+
+	// Deal with sending a snapshot to create a container on another LXD
+	// instance.
+	if s.container.IsSnapshot() {
+		sourceName, _, _ := shared.ContainerGetParentAndSnapshotName(containerName)
+		snapshotsPath := driver.GetSnapshotMountPoint(s.container.Project(), containerPool, sourceName)
+		tmpContainerMntPoint, err := ioutil.TempDir(snapshotsPath, sourceName)
+		if err != nil {
+			return err
+		}
+		defer os.RemoveAll(tmpContainerMntPoint)
+
+		err = os.Chmod(tmpContainerMntPoint, 0700)
+		if err != nil {
+			return err
+		}
+
+		migrationSendSnapshot := fmt.Sprintf("%s/.migration-send", tmpContainerMntPoint)
+		snapshotMntPoint := driver.GetSnapshotMountPoint(s.container.Project(), containerPool, containerName)
+		err = s.btrfs.btrfsPoolVolumesSnapshot(snapshotMntPoint, migrationSendSnapshot, true, true)
+		if err != nil {
+			return err
+		}
+		defer btrfsSubVolumesDelete(migrationSendSnapshot)
+
+		wrapper := StorageProgressReader(op, "fs_progress", containerName)
+		return s.send(conn, migrationSendSnapshot, "", wrapper)
+	}
+
+	if !containerOnly {
+		for i, snap := range s.snapshots {
+			prev := ""
+			if i > 0 {
+				prev = driver.GetSnapshotMountPoint(snap.Project(), containerPool, s.snapshots[i-1].Name())
+			}
+
+			snapMntPoint := driver.GetSnapshotMountPoint(snap.Project(), containerPool, snap.Name())
+			wrapper := StorageProgressReader(op, "fs_progress", snap.Name())
+			if err := s.send(conn, snapMntPoint, prev, wrapper); err != nil {
+				return err
+			}
+		}
+	}
+
+	tmpContainerMntPoint, err := ioutil.TempDir(containersPath, containerName)
+	if err != nil {
+		return err
+	}
+	defer os.RemoveAll(tmpContainerMntPoint)
+
+	err = os.Chmod(tmpContainerMntPoint, 0700)
+	if err != nil {
+		return err
+	}
+
+	migrationSendSnapshot := fmt.Sprintf("%s/.migration-send", tmpContainerMntPoint)
+	containerMntPoint := driver.GetContainerMountPoint(s.container.Project(), containerPool, sourceName)
+	err = s.btrfs.btrfsPoolVolumesSnapshot(containerMntPoint, migrationSendSnapshot, true, true)
+	if err != nil {
+		return err
+	}
+	defer btrfsSubVolumesDelete(migrationSendSnapshot)
+
+	btrfsParent := ""
+	if len(s.btrfsSnapshotNames) > 0 {
+		btrfsParent = s.btrfsSnapshotNames[len(s.btrfsSnapshotNames)-1]
+	}
+
+	wrapper := StorageProgressReader(op, "fs_progress", containerName)
+	return s.send(conn, migrationSendSnapshot, btrfsParent, wrapper)
+}
+
+func (s *btrfsMigrationSourceDriver) SendAfterCheckpoint(conn *websocket.Conn, bwlimit string) error {
+	tmpPath := driver.GetSnapshotMountPoint(s.container.Project(), s.btrfs.pool.Name,
+		fmt.Sprintf("%s/.migration-send", s.container.Name()))
+	err := os.MkdirAll(tmpPath, 0711)
+	if err != nil {
+		return err
+	}
+
+	err = os.Chmod(tmpPath, 0700)
+	if err != nil {
+		return err
+	}
+
+	s.stoppedSnapName = fmt.Sprintf("%s/.root", tmpPath)
+	parentName, _, _ := shared.ContainerGetParentAndSnapshotName(s.container.Name())
+	containerMntPt := driver.GetContainerMountPoint(s.container.Project(), s.btrfs.pool.Name, parentName)
+	err = s.btrfs.btrfsPoolVolumesSnapshot(containerMntPt, s.stoppedSnapName, true, true)
+	if err != nil {
+		return err
+	}
+
+	return s.send(conn, s.stoppedSnapName, s.runningSnapName, nil)
+}
+
+func (s *btrfsMigrationSourceDriver) Cleanup() {
+	if s.stoppedSnapName != "" {
+		btrfsSubVolumesDelete(s.stoppedSnapName)
+	}
+
+	if s.runningSnapName != "" {
+		btrfsSubVolumesDelete(s.runningSnapName)
+	}
+}
+
+func (s *btrfsMigrationSourceDriver) SendStorageVolume(conn *websocket.Conn, op *operation, bwlimit string, storage storage, volumeOnly bool) error {
+	msg := fmt.Sprintf("Function not implemented")
+	logger.Errorf(msg)
+	return fmt.Errorf(msg)
+}

From 2264dbe92e20196f7d1713184a3e18083dd843dc Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Fri, 30 Aug 2019 20:04:04 +0200
Subject: [PATCH 5/6] lxd: Move zfs migration code

The migration code needs to be moved to its own file since
storage_zfs.go will be removed in the future. The code however needs
to stay in the main package (for now) since it depends on the container
interface.

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/storage_migration_zfs.go | 146 +++++++++++++++++++++++++++++++++++
 lxd/storage_zfs.go           | 131 -------------------------------
 2 files changed, 146 insertions(+), 131 deletions(-)
 create mode 100644 lxd/storage_migration_zfs.go

diff --git a/lxd/storage_migration_zfs.go b/lxd/storage_migration_zfs.go
new file mode 100644
index 0000000000..fcfad12e5c
--- /dev/null
+++ b/lxd/storage_migration_zfs.go
@@ -0,0 +1,146 @@
+package main
+
+import (
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os/exec"
+
+	"github.com/gorilla/websocket"
+	"github.com/pborman/uuid"
+
+	"github.com/lxc/lxd/lxd/project"
+	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
+)
+
+type zfsMigrationSourceDriver struct {
+	container        container
+	snapshots        []container
+	zfsSnapshotNames []string
+	zfs              *storageZfs
+	runningSnapName  string
+	stoppedSnapName  string
+	zfsFeatures      []string
+}
+
+func (s *zfsMigrationSourceDriver) send(conn *websocket.Conn, zfsName string, zfsParent string, readWrapper func(io.ReadCloser) io.ReadCloser) error {
+	sourceParentName, _, _ := shared.ContainerGetParentAndSnapshotName(s.container.Name())
+	poolName := s.zfs.getOnDiskPoolName()
+	args := []string{"send"}
+
+	// Negotiated options
+	if s.zfsFeatures != nil && len(s.zfsFeatures) > 0 {
+		if shared.StringInSlice("compress", s.zfsFeatures) {
+			args = append(args, "-c")
+			args = append(args, "-L")
+		}
+	}
+
+	args = append(args, []string{fmt.Sprintf("%s/containers/%s@%s", poolName, project.Prefix(s.container.Project(), sourceParentName), zfsName)}...)
+	if zfsParent != "" {
+		args = append(args, "-i", fmt.Sprintf("%s/containers/%s@%s", poolName, project.Prefix(s.container.Project(), s.container.Name()), zfsParent))
+	}
+
+	cmd := exec.Command("zfs", args...)
+
+	stdout, err := cmd.StdoutPipe()
+	if err != nil {
+		return err
+	}
+
+	readPipe := io.ReadCloser(stdout)
+	if readWrapper != nil {
+		readPipe = readWrapper(stdout)
+	}
+
+	stderr, err := cmd.StderrPipe()
+	if err != nil {
+		return err
+	}
+
+	if err := cmd.Start(); err != nil {
+		return err
+	}
+
+	<-shared.WebsocketSendStream(conn, readPipe, 4*1024*1024)
+
+	output, err := ioutil.ReadAll(stderr)
+	if err != nil {
+		logger.Errorf("Problem reading zfs send stderr: %s", err)
+	}
+
+	err = cmd.Wait()
+	if err != nil {
+		logger.Errorf("Problem with zfs send: %s", string(output))
+	}
+
+	return err
+}
+
+func (s *zfsMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn, op *operation, bwlimit string, containerOnly bool) error {
+	if s.container.IsSnapshot() {
+		_, snapOnlyName, _ := shared.ContainerGetParentAndSnapshotName(s.container.Name())
+		snapshotName := fmt.Sprintf("snapshot-%s", snapOnlyName)
+		wrapper := StorageProgressReader(op, "fs_progress", s.container.Name())
+		return s.send(conn, snapshotName, "", wrapper)
+	}
+
+	lastSnap := ""
+	if !containerOnly {
+		for i, snap := range s.zfsSnapshotNames {
+			prev := ""
+			if i > 0 {
+				prev = s.zfsSnapshotNames[i-1]
+			}
+
+			lastSnap = snap
+
+			wrapper := StorageProgressReader(op, "fs_progress", snap)
+			if err := s.send(conn, snap, prev, wrapper); err != nil {
+				return err
+			}
+		}
+	}
+
+	s.runningSnapName = fmt.Sprintf("migration-send-%s", uuid.NewRandom().String())
+	if err := zfsPoolVolumeSnapshotCreate(s.zfs.getOnDiskPoolName(), fmt.Sprintf("containers/%s", project.Prefix(s.container.Project(), s.container.Name())), s.runningSnapName); err != nil {
+		return err
+	}
+
+	wrapper := StorageProgressReader(op, "fs_progress", s.container.Name())
+	if err := s.send(conn, s.runningSnapName, lastSnap, wrapper); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (s *zfsMigrationSourceDriver) SendAfterCheckpoint(conn *websocket.Conn, bwlimit string) error {
+	s.stoppedSnapName = fmt.Sprintf("migration-send-%s", uuid.NewRandom().String())
+	if err := zfsPoolVolumeSnapshotCreate(s.zfs.getOnDiskPoolName(), fmt.Sprintf("containers/%s", project.Prefix(s.container.Project(), s.container.Name())), s.stoppedSnapName); err != nil {
+		return err
+	}
+
+	if err := s.send(conn, s.stoppedSnapName, s.runningSnapName, nil); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (s *zfsMigrationSourceDriver) Cleanup() {
+	poolName := s.zfs.getOnDiskPoolName()
+	if s.stoppedSnapName != "" {
+		zfsPoolVolumeSnapshotDestroy(poolName, fmt.Sprintf("containers/%s", project.Prefix(s.container.Project(), s.container.Name())), s.stoppedSnapName)
+	}
+	if s.runningSnapName != "" {
+		zfsPoolVolumeSnapshotDestroy(poolName, fmt.Sprintf("containers/%s", project.Prefix(s.container.Project(), s.container.Name())), s.runningSnapName)
+	}
+}
+
+func (s *zfsMigrationSourceDriver) SendStorageVolume(conn *websocket.Conn, op *operation, bwlimit string, storage storage, volumeOnly bool) error {
+	msg := fmt.Sprintf("Function not implemented")
+	logger.Errorf(msg)
+	return fmt.Errorf(msg)
+}
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index 0087bd098c..93585d1ba6 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -2506,131 +2506,6 @@ func (s *storageZfs) ImageUmount(fingerprint string) (bool, error) {
 	return true, nil
 }
 
-type zfsMigrationSourceDriver struct {
-	container        container
-	snapshots        []container
-	zfsSnapshotNames []string
-	zfs              *storageZfs
-	runningSnapName  string
-	stoppedSnapName  string
-	zfsFeatures      []string
-}
-
-func (s *zfsMigrationSourceDriver) send(conn *websocket.Conn, zfsName string, zfsParent string, readWrapper func(io.ReadCloser) io.ReadCloser) error {
-	sourceParentName, _, _ := shared.ContainerGetParentAndSnapshotName(s.container.Name())
-	poolName := s.zfs.getOnDiskPoolName()
-	args := []string{"send"}
-
-	// Negotiated options
-	if s.zfsFeatures != nil && len(s.zfsFeatures) > 0 {
-		if shared.StringInSlice("compress", s.zfsFeatures) {
-			args = append(args, "-c")
-			args = append(args, "-L")
-		}
-	}
-
-	args = append(args, []string{fmt.Sprintf("%s/containers/%s@%s", poolName, project.Prefix(s.container.Project(), sourceParentName), zfsName)}...)
-	if zfsParent != "" {
-		args = append(args, "-i", fmt.Sprintf("%s/containers/%s@%s", poolName, project.Prefix(s.container.Project(), s.container.Name()), zfsParent))
-	}
-
-	cmd := exec.Command("zfs", args...)
-
-	stdout, err := cmd.StdoutPipe()
-	if err != nil {
-		return err
-	}
-
-	readPipe := io.ReadCloser(stdout)
-	if readWrapper != nil {
-		readPipe = readWrapper(stdout)
-	}
-
-	stderr, err := cmd.StderrPipe()
-	if err != nil {
-		return err
-	}
-
-	if err := cmd.Start(); err != nil {
-		return err
-	}
-
-	<-shared.WebsocketSendStream(conn, readPipe, 4*1024*1024)
-
-	output, err := ioutil.ReadAll(stderr)
-	if err != nil {
-		logger.Errorf("Problem reading zfs send stderr: %s", err)
-	}
-
-	err = cmd.Wait()
-	if err != nil {
-		logger.Errorf("Problem with zfs send: %s", string(output))
-	}
-
-	return err
-}
-
-func (s *zfsMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn, op *operation, bwlimit string, containerOnly bool) error {
-	if s.container.IsSnapshot() {
-		_, snapOnlyName, _ := shared.ContainerGetParentAndSnapshotName(s.container.Name())
-		snapshotName := fmt.Sprintf("snapshot-%s", snapOnlyName)
-		wrapper := StorageProgressReader(op, "fs_progress", s.container.Name())
-		return s.send(conn, snapshotName, "", wrapper)
-	}
-
-	lastSnap := ""
-	if !containerOnly {
-		for i, snap := range s.zfsSnapshotNames {
-			prev := ""
-			if i > 0 {
-				prev = s.zfsSnapshotNames[i-1]
-			}
-
-			lastSnap = snap
-
-			wrapper := StorageProgressReader(op, "fs_progress", snap)
-			if err := s.send(conn, snap, prev, wrapper); err != nil {
-				return err
-			}
-		}
-	}
-
-	s.runningSnapName = fmt.Sprintf("migration-send-%s", uuid.NewRandom().String())
-	if err := zfsPoolVolumeSnapshotCreate(s.zfs.getOnDiskPoolName(), fmt.Sprintf("containers/%s", project.Prefix(s.container.Project(), s.container.Name())), s.runningSnapName); err != nil {
-		return err
-	}
-
-	wrapper := StorageProgressReader(op, "fs_progress", s.container.Name())
-	if err := s.send(conn, s.runningSnapName, lastSnap, wrapper); err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (s *zfsMigrationSourceDriver) SendAfterCheckpoint(conn *websocket.Conn, bwlimit string) error {
-	s.stoppedSnapName = fmt.Sprintf("migration-send-%s", uuid.NewRandom().String())
-	if err := zfsPoolVolumeSnapshotCreate(s.zfs.getOnDiskPoolName(), fmt.Sprintf("containers/%s", project.Prefix(s.container.Project(), s.container.Name())), s.stoppedSnapName); err != nil {
-		return err
-	}
-
-	if err := s.send(conn, s.stoppedSnapName, s.runningSnapName, nil); err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (s *zfsMigrationSourceDriver) Cleanup() {
-	poolName := s.zfs.getOnDiskPoolName()
-	if s.stoppedSnapName != "" {
-		zfsPoolVolumeSnapshotDestroy(poolName, fmt.Sprintf("containers/%s", project.Prefix(s.container.Project(), s.container.Name())), s.stoppedSnapName)
-	}
-	if s.runningSnapName != "" {
-		zfsPoolVolumeSnapshotDestroy(poolName, fmt.Sprintf("containers/%s", project.Prefix(s.container.Project(), s.container.Name())), s.runningSnapName)
-	}
-}
-
 func (s *storageZfs) MigrationType() migration.MigrationFSType {
 	return migration.MigrationFSType_ZFS
 }
@@ -3325,12 +3200,6 @@ func (s *storageZfs) StoragePoolVolumeCopy(source *api.StorageVolumeSource) erro
 	return nil
 }
 
-func (s *zfsMigrationSourceDriver) SendStorageVolume(conn *websocket.Conn, op *operation, bwlimit string, storage storage, volumeOnly bool) error {
-	msg := fmt.Sprintf("Function not implemented")
-	logger.Errorf(msg)
-	return fmt.Errorf(msg)
-}
-
 func (s *storageZfs) StorageMigrationSource(args MigrationSourceArgs) (MigrationStorageSourceDriver, error) {
 	return rsyncStorageMigrationSource(args)
 }

From 757e7f035825805fafba50fbd5cc68b94135ef04 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Fri, 30 Aug 2019 20:08:11 +0200
Subject: [PATCH 6/6] lxd: Move ceph migration code

The migration code needs to be moved to its own file since
storage_ceph.go will be removed in the future. The code however needs
to stay in the main package (for now) since it depends on the container
interface.

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/storage_ceph.go                 | 256 +++++++++++++++++++
 lxd/storage_ceph_migration.go       | 366 ----------------------------
 lxd/storage_ceph_migration_utils.go | 128 ----------
 lxd/storage_migration_ceph.go       | 225 +++++++++++++++++
 4 files changed, 481 insertions(+), 494 deletions(-)
 delete mode 100644 lxd/storage_ceph_migration.go
 delete mode 100644 lxd/storage_ceph_migration_utils.go
 create mode 100644 lxd/storage_migration_ceph.go

diff --git a/lxd/storage_ceph.go b/lxd/storage_ceph.go
index ddbc1d7f16..e28fb838b6 100644
--- a/lxd/storage_ceph.go
+++ b/lxd/storage_ceph.go
@@ -7,6 +7,7 @@ import (
 	"io"
 	"io/ioutil"
 	"os"
+	"os/exec"
 	"strings"
 
 	"github.com/gorilla/websocket"
@@ -14,6 +15,7 @@ import (
 	"golang.org/x/sys/unix"
 
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/migration"
 	"github.com/lxc/lxd/lxd/project"
 	driver "github.com/lxc/lxd/lxd/storage"
 	"github.com/lxc/lxd/shared"
@@ -2804,3 +2806,257 @@ func (s *storageCeph) StoragePoolVolumeSnapshotRename(newName string) error {
 
 	return s.s.Cluster.StoragePoolVolumeRename("default", s.volume.Name, fullSnapshotName, storagePoolVolumeTypeCustom, s.poolID)
 }
+
+func (s *storageCeph) MigrationType() migration.MigrationFSType {
+	return migration.MigrationFSType_RBD
+}
+
+func (s *storageCeph) PreservesInodes() bool {
+	return false
+}
+
+func (s *storageCeph) MigrationSource(args MigrationSourceArgs) (MigrationStorageSourceDriver, error) {
+	// If the container is a snapshot, let's just send that. We don't need
+	// to send anything else, because that's all the user asked for.
+	if args.Container.IsSnapshot() {
+		return &rbdMigrationSourceDriver{
+			container: args.Container,
+			ceph:      s,
+		}, nil
+	}
+
+	driver := rbdMigrationSourceDriver{
+		container:        args.Container,
+		snapshots:        []container{},
+		rbdSnapshotNames: []string{},
+		ceph:             s,
+	}
+
+	containerName := args.Container.Name()
+	if args.ContainerOnly {
+		logger.Debugf(`Only migrating the RBD storage volume for container "%s" on storage pool "%s`, containerName, s.pool.Name)
+		return &driver, nil
+	}
+
+	// List all the snapshots in order of reverse creation. The idea here is
+	// that we send the oldest to newest snapshot, hopefully saving on xfer
+	// costs. Then, after all that, we send the container itself.
+	snapshots, err := cephRBDVolumeListSnapshots(s.ClusterName,
+		s.OSDPoolName, project.Prefix(args.Container.Project(), containerName),
+		storagePoolVolumeTypeNameContainer, s.UserName)
+	if err != nil {
+		if err != db.ErrNoSuchObject {
+			logger.Errorf(`Failed to list snapshots for RBD storage volume "%s" on storage pool "%s": %s`, containerName, s.pool.Name, err)
+			return nil, err
+		}
+	}
+	logger.Debugf(`Retrieved snapshots "%v" for RBD storage volume "%s" on storage pool "%s"`, snapshots, containerName, s.pool.Name)
+
+	for _, snap := range snapshots {
+		// In the case of e.g. multiple copies running at the same time,
+		// we will have potentially multiple migration-send snapshots.
+		// (Or in the case of the test suite, sometimes one will take
+		// too long to delete.)
+		if !strings.HasPrefix(snap, "snapshot_") {
+			continue
+		}
+
+		lxdName := fmt.Sprintf("%s%s%s", containerName, shared.SnapshotDelimiter, snap[len("snapshot_"):])
+		snapshot, err := containerLoadByProjectAndName(s.s, args.Container.Project(), lxdName)
+		if err != nil {
+			logger.Errorf(`Failed to load snapshot "%s" for RBD storage volume "%s" on storage pool "%s": %s`, lxdName, containerName, s.pool.Name, err)
+			return nil, err
+		}
+
+		driver.snapshots = append(driver.snapshots, snapshot)
+		driver.rbdSnapshotNames = append(driver.rbdSnapshotNames, snap)
+	}
+
+	return &driver, nil
+}
+
+func (s *storageCeph) MigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+	// Check that we received a valid root disk device with a pool property
+	// set.
+	parentStoragePool := ""
+	parentExpandedDevices := args.Container.ExpandedDevices()
+	parentLocalRootDiskDeviceKey, parentLocalRootDiskDevice, _ := shared.GetRootDiskDevice(parentExpandedDevices)
+	if parentLocalRootDiskDeviceKey != "" {
+		parentStoragePool = parentLocalRootDiskDevice["pool"]
+	}
+
+	// A little neuroticism.
+	if parentStoragePool == "" {
+		return fmt.Errorf(`Detected that the container's root device ` +
+			`is missing the pool property during RBD migration`)
+	}
+	logger.Debugf(`Detected root disk device with pool property set to "%s" during RBD migration`, parentStoragePool)
+
+	// create empty volume for container
+	// TODO: The cluster name can be different between LXD instances. Find
+	// out what to do in this case. Maybe I'm overthinking this and if the
+	// pool exists and we were able to initialize a new storage interface on
+	// the receiving LXD instance it also means that s.ClusterName has been
+	// set to the correct cluster name for that LXD instance. Yeah, I think
+	// that's actually correct.
+	containerName := args.Container.Name()
+	if !cephRBDVolumeExists(s.ClusterName, s.OSDPoolName, project.Prefix(args.Container.Project(), containerName), storagePoolVolumeTypeNameContainer, s.UserName) {
+		err := cephRBDVolumeCreate(s.ClusterName, s.OSDPoolName, project.Prefix(args.Container.Project(), containerName), storagePoolVolumeTypeNameContainer, "0", s.UserName)
+		if err != nil {
+			logger.Errorf(`Failed to create RBD storage volume "%s" for cluster "%s" in OSD pool "%s" on storage pool "%s": %s`, containerName, s.ClusterName, s.OSDPoolName, s.pool.Name, err)
+			return err
+		}
+		logger.Debugf(`Created RBD storage volume "%s" on storage pool "%s"`, containerName, s.pool.Name)
+	}
+
+	if len(args.Snapshots) > 0 {
+		snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "containers-snapshots", project.Prefix(args.Container.Project(), containerName))
+		snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(args.Container.Project(), containerName))
+		if !shared.PathExists(snapshotMntPointSymlink) {
+			err := os.Symlink(snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	// Now we're ready to receive the actual fs.
+	recvName := fmt.Sprintf("%s/container_%s", s.OSDPoolName, project.Prefix(args.Container.Project(), containerName))
+	for _, snap := range args.Snapshots {
+		curSnapName := snap.GetName()
+		ctArgs := snapshotProtobufToContainerArgs(args.Container.Project(), containerName, 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 ctArgs.Devices != nil {
+			snapLocalRootDiskDeviceKey, _, _ := shared.GetRootDiskDevice(ctArgs.Devices)
+			if snapLocalRootDiskDeviceKey != "" {
+				ctArgs.Devices[snapLocalRootDiskDeviceKey]["pool"] = parentStoragePool
+			}
+		}
+		_, err := containerCreateEmptySnapshot(args.Container.DaemonState(), ctArgs)
+		if err != nil {
+			logger.Errorf(`Failed to create empty RBD storage volume for container "%s" on storage pool "%s: %s`, containerName, s.OSDPoolName, err)
+			return err
+		}
+		logger.Debugf(`Created empty RBD storage volume for container "%s" on storage pool "%s`, containerName, s.OSDPoolName)
+
+		wrapper := StorageProgressWriter(op, "fs_progress", curSnapName)
+		err = s.rbdRecv(conn, recvName, wrapper)
+		if err != nil {
+			logger.Errorf(`Failed to receive RBD storage volume "%s": %s`, curSnapName, err)
+			return err
+		}
+		logger.Debugf(`Received RBD storage volume "%s"`, curSnapName)
+
+		snapshotMntPoint := driver.GetSnapshotMountPoint(args.Container.Project(), s.pool.Name, fmt.Sprintf("%s/%s", containerName, *snap.Name))
+		if !shared.PathExists(snapshotMntPoint) {
+			err := os.MkdirAll(snapshotMntPoint, 0700)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	defer func() {
+		snaps, err := cephRBDVolumeListSnapshots(s.ClusterName, s.OSDPoolName, project.Prefix(args.Container.Project(), containerName), storagePoolVolumeTypeNameContainer, s.UserName)
+		if err == nil {
+			for _, snap := range snaps {
+				snapOnlyName, _, _ := shared.ContainerGetParentAndSnapshotName(snap)
+				if !strings.HasPrefix(snapOnlyName, "migration-send") {
+					continue
+				}
+
+				err := cephRBDSnapshotDelete(s.ClusterName, s.OSDPoolName, project.Prefix(args.Container.Project(), containerName), storagePoolVolumeTypeNameContainer, snapOnlyName, s.UserName)
+				if err != nil {
+					logger.Warnf(`Failed to delete RBD container storage for snapshot "%s" of container "%s"`, snapOnlyName, containerName)
+				}
+			}
+		}
+	}()
+
+	// receive the container itself
+	wrapper := StorageProgressWriter(op, "fs_progress", containerName)
+	err := s.rbdRecv(conn, recvName, wrapper)
+	if err != nil {
+		logger.Errorf(`Failed to receive RBD storage volume "%s": %s`, recvName, err)
+		return err
+	}
+	logger.Debugf(`Received RBD storage volume "%s"`, recvName)
+
+	if args.Live {
+		err := s.rbdRecv(conn, recvName, wrapper)
+		if err != nil {
+			logger.Errorf(`Failed to receive RBD storage volume "%s": %s`, recvName, err)
+			return err
+		}
+		logger.Debugf(`Received RBD storage volume "%s"`, recvName)
+	}
+
+	// Re-generate the UUID
+	err = s.cephRBDGenerateUUID(project.Prefix(args.Container.Project(), args.Container.Name()), storagePoolVolumeTypeNameContainer)
+	if err != nil {
+		return err
+	}
+
+	containerMntPoint := driver.GetContainerMountPoint(args.Container.Project(), s.pool.Name, containerName)
+	err = driver.CreateContainerMountpoint(
+		containerMntPoint,
+		args.Container.Path(),
+		args.Container.IsPrivileged())
+	if err != nil {
+		logger.Errorf(`Failed to create mountpoint "%s" for RBD storage volume for container "%s" on storage pool "%s": %s"`, containerMntPoint, containerName, s.pool.Name, err)
+		return err
+	}
+	logger.Debugf(`Created mountpoint "%s" for RBD storage volume for container "%s" on storage pool "%s""`, containerMntPoint, containerName, s.pool.Name)
+
+	return nil
+}
+func (s *storageCeph) rbdRecv(conn *websocket.Conn,
+	volumeName string,
+	writeWrapper func(io.WriteCloser) io.WriteCloser) error {
+	args := []string{
+		"import-diff",
+		"--cluster", s.ClusterName,
+		"-",
+		volumeName,
+	}
+
+	cmd := exec.Command("rbd", args...)
+
+	stdin, err := cmd.StdinPipe()
+	if err != nil {
+		return err
+	}
+
+	stderr, err := cmd.StderrPipe()
+	if err != nil {
+		return err
+	}
+
+	err = cmd.Start()
+	if err != nil {
+		return err
+	}
+
+	writePipe := io.WriteCloser(stdin)
+	if writeWrapper != nil {
+		writePipe = writeWrapper(stdin)
+	}
+
+	<-shared.WebsocketRecvStream(writePipe, conn)
+
+	output, err := ioutil.ReadAll(stderr)
+	if err != nil {
+		logger.Debugf(`Failed to read stderr output from "rbd import-diff": %s`, err)
+	}
+
+	err = cmd.Wait()
+	if err != nil {
+		logger.Errorf(`Failed to perform "rbd import-diff": %s`, string(output))
+	}
+
+	return err
+}
diff --git a/lxd/storage_ceph_migration.go b/lxd/storage_ceph_migration.go
deleted file mode 100644
index 57e7839ef1..0000000000
--- a/lxd/storage_ceph_migration.go
+++ /dev/null
@@ -1,366 +0,0 @@
-package main
-
-import (
-	"fmt"
-	"os"
-	"strings"
-
-	"github.com/gorilla/websocket"
-	"github.com/pborman/uuid"
-
-	"github.com/lxc/lxd/lxd/db"
-	"github.com/lxc/lxd/lxd/migration"
-	"github.com/lxc/lxd/lxd/project"
-	driver "github.com/lxc/lxd/lxd/storage"
-	"github.com/lxc/lxd/shared"
-	"github.com/lxc/lxd/shared/logger"
-)
-
-type rbdMigrationSourceDriver struct {
-	container        container
-	snapshots        []container
-	rbdSnapshotNames []string
-	ceph             *storageCeph
-	runningSnapName  string
-	stoppedSnapName  string
-}
-
-func (s *rbdMigrationSourceDriver) Snapshots() []container {
-	return s.snapshots
-}
-
-func (s *rbdMigrationSourceDriver) Cleanup() {
-	containerName := s.container.Name()
-
-	if s.stoppedSnapName != "" {
-		err := cephRBDSnapshotDelete(s.ceph.ClusterName, s.ceph.OSDPoolName,
-			project.Prefix(s.container.Project(), containerName), storagePoolVolumeTypeNameContainer,
-			s.stoppedSnapName, s.ceph.UserName)
-		if err != nil {
-			logger.Warnf(`Failed to delete RBD snapshot "%s" of container "%s"`, s.stoppedSnapName, containerName)
-		}
-	}
-
-	if s.runningSnapName != "" {
-		err := cephRBDSnapshotDelete(s.ceph.ClusterName, s.ceph.OSDPoolName,
-			project.Prefix(s.container.Project(), containerName), storagePoolVolumeTypeNameContainer,
-			s.runningSnapName, s.ceph.UserName)
-		if err != nil {
-			logger.Warnf(`Failed to delete RBD snapshot "%s" of container "%s"`, s.runningSnapName, containerName)
-		}
-	}
-}
-
-func (s *rbdMigrationSourceDriver) SendAfterCheckpoint(conn *websocket.Conn, bwlimit string) error {
-	containerName := s.container.Name()
-	s.stoppedSnapName = fmt.Sprintf("migration-send-%s", uuid.NewRandom().String())
-	err := cephRBDSnapshotCreate(s.ceph.ClusterName, s.ceph.OSDPoolName,
-		project.Prefix(s.container.Project(), containerName), storagePoolVolumeTypeNameContainer,
-		s.stoppedSnapName, s.ceph.UserName)
-	if err != nil {
-		logger.Errorf(`Failed to create snapshot "%s" for RBD storage volume for image "%s" on storage pool "%s": %s`, s.stoppedSnapName, containerName, s.ceph.pool.Name, err)
-		return err
-	}
-
-	cur := fmt.Sprintf("%s/container_%s@%s", s.ceph.OSDPoolName,
-		project.Prefix(s.container.Project(), containerName), s.stoppedSnapName)
-	err = s.rbdSend(conn, cur, s.runningSnapName, nil)
-	if err != nil {
-		logger.Errorf(`Failed to send exported diff of RBD storage volume "%s" from snapshot "%s": %s`, cur, s.runningSnapName, err)
-		return err
-	}
-	logger.Debugf(`Sent exported diff of RBD storage volume "%s" from snapshot "%s"`, cur, s.stoppedSnapName)
-
-	return nil
-}
-
-func (s *rbdMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn,
-	op *operation, bwlimit string, containerOnly bool) error {
-	containerName := s.container.Name()
-	if s.container.IsSnapshot() {
-		// ContainerSnapshotStart() will create the clone that is
-		// referenced by sendName here.
-		containerOnlyName, snapOnlyName, _ := shared.ContainerGetParentAndSnapshotName(containerName)
-		sendName := fmt.Sprintf(
-			"%s/snapshots_%s_%s_start_clone",
-			s.ceph.OSDPoolName,
-			containerOnlyName,
-			snapOnlyName)
-		wrapper := StorageProgressReader(op, "fs_progress", containerName)
-
-		err := s.rbdSend(conn, sendName, "", wrapper)
-		if err != nil {
-			logger.Errorf(`Failed to send RBD storage volume "%s": %s`, sendName, err)
-			return err
-		}
-		logger.Debugf(`Sent RBD storage volume "%s"`, sendName)
-
-		return nil
-	}
-
-	lastSnap := ""
-	if !containerOnly {
-		for i, snap := range s.rbdSnapshotNames {
-			prev := ""
-			if i > 0 {
-				prev = s.rbdSnapshotNames[i-1]
-			}
-
-			lastSnap = snap
-
-			sendSnapName := fmt.Sprintf(
-				"%s/container_%s@%s",
-				s.ceph.OSDPoolName,
-				project.Prefix(s.container.Project(), containerName),
-				snap)
-
-			wrapper := StorageProgressReader(op, "fs_progress", snap)
-
-			err := s.rbdSend(
-				conn,
-				sendSnapName,
-				prev,
-				wrapper)
-			if err != nil {
-				logger.Errorf(`Failed to send exported diff of RBD storage volume "%s" from snapshot "%s": %s`, sendSnapName, prev, err)
-				return err
-			}
-			logger.Debugf(`Sent exported diff of RBD storage volume "%s" from snapshot "%s"`, sendSnapName, prev)
-		}
-	}
-
-	s.runningSnapName = fmt.Sprintf("migration-send-%s", uuid.NewRandom().String())
-	err := cephRBDSnapshotCreate(s.ceph.ClusterName, s.ceph.OSDPoolName,
-		project.Prefix(s.container.Project(), containerName), storagePoolVolumeTypeNameContainer,
-		s.runningSnapName, s.ceph.UserName)
-	if err != nil {
-		logger.Errorf(`Failed to create snapshot "%s" for RBD storage volume for image "%s" on storage pool "%s": %s`, s.runningSnapName, containerName, s.ceph.pool.Name, err)
-		return err
-	}
-
-	cur := fmt.Sprintf("%s/container_%s@%s", s.ceph.OSDPoolName,
-		project.Prefix(s.container.Project(), containerName), s.runningSnapName)
-	wrapper := StorageProgressReader(op, "fs_progress", containerName)
-	err = s.rbdSend(conn, cur, lastSnap, wrapper)
-	if err != nil {
-		logger.Errorf(`Failed to send exported diff of RBD storage volume "%s" from snapshot "%s": %s`, s.runningSnapName, lastSnap, err)
-		return err
-	}
-	logger.Debugf(`Sent exported diff of RBD storage volume "%s" from snapshot "%s"`, s.runningSnapName, lastSnap)
-
-	return nil
-}
-
-func (s *rbdMigrationSourceDriver) SendStorageVolume(conn *websocket.Conn, op *operation, bwlimit string, storage storage, volumeOnly bool) error {
-	msg := fmt.Sprintf("Function not implemented")
-	logger.Errorf(msg)
-	return fmt.Errorf(msg)
-}
-
-func (s *storageCeph) MigrationType() migration.MigrationFSType {
-	return migration.MigrationFSType_RBD
-}
-
-func (s *storageCeph) PreservesInodes() bool {
-	return false
-}
-
-func (s *storageCeph) MigrationSource(args MigrationSourceArgs) (MigrationStorageSourceDriver, error) {
-	// If the container is a snapshot, let's just send that. We don't need
-	// to send anything else, because that's all the user asked for.
-	if args.Container.IsSnapshot() {
-		return &rbdMigrationSourceDriver{
-			container: args.Container,
-			ceph:      s,
-		}, nil
-	}
-
-	driver := rbdMigrationSourceDriver{
-		container:        args.Container,
-		snapshots:        []container{},
-		rbdSnapshotNames: []string{},
-		ceph:             s,
-	}
-
-	containerName := args.Container.Name()
-	if args.ContainerOnly {
-		logger.Debugf(`Only migrating the RBD storage volume for container "%s" on storage pool "%s`, containerName, s.pool.Name)
-		return &driver, nil
-	}
-
-	// List all the snapshots in order of reverse creation. The idea here is
-	// that we send the oldest to newest snapshot, hopefully saving on xfer
-	// costs. Then, after all that, we send the container itself.
-	snapshots, err := cephRBDVolumeListSnapshots(s.ClusterName,
-		s.OSDPoolName, project.Prefix(args.Container.Project(), containerName),
-		storagePoolVolumeTypeNameContainer, s.UserName)
-	if err != nil {
-		if err != db.ErrNoSuchObject {
-			logger.Errorf(`Failed to list snapshots for RBD storage volume "%s" on storage pool "%s": %s`, containerName, s.pool.Name, err)
-			return nil, err
-		}
-	}
-	logger.Debugf(`Retrieved snapshots "%v" for RBD storage volume "%s" on storage pool "%s"`, snapshots, containerName, s.pool.Name)
-
-	for _, snap := range snapshots {
-		// In the case of e.g. multiple copies running at the same time,
-		// we will have potentially multiple migration-send snapshots.
-		// (Or in the case of the test suite, sometimes one will take
-		// too long to delete.)
-		if !strings.HasPrefix(snap, "snapshot_") {
-			continue
-		}
-
-		lxdName := fmt.Sprintf("%s%s%s", containerName, shared.SnapshotDelimiter, snap[len("snapshot_"):])
-		snapshot, err := containerLoadByProjectAndName(s.s, args.Container.Project(), lxdName)
-		if err != nil {
-			logger.Errorf(`Failed to load snapshot "%s" for RBD storage volume "%s" on storage pool "%s": %s`, lxdName, containerName, s.pool.Name, err)
-			return nil, err
-		}
-
-		driver.snapshots = append(driver.snapshots, snapshot)
-		driver.rbdSnapshotNames = append(driver.rbdSnapshotNames, snap)
-	}
-
-	return &driver, nil
-}
-
-func (s *storageCeph) MigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
-	// Check that we received a valid root disk device with a pool property
-	// set.
-	parentStoragePool := ""
-	parentExpandedDevices := args.Container.ExpandedDevices()
-	parentLocalRootDiskDeviceKey, parentLocalRootDiskDevice, _ := shared.GetRootDiskDevice(parentExpandedDevices)
-	if parentLocalRootDiskDeviceKey != "" {
-		parentStoragePool = parentLocalRootDiskDevice["pool"]
-	}
-
-	// A little neuroticism.
-	if parentStoragePool == "" {
-		return fmt.Errorf(`Detected that the container's root device ` +
-			`is missing the pool property during RBD migration`)
-	}
-	logger.Debugf(`Detected root disk device with pool property set to "%s" during RBD migration`, parentStoragePool)
-
-	// create empty volume for container
-	// TODO: The cluster name can be different between LXD instances. Find
-	// out what to do in this case. Maybe I'm overthinking this and if the
-	// pool exists and we were able to initialize a new storage interface on
-	// the receiving LXD instance it also means that s.ClusterName has been
-	// set to the correct cluster name for that LXD instance. Yeah, I think
-	// that's actually correct.
-	containerName := args.Container.Name()
-	if !cephRBDVolumeExists(s.ClusterName, s.OSDPoolName, project.Prefix(args.Container.Project(), containerName), storagePoolVolumeTypeNameContainer, s.UserName) {
-		err := cephRBDVolumeCreate(s.ClusterName, s.OSDPoolName, project.Prefix(args.Container.Project(), containerName), storagePoolVolumeTypeNameContainer, "0", s.UserName)
-		if err != nil {
-			logger.Errorf(`Failed to create RBD storage volume "%s" for cluster "%s" in OSD pool "%s" on storage pool "%s": %s`, containerName, s.ClusterName, s.OSDPoolName, s.pool.Name, err)
-			return err
-		}
-		logger.Debugf(`Created RBD storage volume "%s" on storage pool "%s"`, containerName, s.pool.Name)
-	}
-
-	if len(args.Snapshots) > 0 {
-		snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "containers-snapshots", project.Prefix(args.Container.Project(), containerName))
-		snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(args.Container.Project(), containerName))
-		if !shared.PathExists(snapshotMntPointSymlink) {
-			err := os.Symlink(snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
-			if err != nil {
-				return err
-			}
-		}
-	}
-
-	// Now we're ready to receive the actual fs.
-	recvName := fmt.Sprintf("%s/container_%s", s.OSDPoolName, project.Prefix(args.Container.Project(), containerName))
-	for _, snap := range args.Snapshots {
-		curSnapName := snap.GetName()
-		ctArgs := snapshotProtobufToContainerArgs(args.Container.Project(), containerName, 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 ctArgs.Devices != nil {
-			snapLocalRootDiskDeviceKey, _, _ := shared.GetRootDiskDevice(ctArgs.Devices)
-			if snapLocalRootDiskDeviceKey != "" {
-				ctArgs.Devices[snapLocalRootDiskDeviceKey]["pool"] = parentStoragePool
-			}
-		}
-		_, err := containerCreateEmptySnapshot(args.Container.DaemonState(), ctArgs)
-		if err != nil {
-			logger.Errorf(`Failed to create empty RBD storage volume for container "%s" on storage pool "%s: %s`, containerName, s.OSDPoolName, err)
-			return err
-		}
-		logger.Debugf(`Created empty RBD storage volume for container "%s" on storage pool "%s`, containerName, s.OSDPoolName)
-
-		wrapper := StorageProgressWriter(op, "fs_progress", curSnapName)
-		err = s.rbdRecv(conn, recvName, wrapper)
-		if err != nil {
-			logger.Errorf(`Failed to receive RBD storage volume "%s": %s`, curSnapName, err)
-			return err
-		}
-		logger.Debugf(`Received RBD storage volume "%s"`, curSnapName)
-
-		snapshotMntPoint := driver.GetSnapshotMountPoint(args.Container.Project(), s.pool.Name, fmt.Sprintf("%s/%s", containerName, *snap.Name))
-		if !shared.PathExists(snapshotMntPoint) {
-			err := os.MkdirAll(snapshotMntPoint, 0700)
-			if err != nil {
-				return err
-			}
-		}
-	}
-
-	defer func() {
-		snaps, err := cephRBDVolumeListSnapshots(s.ClusterName, s.OSDPoolName, project.Prefix(args.Container.Project(), containerName), storagePoolVolumeTypeNameContainer, s.UserName)
-		if err == nil {
-			for _, snap := range snaps {
-				snapOnlyName, _, _ := shared.ContainerGetParentAndSnapshotName(snap)
-				if !strings.HasPrefix(snapOnlyName, "migration-send") {
-					continue
-				}
-
-				err := cephRBDSnapshotDelete(s.ClusterName, s.OSDPoolName, project.Prefix(args.Container.Project(), containerName), storagePoolVolumeTypeNameContainer, snapOnlyName, s.UserName)
-				if err != nil {
-					logger.Warnf(`Failed to delete RBD container storage for snapshot "%s" of container "%s"`, snapOnlyName, containerName)
-				}
-			}
-		}
-	}()
-
-	// receive the container itself
-	wrapper := StorageProgressWriter(op, "fs_progress", containerName)
-	err := s.rbdRecv(conn, recvName, wrapper)
-	if err != nil {
-		logger.Errorf(`Failed to receive RBD storage volume "%s": %s`, recvName, err)
-		return err
-	}
-	logger.Debugf(`Received RBD storage volume "%s"`, recvName)
-
-	if args.Live {
-		err := s.rbdRecv(conn, recvName, wrapper)
-		if err != nil {
-			logger.Errorf(`Failed to receive RBD storage volume "%s": %s`, recvName, err)
-			return err
-		}
-		logger.Debugf(`Received RBD storage volume "%s"`, recvName)
-	}
-
-	// Re-generate the UUID
-	err = s.cephRBDGenerateUUID(project.Prefix(args.Container.Project(), args.Container.Name()), storagePoolVolumeTypeNameContainer)
-	if err != nil {
-		return err
-	}
-
-	containerMntPoint := driver.GetContainerMountPoint(args.Container.Project(), s.pool.Name, containerName)
-	err = driver.CreateContainerMountpoint(
-		containerMntPoint,
-		args.Container.Path(),
-		args.Container.IsPrivileged())
-	if err != nil {
-		logger.Errorf(`Failed to create mountpoint "%s" for RBD storage volume for container "%s" on storage pool "%s": %s"`, containerMntPoint, containerName, s.pool.Name, err)
-		return err
-	}
-	logger.Debugf(`Created mountpoint "%s" for RBD storage volume for container "%s" on storage pool "%s""`, containerMntPoint, containerName, s.pool.Name)
-
-	return nil
-}
diff --git a/lxd/storage_ceph_migration_utils.go b/lxd/storage_ceph_migration_utils.go
deleted file mode 100644
index 77caee4d8d..0000000000
--- a/lxd/storage_ceph_migration_utils.go
+++ /dev/null
@@ -1,128 +0,0 @@
-package main
-
-import (
-	"io"
-	"io/ioutil"
-	"os/exec"
-
-	"github.com/gorilla/websocket"
-
-	"github.com/lxc/lxd/shared"
-	"github.com/lxc/lxd/shared/logger"
-)
-
-// Let's say we want to send the a container "a" including snapshots "snap0" and
-// "snap1" on storage pool "pool1" from LXD "l1" to LXD "l2" on storage pool
-// "pool2":
-//
-// The pool layout on "l1" would be:
-//	pool1/container_a
-//	pool1/container_a at snapshot_snap0
-//	pool1/container_a at snapshot_snap1
-//
-// Then we need to send:
-//	rbd export-diff pool1/container_a at snapshot_snap0 - | rbd import-diff - pool2/container_a
-// (Note that pool2/container_a must have been created by the receiving LXD
-// instance before.)
-//	rbd export-diff pool1/container_a at snapshot_snap1 --from-snap snapshot_snap0 - | rbd import-diff - pool2/container_a
-//	rbd export-diff pool1/container_a --from-snap snapshot_snap1 - | rbd import-diff - pool2/container_a
-func (s *rbdMigrationSourceDriver) rbdSend(conn *websocket.Conn,
-	volumeName string,
-	volumeParentName string,
-	readWrapper func(io.ReadCloser) io.ReadCloser) error {
-	args := []string{
-		"export-diff",
-		"--cluster", s.ceph.ClusterName,
-		volumeName,
-	}
-
-	if volumeParentName != "" {
-		args = append(args, "--from-snap", volumeParentName)
-	}
-
-	// redirect output to stdout
-	args = append(args, "-")
-
-	cmd := exec.Command("rbd", args...)
-
-	stdout, err := cmd.StdoutPipe()
-	if err != nil {
-		return err
-	}
-
-	readPipe := io.ReadCloser(stdout)
-	if readWrapper != nil {
-		readPipe = readWrapper(stdout)
-	}
-
-	stderr, err := cmd.StderrPipe()
-	if err != nil {
-		return err
-	}
-
-	err = cmd.Start()
-	if err != nil {
-		return err
-	}
-
-	<-shared.WebsocketSendStream(conn, readPipe, 4*1024*1024)
-
-	output, err := ioutil.ReadAll(stderr)
-	if err != nil {
-		logger.Debugf(`Failed to read stderr output from "rbd export-diff": %s`, err)
-	}
-
-	err = cmd.Wait()
-	if err != nil {
-		logger.Errorf(`Failed to perform "rbd export-diff": %s`, string(output))
-	}
-
-	return err
-}
-
-func (s *storageCeph) rbdRecv(conn *websocket.Conn,
-	volumeName string,
-	writeWrapper func(io.WriteCloser) io.WriteCloser) error {
-	args := []string{
-		"import-diff",
-		"--cluster", s.ClusterName,
-		"-",
-		volumeName,
-	}
-
-	cmd := exec.Command("rbd", args...)
-
-	stdin, err := cmd.StdinPipe()
-	if err != nil {
-		return err
-	}
-
-	stderr, err := cmd.StderrPipe()
-	if err != nil {
-		return err
-	}
-
-	err = cmd.Start()
-	if err != nil {
-		return err
-	}
-
-	writePipe := io.WriteCloser(stdin)
-	if writeWrapper != nil {
-		writePipe = writeWrapper(stdin)
-	}
-
-	<-shared.WebsocketRecvStream(writePipe, conn)
-
-	output, err := ioutil.ReadAll(stderr)
-	if err != nil {
-		logger.Debugf(`Failed to read stderr output from "rbd import-diff": %s`, err)
-	}
-
-	err = cmd.Wait()
-	if err != nil {
-		logger.Errorf(`Failed to perform "rbd import-diff": %s`, string(output))
-	}
-
-	return err
-}
diff --git a/lxd/storage_migration_ceph.go b/lxd/storage_migration_ceph.go
new file mode 100644
index 0000000000..83cc7cfc0c
--- /dev/null
+++ b/lxd/storage_migration_ceph.go
@@ -0,0 +1,225 @@
+package main
+
+import (
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os/exec"
+
+	"github.com/gorilla/websocket"
+	"github.com/pborman/uuid"
+
+	"github.com/lxc/lxd/lxd/project"
+	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
+)
+
+type rbdMigrationSourceDriver struct {
+	container        container
+	snapshots        []container
+	rbdSnapshotNames []string
+	ceph             *storageCeph
+	runningSnapName  string
+	stoppedSnapName  string
+}
+
+func (s *rbdMigrationSourceDriver) Snapshots() []container {
+	return s.snapshots
+}
+
+func (s *rbdMigrationSourceDriver) Cleanup() {
+	containerName := s.container.Name()
+
+	if s.stoppedSnapName != "" {
+		err := cephRBDSnapshotDelete(s.ceph.ClusterName, s.ceph.OSDPoolName,
+			project.Prefix(s.container.Project(), containerName), storagePoolVolumeTypeNameContainer,
+			s.stoppedSnapName, s.ceph.UserName)
+		if err != nil {
+			logger.Warnf(`Failed to delete RBD snapshot "%s" of container "%s"`, s.stoppedSnapName, containerName)
+		}
+	}
+
+	if s.runningSnapName != "" {
+		err := cephRBDSnapshotDelete(s.ceph.ClusterName, s.ceph.OSDPoolName,
+			project.Prefix(s.container.Project(), containerName), storagePoolVolumeTypeNameContainer,
+			s.runningSnapName, s.ceph.UserName)
+		if err != nil {
+			logger.Warnf(`Failed to delete RBD snapshot "%s" of container "%s"`, s.runningSnapName, containerName)
+		}
+	}
+}
+
+func (s *rbdMigrationSourceDriver) SendAfterCheckpoint(conn *websocket.Conn, bwlimit string) error {
+	containerName := s.container.Name()
+	s.stoppedSnapName = fmt.Sprintf("migration-send-%s", uuid.NewRandom().String())
+	err := cephRBDSnapshotCreate(s.ceph.ClusterName, s.ceph.OSDPoolName,
+		project.Prefix(s.container.Project(), containerName), storagePoolVolumeTypeNameContainer,
+		s.stoppedSnapName, s.ceph.UserName)
+	if err != nil {
+		logger.Errorf(`Failed to create snapshot "%s" for RBD storage volume for image "%s" on storage pool "%s": %s`, s.stoppedSnapName, containerName, s.ceph.pool.Name, err)
+		return err
+	}
+
+	cur := fmt.Sprintf("%s/container_%s@%s", s.ceph.OSDPoolName,
+		project.Prefix(s.container.Project(), containerName), s.stoppedSnapName)
+	err = s.rbdSend(conn, cur, s.runningSnapName, nil)
+	if err != nil {
+		logger.Errorf(`Failed to send exported diff of RBD storage volume "%s" from snapshot "%s": %s`, cur, s.runningSnapName, err)
+		return err
+	}
+	logger.Debugf(`Sent exported diff of RBD storage volume "%s" from snapshot "%s"`, cur, s.stoppedSnapName)
+
+	return nil
+}
+
+func (s *rbdMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn,
+	op *operation, bwlimit string, containerOnly bool) error {
+	containerName := s.container.Name()
+	if s.container.IsSnapshot() {
+		// ContainerSnapshotStart() will create the clone that is
+		// referenced by sendName here.
+		containerOnlyName, snapOnlyName, _ := shared.ContainerGetParentAndSnapshotName(containerName)
+		sendName := fmt.Sprintf(
+			"%s/snapshots_%s_%s_start_clone",
+			s.ceph.OSDPoolName,
+			containerOnlyName,
+			snapOnlyName)
+		wrapper := StorageProgressReader(op, "fs_progress", containerName)
+
+		err := s.rbdSend(conn, sendName, "", wrapper)
+		if err != nil {
+			logger.Errorf(`Failed to send RBD storage volume "%s": %s`, sendName, err)
+			return err
+		}
+		logger.Debugf(`Sent RBD storage volume "%s"`, sendName)
+
+		return nil
+	}
+
+	lastSnap := ""
+	if !containerOnly {
+		for i, snap := range s.rbdSnapshotNames {
+			prev := ""
+			if i > 0 {
+				prev = s.rbdSnapshotNames[i-1]
+			}
+
+			lastSnap = snap
+
+			sendSnapName := fmt.Sprintf(
+				"%s/container_%s@%s",
+				s.ceph.OSDPoolName,
+				project.Prefix(s.container.Project(), containerName),
+				snap)
+
+			wrapper := StorageProgressReader(op, "fs_progress", snap)
+
+			err := s.rbdSend(
+				conn,
+				sendSnapName,
+				prev,
+				wrapper)
+			if err != nil {
+				logger.Errorf(`Failed to send exported diff of RBD storage volume "%s" from snapshot "%s": %s`, sendSnapName, prev, err)
+				return err
+			}
+			logger.Debugf(`Sent exported diff of RBD storage volume "%s" from snapshot "%s"`, sendSnapName, prev)
+		}
+	}
+
+	s.runningSnapName = fmt.Sprintf("migration-send-%s", uuid.NewRandom().String())
+	err := cephRBDSnapshotCreate(s.ceph.ClusterName, s.ceph.OSDPoolName,
+		project.Prefix(s.container.Project(), containerName), storagePoolVolumeTypeNameContainer,
+		s.runningSnapName, s.ceph.UserName)
+	if err != nil {
+		logger.Errorf(`Failed to create snapshot "%s" for RBD storage volume for image "%s" on storage pool "%s": %s`, s.runningSnapName, containerName, s.ceph.pool.Name, err)
+		return err
+	}
+
+	cur := fmt.Sprintf("%s/container_%s@%s", s.ceph.OSDPoolName,
+		project.Prefix(s.container.Project(), containerName), s.runningSnapName)
+	wrapper := StorageProgressReader(op, "fs_progress", containerName)
+	err = s.rbdSend(conn, cur, lastSnap, wrapper)
+	if err != nil {
+		logger.Errorf(`Failed to send exported diff of RBD storage volume "%s" from snapshot "%s": %s`, s.runningSnapName, lastSnap, err)
+		return err
+	}
+	logger.Debugf(`Sent exported diff of RBD storage volume "%s" from snapshot "%s"`, s.runningSnapName, lastSnap)
+
+	return nil
+}
+
+func (s *rbdMigrationSourceDriver) SendStorageVolume(conn *websocket.Conn, op *operation, bwlimit string, storage storage, volumeOnly bool) error {
+	msg := fmt.Sprintf("Function not implemented")
+	logger.Errorf(msg)
+	return fmt.Errorf(msg)
+}
+
+// Let's say we want to send the a container "a" including snapshots "snap0" and
+// "snap1" on storage pool "pool1" from LXD "l1" to LXD "l2" on storage pool
+// "pool2":
+//
+// The pool layout on "l1" would be:
+//	pool1/container_a
+//	pool1/container_a at snapshot_snap0
+//	pool1/container_a at snapshot_snap1
+//
+// Then we need to send:
+//	rbd export-diff pool1/container_a at snapshot_snap0 - | rbd import-diff - pool2/container_a
+// (Note that pool2/container_a must have been created by the receiving LXD
+// instance before.)
+//	rbd export-diff pool1/container_a at snapshot_snap1 --from-snap snapshot_snap0 - | rbd import-diff - pool2/container_a
+//	rbd export-diff pool1/container_a --from-snap snapshot_snap1 - | rbd import-diff - pool2/container_a
+func (s *rbdMigrationSourceDriver) rbdSend(conn *websocket.Conn,
+	volumeName string,
+	volumeParentName string,
+	readWrapper func(io.ReadCloser) io.ReadCloser) error {
+	args := []string{
+		"export-diff",
+		"--cluster", s.ceph.ClusterName,
+		volumeName,
+	}
+
+	if volumeParentName != "" {
+		args = append(args, "--from-snap", volumeParentName)
+	}
+
+	// redirect output to stdout
+	args = append(args, "-")
+
+	cmd := exec.Command("rbd", args...)
+
+	stdout, err := cmd.StdoutPipe()
+	if err != nil {
+		return err
+	}
+
+	readPipe := io.ReadCloser(stdout)
+	if readWrapper != nil {
+		readPipe = readWrapper(stdout)
+	}
+
+	stderr, err := cmd.StderrPipe()
+	if err != nil {
+		return err
+	}
+
+	err = cmd.Start()
+	if err != nil {
+		return err
+	}
+
+	<-shared.WebsocketSendStream(conn, readPipe, 4*1024*1024)
+
+	output, err := ioutil.ReadAll(stderr)
+	if err != nil {
+		logger.Debugf(`Failed to read stderr output from "rbd export-diff": %s`, err)
+	}
+
+	err = cmd.Wait()
+	if err != nil {
+		logger.Errorf(`Failed to perform "rbd export-diff": %s`, string(output))
+	}
+
+	return err
+}


More information about the lxc-devel mailing list