[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