[lxc-devel] [lxd/master] Filesystem migration

stgraber on Github lxc-bot at linuxcontainers.org
Wed Feb 10 02:43:58 UTC 2016


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/20160210/5d83f5cb/attachment.bin>
-------------- next part --------------
From 12197138f641d10ea02200f90207117ca2f0e517 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 5 Feb 2016 09:11:44 +0100
Subject: [PATCH 1/5] Make blkio limits more robust
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Only apply I/O limits when set by the user, ignore failure to find a
block device when clearing its limit, have everything else return
errors.

Closes #1568

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 141 +++++++++++++++++++++++++++++++++------------------
 lxd/devices.go       |  66 ++++++++++++++++--------
 2 files changed, 136 insertions(+), 71 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 325bb41..073b9b7 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -563,37 +563,51 @@ func (c *containerLXC) initLXC() error {
 			}
 		}
 
-		diskLimits, err := c.getDiskLimits()
-		if err != nil {
-			return err
+		hasDiskLimits := false
+		for _, m := range c.expandedDevices {
+			if m["type"] != "disk" {
+				continue
+			}
+
+			if m["limits.read"] != "" || m["limits.write"] != "" || m["limits.max"] != "" {
+				hasDiskLimits = true
+				break
+			}
 		}
 
-		for block, limit := range diskLimits {
-			if limit.readBps > 0 {
-				err = lxcSetConfigItem(cc, "lxc.cgroup.blkio.throttle.read_bps_device", fmt.Sprintf("%s %d", block, limit.readBps))
-				if err != nil {
-					return err
-				}
+		if hasDiskLimits {
+			diskLimits, err := c.getDiskLimits()
+			if err != nil {
+				return err
 			}
 
-			if limit.readIops > 0 {
-				err = lxcSetConfigItem(cc, "lxc.cgroup.blkio.throttle.read_iops_device", fmt.Sprintf("%s %d", block, limit.readIops))
-				if err != nil {
-					return err
+			for block, limit := range diskLimits {
+				if limit.readBps > 0 {
+					err = lxcSetConfigItem(cc, "lxc.cgroup.blkio.throttle.read_bps_device", fmt.Sprintf("%s %d", block, limit.readBps))
+					if err != nil {
+						return err
+					}
 				}
-			}
 
-			if limit.writeBps > 0 {
-				err = lxcSetConfigItem(cc, "lxc.cgroup.blkio.throttle.write_bps_device", fmt.Sprintf("%s %d", block, limit.writeBps))
-				if err != nil {
-					return err
+				if limit.readIops > 0 {
+					err = lxcSetConfigItem(cc, "lxc.cgroup.blkio.throttle.read_iops_device", fmt.Sprintf("%s %d", block, limit.readIops))
+					if err != nil {
+						return err
+					}
 				}
-			}
 
-			if limit.writeIops > 0 {
-				err = lxcSetConfigItem(cc, "lxc.cgroup.blkio.throttle.write_iops_device", fmt.Sprintf("%s %d", block, limit.writeIops))
-				if err != nil {
-					return err
+				if limit.writeBps > 0 {
+					err = lxcSetConfigItem(cc, "lxc.cgroup.blkio.throttle.write_bps_device", fmt.Sprintf("%s %d", block, limit.writeBps))
+					if err != nil {
+						return err
+					}
+				}
+
+				if limit.writeIops > 0 {
+					err = lxcSetConfigItem(cc, "lxc.cgroup.blkio.throttle.write_iops_device", fmt.Sprintf("%s %d", block, limit.writeIops))
+					if err != nil {
+						return err
+					}
 				}
 			}
 		}
@@ -3479,6 +3493,33 @@ func (c *containerLXC) removeDiskDevices() error {
 func (c *containerLXC) getDiskLimits() (map[string]deviceBlockLimit, error) {
 	result := map[string]deviceBlockLimit{}
 
+	// Build a list of all valid block devices
+	validBlocks := []string{}
+
+	dents, err := ioutil.ReadDir("/sys/class/block/")
+	if err != nil {
+		return nil, err
+	}
+
+	for _, f := range dents {
+		fPath := filepath.Join("/sys/class/block/", f.Name())
+		if shared.PathExists(fmt.Sprintf("%s/partition", fPath)) {
+			continue
+		}
+
+		if !shared.PathExists(fmt.Sprintf("%s/dev", fPath)) {
+			continue
+		}
+
+		block, err := ioutil.ReadFile(fmt.Sprintf("%s/dev", fPath))
+		if err != nil {
+			return nil, err
+		}
+
+		validBlocks = append(validBlocks, strings.TrimSuffix(string(block), "\n"))
+	}
+
+	// Process all the limits
 	blockLimits := map[string][]deviceBlockLimit{}
 	for _, m := range c.expandedDevices {
 		if m["type"] != "disk" {
@@ -3491,48 +3532,49 @@ func (c *containerLXC) getDiskLimits() (map[string]deviceBlockLimit, error) {
 			m["limits.write"] = m["limits.max"]
 		}
 
+		// Parse the user input
+		readBps, readIops, writeBps, writeIops, err := deviceParseDiskLimit(m["limits.read"], m["limits.write"])
+		if err != nil {
+			return nil, err
+		}
+
+		// Set the source path
 		source := m["source"]
 		if m["path"] == "" {
 			source = c.RootfsPath()
 		}
 
+		// Get the backing block devices (major:minor)
 		blocks, err := deviceGetParentBlocks(source)
 		if err != nil {
-			return nil, err
+			if readBps == 0 && readIops == 0 && writeBps == 0 && writeIops == 0 {
+				// If the device doesn't exist, there is no limit to clear so ignore the failure
+				continue
+			} else {
+				return nil, err
+			}
 		}
 
-		readBps, readIops, writeBps, writeIops, err := deviceParseDiskLimit(m["limits.read"], m["limits.write"])
-		if err != nil {
-			return nil, err
-		}
 		device := deviceBlockLimit{readBps: readBps, readIops: readIops, writeBps: writeBps, writeIops: writeIops}
-
 		for _, block := range blocks {
-			dev := strings.TrimPrefix(block, "/dev/")
-
-			if strings.Contains(dev, "/") {
-				continue
-			}
+			blockStr := ""
 
-			if !shared.PathExists(fmt.Sprintf("/sys/class/block/%s/dev", dev)) {
-				return nil, fmt.Errorf("Disk %s is missing /sys/class/block entry", dev)
-			}
-
-			block, err := ioutil.ReadFile(fmt.Sprintf("/sys/class/block/%s/dev", dev))
-			if err != nil {
-				return nil, err
-			}
-
-			fields := strings.SplitN(strings.TrimSuffix(string(block), "\n"), ":", 2)
-			if len(fields) != 2 {
-				return nil, fmt.Errorf("Invalid major:minor: %s", block)
+			if shared.StringInSlice(block, validBlocks) {
+				// Straightforward entry (full block device)
+				blockStr = block
+			} else {
+				// Attempt to deal with a partition (guess its parent)
+				fields := strings.SplitN(block, ":", 2)
+				fields[1] = "0"
+				if shared.StringInSlice(fmt.Sprintf("%s:%s", fields[0], fields[1]), validBlocks) {
+					blockStr = fmt.Sprintf("%s:%s", fields[0], fields[1])
+				}
 			}
 
-			if shared.PathExists(fmt.Sprintf("/sys/class/block/%s/partition", dev)) {
-				fields[1] = "0"
+			if blockStr == "" {
+				return nil, fmt.Errorf("Block device doesn't support quotas: %s", block)
 			}
 
-			blockStr := fmt.Sprintf("%s:%s", fields[0], fields[1])
 			if blockLimits[blockStr] == nil {
 				blockLimits[blockStr] = []deviceBlockLimit{}
 			}
@@ -3540,6 +3582,7 @@ func (c *containerLXC) getDiskLimits() (map[string]deviceBlockLimit, error) {
 		}
 	}
 
+	// Average duplicate limits
 	for block, limits := range blockLimits {
 		var readBpsCount, readBpsTotal, readIopsCount, readIopsTotal, writeBpsCount, writeBpsTotal, writeIopsCount, writeIopsTotal int64
 
diff --git a/lxd/devices.go b/lxd/devices.go
index 93b7a40..9c4bd64 100644
--- a/lxd/devices.go
+++ b/lxd/devices.go
@@ -737,6 +737,7 @@ func deviceTotalMemory() (int64, error) {
 
 func deviceGetParentBlocks(path string) ([]string, error) {
 	var devices []string
+	var device []string
 
 	// Expand the mount path
 	absPath, err := filepath.Abs(path)
@@ -757,7 +758,6 @@ func deviceGetParentBlocks(path string) ([]string, error) {
 	defer file.Close()
 
 	scanner := bufio.NewScanner(file)
-	device := ""
 	match := ""
 	for scanner.Scan() {
 		line := scanner.Text()
@@ -774,23 +774,29 @@ func deviceGetParentBlocks(path string) ([]string, error) {
 		match = rows[4]
 
 		// Go backward to avoid problems with optional fields
-		device = rows[len(rows)-2]
+		device = []string{rows[2], rows[len(rows)-2]}
 	}
 
-	if device == "" {
+	if device == nil {
 		return nil, fmt.Errorf("Couldn't find a match /proc/self/mountinfo entry")
 	}
 
+	// Handle the most simple case
+	if !strings.HasPrefix(device[0], "0:") {
+		return []string{device[0]}, nil
+	}
+
 	// Deal with per-filesystem oddities. We don't care about failures here
 	// because any non-special filesystem => directory backend.
 	fs, _ := filesystemDetect(expPath)
 
 	if fs == "zfs" && shared.PathExists("/dev/zfs") {
-		poolName := strings.Split(device, "/")[0]
+		// Accessible zfs filesystems
+		poolName := strings.Split(device[1], "/")[0]
 
 		output, err := exec.Command("zpool", "status", poolName).CombinedOutput()
 		if err != nil {
-			return nil, fmt.Errorf("Failed to query zfs filesystem information for %s: %s", device, output)
+			return nil, fmt.Errorf("Failed to query zfs filesystem information for %s: %s", device[1], output)
 		}
 
 		for _, line := range strings.Split(string(output), "\n") {
@@ -803,9 +809,10 @@ func deviceGetParentBlocks(path string) ([]string, error) {
 				continue
 			}
 
+			var path string
 			if shared.PathExists(fields[0]) {
 				if shared.IsBlockdevPath(fields[0]) {
-					devices = append(devices, fields[0])
+					path = fields[0]
 				} else {
 					subDevices, err := deviceGetParentBlocks(fields[0])
 					if err != nil {
@@ -817,17 +824,27 @@ func deviceGetParentBlocks(path string) ([]string, error) {
 					}
 				}
 			} else if shared.PathExists(fmt.Sprintf("/dev/%s", fields[0])) {
-				devices = append(devices, fmt.Sprintf("/dev/%s", fields[0]))
+				path = fmt.Sprintf("/dev/%s", fields[0])
 			} else if shared.PathExists(fmt.Sprintf("/dev/disk/by-id/%s", fields[0])) {
-				devices = append(devices, fmt.Sprintf("/dev/disk/by-id/%s", fields[0]))
+				path = fmt.Sprintf("/dev/disk/by-id/%s", fields[0])
 			} else {
-				continue
+				return nil, fmt.Errorf("Unsupported zfs backing device: %s", fields[0])
+			}
+
+			if path != "" {
+				_, major, minor, err := deviceGetAttributes(fields[len(fields)-1])
+				if err != nil {
+					return nil, err
+				}
+
+				devices = append(devices, fmt.Sprintf("%d:%d", major, minor))
 			}
 		}
-	} else if fs == "btrfs" && shared.PathExists(device) {
-		output, err := exec.Command("btrfs", "filesystem", "show", device).CombinedOutput()
+	} else if fs == "btrfs" && shared.PathExists(device[1]) {
+		// Accessible btrfs filesystems
+		output, err := exec.Command("btrfs", "filesystem", "show", device[1]).CombinedOutput()
 		if err != nil {
-			return nil, fmt.Errorf("Failed to query btrfs filesystem information for %s: %s", device, output)
+			return nil, fmt.Errorf("Failed to query btrfs filesystem information for %s: %s", device[1], output)
 		}
 
 		for _, line := range strings.Split(string(output), "\n") {
@@ -836,18 +853,23 @@ func deviceGetParentBlocks(path string) ([]string, error) {
 				continue
 			}
 
-			devices = append(devices, fields[len(fields)-1])
-		}
-	} else if shared.PathExists(device) {
-		devices = append(devices, device)
-	}
+			_, major, minor, err := deviceGetAttributes(fields[len(fields)-1])
+			if err != nil {
+				return nil, err
+			}
 
-	// Expand the device paths
-	for i, dev := range devices {
-		target, err := filepath.EvalSymlinks(dev)
-		if err == nil {
-			devices[i] = target
+			devices = append(devices, fmt.Sprintf("%d:%d", major, minor))
 		}
+	} else if shared.PathExists(device[1]) {
+		// Anything else with a valid path
+		_, major, minor, err := deviceGetAttributes(device[1])
+		if err != nil {
+			return nil, err
+		}
+
+		devices = append(devices, fmt.Sprintf("%d:%d", major, minor))
+	} else {
+		return nil, fmt.Errorf("Invalid block device: %s", device[1])
 	}
 
 	return devices, nil

From 4a15a0918a33ee0be5689e42f941d33e33c55550 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 9 Feb 2016 20:49:43 -0500
Subject: [PATCH 2/5] Change ShiftIfNecessary to shift on startup
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #1585

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/storage.go | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/lxd/storage.go b/lxd/storage.go
index f3e4c9a..cb99710 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"encoding/json"
 	"fmt"
 	"os"
 	"os/exec"
@@ -535,11 +536,19 @@ func ShiftIfNecessary(container container, srcIdmap *shared.IdmapSet) error {
 	}
 
 	if !reflect.DeepEqual(srcIdmap, dstIdmap) {
-		if err := srcIdmap.UnshiftRootfs(container.Path()); err != nil {
-			return err
+		var jsonIdmap string
+		if srcIdmap != nil {
+			idmapBytes, err := json.Marshal(srcIdmap.Idmap)
+			if err != nil {
+				return err
+			}
+			jsonIdmap = string(idmapBytes)
+		} else {
+			jsonIdmap = "[]"
 		}
 
-		if err := dstIdmap.ShiftRootfs(container.Path()); err != nil {
+		err := container.ConfigKeySet("volatile.last_state.idmap", jsonIdmap)
+		if err != nil {
 			return err
 		}
 	}

From cd92657a0ad30eec410944b427ec18f337f6629f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 9 Feb 2016 16:09:19 -0500
Subject: [PATCH 3/5] Add btrfs send/receive support
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #1176

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/storage_btrfs.go | 231 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 227 insertions(+), 4 deletions(-)

diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index 7e2207d..0aa522d 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -2,6 +2,7 @@ package main
 
 import (
 	"fmt"
+	"io/ioutil"
 	"os"
 	"os/exec"
 	"path"
@@ -10,6 +11,7 @@ import (
 	"syscall"
 
 	"github.com/gorilla/websocket"
+	"github.com/pborman/uuid"
 
 	"github.com/lxc/lxd/shared"
 
@@ -766,14 +768,235 @@ func (s *storageBtrfs) getSubVolumes(path string) ([]string, error) {
 	return result, nil
 }
 
+type btrfsMigrationSource struct {
+	lxdName            string
+	deleteAfterSending bool
+	btrfsPath          string
+	btrfsParent        string
+
+	btrfs *storageBtrfs
+}
+
+func (s btrfsMigrationSource) Name() string {
+	return s.lxdName
+}
+
+func (s btrfsMigrationSource) IsSnapshot() bool {
+	return !s.deleteAfterSending
+}
+
+func (s btrfsMigrationSource) Send(conn *websocket.Conn) error {
+	args := []string{"send", s.btrfsPath}
+	if s.btrfsParent != "" {
+		args = append(args, "-p", s.btrfsParent)
+	}
+
+	cmd := exec.Command("btrfs", args...)
+
+	deleteAfterSending := func(path string) {
+		s.btrfs.subvolsDelete(path)
+		os.Remove(filepath.Dir(path))
+	}
+
+	stdout, err := cmd.StdoutPipe()
+	if err != nil {
+		if s.deleteAfterSending {
+			deleteAfterSending(s.btrfsPath)
+		}
+		return err
+	}
+
+	stderr, err := cmd.StderrPipe()
+	if err != nil {
+		if s.deleteAfterSending {
+			deleteAfterSending(s.btrfsPath)
+		}
+		return err
+	}
+
+	if err := cmd.Start(); err != nil {
+		if s.deleteAfterSending {
+			deleteAfterSending(s.btrfsPath)
+		}
+		return err
+	}
+
+	<-shared.WebsocketSendStream(conn, stdout)
+
+	output, err := ioutil.ReadAll(stderr)
+	if err != nil {
+		shared.Log.Error("problem reading btrfs send stderr", "err", err)
+	}
+
+	err = cmd.Wait()
+	if err != nil {
+		shared.Log.Error("problem with btrfs send", "output", string(output))
+	}
+	if s.deleteAfterSending {
+		deleteAfterSending(s.btrfsPath)
+	}
+	return err
+}
+
 func (s *storageBtrfs) MigrationType() MigrationFSType {
-	return MigrationFSType_RSYNC
+	if runningInUserns {
+		return MigrationFSType_RSYNC
+	} else {
+		return MigrationFSType_BTRFS
+	}
 }
 
-func (s *storageBtrfs) MigrationSource(container container) ([]MigrationStorageSource, error) {
-	return rsyncMigrationSource(container)
+func (s *storageBtrfs) MigrationSource(c container) ([]MigrationStorageSource, error) {
+	if runningInUserns {
+		return rsyncMigrationSource(c)
+	}
+
+	sources := []MigrationStorageSource{}
+
+	/* If the container is a snapshot, let's just send that; we don't need
+	 * to send anything else, because that's all the user asked for.
+	 */
+	if c.IsSnapshot() {
+		sources = append(sources, btrfsMigrationSource{c.Name(), false, c.Path(), "", s})
+		return sources, nil
+	}
+
+	/* List all the snapshots in order of reverse creation. The idea here
+	 * is that we send the oldest to newest snapshot, hopefully saving on
+	 * xfer costs. Then, after all that, we send the container itself.
+	 */
+	snapshots, err := c.Snapshots()
+	if err != nil {
+		return nil, err
+	}
+
+	for i, snap := range snapshots {
+		var prev container
+		if i > 0 {
+			prev = snapshots[i-1]
+		}
+
+		btrfsPath := snap.Path()
+		parentName := ""
+		if prev != nil {
+			parentName = prev.Path()
+		}
+
+		sources = append(sources, btrfsMigrationSource{snap.Name(), false, btrfsPath, parentName, s})
+	}
+
+	/* We can't send running fses, so let's snapshot the fs and send
+	 * the snapshot.
+	 */
+
+	tmpPath := containerPath(fmt.Sprintf("%s/.migration-send-%s", c.Name(), uuid.NewRandom().String()), true)
+	err = os.MkdirAll(tmpPath, 0700)
+	if err != nil {
+		return nil, err
+	}
+
+	btrfsPath := fmt.Sprintf("%s/.root", tmpPath)
+	if err := s.subvolSnapshot(c.Path(), btrfsPath, true); err != nil {
+		return nil, err
+	}
+
+	btrfsParent := ""
+	if len(sources) > 0 {
+		btrfsParent = sources[len(sources)-1].(btrfsMigrationSource).btrfsPath
+	}
+
+	sources = append(sources, btrfsMigrationSource{c.Name(), true, btrfsPath, btrfsParent, s})
+
+	return sources, nil
 }
 
 func (s *storageBtrfs) MigrationSink(container container, snapshots []container, conn *websocket.Conn) error {
-	return rsyncMigrationSink(container, snapshots, conn)
+	if runningInUserns {
+		return rsyncMigrationSink(container, snapshots, conn)
+	}
+
+	cName := container.Name()
+
+	snapshotsPath := shared.VarPath(fmt.Sprintf("snapshots/%s", cName))
+	if !shared.PathExists(snapshotsPath) {
+		err := os.MkdirAll(shared.VarPath(fmt.Sprintf("snapshots/%s", cName)), 0700)
+		if err != nil {
+			return err
+		}
+	}
+
+	btrfsRecv := func(btrfsPath string, targetPath string, issnapshot bool) error {
+		args := []string{"receive", "-e", btrfsPath}
+		cmd := exec.Command("btrfs", args...)
+
+		// Remove the existing pre-created subvolume
+		err := s.subvolsDelete(targetPath)
+		if err != nil {
+			return err
+		}
+
+		stdin, err := cmd.StdinPipe()
+		if err != nil {
+			return err
+		}
+
+		stderr, err := cmd.StderrPipe()
+		if err != nil {
+			return err
+		}
+
+		if err := cmd.Start(); err != nil {
+			return err
+		}
+
+		<-shared.WebsocketRecvStream(stdin, conn)
+
+		output, err := ioutil.ReadAll(stderr)
+		if err != nil {
+			shared.Debugf("problem reading btrfs receive stderr %s", err)
+		}
+
+		err = cmd.Wait()
+		if err != nil {
+			shared.Log.Error("problem with btrfs receive", log.Ctx{"output": string(output)})
+			return err
+		}
+
+		if !issnapshot {
+			err := s.subvolSnapshot(containerPath(fmt.Sprintf("%s/.root", cName), true), targetPath, false)
+			if err != nil {
+				shared.Log.Error("problem with btrfs snapshot", log.Ctx{"err": err})
+				return err
+			}
+
+			err = s.subvolsDelete(containerPath(fmt.Sprintf("%s/.root", cName), true))
+			if err != nil {
+				shared.Log.Error("problem with btrfs delete", log.Ctx{"err": err})
+				return err
+			}
+		}
+
+		return nil
+	}
+
+	for _, snap := range snapshots {
+		if err := btrfsRecv(containerPath(cName, true), snap.Path(), true); err != nil {
+			return err
+		}
+	}
+
+	/* finally, do the real container */
+	if err := btrfsRecv(containerPath(cName, true), container.Path(), false); err != nil {
+		return err
+	}
+
+	// Cleanup
+	if ok, _ := shared.PathIsEmpty(snapshotsPath); ok {
+		err := os.Remove(snapshotsPath)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
 }

From 498c5d441c31a089ba27ccc9dcf3f7ae9834123d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 9 Feb 2016 21:29:01 -0500
Subject: [PATCH 4/5] Implement migration fallback to rsync
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/migrate.go | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)

diff --git a/lxd/migrate.go b/lxd/migrate.go
index 946d1be..b653bea 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -313,11 +313,11 @@ func (s *migrationSourceWs) Do(op *operation) error {
 		return err
 	}
 
-	// TODO: actually fall back on rsync.
 	if *header.Fs != myType {
-		err := fmt.Errorf("mismatched storage types not supported yet")
-		s.sendControl(err)
-		return err
+		myType = MigrationFSType_RSYNC
+		header.Fs = &myType
+
+		sources, _ = rsyncMigrationSource(s.container)
 	}
 
 	if s.live {
@@ -490,15 +490,20 @@ func (c *migrationSink) do() error {
 	if !c.live {
 		criuType = nil
 	}
+
+	mySink := c.container.Storage().MigrationSink
 	myType := c.container.Storage().MigrationType()
 	resp := MigrationHeader{
 		Fs:   &myType,
 		Criu: criuType,
 	}
+
 	// If the storage type the source has doesn't match what we have, then
 	// we have to use rsync.
 	if *header.Fs != *resp.Fs {
-		resp.Fs = MigrationFSType_RSYNC.Enum()
+		mySink = rsyncMigrationSink
+		myType = MigrationFSType_RSYNC
+		resp.Fs = &myType
 	}
 
 	if err := c.send(&resp); err != nil {
@@ -593,7 +598,7 @@ func (c *migrationSink) do() error {
 			srcIdmap.Idmap = shared.Extend(srcIdmap.Idmap, e)
 		}
 
-		if err := c.container.Storage().MigrationSink(c.container, snapshots, c.fsConn); err != nil {
+		if err := mySink(c.container, snapshots, c.fsConn); err != nil {
 			restore <- err
 			c.sendControl(err)
 			return

From 8538acf138a5fd97e7fd9a83bebd381aa18e19a1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 9 Feb 2016 21:40:03 -0500
Subject: [PATCH 5/5] Fix migration of snapshots using rsync
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/storage.go | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/lxd/storage.go b/lxd/storage.go
index cb99710..a77bd6e 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -596,6 +596,13 @@ func rsyncMigrationSink(container container, snapshots []container, conn *websoc
 		return err
 	}
 
+	if len(snapshots) > 0 {
+		err := os.MkdirAll(shared.VarPath(fmt.Sprintf("snapshots/%s", container.Name())), 0700)
+		if err != nil {
+			return err
+		}
+	}
+
 	for _, snap := range snapshots {
 		if err := RsyncRecv(shared.AddSlash(snap.Path()), conn); err != nil {
 			return err


More information about the lxc-devel mailing list