[lxc-devel] [lxd/master] zfs: introduce zfs.clone_copy property

brauner on Github lxc-bot at linuxcontainers.org
Tue Mar 28 11:57:44 UTC 2017


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 301 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20170328/5eef0377/attachment.bin>
-------------- next part --------------
From b1897a038ff69f0a4e8ab7fe14a9113d9953bd85 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 28 Mar 2017 13:27:33 +0200
Subject: [PATCH 1/2] zfs: introduce zfs.clone_copy property

Introduces a new boolean "storage_zfs_clone_copy" property for ZFS storage
pools. When set to false copying a container will be done through zfs send and
receive. This will make the target container independent of its source
container thus avoiding the need to keep dependent snapshots in the ZFS pool
around. However, this also entails less efficient storage usage for the
affected pool.
The default value for this property is true, i.e. space-efficient snapshots
will be used unless explicitly set to "false".

Closes #2124.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 doc/api-extensions.md       | 10 +++++
 doc/configuration.md        |  1 +
 lxd/api_1.0.go              |  1 +
 lxd/storage_pools_config.go |  5 +++
 lxd/storage_zfs.go          | 95 ++++++++++++++++++++++++++++++++++++++++++++-
 5 files changed, 110 insertions(+), 2 deletions(-)

diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index 99f1615..9149471 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -237,3 +237,13 @@ Setting it to false tells LXD not to attempt running state transfer.
 ## container\_only\_migration
 Introduces a new boolean "container_only" attribute. When set to true only the
 container will be copied or moved.
+
+## storage\_zfs\_clone_copy
+Introduces a new boolean "storage_zfs_clone_copy" property for ZFS storage
+pools. When set to false copying a container will be done through zfs send and
+receive. This will make the target container independent of its source
+container thus avoiding the need to keep dependent snapshots in the ZFS pool
+around. However, this also entails less efficient storage usage for the
+affected pool.
+The default value for this property is true, i.e. space-efficient snapshots
+will be used unless explicitly set to "false".
diff --git a/doc/configuration.md b/doc/configuration.md
index aa8f07c..6176552 100644
--- a/doc/configuration.md
+++ b/doc/configuration.md
@@ -399,6 +399,7 @@ volume.size                     | string    | appropriate driver
 volume.zfs.remove\_snapshots    | bool      | zfs driver                        | false             | Remove snapshots as needed
 volume.zfs.use\_refquota        | bool      | zfs driver                        | false             | Use refquota instead of quota for space.
 zfs.pool\_name                  | string    | zfs driver                        | name of the pool  | Name of the zpool
+zfs.clone\_copy                 | bool      | zfs driver                        | false             | Whether copied containers require dependent snapshots in the zfs storage pool.
 
 Storage pool configuration keys can be set using the lxc tool with:
 
diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index 3f386d4..118f321 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -99,6 +99,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
 			"image_create_aliases",
 			"container_stateless_copy",
 			"container_only_migration",
+			"storage_zfs_clone_copy",
 		},
 		APIStatus:  "stable",
 		APIVersion: version.APIVersion,
