[lxc-devel] [lxd/master] [WIP] consistent container copies

brauner on Github lxc-bot at linuxcontainers.org
Tue Mar 21 23:16:15 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/20170321/97ffcff2/attachment.bin>
-------------- next part --------------
From a7d5f7118eac4388fc87d6134ae6e27c206e22a0 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 21 Mar 2017 23:42:33 +0100
Subject: [PATCH 1/3] copy: add ContainerOnly field

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 client.go               |  7 ++++---
 lxc/copy.go             | 10 ++++++----
 shared/api/container.go |  3 ++-
 3 files changed, 12 insertions(+), 8 deletions(-)

diff --git a/client.go b/client.go
index 0f2dd5f..2ed045a 100644
--- a/client.go
+++ b/client.go
@@ -1431,15 +1431,16 @@ func (c *Client) Init(name string, imgremote string, image string, profiles *[]s
 	return resp, nil
 }
 
-func (c *Client) LocalCopy(source string, name string, config map[string]string, profiles []string, ephemeral bool) (*api.Response, error) {
+func (c *Client) LocalCopy(source string, name string, config map[string]string, profiles []string, ephemeral bool, containerOnly bool) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
 	body := shared.Jmap{
 		"source": shared.Jmap{
-			"type":   "copy",
-			"source": source,
+			"type":           "copy",
+			"source":         source,
+			"container_only": containerOnly,
 		},
 		"name":      name,
 		"config":    config,
diff --git a/lxc/copy.go b/lxc/copy.go
index 539d616..2e8f05c 100644
--- a/lxc/copy.go
+++ b/lxc/copy.go
@@ -12,9 +12,10 @@ import (
 )
 
 type copyCmd struct {
-	profArgs profileList
-	confArgs configList
-	ephem    bool
+	profArgs      profileList
+	confArgs      configList
+	ephem         bool
+	containerOnly bool
 }
 
 func (c *copyCmd) showByDefault() bool {
@@ -35,6 +36,7 @@ func (c *copyCmd) flags() {
 	gnuflag.Var(&c.profArgs, "p", i18n.G("Profile to apply to the new container"))
 	gnuflag.BoolVar(&c.ephem, "ephemeral", false, i18n.G("Ephemeral container"))
 	gnuflag.BoolVar(&c.ephem, "e", false, i18n.G("Ephemeral container"))
+	gnuflag.BoolVar(&c.containerOnly, "container-only", false, i18n.G("Copy container without snapshots"))
 }
 
 func (c *copyCmd) copyContainer(config *lxd.Config, sourceResource string, destResource string, keepVolatile bool, ephemeral int) error {
@@ -116,7 +118,7 @@ func (c *copyCmd) copyContainer(config *lxd.Config, sourceResource string, destR
 			return fmt.Errorf(i18n.G("can't copy to the same container name"))
 		}
 
-		cp, err := source.LocalCopy(sourceName, destName, status.Config, status.Profiles, ephemeral == 1)
+		cp, err := source.LocalCopy(sourceName, destName, status.Config, status.Profiles, ephemeral == 1, c.containerOnly)
 		if err != nil {
 			return err
 		}
diff --git a/shared/api/container.go b/shared/api/container.go
index 33154bb..5ad7a23 100644
--- a/shared/api/container.go
+++ b/shared/api/container.go
@@ -86,5 +86,6 @@ type ContainerSource struct {
 	Live bool `json:"live,omitempty" yaml:"live,omitempty"`
 
 	// For "copy" type
-	Source string `json:"source,omitempty" yaml:"source,omitempty"`
+	Source        string `json:"source,omitempty" yaml:"source,omitempty"`
+	ContainerOnly bool   `json:"container_only,omitempty" yaml:"container_only,omitempty"`
 }

From a8684b06c1f8c3dd4e857611ea91c8a1cc219a61 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 21 Mar 2017 23:43:35 +0100
Subject: [PATCH 2/3] copy: implement local copy with snapshots

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container.go       |  62 +++++++++++++++---
 lxd/containers_post.go |   3 +-
 lxd/storage.go         |   2 +-
 lxd/storage_btrfs.go   | 170 ++++++++++++++++++++++++++++++++-----------------
 lxd/storage_dir.go     | 117 +++++++++++++++++++++++-----------
 lxd/storage_lvm.go     | 135 ++++++++++++++++++++++++---------------
 lxd/storage_mock.go    |   4 +-
 lxd/storage_zfs.go     | 137 +++++++++++++++++++++++++++++++++++----
 8 files changed, 453 insertions(+), 177 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index 230fe86..4f78e7e 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -576,27 +576,69 @@ func containerCreateFromImage(d *Daemon, args containerArgs, hash string) (conta
 	return c, nil
 }
 
-func containerCreateAsCopy(d *Daemon, args containerArgs, sourceContainer container) (container, error) {
-	// Create the container
-	c, err := containerCreateInternal(d, args)
+func containerCreateAsCopy(d *Daemon, args containerArgs, sourceContainer container, containerOnly bool) (container, error) {
+	// Create the container.
+	ct, err := containerCreateInternal(d, args)
 	if err != nil {
 		return nil, err
 	}
 
-	// Now clone the storage
-	if err := c.Storage().ContainerCopy(c, sourceContainer); err != nil {
-		c.Delete()
+	csList := []*container{}
+	if !containerOnly {
+		snapshots, err := sourceContainer.Snapshots()
+		if err != nil {
+			return nil, err
+		}
+
+		csList = make([]*container, len(snapshots))
+		for i, snap := range snapshots {
+			fields := strings.SplitN(snap.Name(), shared.SnapshotDelimiter, 2)
+			newSnapName := fmt.Sprintf("%s/%s", ct.Name(), fields[1])
+			csArgs := containerArgs{
+				Architecture: snap.Architecture(),
+				Config:       snap.LocalConfig(),
+				Ctype:        cTypeSnapshot,
+				Devices:      snap.LocalDevices(),
+				Ephemeral:    snap.IsEphemeral(),
+				Name:         newSnapName,
+				Profiles:     snap.Profiles(),
+			}
+
+			// Create the snapshots.
+			cs, err := containerCreateInternal(d, csArgs)
+			if err != nil {
+				return nil, err
+			}
+
+			csList[i] = &cs
+		}
+	}
+
+	// Now clone the storage.
+	if err := ct.Storage().ContainerCopy(ct, sourceContainer, containerOnly); err != nil {
+		ct.Delete()
 		return nil, err
 	}
 
-	// Apply any post-storage configuration
-	err = containerConfigureInternal(c)
+	// Apply any post-storage configuration.
+	err = containerConfigureInternal(ct)
 	if err != nil {
-		c.Delete()
+		ct.Delete()
 		return nil, err
 	}
 
-	return c, nil
+	if !containerOnly {
+		for _, cs := range csList {
+			// Apply any post-storage configuration.
+			err = containerConfigureInternal(*cs)
+			if err != nil {
+				(*cs).Delete()
+				return nil, err
+			}
+		}
+	}
+
+	return ct, nil
 }
 
 func containerCreateAsSnapshot(d *Daemon, args containerArgs, sourceContainer container) (container, error) {
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 2db5f17..6fd3723 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -466,11 +466,10 @@ func createFromCopy(d *Daemon, req *api.ContainersPost) Response {
 	}
 
 	run := func(op *operation) error {
-		_, err := containerCreateAsCopy(d, args, source)
+		_, err := containerCreateAsCopy(d, args, source, req.Source.ContainerOnly)
 		if err != nil {
 			return err
 		}
-
 		return nil
 	}
 
diff --git a/lxd/storage.go b/lxd/storage.go
index b19612c..8e4d801 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -222,7 +222,7 @@ type storage interface {
 	ContainerCreateFromImage(container container, imageFingerprint string) error
 	ContainerCanRestore(container container, sourceContainer container) error
 	ContainerDelete(container container) error
-	ContainerCopy(container container, sourceContainer container) error
+	ContainerCopy(target container, source container, containerOnly bool) error
 	ContainerMount(name string, path string) (bool, error)
 	ContainerUmount(name string, path string) (bool, error)
 	ContainerRename(container container, newName string) error
diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index 6a21038..ce91e93 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -745,95 +745,144 @@ func (s *storageBtrfs) ContainerDelete(container container) error {
 	return nil
 }
 
-func (s *storageBtrfs) ContainerCopy(container container, sourceContainer container) error {
-	shared.LogDebugf("Copying BTRFS container storage %s -> %s.", sourceContainer.Name(), container.Name())
+func (s *storageBtrfs) copyContainer(target container, source container) error {
+	sourceContainerSubvolumeName := getContainerMountPoint(s.pool.Name, source.Name())
+	if source.IsSnapshot() {
+		sourceContainerSubvolumeName = getSnapshotMountPoint(s.pool.Name, source.Name())
+	}
+	targetContainerSubvolumeName := getContainerMountPoint(s.pool.Name, target.Name())
 
-	// The storage pool needs to be mounted.
-	_, err := s.StoragePoolMount()
+	containersPath := getContainerMountPoint(s.pool.Name, "")
+	// Ensure that the directories immediately preceeding the
+	// subvolume directory exist.
+	if !shared.PathExists(containersPath) {
+		err := os.MkdirAll(containersPath, 0700)
+		if err != nil {
+			return err
+		}
+	}
+
+	err := s.btrfsPoolVolumesSnapshot(sourceContainerSubvolumeName, targetContainerSubvolumeName, false)
 	if err != nil {
 		return err
 	}
 
-	ourStart, err := sourceContainer.StorageStart()
+	err = createContainerMountpoint(targetContainerSubvolumeName, target.Path(), target.IsPrivileged())
 	if err != nil {
 		return err
 	}
-	if ourStart {
-		defer sourceContainer.StorageStop()
+
+	err = s.setUnprivUserAcl(source, targetContainerSubvolumeName)
+	if err != nil {
+		s.ContainerDelete(target)
+		return err
 	}
 
-	_, sourcePool := sourceContainer.Storage().GetContainerPoolInfo()
-	sourceContainerSubvolumeName := ""
-	if sourceContainer.IsSnapshot() {
-		sourceContainerSubvolumeName = getSnapshotMountPoint(sourcePool, sourceContainer.Name())
-	} else {
-		sourceContainerSubvolumeName = getContainerMountPoint(sourcePool, sourceContainer.Name())
+	err = target.TemplateApply("copy")
+	if err != nil {
+		return err
 	}
 
-	targetContainerSubvolumeName := ""
-	if container.IsSnapshot() {
-		targetContainerSubvolumeName = getSnapshotMountPoint(s.pool.Name, container.Name())
-	} else {
-		targetContainerSubvolumeName = getContainerMountPoint(s.pool.Name, container.Name())
+	return nil
+}
+
+func (s *storageBtrfs) copySnapshot(target container, source container) error {
+	sourceName := source.Name()
+	targetName := target.Name()
+	sourceContainerSubvolumeName := getSnapshotMountPoint(s.pool.Name, sourceName)
+	targetContainerSubvolumeName := getSnapshotMountPoint(s.pool.Name, targetName)
+
+	fields := strings.SplitN(target.Name(), shared.SnapshotDelimiter, 2)
+	containersPath := getSnapshotMountPoint(s.pool.Name, fields[0])
+	snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "snapshots", fields[0])
+	snapshotMntPointSymlink := shared.VarPath("snapshots", fields[0])
+	err := createSnapshotMountpoint(containersPath, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
+	if err != nil {
+		return err
 	}
-	_, targetPool := s.GetContainerPoolInfo()
-	if targetPool == sourcePool {
-		// COMMNET(brauner): They are on the same storage pool which
-		// means they both use btrfs. So we can simply create a new
-		// snapshot of the source container. For this we only mount the
-		// btrfs storage pool and snapshot the subvolume names. No
-		// f*cking around with mounting the containers.
-		ourMount, err := s.StoragePoolMount()
-		if err != nil {
-			return err
-		}
-		if ourMount {
-			defer s.StoragePoolUmount()
-		}
 
-		err = s.btrfsPoolVolumesSnapshot(sourceContainerSubvolumeName, targetContainerSubvolumeName, false)
+	// Ensure that the directories immediately preceeding the
+	// subvolume directory exist.
+	if !shared.PathExists(containersPath) {
+		err := os.MkdirAll(containersPath, 0700)
 		if err != nil {
 			return err
 		}
+	}
 
-		err = createContainerMountpoint(targetContainerSubvolumeName, container.Path(), container.IsPrivileged())
-		if err != nil {
-			return err
-		}
+	err = s.btrfsPoolVolumesSnapshot(sourceContainerSubvolumeName, targetContainerSubvolumeName, false)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (s *storageBtrfs) ContainerCopy(target container, source container, containerOnly bool) error {
+	shared.LogDebugf("Copying BTRFS container storage %s -> %s.", source.Name(), target.Name())
+
+	// The storage pool needs to be mounted.
+	_, err := s.StoragePoolMount()
+	if err != nil {
+		return err
+	}
+
+	ourStart, err := source.StorageStart()
+	if err != nil {
+		return err
+	}
+	if ourStart {
+		defer source.StorageStop()
+	}
+
+	_, sourcePool := source.Storage().GetContainerPoolInfo()
+	_, targetPool := target.Storage().GetContainerPoolInfo()
+	if sourcePool != targetPool {
+		return fmt.Errorf("Copying containers between different storage pools is not implemented.")
+	}
+
+	err = s.copyContainer(target, source)
+	if err != nil {
+		return err
+	}
+
+	if containerOnly {
+		shared.LogDebugf("Copied BTRFS container storage %s -> %s.", source.Name(), target.Name())
+		return nil
+	}
+
+	snapshots, err := source.Snapshots()
+	if err != nil {
+		return err
+	}
 
-		// Make sure that template apply finds the
-		// container mounted.
-		ourMount, err = s.ContainerMount(container.Name(), container.Path())
+	if len(snapshots) == 0 {
+		shared.LogDebugf("Copied BTRFS container storage %s -> %s.", source.Name(), target.Name())
+		return nil
+	}
+
+	for _, snap := range snapshots {
+		sourceSnapshot, err := containerLoadByName(s.d, snap.Name())
 		if err != nil {
 			return err
 		}
-		if ourMount {
-			defer s.ContainerUmount(container.Name(), container.Path())
-		}
-	} else {
-		// Create an empty btrfs storage volume.
-		err = s.ContainerCreate(container)
+
+		fields := strings.SplitN(snap.Name(), shared.SnapshotDelimiter, 2)
+		newSnapName := fmt.Sprintf("%s/%s", target.Name(), fields[1])
+		targetSnapshot, err := containerLoadByName(s.d, newSnapName)
 		if err != nil {
+			shared.LogErrorf("1111: %s", newSnapName)
 			return err
 		}
 
-		// Use rsync to fill the empty volume.
-		output, err := storageRsyncCopy(sourceContainerSubvolumeName, targetContainerSubvolumeName)
+		err = s.copySnapshot(targetSnapshot, sourceSnapshot)
 		if err != nil {
-			s.ContainerDelete(container)
-			shared.LogErrorf("ContainerCopy: rsync failed: %s.", string(output))
-			return fmt.Errorf("rsync failed: %s", string(output))
+			return err
 		}
 	}
 
-	err = s.setUnprivUserAcl(sourceContainer, targetContainerSubvolumeName)
-	if err != nil {
-		s.ContainerDelete(container)
-		return err
-	}
-
-	shared.LogDebugf("Copied BTRFS container storage %s -> %s.", sourceContainer.Name(), container.Name())
-	return container.TemplateApply("copy")
+	shared.LogDebugf("Copied BTRFS container storage %s -> %s.", source.Name(), target.Name())
+	return nil
 }
 
 func (s *storageBtrfs) ContainerMount(name string, path string) (bool, error) {
@@ -1552,7 +1601,8 @@ func (s *storageBtrfs) btrfsPoolVolumesSnapshot(source string, dest string, read
 		// Clear the target for the subvol to use
 		os.Remove(path.Join(dest, subsubvol))
 
-		if err := s.btrfsPoolVolumeSnapshot(path.Join(source, subsubvol), path.Join(dest, subsubvol), readonly); err != nil {
+		err := s.btrfsPoolVolumeSnapshot(path.Join(source, subsubvol), path.Join(dest, subsubvol), readonly)
+		if err != nil {
 			return err
 		}
 	}
diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index 05b717a..18b71b2 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -354,51 +354,49 @@ func (s *storageDir) ContainerDelete(container container) error {
 	return nil
 }
 
-func (s *storageDir) ContainerCopy(container container, sourceContainer container) error {
-	shared.LogDebugf("Copying DIR container storage %s -> %s.", sourceContainer.Name(), container.Name())
+func (s *storageDir) copyContainer(target container, source container) error {
+	sourceContainerMntPoint := getContainerMountPoint(s.pool.Name, source.Name())
+	if source.IsSnapshot() {
+		sourceContainerMntPoint = getSnapshotMountPoint(s.pool.Name, source.Name())
+	}
+	targetContainerMntPoint := getContainerMountPoint(s.pool.Name, target.Name())
 
-	ourStart, err := sourceContainer.StorageStart()
+	err := createContainerMountpoint(targetContainerMntPoint, target.Path(), target.IsPrivileged())
 	if err != nil {
 		return err
 	}
-	if ourStart {
-		defer sourceContainer.StorageStop()
-	}
 
-	// Deal with the source container.
-	_, sourcePool := sourceContainer.Storage().GetContainerPoolInfo()
-	sourceContainerConfig := sourceContainer.Storage().GetStoragePoolWritable()
-	sourceSource := sourceContainerConfig.Config["source"]
-	if sourceSource == "" {
-		return fmt.Errorf("No \"source\" property found for the storage pool.")
+	output, err := storageRsyncCopy(sourceContainerMntPoint, targetContainerMntPoint)
+	if err != nil {
+		return fmt.Errorf("Failed to rsync container: %s: %s.", string(output), err)
 	}
 
-	targetIsPrivileged := container.IsPrivileged()
-	targetContainerMntPoint := ""
-	if container.IsSnapshot() {
-		targetContainerMntPoint = getSnapshotMountPoint(s.pool.Name, container.Name())
-	} else {
-		targetContainerMntPoint = getContainerMountPoint(s.pool.Name, container.Name())
+	err = s.setUnprivUserAcl(source, targetContainerMntPoint)
+	if err != nil {
+		return err
 	}
 
-	targetContainerSymlink := container.Path()
-	err = createContainerMountpoint(targetContainerMntPoint, targetContainerSymlink, targetIsPrivileged)
+	err = target.TemplateApply("copy")
 	if err != nil {
 		return err
 	}
-	revert := true
-	defer func() {
-		if !revert {
-			return
-		}
-		s.ContainerDelete(container)
-	}()
 
-	sourceContainerMntPoint := ""
-	if sourceContainer.IsSnapshot() {
-		sourceContainerMntPoint = getSnapshotMountPoint(sourcePool, sourceContainer.Name())
-	} else {
-		sourceContainerMntPoint = getContainerMountPoint(sourcePool, sourceContainer.Name())
+	return nil
+}
+
+func (s *storageDir) copySnapshot(target container, source container) error {
+	sourceName := source.Name()
+	targetName := target.Name()
+	sourceContainerMntPoint := getSnapshotMountPoint(s.pool.Name, sourceName)
+	targetContainerMntPoint := getSnapshotMountPoint(s.pool.Name, targetName)
+
+	fields := strings.SplitN(target.Name(), shared.SnapshotDelimiter, 2)
+	containersPath := getSnapshotMountPoint(s.pool.Name, fields[0])
+	snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "snapshots", fields[0])
+	snapshotMntPointSymlink := shared.VarPath("snapshots", fields[0])
+	err := createSnapshotMountpoint(containersPath, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
+	if err != nil {
+		return err
 	}
 
 	output, err := storageRsyncCopy(sourceContainerMntPoint, targetContainerMntPoint)
@@ -406,19 +404,66 @@ func (s *storageDir) ContainerCopy(container container, sourceContainer containe
 		return fmt.Errorf("Failed to rsync container: %s: %s.", string(output), err)
 	}
 
-	err = s.setUnprivUserAcl(sourceContainer, targetContainerMntPoint)
+	return nil
+}
+
+func (s *storageDir) ContainerCopy(target container, source container, containerOnly bool) error {
+	shared.LogDebugf("Copying DIR container storage %s -> %s.", source.Name(), target.Name())
+
+	ourStart, err := source.StorageStart()
+	if err != nil {
+		return err
+	}
+	if ourStart {
+		defer source.StorageStop()
+	}
+
+	_, sourcePool := source.Storage().GetContainerPoolInfo()
+	_, targetPool := target.Storage().GetContainerPoolInfo()
+	if sourcePool != targetPool {
+		return fmt.Errorf("Copying containers between different storage pools is not implemented.")
+	}
+
+	err = s.copyContainer(target, source)
 	if err != nil {
 		return err
 	}
 
-	err = container.TemplateApply("copy")
+	if containerOnly {
+		shared.LogDebugf("Copied DIR container storage %s -> %s.", source.Name(), target.Name())
+		return nil
+	}
+
+	snapshots, err := source.Snapshots()
 	if err != nil {
 		return err
 	}
 
-	revert = false
+	if len(snapshots) == 0 {
+		shared.LogDebugf("Copied DIR container storage %s -> %s.", source.Name(), target.Name())
+		return nil
+	}
+
+	for _, snap := range snapshots {
+		sourceSnapshot, err := containerLoadByName(s.d, snap.Name())
+		if err != nil {
+			return err
+		}
+
+		fields := strings.SplitN(snap.Name(), shared.SnapshotDelimiter, 2)
+		newSnapName := fmt.Sprintf("%s/%s", target.Name(), fields[1])
+		targetSnapshot, err := containerLoadByName(s.d, newSnapName)
+		if err != nil {
+			return err
+		}
+
+		err = s.copySnapshot(targetSnapshot, sourceSnapshot)
+		if err != nil {
+			return err
+		}
+	}
 
-	shared.LogDebugf("Copied DIR container storage %s -> %s.", sourceContainer.Name(), container.Name())
+	shared.LogDebugf("Copied DIR container storage %s -> %s.", source.Name(), target.Name())
 	return nil
 }
 
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index b66bcb8..37a1798 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -1258,78 +1258,108 @@ func (s *storageLvm) ContainerDelete(container container) error {
 	return nil
 }
 
-func (s *storageLvm) ContainerCopy(container container, sourceContainer container) error {
-	shared.LogDebugf("Copying LVM container storage %s -> %s.", sourceContainer.Name(), container.Name())
+func (s *storageLvm) copyContainer(target container, source container) error {
+	targetContainerMntPoint := getContainerMountPoint(s.pool.Name, target.Name())
+	err := createContainerMountpoint(targetContainerMntPoint, target.Path(), target.IsPrivileged())
+	if err != nil {
+		return err
+	}
 
-	tryUndo := true
+	err = s.createSnapshotContainer(target, source, false)
+	if err != nil {
+		shared.LogErrorf("Error creating snapshot LV for copy: %s.", err)
+		return err
+	}
 
-	ourStart, err := sourceContainer.StorageStart()
+	err = s.setUnprivUserAcl(source, targetContainerMntPoint)
+	if err != nil {
+		return err
+	}
+
+	err = target.TemplateApply("copy")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (s *storageLvm) copySnapshot(target container, source container) error {
+	fields := strings.SplitN(target.Name(), shared.SnapshotDelimiter, 2)
+	containersPath := getSnapshotMountPoint(s.pool.Name, fields[0])
+	snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "snapshots", fields[0])
+	snapshotMntPointSymlink := shared.VarPath("snapshots", fields[0])
+	err := createSnapshotMountpoint(containersPath, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
+	if err != nil {
+		return err
+	}
+
+	err = s.createSnapshotContainer(target, source, true)
+	if err != nil {
+		shared.LogErrorf("Error creating snapshot LV for copy: %s.", err)
+		return err
+	}
+
+	return nil
+}
+
+func (s *storageLvm) ContainerCopy(target container, source container, containerOnly bool) error {
+	shared.LogDebugf("Copying LVM container storage %s -> %s.", source.Name(), target.Name())
+
+	ourStart, err := source.StorageStart()
 	if err != nil {
 		return err
 	}
 	if ourStart {
-		defer sourceContainer.StorageStop()
+		defer source.StorageStop()
 	}
 
-	if sourceContainer.Storage().GetStorageType() == storageTypeLvm {
-		err := s.createSnapshotContainer(container, sourceContainer, false)
-		if err != nil {
-			shared.LogErrorf("Error creating snapshot LV for copy: %s.", err)
-			return err
-		}
-	} else {
-		sourceContainerName := sourceContainer.Name()
-		targetContainerName := container.Name()
-		shared.LogDebugf("Copy from Non-LVM container: %s -> %s.", sourceContainerName, targetContainerName)
-		err := s.ContainerCreate(container)
-		if err != nil {
-			shared.LogErrorf("Error creating empty container: %s.", err)
-			return err
-		}
-		defer func() {
-			if tryUndo {
-				s.ContainerDelete(container)
-			}
-		}()
+	_, sourcePool := source.Storage().GetContainerPoolInfo()
+	_, targetPool := target.Storage().GetContainerPoolInfo()
+	if sourcePool != targetPool {
+		return fmt.Errorf("Copying containers between different storage pools is not implemented.")
+	}
+
+	err = s.copyContainer(target, source)
+	if err != nil {
+		return err
+	}
+
+	if containerOnly {
+		shared.LogDebugf("Copied LVM container storage %s -> %s.", source.Name(), target.Name())
+		return nil
+	}
 
-		targetContainerPath := container.Path()
-		ourSourceMount, err := s.ContainerMount(targetContainerName, targetContainerPath)
+	snapshots, err := source.Snapshots()
+	if err != nil {
+		return err
+	}
+
+	if len(snapshots) == 0 {
+		shared.LogDebugf("Copied LVM container storage %s -> %s.", source.Name(), target.Name())
+		return nil
+	}
+
+	for _, snap := range snapshots {
+		sourceSnapshot, err := containerLoadByName(s.d, snap.Name())
 		if err != nil {
-			shared.LogErrorf("Error starting/mounting container \"%s\": %s.", targetContainerName, err)
 			return err
 		}
-		if ourSourceMount {
-			defer s.ContainerUmount(targetContainerName, targetContainerPath)
-		}
 
-		sourceContainerPath := sourceContainer.Path()
-		ourTargetMount, err := sourceContainer.Storage().ContainerMount(sourceContainerName, sourceContainerPath)
+		fields := strings.SplitN(snap.Name(), shared.SnapshotDelimiter, 2)
+		newSnapName := fmt.Sprintf("%s/%s", target.Name(), fields[1])
+		targetSnapshot, err := containerLoadByName(s.d, newSnapName)
 		if err != nil {
 			return err
 		}
-		if ourTargetMount {
-			sourceContainer.Storage().ContainerUmount(sourceContainerName, sourceContainerPath)
-		}
 
-		_, sourcePool := sourceContainer.Storage().GetContainerPoolInfo()
-		sourceContainerMntPoint := getContainerMountPoint(sourcePool, sourceContainerName)
-		targetContainerMntPoint := getContainerMountPoint(s.pool.Name, targetContainerName)
-		output, err := storageRsyncCopy(sourceContainerMntPoint, targetContainerMntPoint)
+		err = s.copySnapshot(targetSnapshot, sourceSnapshot)
 		if err != nil {
-			shared.LogErrorf("ContainerCopy: rsync failed: %s.", string(output))
-			s.ContainerDelete(container)
-			return fmt.Errorf("rsync failed: %s", string(output))
+			return err
 		}
 	}
 
-	err = container.TemplateApply("copy")
-	if err != nil {
-		return err
-	}
-
-	tryUndo = false
-
-	shared.LogDebugf("Copied LVM container storage %s -> %s.", sourceContainer.Name(), container.Name())
+	shared.LogDebugf("Copied LVM container storage %s -> %s.", source.Name(), target.Name())
 	return nil
 }
 
@@ -1602,8 +1632,7 @@ func (s *storageLvm) createSnapshotContainer(snapshotContainer container, source
 		targetContainerMntPoint = getSnapshotMountPoint(s.pool.Name, targetContainerName)
 		sourceFields := strings.SplitN(sourceContainerName, shared.SnapshotDelimiter, 2)
 		sourceName := sourceFields[0]
-		_, sourcePool := sourceContainer.Storage().GetContainerPoolInfo()
-		snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", sourcePool, "snapshots", sourceName)
+		snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", poolName, "snapshots", sourceName)
 		snapshotMntPointSymlink := shared.VarPath("snapshots", sourceName)
 		err = createSnapshotMountpoint(targetContainerMntPoint, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
 	} else {
diff --git a/lxd/storage_mock.go b/lxd/storage_mock.go
index 7f2f990..d27a6f6 100644
--- a/lxd/storage_mock.go
+++ b/lxd/storage_mock.go
@@ -125,9 +125,7 @@ func (s *storageMock) ContainerDelete(container container) error {
 	return nil
 }
 
-func (s *storageMock) ContainerCopy(
-	container container, sourceContainer container) error {
-
+func (s *storageMock) ContainerCopy(target container, source container, containerOnly bool) error {
 	return nil
 }
 
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index d5b8df6..6f6d3cc 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -723,14 +723,12 @@ func (s *storageZfs) ContainerDelete(container container) error {
 	return nil
 }
 
-func (s *storageZfs) ContainerCopy(container container, sourceContainer container) error {
-	shared.LogDebugf("Copying ZFS container storage %s -> %s.", sourceContainer.Name(), container.Name())
+func (s *storageZfs) copyWithoutSnapshots(target container, source container) error {
+	sourceContainerName := source.Name()
+	sourceContainerPath := source.Path()
 
-	sourceContainerName := sourceContainer.Name()
-	sourceContainerPath := sourceContainer.Path()
-
-	targetContainerName := container.Name()
-	targetContainerPath := container.Path()
+	targetContainerName := target.Name()
+	targetContainerPath := target.Path()
 	targetContainerMountPoint := getContainerMountPoint(s.pool.Name, targetContainerName)
 
 	sourceZfsDataset := ""
@@ -787,7 +785,7 @@ func (s *storageZfs) ContainerCopy(container container, sourceContainer containe
 			defer s.ContainerUmount(targetContainerName, targetContainerPath)
 		}
 
-		err = createContainerMountpoint(targetContainerMountPoint, targetContainerPath, container.IsPrivileged())
+		err = createContainerMountpoint(targetContainerMountPoint, targetContainerPath, target.IsPrivileged())
 		if err != nil {
 			return err
 		}
@@ -798,7 +796,7 @@ func (s *storageZfs) ContainerCopy(container container, sourceContainer containe
 			deleteContainerMountpoint(targetContainerMountPoint, targetContainerPath, s.GetStorageTypeName())
 		}()
 	} else {
-		err := s.ContainerCreate(container)
+		err := s.ContainerCreate(target)
 		if err != nil {
 			return err
 		}
@@ -806,7 +804,7 @@ func (s *storageZfs) ContainerCopy(container container, sourceContainer containe
 			if !revert {
 				return
 			}
-			s.ContainerDelete(container)
+			s.ContainerDelete(target)
 		}()
 
 		output, err := storageRsyncCopy(sourceContainerPath, targetContainerPath)
@@ -815,14 +813,129 @@ func (s *storageZfs) ContainerCopy(container container, sourceContainer containe
 		}
 	}
 
-	err := container.TemplateApply("copy")
+	err := target.TemplateApply("copy")
 	if err != nil {
 		return err
 	}
 
 	revert = false
 
-	shared.LogDebugf("Copied ZFS container storage %s -> %s.", sourceContainer.Name(), container.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)
+	containersPath := getSnapshotMountPoint(s.pool.Name, fields[0])
+	snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "snapshots", fields[0])
+	snapshotMntPointSymlink := shared.VarPath("snapshots", fields[0])
+	err := createSnapshotMountpoint(containersPath, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
+	if err != nil {
+		return err
+	}
+
+	poolName := s.getOnDiskPoolName()
+	sourceFields := strings.SplitN(sourceName, shared.SnapshotDelimiter, 2)
+	currentSnapshotDataset := fmt.Sprintf("%s/containers/%s at snapshot-%s", poolName, sourceFields[0], sourceFields[1])
+	args := []string{"send", currentSnapshotDataset}
+	if parentSnapshot != "" {
+		parentFields := strings.SplitN(parentSnapshot, shared.SnapshotDelimiter, 2)
+		parentSnapshotDataset := fmt.Sprintf("%s/containers/%s at snapshot-%s", poolName, parentFields[0], parentFields[1])
+		args = append(args, "-i", parentSnapshotDataset)
+	}
+
+	zfsSendCmd := exec.Command("zfs", args...)
+	targetSnapshotDataset := fmt.Sprintf("%s/containers/%s at snapshot-%s", poolName, fields[0], fields[1])
+	zfsRecvCmd := exec.Command("zfs", "receive", targetSnapshotDataset)
+
+	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
+	}
+
+	return nil
+}
+
+func (s *storageZfs) ContainerCopy(target container, source container, containerOnly bool) error {
+	shared.LogDebugf("Copying ZFS container storage %s -> %s.", source.Name(), target.Name())
+
+	ourStart, err := source.StorageStart()
+	if err != nil {
+		return err
+	}
+	if ourStart {
+		defer source.StorageStop()
+	}
+
+	_, sourcePool := source.Storage().GetContainerPoolInfo()
+	_, targetPool := target.Storage().GetContainerPoolInfo()
+	if sourcePool != targetPool {
+		return fmt.Errorf("Copying containers between different storage pools is not implemented.")
+	}
+
+	snapshots, err := source.Snapshots()
+	if err != nil {
+		return err
+	}
+
+	if containerOnly || len(snapshots) == 0 {
+		err = s.copyWithoutSnapshots(target, source)
+	} else {
+		targetContainerName := target.Name()
+		targetContainerPath := target.Path()
+		targetContainerMountPoint := getContainerMountPoint(s.pool.Name, targetContainerName)
+		err = createContainerMountpoint(targetContainerMountPoint, targetContainerPath, target.IsPrivileged())
+		if err != nil {
+			return err
+		}
+
+		for i, snap := range snapshots {
+			prev := ""
+			if i > 0 {
+				prev = snapshots[i-1].Name()
+			}
+
+			sourceSnapshot, err := containerLoadByName(s.d, snap.Name())
+			if err != nil {
+				return err
+			}
+
+			fields := strings.SplitN(snap.Name(), shared.SnapshotDelimiter, 2)
+			newSnapName := fmt.Sprintf("%s/%s", target.Name(), fields[1])
+			targetSnapshot, err := containerLoadByName(s.d, newSnapName)
+			if err != nil {
+				return err
+			}
+
+			err = s.copyWithSnapshots(targetSnapshot, sourceSnapshot, prev)
+			if err != nil {
+				return err
+			}
+		}
+
+		fs := fmt.Sprintf("containers/%s", target.Name())
+		err = s.zfsPoolVolumeSet(fs, "mountpoint", targetContainerMountPoint)
+		if err != nil {
+			return err
+		}
+
+	}
+
+	shared.LogDebugf("Copied ZFS container storage %s -> %s.", source.Name(), target.Name())
 	return nil
 }
 

From 80c29b715f8ba476cdf49d25f961660df3c21a46 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 21 Mar 2017 23:47:34 +0100
Subject: [PATCH 3/3] make i18n

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 po/lxd.pot | 24 ++++++++++++++----------
 1 file changed, 14 insertions(+), 10 deletions(-)

diff --git a/po/lxd.pot b/po/lxd.pot
index ac74b16..7802b36 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2017-03-10 14:49-0500\n"
+        "POT-Creation-Date: 2017-03-21 23:47+0100\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -216,7 +216,7 @@ msgstr  ""
 msgid   "Commands:"
 msgstr  ""
 
-#: lxc/copy.go:32 lxc/copy.go:33 lxc/init.go:135 lxc/init.go:136
+#: lxc/copy.go:33 lxc/copy.go:34 lxc/init.go:135 lxc/init.go:136
 msgid   "Config key/value to apply to the new container"
 msgstr  ""
 
@@ -233,7 +233,7 @@ msgstr  ""
 msgid   "Container name is mandatory"
 msgstr  ""
 
-#: lxc/copy.go:141 lxc/copy.go:242 lxc/init.go:244
+#: lxc/copy.go:143 lxc/copy.go:244 lxc/init.go:244
 #, c-format
 msgid   "Container name is: %s"
 msgstr  ""
@@ -247,6 +247,10 @@ msgstr  ""
 msgid   "Copy aliases from source"
 msgstr  ""
 
+#: lxc/copy.go:39
+msgid   "Copy container without snapshots"
+msgstr  ""
+
 #: lxc/image.go:247
 #, c-format
 msgid   "Copying the image: %s"
@@ -329,7 +333,7 @@ msgstr  ""
 msgid   "Environment:"
 msgstr  ""
 
-#: lxc/copy.go:36 lxc/copy.go:37 lxc/init.go:139 lxc/init.go:140
+#: lxc/copy.go:37 lxc/copy.go:38 lxc/init.go:139 lxc/init.go:140
 msgid   "Ephemeral container"
 msgstr  ""
 
@@ -698,7 +702,7 @@ msgstr  ""
 msgid   "Profile %s removed from %s"
 msgstr  ""
 
-#: lxc/copy.go:34 lxc/copy.go:35 lxc/init.go:137 lxc/init.go:138
+#: lxc/copy.go:35 lxc/copy.go:36 lxc/init.go:137 lxc/init.go:138
 msgid   "Profile to apply to the new container"
 msgstr  ""
 
@@ -1100,7 +1104,7 @@ msgid   "Usage: lxc config <subcommand> [options]\n"
         "    Will set the server's trust password to blah."
 msgstr  ""
 
-#: lxc/copy.go:25
+#: lxc/copy.go:26
 msgid   "Usage: lxc copy [<remote>:]<source>[/<snapshot>] [[<remote>:]<destination>] [--ephemeral|e] [--profile|-p <profile>...] [--config|-c <key=value>...]\n"
         "\n"
         "Copy containers within or in between LXD instances."
@@ -1681,7 +1685,7 @@ msgstr  ""
 msgid   "bad result type from action"
 msgstr  ""
 
-#: lxc/copy.go:116
+#: lxc/copy.go:118
 msgid   "can't copy to the same container name"
 msgstr  ""
 
@@ -1701,7 +1705,7 @@ msgstr  ""
 msgid   "default"
 msgstr  ""
 
-#: lxc/copy.go:132 lxc/copy.go:137 lxc/copy.go:233 lxc/copy.go:238 lxc/init.go:234 lxc/init.go:239 lxc/launch.go:115 lxc/launch.go:121
+#: lxc/copy.go:134 lxc/copy.go:139 lxc/copy.go:235 lxc/copy.go:240 lxc/init.go:234 lxc/init.go:239 lxc/launch.go:115 lxc/launch.go:121
 msgid   "didn't get any affected image, container or snapshot from server"
 msgstr  ""
 
@@ -1731,7 +1735,7 @@ msgstr  ""
 msgid   "no"
 msgstr  ""
 
-#: lxc/copy.go:165
+#: lxc/copy.go:167
 msgid   "not all the profiles from the source exist on the target"
 msgstr  ""
 
@@ -1793,7 +1797,7 @@ msgstr  ""
 msgid   "yes"
 msgstr  ""
 
-#: lxc/copy.go:45
+#: lxc/copy.go:47
 msgid   "you must specify a source container name"
 msgstr  ""
 


More information about the lxc-devel mailing list