[lxc-devel] [lxd/master] Fix custom volume copies

monstermunchkin on Github lxc-bot at linuxcontainers.org
Thu Apr 4 10:57:00 UTC 2019


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 318 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20190404/b717bfc0/attachment-0001.bin>
-------------- next part --------------
From 2191b41fef4ada29f8237934b28eca6a94a13711 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Thu, 28 Mar 2019 20:52:17 +0100
Subject: [PATCH 1/8] storage: Fix copying and moving volume snapshots

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/storage_volumes.go       | 83 +-----------------------------
 lxd/storage_volumes_utils.go | 99 +++++++++++++++++-------------------
 2 files changed, 49 insertions(+), 133 deletions(-)

diff --git a/lxd/storage_volumes.go b/lxd/storage_volumes.go
index 9dcda8c89d..a5d7638a06 100644
--- a/lxd/storage_volumes.go
+++ b/lxd/storage_volumes.go
@@ -272,53 +272,7 @@ func storagePoolVolumesTypePost(d *Daemon, r *http.Request) Response {
 
 func doVolumeCreateOrCopy(d *Daemon, poolName string, req *api.StorageVolumesPost) Response {
 	doWork := func() error {
-		err := storagePoolVolumeCreateInternal(d.State(), poolName, req)
-		if err != nil {
-			return err
-		}
-
-		if req.Source.VolumeOnly {
-			return nil
-		}
-
-		if req.Source.Pool == "" {
-			return nil
-		}
-
-		// Convert the volume type name to our internal integer representation.
-		volumeType, err := storagePoolVolumeTypeNameToType(req.Type)
-		if err != nil {
-			return err
-		}
-
-		// Get poolID of source pool
-		poolID, err := d.cluster.StoragePoolGetID(req.Source.Pool)
-		if err != nil {
-			return err
-		}
-
-		// Get volumes attached to source storage volume
-		volumes, err := d.cluster.StoragePoolVolumeSnapshotsGetType(req.Source.Name, volumeType, poolID)
-		if err != nil {
-			return err
-		}
-
-		for _, vol := range volumes {
-			_, snapshotName, _ := containerGetParentAndSnapshotName(vol)
-
-			copyReq := api.StorageVolumesPost{}
-			copyReq.Name = fmt.Sprintf("%s%s%s", req.Name, shared.SnapshotDelimiter, snapshotName)
-			copyReq.Type = "custom"
-			copyReq.Source.Name = fmt.Sprintf("%s%s%s", req.Source.Name, shared.SnapshotDelimiter, snapshotName)
-			copyReq.Source.Pool = req.Source.Pool
-
-			err = storagePoolVolumeSnapshotCreateInternal(d.State(), poolName, &copyReq)
-			if err != nil {
-				return err
-			}
-		}
-
-		return nil
+		return storagePoolVolumeCreateInternal(d.State(), poolName, req)
 	}
 
 	if req.Source.Name == "" {
@@ -647,41 +601,6 @@ func storagePoolVolumeTypePost(d *Daemon, r *http.Request, volumeTypeName string
 			if err != nil {
 				return err
 			}
-
-			// Rename volume snapshots
-			// Get volumes attached to source storage volume
-			volumes, err := d.cluster.StoragePoolVolumeSnapshotsGetType(volumeName,
-				storagePoolVolumeTypeCustom, poolID)
-			if err != nil {
-				return err
-			}
-
-			for _, vol := range volumes {
-				// Rename volume snapshots
-				snapshot, err := storagePoolVolumeInit(d.State(), "default", poolName,
-					vol, storagePoolVolumeTypeCustom)
-				if err != nil {
-					return err
-				}
-
-				dstVolumeName, dstSnapshotName, _ := containerGetParentAndSnapshotName(req.Name)
-
-				moveReq := api.StorageVolumesPost{}
-				moveReq.Name = fmt.Sprintf("%s%s%s", dstVolumeName, shared.SnapshotDelimiter, dstSnapshotName)
-				moveReq.Type = "custom"
-				moveReq.Source.Name = vol
-				moveReq.Source.Pool = poolName
-
-				err = storagePoolVolumeSnapshotCreateInternal(d.State(), req.Pool, &moveReq)
-				if err != nil {
-					return err
-				}
-
-				err = snapshot.StoragePoolVolumeSnapshotDelete()
-				if err != nil {
-					return err
-				}
-			}
 		}
 
 		return nil
diff --git a/lxd/storage_volumes_utils.go b/lxd/storage_volumes_utils.go
index f8d4ab5066..268efb0062 100644
--- a/lxd/storage_volumes_utils.go
+++ b/lxd/storage_volumes_utils.go
@@ -608,75 +608,72 @@ func storagePoolVolumeCreateInternal(state *state.State, poolName string, vol *a
 		return err
 	}
 
-	if vol.Source.Name == "" {
-		err = s.StoragePoolVolumeCreate()
-	} else {
-		err = s.StoragePoolVolumeCopy(&vol.Source)
-	}
-	if err != nil {
-		poolID, _, _ := s.GetContainerPoolInfo()
+	volumeType, err1 := storagePoolVolumeTypeNameToType(vol.Type)
+	poolID, _, _ := s.GetContainerPoolInfo()
+	revert := true
 
-		volumeType, err1 := storagePoolVolumeTypeNameToType(vol.Type)
-		if err1 == nil {
+	defer func() {
+		if revert && err1 == nil {
 			state.Cluster.StoragePoolVolumeDelete("default", vol.Name, volumeType, poolID)
 		}
+	}()
 
-		return err
-	}
+	if vol.Source.Name == "" {
+		err = s.StoragePoolVolumeCreate()
+	} else {
+		if !vol.Source.VolumeOnly {
+			sourcePoolID, err := state.Cluster.StoragePoolGetID(vol.Source.Pool)
+			if err != nil {
+				return err
+			}
 
-	return nil
-}
+			snapshots, err := storagePoolVolumeSnapshotsGet(state, vol.Source.Pool, vol.Source.Name, volumeType)
+			if err != nil {
+				return err
+			}
 
-func storagePoolVolumeSnapshotCreateInternal(state *state.State, poolName string, vol *api.StorageVolumesPost) error {
-	// Get poolID of source pool
-	poolID, err := state.Cluster.StoragePoolGetID(vol.Source.Pool)
-	if err != nil {
-		return err
-	}
+			for _, snap := range snapshots {
+				_, snapOnlyName, _ := containerGetParentAndSnapshotName(snap)
 
-	// Convert the volume type name to our internal integer representation.
-	volumeType, err := storagePoolVolumeTypeNameToType(vol.Type)
-	if err != nil {
-		return err
-	}
+				volumeID, err := state.Cluster.StoragePoolNodeVolumeGetTypeID(snap, volumeType, sourcePoolID)
+				if err != nil {
+					return err
+				}
 
-	_, snapshot, err := state.Cluster.StoragePoolNodeVolumeGetType(vol.Source.Name, volumeType, poolID)
-	if err != nil {
-		return err
-	}
+				volumeConfig, err := state.Cluster.StorageVolumeConfigGet(volumeID)
+				if err != nil {
+					return err
+				}
 
-	writable := snapshot.Writable()
+				volumeDescription, err := state.Cluster.StorageVolumeDescriptionGet(volumeID)
+				if err != nil {
+					return err
+				}
 
-	dbArgs := &db.StorageVolumeArgs{
-		Name:        vol.Name,
-		PoolName:    poolName,
-		TypeName:    vol.Type,
-		Snapshot:    true,
-		Config:      writable.Config,
-		Description: writable.Description,
-	}
+				dbArgs := &db.StorageVolumeArgs{
+					Name:        fmt.Sprintf("%s/%s", vol.Name, snapOnlyName),
+					PoolName:    poolName,
+					TypeName:    vol.Type,
+					Snapshot:    true,
+					Config:      volumeConfig,
+					Description: volumeDescription,
+				}
 
-	s, err := storagePoolVolumeSnapshotDBCreateInternal(state, dbArgs)
-	if err != nil {
-		return err
-	}
+				_, err = storagePoolVolumeSnapshotDBCreateInternal(state, dbArgs)
+				if err != nil {
+					return err
+				}
+			}
+		}
 
-	if vol.Source.Name == "" {
-		err = s.StoragePoolVolumeCreate()
-	} else {
 		err = s.StoragePoolVolumeCopy(&vol.Source)
 	}
 	if err != nil {
-		poolID, _, _ := s.GetContainerPoolInfo()
-
-		volumeType, err1 := storagePoolVolumeTypeNameToType(vol.Type)
-		if err1 == nil {
-			state.Cluster.StoragePoolVolumeDelete("default", vol.Name, volumeType, poolID)
-		}
-
 		return err
 	}
 
+	revert = false
+
 	return nil
 }
 

From 2cc94df062f6de95c00c84325674d43adadd953f Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Wed, 3 Apr 2019 08:11:05 +0200
Subject: [PATCH 2/8] storage: Add helper function to get volume snapshots

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/storage_volumes_utils.go | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/lxd/storage_volumes_utils.go b/lxd/storage_volumes_utils.go
index 268efb0062..f1c4368540 100644
--- a/lxd/storage_volumes_utils.go
+++ b/lxd/storage_volumes_utils.go
@@ -705,3 +705,18 @@ func storagePoolVolumeSnapshotDBCreateInternal(state *state.State, dbArgs *db.St
 
 	return s, nil
 }
+
+// storagePoolVolumeSnapshotsGet returns a list of snapshots of the form <volume>/<snapshot-name>.
+func storagePoolVolumeSnapshotsGet(s *state.State, pool string, volume string, volType int) ([]string, error) {
+	poolID, err := s.Cluster.StoragePoolGetID(pool)
+	if err != nil {
+		return nil, err
+	}
+
+	snapshots, err := s.Cluster.StoragePoolVolumeSnapshotsGetType(volume, volType, poolID)
+	if err != nil {
+		return nil, err
+	}
+
+	return snapshots, nil
+}

From cb5b833e585aaf7b66cd677d924b336d7c52eff6 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Thu, 28 Mar 2019 16:31:18 +0100
Subject: [PATCH 3/8] storage/ceph: Fix volume copy with snapshots

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/storage_ceph.go       | 243 ++++++++++++++++++++------------------
 lxd/storage_ceph_utils.go | 193 ++++++++++++++++++++++++++++++
 2 files changed, 324 insertions(+), 112 deletions(-)

diff --git a/lxd/storage_ceph.go b/lxd/storage_ceph.go
index 8e99ff126a..447113e7b2 100644
--- a/lxd/storage_ceph.go
+++ b/lxd/storage_ceph.go
@@ -18,8 +18,6 @@ import (
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/ioprogress"
 	"github.com/lxc/lxd/shared/logger"
-
-	"github.com/pborman/uuid"
 )
 
 type storageCeph struct {
@@ -2571,147 +2569,168 @@ func (s *storageCeph) StoragePoolVolumeCopy(source *api.StorageVolumeSource) err
 	logger.Infof("Copying RBD storage volume \"%s\" on storage pool \"%s\" as \"%s\" to storage pool \"%s\"", source.Name, source.Pool, s.volume.Name, s.pool.Name)
 	successMsg := fmt.Sprintf("Copied RBD storage volume \"%s\" on storage pool \"%s\" as \"%s\" to storage pool \"%s\"", source.Name, source.Pool, s.volume.Name, s.pool.Name)
 
-	isSnapshot := strings.Contains(s.volume.Name, "/")
+	if s.pool.Name != source.Pool {
+		return s.doCrossPoolVolumeCopy(source)
+	}
 
-	var srcMountPoint string
-	var dstMountPoint string
+	snapshots, err := cephRBDVolumeListSnapshots(s.ClusterName, s.OSDPoolName, source.Name, storagePoolVolumeTypeNameCustom, s.UserName)
+	if err != nil {
+		return err
+	}
 
-	if isSnapshot {
-		srcMountPoint = getStoragePoolVolumeSnapshotMountPoint(source.Pool, source.Name)
-		dstMountPoint = getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, s.volume.Name)
+	if source.VolumeOnly || len(snapshots) == 0 {
+		if s.pool.Config["ceph.rbd.clone_copy"] != "" && !shared.IsTrue(s.pool.Config["ceph.rbd.clone_copy"]) {
+			err = s.copyVolumeWithoutSnapshotsFull(source)
+		} else {
+			err = s.copyVolumeWithoutSnapshotsSparse(source)
+		}
+		if err != nil {
+			logger.Errorf("Failed to create RBD storage volume \"%s\" on storage pool \"%s\": %s", source.Name, source.Pool, err)
+			return err
+		}
 	} else {
-		srcMountPoint = getStoragePoolVolumeMountPoint(source.Pool, source.Name)
-		dstMountPoint = getStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
-	}
+		logger.Debugf(`Creating non-sparse copy of RBD storage volume for container "%s" to "%s" including snapshots`,
+			source.Name, s.volume.Name)
+
+		revert := true
+		volumeMntPoint := getStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
+
+		err = os.MkdirAll(volumeMntPoint, 0711)
+		if err != nil {
+			logger.Errorf("Failed to create mountpoint \"%s\" for RBD storage volume \"%s\" on storage pool \"%s\": %s", volumeMntPoint, s.volume.Name, s.pool.Name, err)
+			return err
+		}
 
-	if s.pool.Name == source.Pool {
-		var oldVolumeName string
-		var newVolumeName string
+		defer func() {
+			if !revert {
+				return
+			}
 
-		if isSnapshot {
-			_, srcSnapshotOnlyName, _ := containerGetParentAndSnapshotName(source.Name)
-			_, dstSnapshotOnlyName, _ := containerGetParentAndSnapshotName(s.volume.Name)
+			err = os.RemoveAll(volumeMntPoint)
+			if err != nil {
+				logger.Warnf(`Failed to delete mountpoint "%s" for RBD storage volume "%s" on storage pool "%s": %s"`, volumeMntPoint, s.volume.Name, s.pool.Name, err)
+			}
+		}()
 
-			oldVolumeName = fmt.Sprintf("%s/snapshot_%s", s.OSDPoolName, srcSnapshotOnlyName)
-			newVolumeName = fmt.Sprintf("%s/snapshot_%s", s.OSDPoolName, dstSnapshotOnlyName)
-		} else {
-			oldVolumeName = fmt.Sprintf("%s/custom_%s", s.OSDPoolName, source.Name)
-			newVolumeName = fmt.Sprintf("%s/custom_%s", s.OSDPoolName, s.volume.Name)
+		// create empty dummy volume
+		err = cephRBDVolumeCreate(s.ClusterName, s.OSDPoolName,
+			s.volume.Name, storagePoolVolumeTypeNameCustom,
+			"0", s.UserName)
+		if err != nil {
+			logger.Errorf(`Failed to create RBD storage volume "%s" on storage pool "%s": %s`, s.volume.Name, s.pool.Name, err)
+			return err
 		}
+		logger.Debugf(`Created RBD storage volume "%s" on storage pool "%s"`,
+			s.volume.Name, s.pool.Name)
 
-		if s.pool.Config["ceph.rbd.clone_copy"] != "" && !shared.IsTrue(s.pool.Config["ceph.rbd.clone_copy"]) {
-			// create full copy
-			err := cephRBDVolumeCopy(s.ClusterName, oldVolumeName, newVolumeName, s.UserName)
+		defer func() {
+			if !revert {
+				return
+			}
+
+			err := cephRBDVolumeDelete(s.ClusterName, s.OSDPoolName,
+				s.volume.Name,
+				storagePoolVolumeTypeNameCustom, s.UserName)
 			if err != nil {
-				logger.Errorf("Failed to create non-sparse copy of RBD storage volume \"%s\" on storage pool \"%s\": %s", source.Name, source.Pool, err)
-				return err
+				logger.Warnf(`Failed to delete RBD storage volume "%s" on storage pool "%s": %s`, s.volume.Name, s.pool.Name, err)
 			}
-		} else {
-			sourceOnlyName, snapshotOnlyName, isSnapshot := containerGetParentAndSnapshotName(source.Name)
-			if isSnapshot {
-				snapshotOnlyName = fmt.Sprintf("snapshot_%s", snapshotOnlyName)
-			} else {
-				// create sparse copy
-				snapshotOnlyName = uuid.NewRandom().String()
-
-				// create snapshot of original volume
-				err := cephRBDSnapshotCreate(s.ClusterName, s.OSDPoolName, sourceOnlyName, storagePoolVolumeTypeNameCustom, snapshotOnlyName, s.UserName)
-				if err != nil {
-					logger.Errorf("Failed to create snapshot of RBD storage volume \"%s\" on storage pool \"%s\": %s", sourceOnlyName, source.Pool, err)
-					return err
-				}
+		}()
+
+		// receive over the dummy volume we created above
+		targetVolumeName := fmt.Sprintf(
+			"%s/custom_%s",
+			s.OSDPoolName,
+			s.volume.Name)
+
+		lastSnap := ""
+		for i, snap := range snapshots {
+			prev := ""
+			if i > 0 {
+				_, snapOnlyName, _ := containerGetParentAndSnapshotName(snapshots[i-1])
+				prev = fmt.Sprintf("snapshot_%s", snapOnlyName)
 			}
 
-			// protect volume so we can create clones of it
-			err := cephRBDSnapshotProtect(s.ClusterName, s.OSDPoolName, sourceOnlyName, storagePoolVolumeTypeNameCustom, snapshotOnlyName, s.UserName)
+			_, snapOnlyName, _ := containerGetParentAndSnapshotName(snap)
+			lastSnap = fmt.Sprintf("snapshot_%s", snapOnlyName)
+			sourceVolumeName := fmt.Sprintf(
+				"%s/custom_%s at snapshot_%s",
+				s.OSDPoolName,
+				source.Name,
+				snapOnlyName)
+
+			err = s.copyWithSnapshots(
+				sourceVolumeName,
+				targetVolumeName,
+				prev)
 			if err != nil {
-				logger.Errorf("Failed to protect snapshot for RBD storage volume \"%s\" on storage pool \"%s\": %s", sourceOnlyName, s.pool.Name, err)
+				logger.Errorf(`Failed to copy RBD volume storage %s to %s`, sourceVolumeName,
+					targetVolumeName)
 				return err
 			}
+			logger.Debugf(`Copied RBD volume storage %s to %s`,
+				sourceVolumeName, targetVolumeName)
+
+			defer func() {
+				if !revert {
+					return
+				}
+
+				err := cephRBDSnapshotDelete(s.ClusterName,
+					s.OSDPoolName, s.volume.Name,
+					storagePoolVolumeTypeNameCustom,
+					snapOnlyName, s.UserName)
+				if err != nil {
+					logger.Warnf(`Failed to delete RBD container storage for snapshot "%s" of container "%s"`, snapOnlyName, s.volume.Name)
+				}
+			}()
 
-			// create new clone
-			err = cephRBDCloneCreate(s.ClusterName, s.OSDPoolName, sourceOnlyName, storagePoolVolumeTypeNameCustom, snapshotOnlyName, s.OSDPoolName, s.volume.Name, storagePoolVolumeTypeNameCustom, s.UserName)
+			// create snapshot mountpoint
+			newTargetName := fmt.Sprintf("%s/%s", s.volume.Name, snapOnlyName)
+			targetPath := getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, newTargetName)
+			err = os.MkdirAll(targetPath, snapshotsDirMode)
 			if err != nil {
-				logger.Errorf("Failed to clone RBD storage volume \"%s\" on storage pool \"%s\": %s", source.Name, source.Pool, err)
+				logger.Errorf("Failed to create mountpoint \"%s\" for RBD storage volume \"%s\" on storage pool \"%s\": %s", targetPath, s.volume.Name, s.pool.Name, err)
 				return err
 			}
-		}
 
-		// Map the rbd
-		RBDDevPath, err := cephRBDVolumeMap(s.ClusterName, s.OSDPoolName, s.volume.Name, storagePoolVolumeTypeNameCustom, s.UserName)
-		if err != nil {
-			logger.Errorf("Failed to map RBD storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
-			return err
-		}
+			defer func() {
+				if !revert {
+					return
+				}
 
-		// Generate a new UUID
-		RBDFilesystem := s.getRBDFilesystem()
-		msg, err := fsGenerateNewUUID(RBDFilesystem, RBDDevPath)
-		if err != nil {
-			logger.Errorf("Failed to create new UUID for filesystem \"%s\" for RBD storage volume \"%s\" on storage pool \"%s\": %s: %s", RBDFilesystem, s.volume.Name, s.pool.Name, msg, err)
-			return err
+				err = os.RemoveAll(targetPath)
+				if err != nil {
+					logger.Errorf("Failed to delete mountpoint \"%s\" for RBD storage volume \"%s\" on storage pool \"%s\": %s", targetPath, s.volume.Name, s.pool.Name, err)
+				}
+			}()
 		}
 
-		// Unmap the rbd
-		err = cephRBDVolumeUnmap(s.ClusterName, s.OSDPoolName, s.volume.Name, storagePoolVolumeTypeNameCustom, s.UserName, true)
+		// copy snapshot
+		sourceVolumeName := fmt.Sprintf(
+			"%s/custom_%s",
+			s.OSDPoolName,
+			source.Name)
+		err = s.copyWithSnapshots(
+			sourceVolumeName,
+			targetVolumeName,
+			lastSnap)
 		if err != nil {
-			logger.Errorf("Failed to unmap RBD storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
+			logger.Errorf(`Failed to copy RBD custom storage %s to %s`, sourceVolumeName, targetVolumeName)
 			return err
 		}
+		logger.Debugf(`Copied RBD custom storage %s to %s`, sourceVolumeName, targetVolumeName)
 
-		var volumeMntPoint string
-		if isSnapshot {
-			volumeMntPoint = getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, s.volume.Name)
-		} else {
-			volumeMntPoint = getStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
-		}
-		err = os.MkdirAll(volumeMntPoint, 0711)
+		_, err = cephRBDVolumeMap(s.ClusterName, s.OSDPoolName,
+			s.volume.Name, storagePoolVolumeTypeNameCustom,
+			s.UserName)
 		if err != nil {
-			logger.Errorf("Failed to create mountpoint \"%s\" for RBD storage volume \"%s\" on storage pool \"%s\": %s", volumeMntPoint, s.volume.Name, s.pool.Name, err)
+			logger.Errorf(`Failed to map RBD storage volume for custom volume "%s" on storage pool "%s": %s`, s.volume.Name, s.pool.Name, err)
 			return err
 		}
+		logger.Debugf(`Mapped RBD storage volume for custom volume "%s" on storage pool "%s"`, s.volume.Name, s.pool.Name)
 
-		logger.Infof(successMsg)
-		return nil
-	}
-
-	// setup storage for the source volume
-	srcStorage, err := storagePoolVolumeInit(s.s, "default", source.Pool, source.Name, storagePoolVolumeTypeCustom)
-	if err != nil {
-		logger.Errorf("Failed to initialize CEPH storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
-		return err
-	}
-
-	ourMount, err := srcStorage.StoragePoolVolumeMount()
-	if err != nil {
-		logger.Errorf("Failed to mount CEPH storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
-		return err
-	}
-
-	if ourMount {
-		defer srcStorage.StoragePoolVolumeUmount()
-	}
-
-	err = s.StoragePoolVolumeCreate()
-	if err != nil {
-		logger.Errorf("Failed to create RBD storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
-		return err
-	}
-
-	ourMount, err = s.StoragePoolVolumeMount()
-	if err != nil {
-		return err
-	}
-	if ourMount {
-		defer s.StoragePoolVolumeUmount()
-	}
-
-	bwlimit := s.pool.Config["rsync.bwlimit"]
-	_, err = rsyncLocalCopy(srcMountPoint, dstMountPoint, bwlimit)
-	if err != nil {
-		os.RemoveAll(dstMountPoint)
-		logger.Errorf("Failed to rsync into RBD storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
-		return err
+		logger.Debugf(`Created non-sparse copy of RBD storage volume for custom volume "%s" to "%s" including snapshots`,
+			source.Name, s.volume.Name)
 	}
 
 	logger.Infof(successMsg)
diff --git a/lxd/storage_ceph_utils.go b/lxd/storage_ceph_utils.go
index d39da395d2..09cef4adad 100644
--- a/lxd/storage_ceph_utils.go
+++ b/lxd/storage_ceph_utils.go
@@ -12,6 +12,7 @@ import (
 
 	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logger"
 
 	"github.com/pborman/uuid"
@@ -1872,3 +1873,195 @@ func (s *storageCeph) doContainerSnapshotCreate(project, targetName string, sour
 
 	return nil
 }
+
+func (s *storageCeph) doCrossPoolVolumeCopy(source *api.StorageVolumeSource) error {
+	// setup storage for the source volume
+	srcStorage, err := storagePoolVolumeInit(s.s, "default", source.Pool, source.Name, storagePoolVolumeTypeCustom)
+	if err != nil {
+		logger.Errorf("Failed to initialize CEPH storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
+		return err
+	}
+
+	ourMount, err := srcStorage.StoragePoolMount()
+	if err != nil {
+		logger.Errorf("Failed to mount CEPH storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
+		return err
+	}
+
+	if ourMount {
+		defer srcStorage.StoragePoolUmount()
+	}
+
+	snapshots, err := storagePoolVolumeSnapshotsGet(s.s, source.Pool, source.Name, storagePoolVolumeTypeCustom)
+	if err != nil {
+		return err
+	}
+
+	err = s.StoragePoolVolumeCreate()
+	if err != nil {
+		logger.Errorf("Failed to create RBD storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
+		return err
+	}
+
+	ourMount, err = s.StoragePoolVolumeMount()
+	if err != nil {
+		return err
+	}
+	if ourMount {
+		defer s.StoragePoolVolumeUmount()
+	}
+
+	dstVolumeMntPoint := getStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
+	bwlimit := s.pool.Config["rsync.bwlimit"]
+
+	if !source.VolumeOnly {
+		for _, snap := range snapshots {
+			_, snapOnlyName, _ := containerGetParentAndSnapshotName(snap)
+			srcSnapshotMntPoint := getStoragePoolVolumeSnapshotMountPoint(source.Pool, snap)
+
+			_, err = rsyncLocalCopy(srcSnapshotMntPoint, dstVolumeMntPoint, bwlimit)
+			if err != nil {
+				return err
+			}
+
+			err = s.StoragePoolVolumeSnapshotCreate(&api.StorageVolumeSnapshotsPost{Name: fmt.Sprintf("%s/%s", s.volume.Name, snapOnlyName)})
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	var srcVolumeMntPoint string
+
+	if strings.Contains(source.Name, "/") {
+		srcVolumeMntPoint = getStoragePoolVolumeSnapshotMountPoint(source.Pool, source.Name)
+	} else {
+		srcVolumeMntPoint = getStoragePoolVolumeMountPoint(source.Pool, source.Name)
+	}
+
+	_, err = rsyncLocalCopy(srcVolumeMntPoint, dstVolumeMntPoint, bwlimit)
+	if err != nil {
+		os.RemoveAll(dstVolumeMntPoint)
+		logger.Errorf("Failed to rsync into RBD storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
+		return err
+	}
+
+	return nil
+}
+
+func (s *storageCeph) copyVolumeWithoutSnapshotsFull(source *api.StorageVolumeSource) error {
+	var oldVolumeName string
+
+	isSnapshot := strings.Contains(source.Name, "/")
+
+	if isSnapshot {
+		_, srcSnapshotOnlyName, _ := containerGetParentAndSnapshotName(source.Name)
+		oldVolumeName = fmt.Sprintf("%s/snapshot_%s", s.OSDPoolName, srcSnapshotOnlyName)
+	} else {
+		oldVolumeName = fmt.Sprintf("%s/custom_%s", s.OSDPoolName, source.Name)
+	}
+
+	newVolumeName := fmt.Sprintf("%s/custom_%s", s.OSDPoolName, s.volume.Name)
+
+	err := cephRBDVolumeCopy(s.ClusterName, oldVolumeName, newVolumeName, s.UserName)
+	if err != nil {
+		logger.Errorf("Failed to create non-sparse copy of RBD storage volume \"%s\" on storage pool \"%s\": %s", source.Name, source.Pool, err)
+		return err
+	}
+
+	// Map the rbd
+	RBDDevPath, err := cephRBDVolumeMap(s.ClusterName, s.OSDPoolName, s.volume.Name, storagePoolVolumeTypeNameCustom, s.UserName)
+	if err != nil {
+		logger.Errorf("Failed to map RBD storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
+		return err
+	}
+
+	// Generate a new UUID
+	RBDFilesystem := s.getRBDFilesystem()
+	msg, err := fsGenerateNewUUID(RBDFilesystem, RBDDevPath)
+	if err != nil {
+		logger.Errorf("Failed to create new UUID for filesystem \"%s\" for RBD storage volume \"%s\" on storage pool \"%s\": %s: %s", RBDFilesystem, s.volume.Name, s.pool.Name, msg, err)
+		return err
+	}
+
+	// Unmap the rbd
+	err = cephRBDVolumeUnmap(s.ClusterName, s.OSDPoolName, s.volume.Name, storagePoolVolumeTypeNameCustom, s.UserName, true)
+	if err != nil {
+		logger.Errorf("Failed to unmap RBD storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
+		return err
+	}
+
+	volumeMntPoint := getStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
+
+	err = os.MkdirAll(volumeMntPoint, 0711)
+	if err != nil {
+		logger.Errorf("Failed to create mountpoint \"%s\" for RBD storage volume \"%s\" on storage pool \"%s\": %s", volumeMntPoint, s.volume.Name, s.pool.Name, err)
+		return err
+	}
+
+	return nil
+}
+
+func (s *storageCeph) copyVolumeWithoutSnapshotsSparse(source *api.StorageVolumeSource) error {
+	sourceOnlyName, snapshotOnlyName, isSnapshot := containerGetParentAndSnapshotName(source.Name)
+
+	if isSnapshot {
+		snapshotOnlyName = fmt.Sprintf("snapshot_%s", snapshotOnlyName)
+	} else {
+		// create sparse copy
+		snapshotOnlyName = uuid.NewRandom().String()
+
+		// create snapshot of original volume
+		err := cephRBDSnapshotCreate(s.ClusterName, s.OSDPoolName, sourceOnlyName, storagePoolVolumeTypeNameCustom, snapshotOnlyName, s.UserName)
+		if err != nil {
+			logger.Errorf("Failed to create snapshot of RBD storage volume \"%s\" on storage pool \"%s\": %s", sourceOnlyName, source.Pool, err)
+			return err
+		}
+	}
+
+	// protect volume so we can create clones of it
+	err := cephRBDSnapshotProtect(s.ClusterName, s.OSDPoolName, sourceOnlyName, storagePoolVolumeTypeNameCustom, snapshotOnlyName, s.UserName)
+	if err != nil {
+		logger.Errorf("Failed to protect snapshot for RBD storage volume \"%s\" on storage pool \"%s\": %s", sourceOnlyName, s.pool.Name, err)
+		return err
+	}
+
+	// create new clone
+	err = cephRBDCloneCreate(s.ClusterName, s.OSDPoolName, sourceOnlyName, storagePoolVolumeTypeNameCustom, snapshotOnlyName, s.OSDPoolName, s.volume.Name, storagePoolVolumeTypeNameCustom, s.UserName)
+	if err != nil {
+		logger.Errorf("Failed to clone RBD storage volume \"%s\" on storage pool \"%s\": %s", source.Name, source.Pool, err)
+		return err
+	}
+
+	// Map the rbd
+	RBDDevPath, err := cephRBDVolumeMap(s.ClusterName, s.OSDPoolName, s.volume.Name, storagePoolVolumeTypeNameCustom, s.UserName)
+	if err != nil {
+		logger.Errorf("Failed to map RBD storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
+		return err
+	}
+
+	// Generate a new UUID
+	RBDFilesystem := s.getRBDFilesystem()
+	msg, err := fsGenerateNewUUID(RBDFilesystem, RBDDevPath)
+	if err != nil {
+		logger.Errorf("Failed to create new UUID for filesystem \"%s\" for RBD storage volume \"%s\" on storage pool \"%s\": %s: %s", RBDFilesystem, s.volume.Name, s.pool.Name, msg, err)
+		return err
+	}
+
+	// Unmap the rbd
+	err = cephRBDVolumeUnmap(s.ClusterName, s.OSDPoolName, s.volume.Name, storagePoolVolumeTypeNameCustom, s.UserName, true)
+	if err != nil {
+		logger.Errorf("Failed to unmap RBD storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
+		return err
+	}
+
+	volumeMntPoint := getStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
+
+	err = os.MkdirAll(volumeMntPoint, 0711)
+	if err != nil {
+		logger.Errorf("Failed to create mountpoint \"%s\" for RBD storage volume \"%s\" on storage pool \"%s\": %s", volumeMntPoint, s.volume.Name, s.pool.Name, err)
+		return err
+	}
+
+	return nil
+}

From 96b1c2522ba171245adb28448f2762c63fe0b94e Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Tue, 2 Apr 2019 11:23:33 +0200
Subject: [PATCH 4/8] storage/lvm: Fix volume copy with snapshots

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/storage_lvm.go       |  79 +++++-----------------
 lxd/storage_lvm_utils.go | 140 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 157 insertions(+), 62 deletions(-)

diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index b9fd4487da..badf55fc60 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -2201,91 +2201,46 @@ func (s *storageLvm) StoragePoolVolumeCopy(source *api.StorageVolumeSource) erro
 	logger.Infof("Copying LVM storage volume \"%s\" on storage pool \"%s\" as \"%s\" to storage pool \"%s\"", source.Name, source.Pool, s.volume.Name, s.pool.Name)
 	successMsg := fmt.Sprintf("Copied LVM storage volume \"%s\" on storage pool \"%s\" as \"%s\" to storage pool \"%s\"", source.Name, source.Pool, s.volume.Name, s.pool.Name)
 
-	srcMountPoint := getStoragePoolVolumeMountPoint(source.Pool, source.Name)
-	dstMountPoint := getStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
-
-	sourceName := source.Name
-	targetName := s.volume.Name
-
-	if strings.Contains(sourceName, "/") {
-		sourceName = containerNameToLVName(sourceName)
-	}
-
-	if strings.Contains(targetName, "/") {
-		targetName = containerNameToLVName(targetName)
-	}
-
-	if s.pool.Name == source.Pool && s.useThinpool {
-		err := os.MkdirAll(dstMountPoint, 0711)
-		if err != nil {
-			logger.Errorf("Failed to create mountpoint \"%s\" for LVM storage volume \"%s\" on storage pool \"%s\": %s", dstMountPoint, s.volume.Name, s.pool.Name, err)
-			return err
-		}
-
-		poolName := s.getOnDiskPoolName()
-		lvFsType := s.getLvmFilesystem()
-		lvSize, err := s.getLvmVolumeSize()
-		if lvSize == "" {
-			logger.Errorf("Failed to get size for LVM storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
-			return err
-		}
-
-		_, err = s.createSnapshotLV("default", poolName, sourceName, storagePoolVolumeAPIEndpointCustom, targetName, storagePoolVolumeAPIEndpointCustom, false, s.useThinpool)
-		if err != nil {
-			logger.Errorf("Failed to create snapshot for LVM storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
-			return err
-		}
-
-		lvDevPath := getLvmDevPath("default", poolName, storagePoolVolumeAPIEndpointCustom, targetName)
-		msg, err := fsGenerateNewUUID(lvFsType, lvDevPath)
-		if err != nil {
-			logger.Errorf("Failed to create new UUID for filesystem \"%s\" for RBD storage volume \"%s\" on storage pool \"%s\": %s: %s", lvFsType, s.volume.Name, s.pool.Name, msg, err)
-			return err
-		}
-
-		logger.Infof(successMsg)
-		return nil
-	}
-
 	if s.pool.Name != source.Pool {
+		// Cross-pool copy
 		// setup storage for the source volume
 		srcStorage, err := storagePoolVolumeInit(s.s, "default", source.Pool, source.Name, storagePoolVolumeTypeCustom)
 		if err != nil {
-			logger.Errorf("Failed to initialize LVM storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
 			return err
 		}
 
-		ourMount, err := srcStorage.StoragePoolVolumeMount()
+		ourMount, err := srcStorage.StoragePoolMount()
 		if err != nil {
-			logger.Errorf("Failed to mount LVM storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
 			return err
 		}
-
 		if ourMount {
-			defer srcStorage.StoragePoolVolumeUmount()
+			defer srcStorage.StoragePoolUmount()
 		}
 	}
 
-	err := s.StoragePoolVolumeCreate()
+	err := s.copyVolume(source.Pool, source.Name)
 	if err != nil {
-		logger.Errorf("Failed to create LVM storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
 		return err
 	}
 
-	ourMount, err := s.StoragePoolVolumeMount()
+	if source.VolumeOnly {
+		return nil
+	}
+
+	snapshots, err := storagePoolVolumeSnapshotsGet(s.s, source.Pool, source.Name, storagePoolVolumeTypeCustom)
 	if err != nil {
 		return err
 	}
-	if ourMount {
-		defer s.StoragePoolVolumeUmount()
+
+	if len(snapshots) == 0 {
+		return nil
 	}
 
-	bwlimit := s.pool.Config["rsync.bwlimit"]
-	_, err = rsyncLocalCopy(srcMountPoint, dstMountPoint, bwlimit)
-	if err != nil {
-		os.RemoveAll(dstMountPoint)
-		logger.Errorf("Failed to rsync into LVM storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
-		return err
+	for _, snap := range snapshots {
+		err = s.copyVolumeSnapshot(source.Pool, snap)
+		if err != nil {
+			return err
+		}
 	}
 
 	logger.Infof(successMsg)
diff --git a/lxd/storage_lvm_utils.go b/lxd/storage_lvm_utils.go
index c5a883d096..9963f69069 100644
--- a/lxd/storage_lvm_utils.go
+++ b/lxd/storage_lvm_utils.go
@@ -2,6 +2,7 @@ package main
 
 import (
 	"fmt"
+	"os"
 	"os/exec"
 	"strconv"
 	"strings"
@@ -10,6 +11,7 @@ import (
 	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/version"
 	"github.com/pkg/errors"
@@ -943,3 +945,141 @@ func lvmVersionIsAtLeast(sTypeVersion string, versionString string) (bool, error
 
 	return true, nil
 }
+
+// Copy an LVM custom volume.
+func (s *storageLvm) copyVolume(sourcePool string, source string) error {
+	targetMntPoint := getStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
+
+	err := os.MkdirAll(targetMntPoint, 0711)
+	if err != nil {
+		return err
+	}
+
+	if s.useThinpool && sourcePool == s.pool.Name {
+		err = s.copyVolumeThinpool(source, s.volume.Name, false)
+	} else {
+		err = s.copyVolumeLv(sourcePool, source, s.volume.Name, false)
+	}
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (s *storageLvm) copyVolumeSnapshot(sourcePool string, source string) error {
+	_, snapOnlyName, _ := containerGetParentAndSnapshotName(source)
+	target := fmt.Sprintf("%s/%s", s.volume.Name, snapOnlyName)
+	targetMntPoint := getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, target)
+
+	err := os.MkdirAll(targetMntPoint, 0711)
+	if err != nil {
+		return err
+	}
+
+	if s.useThinpool && sourcePool == s.pool.Name {
+		err = s.copyVolumeThinpool(source, target, true)
+	} else {
+		err = s.copyVolumeLv(sourcePool, source, target, true)
+	}
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (s *storageLvm) copyVolumeLv(sourcePool string, source string, target string, readOnly bool) error {
+	var srcMountPoint string
+	var dstMountPoint string
+
+	sourceIsSnapshot := strings.Contains(source, "/")
+
+	if sourceIsSnapshot {
+		srcMountPoint = getStoragePoolVolumeSnapshotMountPoint(sourcePool, source)
+	} else {
+		srcMountPoint = getStoragePoolVolumeMountPoint(sourcePool, source)
+
+	}
+
+	targetIsSnapshot := strings.Contains(target, "/")
+
+	if targetIsSnapshot {
+		dstMountPoint = getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, target)
+	} else {
+		dstMountPoint = getStoragePoolVolumeMountPoint(s.pool.Name, target)
+	}
+
+	var err error
+
+	if targetIsSnapshot {
+		err = s.StoragePoolVolumeSnapshotCreate(&api.StorageVolumeSnapshotsPost{Name: target})
+	} else {
+		err = s.StoragePoolVolumeCreate()
+	}
+	if err != nil {
+		logger.Errorf("Failed to create LVM storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
+		return err
+	}
+
+	ourMount, err := s.StoragePoolVolumeMount()
+	if err != nil {
+		return err
+	}
+	if ourMount {
+		defer s.StoragePoolVolumeUmount()
+	}
+
+	bwlimit := s.pool.Config["rsync.bwlimit"]
+	_, err = rsyncLocalCopy(srcMountPoint, dstMountPoint, bwlimit)
+	if err != nil {
+		os.RemoveAll(dstMountPoint)
+		logger.Errorf("Failed to rsync into LVM storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
+		return err
+	}
+
+	// Snapshot are already read-only, and this will fail if trying to set them
+	// read-only again.
+	if readOnly && !targetIsSnapshot {
+		targetLvmName := containerNameToLVName(target)
+		poolName := s.getOnDiskPoolName()
+
+		output, err := shared.TryRunCommand("lvchange", "-pr", fmt.Sprintf("%s/%s_%s", poolName, storagePoolVolumeAPIEndpointCustom, targetLvmName))
+		if err != nil {
+			logger.Errorf("Failed to make LVM snapshot \"%s\" read-only: %s", s.volume.Name, output)
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (s *storageLvm) copyVolumeThinpool(source string, target string, readOnly bool) error {
+	sourceLvmName := containerNameToLVName(source)
+	targetLvmName := containerNameToLVName(target)
+
+	poolName := s.getOnDiskPoolName()
+	lvFsType := s.getLvmFilesystem()
+
+	lvSize, err := s.getLvmVolumeSize()
+	if lvSize == "" {
+		logger.Errorf("Failed to get size for LVM storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
+		return err
+	}
+
+	_, err = s.createSnapshotLV("default", poolName, sourceLvmName, storagePoolVolumeAPIEndpointCustom, targetLvmName, storagePoolVolumeAPIEndpointCustom, readOnly, s.useThinpool)
+	if err != nil {
+		logger.Errorf("Failed to create snapshot for LVM storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
+		return err
+	}
+
+	lvDevPath := getLvmDevPath("default", poolName, storagePoolVolumeAPIEndpointCustom, targetLvmName)
+
+	msg, err := fsGenerateNewUUID(lvFsType, lvDevPath)
+	if err != nil {
+		logger.Errorf("Failed to create new UUID for filesystem \"%s\" for RBD storage volume \"%s\" on storage pool \"%s\": %s: %s", lvFsType, s.volume.Name, s.pool.Name, msg, err)
+		return err
+	}
+
+	return nil
+}

From 14c589cf2a7cf55a90ae40a93366e2127e67107d Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Tue, 2 Apr 2019 17:18:11 +0200
Subject: [PATCH 5/8] storage/btrfs: Fix volume copy with snapshots

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/storage_btrfs.go | 170 +++++++++++++++++++++++++++++++------------
 1 file changed, 124 insertions(+), 46 deletions(-)

diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index a1b3a8aed7..4888073642 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -2931,84 +2931,155 @@ func (s *storageBtrfs) StoragePoolVolumeCopy(source *api.StorageVolumeSource) er
 	logger.Infof("Copying BTRFS storage volume \"%s\" on storage pool \"%s\" as \"%s\" to storage pool \"%s\"", source.Name, source.Pool, s.volume.Name, s.pool.Name)
 	successMsg := fmt.Sprintf("Copied BTRFS storage volume \"%s\" on storage pool \"%s\" as \"%s\" to storage pool \"%s\"", source.Name, source.Pool, s.volume.Name, s.pool.Name)
 
-	isSrcSnapshot := strings.Contains(source.Name, "/")
-	isDstSnapshot := strings.Contains(s.volume.Name, "/")
+	// The storage pool needs to be mounted.
+	_, err := s.StoragePoolMount()
+	if err != nil {
+		return err
+	}
+
+	if s.pool.Name != source.Pool {
+		return s.doCrossPoolVolumeCopy(source.Pool, source.Name, source.VolumeOnly)
+	}
+
+	err = s.copyVolume(source.Pool, source.Name, s.volume.Name, source.VolumeOnly)
+	if err != nil {
+		logger.Errorf("Failed to create BTRFS storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
+		return err
+	}
+
+	if source.VolumeOnly {
+		return nil
+	}
+
+	subvols, err := btrfsSubVolumesGet(s.getCustomSnapshotSubvolumePath(source.Pool))
+	if err != nil {
+		return err
+	}
+
+	for _, snapOnlyName := range subvols {
+		snap := fmt.Sprintf("%s/%s", source.Name, snapOnlyName)
+
+		err := s.copyVolume(source.Pool, snap, fmt.Sprintf("%s/%s", s.volume.Name, snapOnlyName), false)
+		if err != nil {
+			logger.Errorf("Failed to create BTRFS storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
+			return err
+		}
+	}
+
+	logger.Infof(successMsg)
+	return nil
+}
 
+func (s *storageBtrfs) copyVolume(sourcePool string, sourceName string, targetName string, volumeOnly bool) error {
+	var customDir string
 	var srcMountPoint string
 	var dstMountPoint string
 
+	isSrcSnapshot := strings.Contains(sourceName, "/")
+	isDstSnapshot := strings.Contains(targetName, "/")
+
 	if isSrcSnapshot {
-		srcMountPoint = getStoragePoolVolumeSnapshotMountPoint(source.Pool, source.Name)
+		srcMountPoint = getStoragePoolVolumeSnapshotMountPoint(sourcePool, sourceName)
 	} else {
-		srcMountPoint = getStoragePoolVolumeMountPoint(source.Pool, source.Name)
+		srcMountPoint = getStoragePoolVolumeMountPoint(sourcePool, sourceName)
 	}
 
 	if isDstSnapshot {
-		dstMountPoint = getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, s.volume.Name)
+		dstMountPoint = getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, targetName)
 	} else {
-		dstMountPoint = getStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
+		dstMountPoint = getStoragePoolVolumeMountPoint(s.pool.Name, targetName)
 	}
 
-	if s.pool.Name == source.Pool {
-		var customDir string
-
-		// Ensure that the directories immediately preceding the subvolume directory exist.
-		if isDstSnapshot {
-			volName, _, _ := containerGetParentAndSnapshotName(s.volume.Name)
-			customDir = getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, volName)
-		} else {
-			customDir = getStoragePoolVolumeMountPoint(s.pool.Name, "")
-		}
-
-		if !shared.PathExists(customDir) {
-			err := os.MkdirAll(customDir, customDirMode)
-			if err != nil {
-				logger.Errorf("Failed to create directory \"%s\" for storage volume \"%s\" on storage pool \"%s\": %s", customDir, s.volume.Name, s.pool.Name, err)
-				return err
-			}
-		}
+	// Ensure that the directories immediately preceding the subvolume directory exist.
+	if isDstSnapshot {
+		volName, _, _ := containerGetParentAndSnapshotName(targetName)
+		customDir = getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, volName)
+	} else {
+		customDir = getStoragePoolVolumeMountPoint(s.pool.Name, "")
+	}
 
-		err := s.btrfsPoolVolumesSnapshot(srcMountPoint, dstMountPoint, false, true)
+	if !shared.PathExists(customDir) {
+		err := os.MkdirAll(customDir, customDirMode)
 		if err != nil {
-			logger.Errorf("Failed to create BTRFS snapshot for storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
+			logger.Errorf("Failed to create directory \"%s\" for storage volume \"%s\" on storage pool \"%s\": %s", customDir, s.volume.Name, s.pool.Name, err)
 			return err
 		}
+	}
 
-		logger.Infof(successMsg)
-		return nil
+	err := s.btrfsPoolVolumesSnapshot(srcMountPoint, dstMountPoint, false, true)
+	if err != nil {
+		logger.Errorf("Failed to create BTRFS snapshot for storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
+		return err
 	}
 
+	return nil
+}
+
+func (s *storageBtrfs) doCrossPoolVolumeCopy(sourcePool string, sourceName string, volumeOnly bool) error {
 	// setup storage for the source volume
-	srcStorage, err := storagePoolVolumeInit(s.s, "default", source.Pool, source.Name, storagePoolVolumeTypeCustom)
+	srcStorage, err := storagePoolVolumeInit(s.s, "default", sourcePool, sourceName, storagePoolVolumeTypeCustom)
 	if err != nil {
-		logger.Errorf("Failed to initialize storage for BTRFS storage volume \"%s\" on storage pool \"%s\": %s", source.Name, source.Pool, err)
 		return err
 	}
 
-	ourMount, err := srcStorage.StoragePoolVolumeMount()
+	ourMount, err := srcStorage.StoragePoolMount()
 	if err != nil {
-		logger.Errorf("Failed to mount BTRFS storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
 		return err
 	}
 	if ourMount {
-		defer srcStorage.StoragePoolVolumeUmount()
+		defer srcStorage.StoragePoolUmount()
 	}
 
 	err = s.StoragePoolVolumeCreate()
 	if err != nil {
-		logger.Errorf("Failed to create BTRFS storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
 		return err
 	}
 
+	destVolumeMntPoint := getStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
 	bwlimit := s.pool.Config["rsync.bwlimit"]
-	_, err = rsyncLocalCopy(srcMountPoint, dstMountPoint, bwlimit)
+
+	if !volumeOnly {
+		// Handle snapshots
+		snapshots, err := storagePoolVolumeSnapshotsGet(s.s, sourcePool, sourceName, storagePoolVolumeTypeCustom)
+		if err != nil {
+			return err
+		}
+
+		for _, snap := range snapshots {
+			srcSnapshotMntPoint := getStoragePoolVolumeSnapshotMountPoint(sourcePool, snap)
+
+			_, err = rsyncLocalCopy(srcSnapshotMntPoint, destVolumeMntPoint, bwlimit)
+			if err != nil {
+				logger.Errorf("Failed to rsync into BTRFS storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
+				return err
+			}
+
+			// create snapshot
+			_, snapOnlyName, _ := containerGetParentAndSnapshotName(snap)
+
+			err = s.doVolumeSnapshotCreate(s.pool.Name, s.volume.Name, fmt.Sprintf("%s/%s", s.volume.Name, snapOnlyName))
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	var srcVolumeMntPoint string
+
+	if strings.Contains(sourceName, "/") {
+		// copy snapshot to volume
+		srcVolumeMntPoint = getStoragePoolVolumeSnapshotMountPoint(sourcePool, sourceName)
+	} else {
+		// copy volume to volume
+		srcVolumeMntPoint = getStoragePoolVolumeMountPoint(sourcePool, sourceName)
+	}
+
+	_, err = rsyncLocalCopy(srcVolumeMntPoint, destVolumeMntPoint, bwlimit)
 	if err != nil {
-		s.StoragePoolVolumeDelete()
 		logger.Errorf("Failed to rsync into BTRFS storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
 		return err
 	}
 
-	logger.Infof(successMsg)
 	return nil
 }
 
@@ -3041,33 +3112,40 @@ func (s *storageBtrfs) GetState() *state.State {
 func (s *storageBtrfs) StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeSnapshotsPost) error {
 	logger.Infof("Creating BTRFS storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
 
+	err := s.doVolumeSnapshotCreate(s.pool.Name, s.volume.Name, target.Name)
+	if err != nil {
+		return err
+	}
+
+	logger.Infof("Created BTRFS storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
+	return nil
+}
+
+func (s *storageBtrfs) doVolumeSnapshotCreate(sourcePool string, sourceName string, targetName string) error {
 	// Create subvolume path on the storage pool.
 	customSubvolumePath := s.getCustomSubvolumePath(s.pool.Name)
+
 	err := os.MkdirAll(customSubvolumePath, 0700)
 	if err != nil && !os.IsNotExist(err) {
 		return err
 	}
 
-	_, _, ok := containerGetParentAndSnapshotName(target.Name)
+	_, _, ok := containerGetParentAndSnapshotName(targetName)
 	if !ok {
 		return err
 	}
 
 	customSnapshotSubvolumeName := getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, s.volume.Name)
+
 	err = os.MkdirAll(customSnapshotSubvolumeName, snapshotsDirMode)
 	if err != nil && !os.IsNotExist(err) {
 		return err
 	}
 
-	sourcePath := getStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
-	targetPath := getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, target.Name)
-	err = s.btrfsPoolVolumesSnapshot(sourcePath, targetPath, true, true)
-	if err != nil {
-		return err
-	}
+	sourcePath := getStoragePoolVolumeMountPoint(sourcePool, sourceName)
+	targetPath := getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, targetName)
 
-	logger.Infof("Created BTRFS storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-	return nil
+	return s.btrfsPoolVolumesSnapshot(sourcePath, targetPath, true, true)
 }
 
 func (s *storageBtrfs) StoragePoolVolumeSnapshotDelete() error {

From e4f66fe7a274fe355ec4d343d8747e121f131c7e Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Wed, 3 Apr 2019 11:13:17 +0200
Subject: [PATCH 6/8] storage/dir: Fix volume copy with snapshots

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/storage_dir.go | 91 ++++++++++++++++++++++++++++++++--------------
 1 file changed, 64 insertions(+), 27 deletions(-)

diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index 2b29832f65..898ff33f77 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -1292,48 +1292,36 @@ func (s *storageDir) StoragePoolVolumeCopy(source *api.StorageVolumeSource) erro
 			return err
 		}
 
-		ourMount, err := srcStorage.StoragePoolVolumeMount()
+		ourMount, err := srcStorage.StoragePoolMount()
 		if err != nil {
 			logger.Errorf("Failed to mount DIR storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
 			return err
 		}
 		if ourMount {
-			defer srcStorage.StoragePoolVolumeUmount()
+			defer srcStorage.StoragePoolUmount()
 		}
 	}
 
-	err := s.StoragePoolVolumeCreate()
-	if err != nil {
-		logger.Errorf("Failed to create DIR storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
-		return err
-	}
-
-	var srcMountPoint string
-	var dstMountPoint string
-
-	isSrcSnapshot := strings.Contains(source.Name, "/")
-	isDstSnapshot := strings.Contains(s.volume.Name, "/")
+	err := s.copyVolume(source.Pool, source.Name, s.volume.Name)
 
-	if isSrcSnapshot {
-		srcMountPoint = getStoragePoolVolumeSnapshotMountPoint(source.Pool, source.Name)
-	} else {
-		srcMountPoint = getStoragePoolVolumeMountPoint(source.Pool, source.Name)
-	}
-
-	if isDstSnapshot {
-		dstMountPoint = getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, s.volume.Name)
-	} else {
-		dstMountPoint = getStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
+	if source.VolumeOnly {
+		logger.Infof(successMsg)
+		return nil
 	}
 
-	bwlimit := s.pool.Config["rsync.bwlimit"]
-	_, err = rsyncLocalCopy(srcMountPoint, dstMountPoint, bwlimit)
+	snapshots, err := storagePoolVolumeSnapshotsGet(s.s, source.Pool, source.Name, storagePoolVolumeTypeCustom)
 	if err != nil {
-		os.RemoveAll(dstMountPoint)
-		logger.Errorf("Failed to rsync into DIR storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
 		return err
 	}
 
+	for _, snap := range snapshots {
+		_, snapOnlyName, _ := containerGetParentAndSnapshotName(snap)
+		err = s.copyVolumeSnapshot(source.Pool, snap, fmt.Sprintf("%s/%s", s.volume.Name, snapOnlyName))
+		if err != nil {
+			return err
+		}
+	}
+
 	logger.Infof(successMsg)
 	return nil
 }
@@ -1463,3 +1451,52 @@ func (s *storageDir) StoragePoolVolumeSnapshotRename(newName string) error {
 
 	return s.s.Cluster.StoragePoolVolumeRename("default", s.volume.Name, fullSnapshotName, storagePoolVolumeTypeCustom, s.poolID)
 }
+
+func (s *storageDir) copyVolume(sourcePool string, source string, target string) error {
+	var srcMountPoint string
+
+	if strings.Contains(source, "/") {
+		srcMountPoint = getStoragePoolVolumeSnapshotMountPoint(sourcePool, source)
+	} else {
+		srcMountPoint = getStoragePoolVolumeMountPoint(sourcePool, source)
+	}
+
+	dstMountPoint := getStoragePoolVolumeMountPoint(s.pool.Name, target)
+
+	err := os.MkdirAll(dstMountPoint, 0711)
+	if err != nil {
+		return err
+	}
+
+	bwlimit := s.pool.Config["rsync.bwlimit"]
+
+	_, err = rsyncLocalCopy(srcMountPoint, dstMountPoint, bwlimit)
+	if err != nil {
+		os.RemoveAll(dstMountPoint)
+		logger.Errorf("Failed to rsync into DIR storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
+		return err
+	}
+
+	return nil
+}
+
+func (s *storageDir) copyVolumeSnapshot(sourcePool string, source string, target string) error {
+	srcMountPoint := getStoragePoolVolumeSnapshotMountPoint(sourcePool, source)
+	dstMountPoint := getStoragePoolVolumeSnapshotMountPoint(s.pool.Name, target)
+
+	err := os.MkdirAll(dstMountPoint, 0711)
+	if err != nil {
+		return err
+	}
+
+	bwlimit := s.pool.Config["rsync.bwlimit"]
+
+	_, err = rsyncLocalCopy(srcMountPoint, dstMountPoint, bwlimit)
+	if err != nil {
+		os.RemoveAll(dstMountPoint)
+		logger.Errorf("Failed to rsync into DIR storage volume \"%s\" on storage pool \"%s\": %s", target, s.pool.Name, err)
+		return err
+	}
+
+	return nil
+}

From d49ed65f1740a64ead3a4d376f06f74b54c6271d Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Fri, 29 Mar 2019 14:41:18 +0100
Subject: [PATCH 7/8] storage/zfs: Fix volume copy with snapshots

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/storage_zfs.go | 114 +++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 105 insertions(+), 9 deletions(-)

diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index 197e17a9dd..d9cab706fe 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -2966,15 +2966,15 @@ func (s *storageZfs) doCrossPoolStorageVolumeCopy(source *api.StorageVolumeSourc
 		return err
 	}
 
-	ourMount, err := srcStorage.StoragePoolVolumeMount()
+	ourMount, err := srcStorage.StoragePoolMount()
 	if err != nil {
-		logger.Errorf("Failed to mount ZFS storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
 		return err
 	}
 	if ourMount {
-		defer srcStorage.StoragePoolVolumeUmount()
+		defer srcStorage.StoragePoolUmount()
 	}
 
+	// Create the main volume
 	err = s.StoragePoolVolumeCreate()
 	if err != nil {
 		logger.Errorf("Failed to create ZFS storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
@@ -2990,10 +2990,38 @@ func (s *storageZfs) doCrossPoolStorageVolumeCopy(source *api.StorageVolumeSourc
 		defer s.StoragePoolVolumeUmount()
 	}
 
-	srcMountPoint := getStoragePoolVolumeMountPoint(source.Pool, source.Name)
 	dstMountPoint := getStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
-
 	bwlimit := s.pool.Config["rsync.bwlimit"]
+
+	snapshots, err := storagePoolVolumeSnapshotsGet(s.s, source.Pool, source.Name, storagePoolVolumeTypeCustom)
+	if err != nil {
+		return err
+	}
+
+	if !source.VolumeOnly {
+		for _, snap := range snapshots {
+			srcMountPoint := getStoragePoolVolumeSnapshotMountPoint(source.Pool, snap)
+
+			_, err = rsyncLocalCopy(srcMountPoint, dstMountPoint, bwlimit)
+			if err != nil {
+				logger.Errorf("Failed to rsync into ZFS storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
+				return err
+			}
+
+			_, snapOnlyName, _ := containerGetParentAndSnapshotName(source.Name)
+
+			s.StoragePoolVolumeSnapshotCreate(&api.StorageVolumeSnapshotsPost{Name: fmt.Sprintf("%s/%s", s.volume.Name, snapOnlyName)})
+		}
+	}
+
+	var srcMountPoint string
+
+	if strings.Contains(source.Name, "/") {
+		srcMountPoint = getStoragePoolVolumeSnapshotMountPoint(source.Pool, source.Name)
+	} else {
+		srcMountPoint = getStoragePoolVolumeMountPoint(source.Pool, source.Name)
+	}
+
 	_, err = rsyncLocalCopy(srcMountPoint, dstMountPoint, bwlimit)
 	if err != nil {
 		os.RemoveAll(dstMountPoint)
@@ -3167,15 +3195,20 @@ func (s *storageZfs) StoragePoolVolumeCopy(source *api.StorageVolumeSource) erro
 	}
 
 	var snapshots []string
-	var err error
 
 	poolName := s.getOnDiskPoolName()
 
 	if !strings.Contains(source.Name, "/") {
-		snapshots, err = zfsPoolListSnapshots(poolName, fmt.Sprintf("custom/%s", source.Name))
+		allSnapshots, err := zfsPoolListSnapshots(poolName, fmt.Sprintf("custom/%s", source.Name))
 		if err != nil {
 			return err
 		}
+
+		for _, snap := range allSnapshots {
+			if strings.HasPrefix(snap, "snapshot-") {
+				snapshots = append(snapshots, strings.TrimPrefix(snap, "snapshot-"))
+			}
+		}
 	}
 
 	targetStorageVolumeMountPoint := getStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
@@ -3193,8 +3226,66 @@ func (s *storageZfs) StoragePoolVolumeCopy(source *api.StorageVolumeSource) erro
 			return err
 		}
 	} else {
+		targetVolumeMountPoint := getStoragePoolVolumeMountPoint(poolName, s.volume.Name)
+
+		err := os.MkdirAll(targetVolumeMountPoint, 0711)
+		if err != nil {
+			return err
+		}
+
+		prev := ""
+		prevSnapOnlyName := ""
+
+		for i, snap := range snapshots {
+			if i > 0 {
+				prev = snapshots[i-1]
+			}
+
+			sourceDataset := fmt.Sprintf("%s/custom/%s at snapshot-%s", poolName, source.Name, snap)
+			targetDataset := fmt.Sprintf("%s/custom/%s at snapshot-%s", poolName, s.volume.Name, snap)
+
+			snapshotMntPoint := getStoragePoolVolumeSnapshotMountPoint(poolName, fmt.Sprintf("%s/%s", s.volume.Name, snap))
+
+			err := os.MkdirAll(snapshotMntPoint, 0700)
+			if err != nil {
+				return err
+			}
+
+			prevSnapOnlyName = snap
+
+			args := []string{"send", sourceDataset}
+
+			if prev != "" {
+				parentDataset := fmt.Sprintf("%s/custom/%s/snapshot-%s", poolName, source.Name, prev)
+				args = append(args, "-i", parentDataset)
+			}
+
+			zfsSendCmd := exec.Command("zfs", args...)
+			zfsRecvCmd := exec.Command("zfs", "receive", "-F", targetDataset)
+
+			zfsRecvCmd.Stdin, _ = zfsSendCmd.StdoutPipe()
+			zfsRecvCmd.Stdout = os.Stdout
+			zfsRecvCmd.Stderr = os.Stderr
+
+			err = zfsRecvCmd.Start()
+			if err != nil {
+				return err
+			}
+
+			err = zfsSendCmd.Run()
+			if err != nil {
+				return err
+			}
+
+			err = zfsRecvCmd.Wait()
+			if err != nil {
+				return err
+			}
+		}
+
 		tmpSnapshotName := fmt.Sprintf("copy-send-%s", uuid.NewRandom().String())
-		err := zfsPoolVolumeSnapshotCreate(poolName, fmt.Sprintf("custom/%s", source.Name), tmpSnapshotName)
+
+		err = zfsPoolVolumeSnapshotCreate(poolName, fmt.Sprintf("custom/%s", source.Name), tmpSnapshotName)
 		if err != nil {
 			return err
 		}
@@ -3202,7 +3293,12 @@ func (s *storageZfs) StoragePoolVolumeCopy(source *api.StorageVolumeSource) erro
 		defer zfsPoolVolumeSnapshotDestroy(poolName, fmt.Sprintf("custom/%s", source.Name), tmpSnapshotName)
 
 		currentSnapshotDataset := fmt.Sprintf("%s/custom/%s@%s", poolName, source.Name, tmpSnapshotName)
-		args := []string{"send", "-R", currentSnapshotDataset}
+
+		args := []string{"send", currentSnapshotDataset}
+
+		if prevSnapOnlyName != "" {
+			args = append(args, "-i", fmt.Sprintf("%s/custom/%s at snapshot-%s", poolName, source.Name, prevSnapOnlyName))
+		}
 
 		zfsSendCmd := exec.Command("zfs", args...)
 		targetDataset := fmt.Sprintf("%s/custom/%s", poolName, s.volume.Name)

From 2550931ba59fcf716f3da12233917b4d46fdb462 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Thu, 4 Apr 2019 11:11:59 +0200
Subject: [PATCH 8/8] test: Add volume copy tests

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 test/suites/storage_local_volume_handling.sh | 23 +++++++++++++++++++-
 1 file changed, 22 insertions(+), 1 deletion(-)

diff --git a/test/suites/storage_local_volume_handling.sh b/test/suites/storage_local_volume_handling.sh
index b2c4bdb210..12cf50a186 100644
--- a/test/suites/storage_local_volume_handling.sh
+++ b/test/suites/storage_local_volume_handling.sh
@@ -73,8 +73,19 @@ test_storage_local_volume_handling() {
         fi
 
         lxc storage volume create "lxdtest-$(basename "${LXD_DIR}")-${driver}" vol1
+        # This will create the snapshot vol1/snap0
+        lxc storage volume snapshot "lxdtest-$(basename "${LXD_DIR}")-${driver}" vol1
+        # Copy volume with snapshots
         lxc storage volume copy "lxdtest-$(basename "${LXD_DIR}")-${driver}/vol1" "lxdtest-$(basename "${LXD_DIR}")-${driver}1/vol1"
+        # Ensure the target snapshot is there
+        lxc storage volume show "lxdtest-$(basename "${LXD_DIR}")-${driver}1" vol1/snap0
+        # Copy volume only
+        lxc storage volume copy --volume-only "lxdtest-$(basename "${LXD_DIR}")-${driver}/vol1" "lxdtest-$(basename "${LXD_DIR}")-${driver}1/vol2"
+        # Copy snapshot to volume
+        lxc storage volume copy "lxdtest-$(basename "${LXD_DIR}")-${driver}/vol1/snap0" "lxdtest-$(basename "${LXD_DIR}")-${driver}1/vol3"
         lxc storage volume delete "lxdtest-$(basename "${LXD_DIR}")-${driver}1" vol1
+        lxc storage volume delete "lxdtest-$(basename "${LXD_DIR}")-${driver}1" vol2
+        lxc storage volume delete "lxdtest-$(basename "${LXD_DIR}")-${driver}1" vol3
         lxc storage volume move "lxdtest-$(basename "${LXD_DIR}")-${driver}/vol1" "lxdtest-$(basename "${LXD_DIR}")-${driver}1/vol1"
         ! lxc storage volume show "lxdtest-$(basename "${LXD_DIR}")-${driver}" vol1 || false
         lxc storage volume show "lxdtest-$(basename "${LXD_DIR}")-${driver}1" vol1
@@ -88,9 +99,19 @@ test_storage_local_volume_handling() {
         if [ "$source_driver" != "$target_driver" ] && [ "$lxd_backend" = "$source_driver" ] && storage_backend_available "$target_driver"; then
           # source_driver -> target_driver
           lxc storage volume create "lxdtest-$(basename "${LXD_DIR}")-${source_driver}" vol1
+          # This will create the snapshot vol1/snap0
+          lxc storage volume snapshot "lxdtest-$(basename "${LXD_DIR}")-${source_driver}" vol1
+          # Copy volume with snapshots
           lxc storage volume copy "lxdtest-$(basename "${LXD_DIR}")-${source_driver}/vol1" "lxdtest-$(basename "${LXD_DIR}")-${target_driver}/vol1"
+          # Ensure the target snapshot is there
+          lxc storage volume show "lxdtest-$(basename "${LXD_DIR}")-${target_driver}" vol1/snap0
+          # Copy volume only
+          lxc storage volume copy --volume-only "lxdtest-$(basename "${LXD_DIR}")-${source_driver}/vol1" "lxdtest-$(basename "${LXD_DIR}")-${target_driver}/vol2"
+          # Copy snapshot to volume
+          lxc storage volume copy "lxdtest-$(basename "${LXD_DIR}")-${source_driver}/vol1/snap0" "lxdtest-$(basename "${LXD_DIR}")-${target_driver}/vol3"
           lxc storage volume delete "lxdtest-$(basename "${LXD_DIR}")-${target_driver}" vol1
-
+          lxc storage volume delete "lxdtest-$(basename "${LXD_DIR}")-${target_driver}" vol2
+          lxc storage volume delete "lxdtest-$(basename "${LXD_DIR}")-${target_driver}" vol3
           lxc storage volume move "lxdtest-$(basename "${LXD_DIR}")-${source_driver}/vol1" "lxdtest-$(basename "${LXD_DIR}")-${target_driver}/vol1"
           ! lxc storage volume show "lxdtest-$(basename "${LXD_DIR}")-${source_driver}" vol1 || false
           lxc storage volume show "lxdtest-$(basename "${LXD_DIR}")-${target_driver}" vol1


More information about the lxc-devel mailing list