diff --git a/lxd/storage_pools_config.go b/lxd/storage_pools_config.go
index 65ebe29..076be9a 100644
--- a/lxd/storage_pools_config.go
+++ b/lxd/storage_pools_config.go
@@ -36,6 +36,7 @@ var storagePoolConfigKeys = map[string]func(value string) error{
 	"lvm.thinpool_name":           shared.IsAny,
 	"lvm.vg_name":                 shared.IsAny,
 	"zfs.pool_name":               shared.IsAny,
+	"zfs.clone_copy":              shared.IsBool,
 }
 
 func storagePoolValidateConfig(name string, driver string, config map[string]string) error {
@@ -75,6 +76,10 @@ func storagePoolValidateConfig(name string, driver string, config map[string]str
 			if config["zfs.pool_name"] != "" {
 				return fmt.Errorf("The key zfs.pool_name cannot be used with non zfs storage pools.")
 			}
+
+			if config["zfs.clone_copy"] != "" {
+				return fmt.Errorf("The key zfs.clone_copy cannot be used with non zfs storage pools.")
+			}
 		}
 
 		if driver == "dir" {
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index d6225ef..a1f4fea 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -723,7 +723,7 @@ func (s *storageZfs) ContainerDelete(container container) error {
 	return nil
 }
 
-func (s *storageZfs) copyWithoutSnapshots(target container, source container) error {
+func (s *storageZfs) copyWithoutSnapshotsSparse(target container, source container) error {
 	sourceContainerName := source.Name()
 	sourceContainerPath := source.Path()
 
@@ -823,6 +823,93 @@ func (s *storageZfs) copyWithoutSnapshots(target container, source container) er
 	return nil
 }
 
+func (s *storageZfs) copyWithoutSnapshotFull(target container, source container) error {
+	shared.LogDebugf("Creating full ZFS copy \"%s\" -> \"%s\".", source.Name(), target.Name())
+
+	sourceIsSnapshot := source.IsSnapshot()
+	poolName := s.getOnDiskPoolName()
+
+	sourceName := source.Name()
+	sourceDataset := ""
+	snapshotSuffix := ""
+
+	targetName := target.Name()
+	targetDataset := fmt.Sprintf("%s/containers/%s", poolName, targetName)
+	targetSnapshotDataset := targetDataset
+
+	if sourceIsSnapshot {
+		sourceFields := strings.SplitN(source.Name(), shared.SnapshotDelimiter, 2)
+		snapshotSuffix = fmt.Sprintf("snapshot-%s", sourceFields[1])
+		sourceDataset = fmt.Sprintf("%s/containers/%s@%s", poolName, sourceFields[0], snapshotSuffix)
+		targetSnapshotDataset = fmt.Sprintf("%s/containers/%s at snapshot-%s", poolName, targetName, sourceFields[1])
+	} else {
+		snapshotSuffix = uuid.NewRandom().String()
+		sourceDataset = fmt.Sprintf("%s/containers/%s@%s", poolName, sourceName, snapshotSuffix)
+		targetSnapshotDataset = fmt.Sprintf("%s/containers/%s@%s", poolName, targetName, snapshotSuffix)
+
+		fs := fmt.Sprintf("containers/%s", sourceName)
+		err := s.zfsPoolVolumeSnapshotCreate(fs, snapshotSuffix)
+		if err != nil {
+			return err
+		}
+		defer func() {
+			err := s.zfsPoolVolumeSnapshotDestroy(fs, snapshotSuffix)
+			if err != nil {
+				shared.LogWarnf("Failed to delete temporary ZFS snapshot \"%s\". Manual cleanup needed.", sourceDataset)
+			}
+		}()
+	}
+
+	zfsSendCmd := exec.Command("zfs", "send", sourceDataset)
+
+	zfsRecvCmd := exec.Command("zfs", "receive", 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
+	}
+
+	msg, err := shared.RunCommand("zfs", "rollback", "-r", "-R", targetSnapshotDataset)
+	if err != nil {
+		shared.LogErrorf("Failed to rollback ZFS dataset: %s.", msg)
+		return err
+	}
+
+	targetContainerMountPoint := getContainerMountPoint(s.pool.Name, targetName)
+	targetfs := fmt.Sprintf("containers/%s", targetName)
+	err = s.zfsPoolVolumeSet(targetfs, "mountpoint", targetContainerMountPoint)
+	if err != nil {
+		return err
+	}
+
+	err = s.zfsPoolVolumeSnapshotDestroy(targetfs, snapshotSuffix)
+	if err != nil {
+		return err
+	}
+
+	err = createContainerMountpoint(targetContainerMountPoint, target.Path(), target.IsPrivileged())
+	if err != nil {
+		return err
+	}
+
+	shared.LogDebugf("Created full ZFS copy \"%s\" -> \"%s\".", source.Name(), target.Name())
+	return nil
+}
+
 func (s *storageZfs) copyWithSnapshots(target container, source container, parentSnapshot string) error {
 	sourceName := source.Name()
 	fields := strings.SplitN(target.Name(), shared.SnapshotDelimiter, 2)
@@ -893,7 +980,11 @@ func (s *storageZfs) ContainerCopy(target container, source container, container
 	}
 
 	if containerOnly || len(snapshots) == 0 {
-		err = s.copyWithoutSnapshots(target, source)
+		if s.pool.Config["zfs.clone_copy"] != "" && !shared.IsTrue(s.pool.Config["zfs.clone_copy"]) {
+			err = s.copyWithoutSnapshotFull(target, source)
+		} else {
+			err = s.copyWithoutSnapshotsSparse(target, source)
+		}
 	} else {
 		targetContainerName := target.Name()
 		targetContainerPath := target.Path()

From 9abcb06240618ca8053a0535b130de0dcd99ca42 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 28 Mar 2017 13:56:18 +0200
Subject: [PATCH 2/2] test: add migration tests with zfs.clone_copy

Closes #2124.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 test/suites/migration.sh | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/test/suites/migration.sh b/test/suites/migration.sh
index e9dfca8..ed63edf 100644
--- a/test/suites/migration.sh
+++ b/test/suites/migration.sh
@@ -125,6 +125,25 @@ test_migration() {
   [ "$(lxc info udssr | grep -c snap)" -eq 2 ]
   lxc delete udssr
 
+  # Test container only copies when zfs.clone_copy is set to false.
+  lxc storage set "lxdtest-$(basename "${LXD_DIR}")" zfs.clone_copy false
+  lxc init testimage cccp
+  lxc snapshot cccp
+  lxc snapshot cccp
+
+  # Test container only copies when zfs.clone_copy is set to false.
+  lxc copy cccp udssr --container-only
+  [ "$(lxc info udssr | grep -c snap)" -eq 0 ]
+  lxc delete udssr
+
+  # Test container with snapshots copy when zfs.clone_copy is set to false.
+  lxc copy cccp udssr
+  [ "$(lxc info udssr | grep -c snap)" -eq 2 ]
+  lxc delete cccp
+  lxc delete udssr
+
+  lxc storage unset "lxdtest-$(basename "${LXD_DIR}")" zfs.clone_copy
+
   if ! which criu >/dev/null 2>&1; then
     echo "==> SKIP: live migration with CRIU (missing binary)"
     return


More information about the lxc-devel mailing list