[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, ©Req)
- 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