[lxc-devel] [lxd/master] storage pools: implement rsync.bwlimit property

brauner on Github lxc-bot at linuxcontainers.org
Tue Apr 18 13:44:25 UTC 2017


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 364 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20170418/49c6a7b1/attachment.bin>
-------------- next part --------------
From d8b8ee7e45a3c52fe6d3b3cd03c1d122ccd5b566 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 18 Apr 2017 15:12:36 +0200
Subject: [PATCH 1/2] storage pools: implement rsync.bwlimit property

When rsync has to be invoked to transfer storage entities (e.g. on local or
remote migration) there is currently no way to place an upper limit on the
amount of socket I/O allowed. This can lead to problems on systems with limited
ram available. Starting with this commit LXD allows setting rsync.bwlimit to
place an upper limit on the amount of socket I/O allowed. This property is
storage pool specific rather than a global daemon config key. In this way,
users can grant important pools more I/O than less important pools.

Closes #2678.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 doc/configuration.md        |  1 +
 doc/storage-backends.md     |  3 +++
 lxd/api_1.0.go              |  1 +
 lxd/db_update.go            |  2 +-
 lxd/migrate.go              | 13 ++++++++++---
 lxd/patches.go              | 12 ++++++------
 lxd/rsync.go                | 46 +++++++++++++++++++++++++++++++++++++++------
 lxd/storage.go              | 27 --------------------------
 lxd/storage_btrfs.go        | 13 +++++++++----
 lxd/storage_dir.go          | 24 +++++++++++++++--------
 lxd/storage_lvm.go          | 17 +++++++++++------
 lxd/storage_migration.go    | 15 ++++++++-------
 lxd/storage_pools_config.go |  9 +++++++++
 lxd/storage_zfs.go          |  9 ++++++---
 14 files changed, 121 insertions(+), 71 deletions(-)

diff --git a/doc/configuration.md b/doc/configuration.md
index ff69154..a95e46c 100644
--- a/doc/configuration.md
+++ b/doc/configuration.md
@@ -398,6 +398,7 @@ volume.block.mount\_options     | string    | block based driver (lvm)
 lvm.thinpool\_name              | string    | lvm driver                        | LXDPool           | Thin pool where images and containers are created.
 lvm.use\_thinpool               | bool      | lvm driver                        | true              | Whether the storage pool uses a thinpool for logical volumes.
 lvm.vg\_name                    | string    | lvm driver                        | name of the pool  | Name of the volume group to create.
+rsync.bwlimit                   | string    | -				        | 0 (no limit)      | Specifies the upper limit to be placed on the socket I/O whenever rsync has to be used to transfer storage entities.
 volume.size                     | string    | appropriate driver                | 0                 | Default volume size
 volume.zfs.remove\_snapshots    | bool      | zfs driver                        | false             | Remove snapshots as needed
 volume.zfs.use\_refquota        | bool      | zfs driver                        | false             | Use refquota instead of quota for space.
diff --git a/doc/storage-backends.md b/doc/storage-backends.md
index e5f3d4b..58ae60d 100644
--- a/doc/storage-backends.md
+++ b/doc/storage-backends.md
@@ -43,6 +43,9 @@ LXD uses those features to transfer containers and snapshots between servers.
 When such capabilities aren't available, either because the storage driver doesn't support it  
 or because the storage backend of the source and target servers differ,  
 LXD will fallback to using rsync to transfer the individual files instead.
+When rsync has to be used LXD allows to specify an upper limit on the amount of
+socket I/O by setting the "rsync.bwlimit" storage pool property to a non-zero
+value.
 
 ## Default storage pool
 There is no concept of a default storage pool in LXD.  
diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index dff9690..7cd7b5b 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -102,6 +102,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
 			"storage_zfs_clone_copy",
 			"unix_device_rename",
 			"storage_lvm_use_thinpool",
+			"storage_rsync_bwlimit",
 		},
 		APIStatus:  "stable",
 		APIVersion: version.APIVersion,
diff --git a/lxd/db_update.go b/lxd/db_update.go
index f40208b..bc641c6 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -576,7 +576,7 @@ func dbUpdateFromV11(currentVersion int, version int, d *Daemon) error {
 			// containers/<container>/snapshots/<snap0>
 			// to
 			// snapshots/<container>/<snap0>
-			output, err := storageRsyncCopy(oldPath, newPath)
+			output, err := rsyncLocalCopy(oldPath, newPath, "")
 			if err != nil {
 				logger.Error(
 					"Failed rsync snapshot",
diff --git a/lxd/migrate.go b/lxd/migrate.go
index 77ea9d3..1a8ad44 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -374,11 +374,18 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 		return err
 	}
 
+	bwlimit := ""
 	if *header.Fs != myType {
 		myType = MigrationFSType_RSYNC
 		header.Fs = &myType
 
 		driver, _ = rsyncMigrationSource(s.container, s.containerOnly)
+
+		// Check if this storage pool has a rate limit set for rsync.
+		poolwritable := s.container.Storage().GetStoragePoolWritable()
+		if poolwritable.Config != nil {
+			bwlimit = poolwritable.Config["rsync.bwlimit"]
+		}
 	}
 
 	// All failure paths need to do a few things to correctly handle errors before returning.
@@ -394,7 +401,7 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 		return err
 	}
 
-	err = driver.SendWhileRunning(s.fsConn, migrateOp)
+	err = driver.SendWhileRunning(s.fsConn, migrateOp, bwlimit)
 	if err != nil {
 		return abort(err)
 	}
@@ -515,12 +522,12 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 		 * no reason to do these in parallel. In the future when we're using
 		 * p.haul's protocol, it will make sense to do these in parallel.
 		 */
-		err = RsyncSend(shared.AddSlash(checkpointDir), s.criuConn, nil)
+		err = RsyncSend(shared.AddSlash(checkpointDir), s.criuConn, nil, bwlimit)
 		if err != nil {
 			return abort(err)
 		}
 
-		err = driver.SendAfterCheckpoint(s.fsConn)
+		err = driver.SendAfterCheckpoint(s.fsConn, bwlimit)
 		if err != nil {
 			return abort(err)
 		}
diff --git a/lxd/patches.go b/lxd/patches.go
index 1123865..83bdd16 100644
--- a/lxd/patches.go
+++ b/lxd/patches.go
@@ -393,7 +393,7 @@ func upgradeFromStorageTypeBtrfs(name string, d *Daemon, defaultPoolName string,
 					return err
 				}
 
-				output, err := storageRsyncCopy(oldContainerMntPoint, newContainerMntPoint)
+				output, err := rsyncLocalCopy(oldContainerMntPoint, newContainerMntPoint, "")
 				if err != nil {
 					logger.Errorf("Failed to rsync: %s: %s.", output, err)
 					return err
@@ -480,7 +480,7 @@ func upgradeFromStorageTypeBtrfs(name string, d *Daemon, defaultPoolName string,
 						return err
 					}
 
-					output, err := storageRsyncCopy(oldSnapshotMntPoint, newSnapshotMntPoint)
+					output, err := rsyncLocalCopy(oldSnapshotMntPoint, newSnapshotMntPoint, "")
 					if err != nil {
 						logger.Errorf("Failed to rsync: %s: %s.", output, err)
 						return err
@@ -684,7 +684,7 @@ func upgradeFromStorageTypeDir(name string, d *Daemon, defaultPoolName string, d
 			// First try to rename.
 			err := os.Rename(oldContainerMntPoint, newContainerMntPoint)
 			if err != nil {
-				output, err := storageRsyncCopy(oldContainerMntPoint, newContainerMntPoint)
+				output, err := rsyncLocalCopy(oldContainerMntPoint, newContainerMntPoint, "")
 				if err != nil {
 					logger.Errorf("Failed to rsync: %s: %s.", output, err)
 					return err
@@ -731,7 +731,7 @@ func upgradeFromStorageTypeDir(name string, d *Daemon, defaultPoolName string, d
 		if shared.PathExists(oldSnapshotMntPoint) && !shared.PathExists(newSnapshotMntPoint) {
 			err := os.Rename(oldSnapshotMntPoint, newSnapshotMntPoint)
 			if err != nil {
-				output, err := storageRsyncCopy(oldSnapshotMntPoint, newSnapshotMntPoint)
+				output, err := rsyncLocalCopy(oldSnapshotMntPoint, newSnapshotMntPoint, "")
 				if err != nil {
 					logger.Errorf("Failed to rsync: %s: %s.", output, err)
 					return err
@@ -1057,7 +1057,7 @@ func upgradeFromStorageTypeLvm(name string, d *Daemon, defaultPoolName string, d
 				}
 
 				// Use rsync to fill the empty volume.
-				output, err := storageRsyncCopy(oldContainerMntPoint, newContainerMntPoint)
+				output, err := rsyncLocalCopy(oldContainerMntPoint, newContainerMntPoint, "")
 				if err != nil {
 					ctStorage.ContainerDelete(ctStruct)
 					return fmt.Errorf("rsync failed: %s", string(output))
@@ -1211,7 +1211,7 @@ func upgradeFromStorageTypeLvm(name string, d *Daemon, defaultPoolName string, d
 					}
 
 					// Use rsync to fill the empty volume.
-					output, err := storageRsyncCopy(oldSnapshotMntPoint, newSnapshotMntPoint)
+					output, err := rsyncLocalCopy(oldSnapshotMntPoint, newSnapshotMntPoint, "")
 					if err != nil {
 						csStorage.ContainerDelete(csStruct)
 						return fmt.Errorf("rsync failed: %s", string(output))
diff --git a/lxd/rsync.go b/lxd/rsync.go
index 456bd5a..44775f4 100644
--- a/lxd/rsync.go
+++ b/lxd/rsync.go
@@ -14,7 +14,36 @@ import (
 	"github.com/lxc/lxd/shared/logger"
 )
 
-func rsyncSendSetup(path string) (*exec.Cmd, net.Conn, io.ReadCloser, error) {
+// rsyncCopy copies a directory using rsync (with the --devices option).
+func rsyncLocalCopy(source string, dest string, bwlimit string) (string, error) {
+	err := os.MkdirAll(dest, 0755)
+	if err != nil {
+		return "", err
+	}
+
+	rsyncVerbosity := "-q"
+	if debug {
+		rsyncVerbosity = "-vi"
+	}
+
+	if bwlimit == "" {
+		bwlimit = "0"
+	}
+
+	return shared.RunCommand("rsync",
+		"-a",
+		"-HAX",
+		"--devices",
+		"--delete",
+		"--checksum",
+		"--numeric-ids",
+		"--bwlimit", bwlimit,
+		rsyncVerbosity,
+		shared.AddSlash(source),
+		dest)
+}
+
+func rsyncSendSetup(path string, bwlimit string) (*exec.Cmd, net.Conn, io.ReadCloser, error) {
 	/*
 	 * It's sort of unfortunate, but there's no library call to get a
 	 * temporary name, so we get the file and close it and use its name.
@@ -57,8 +86,11 @@ func rsyncSendSetup(path string) (*exec.Cmd, net.Conn, io.ReadCloser, error) {
 	 * hardcoding that at the other end, so we can just ignore it.
 	 */
 	rsyncCmd := fmt.Sprintf("sh -c \"%s netcat %s\"", execPath, f.Name())
-	cmd := exec.Command(
-		"rsync",
+	if bwlimit == "" {
+		bwlimit = "0"
+	}
+
+	cmd := exec.Command("rsync",
 		"-arvP",
 		"--devices",
 		"--numeric-ids",
@@ -66,7 +98,9 @@ func rsyncSendSetup(path string) (*exec.Cmd, net.Conn, io.ReadCloser, error) {
 		path,
 		"localhost:/tmp/foo",
 		"-e",
-		rsyncCmd)
+		rsyncCmd,
+		"--bwlimit",
+		bwlimit)
 
 	stderr, err := cmd.StderrPipe()
 	if err != nil {
@@ -90,8 +124,8 @@ func rsyncSendSetup(path string) (*exec.Cmd, net.Conn, io.ReadCloser, error) {
 
 // RsyncSend sets up the sending half of an rsync, to recursively send the
 // directory pointed to by path over the websocket.
-func RsyncSend(path string, conn *websocket.Conn, readWrapper func(io.ReadCloser) io.ReadCloser) error {
-	cmd, dataSocket, stderr, err := rsyncSendSetup(path)
+func RsyncSend(path string, conn *websocket.Conn, readWrapper func(io.ReadCloser) io.ReadCloser, bwlimit string) error {
+	cmd, dataSocket, stderr, err := rsyncSendSetup(path, bwlimit)
 	if err != nil {
 		return err
 	}
diff --git a/lxd/storage.go b/lxd/storage.go
index 99e3ba7..98c8f69 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -112,33 +112,6 @@ func filesystemDetect(path string) (string, error) {
 	}
 }
 
-// storageRsyncCopy copies a directory using rsync (with the --devices option).
-func storageRsyncCopy(source string, dest string) (string, error) {
-	err := os.MkdirAll(dest, 0755)
-	if err != nil {
-		return "", err
-	}
-
-	rsyncVerbosity := "-q"
-	if debug {
-		rsyncVerbosity = "-vi"
-	}
-
-	output, err := shared.RunCommand(
-		"rsync",
-		"-a",
-		"-HAX",
-		"--devices",
-		"--delete",
-		"--checksum",
-		"--numeric-ids",
-		rsyncVerbosity,
-		shared.AddSlash(source),
-		dest)
-
-	return output, err
-}
-
 // storageType defines the type of a storage
 type storageType int
 
diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index cb14b58..5561663 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -463,7 +463,11 @@ func (s *storageBtrfs) StoragePoolUmount() (bool, error) {
 }
 
 func (s *storageBtrfs) StoragePoolUpdate(writable *api.StoragePoolPut, changedConfig []string) error {
-	return fmt.Errorf("Btrfs storage properties cannot be changed.")
+	if shared.StringInSlice("rsync.bwlimit", changedConfig) {
+		return nil
+	}
+
+	return fmt.Errorf("Storage property cannot be changed.")
 }
 
 func (s *storageBtrfs) GetStoragePoolWritable() api.StoragePoolPut {
@@ -1005,7 +1009,8 @@ func (s *storageBtrfs) ContainerRestore(container container, sourceContainer con
 		if err == nil {
 			// Use rsync to fill the empty volume.  Sync by using
 			// the subvolume name.
-			output, err := storageRsyncCopy(sourceContainerSubvolumeName, targetContainerSubvolumeName)
+			bwlimit := s.pool.Config["rsync.bwlimit"]
+			output, err := rsyncLocalCopy(sourceContainerSubvolumeName, targetContainerSubvolumeName, bwlimit)
 			if err != nil {
 				s.ContainerDelete(container)
 				logger.Errorf("ContainerRestore: rsync failed: %s.", string(output))
@@ -1743,7 +1748,7 @@ func (s *btrfsMigrationSourceDriver) send(conn *websocket.Conn, btrfsPath string
 	return err
 }
 
-func (s *btrfsMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn, op *operation) error {
+func (s *btrfsMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn, op *operation, bwlimit string) error {
 	_, containerPool := s.container.Storage().GetContainerPoolInfo()
 	containerName := s.container.Name()
 	containersPath := getContainerMountPoint(containerPool, "")
@@ -1821,7 +1826,7 @@ func (s *btrfsMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn, op *
 	return s.send(conn, migrationSendSnapshot, btrfsParent, wrapper)
 }
 
-func (s *btrfsMigrationSourceDriver) SendAfterCheckpoint(conn *websocket.Conn) error {
+func (s *btrfsMigrationSourceDriver) SendAfterCheckpoint(conn *websocket.Conn, bwlimit string) error {
 	tmpPath := containerPath(fmt.Sprintf("%s/.migration-send", s.container.Name()), true)
 	err := os.MkdirAll(tmpPath, 0700)
 	if err != nil {
diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index c50a77b..27890df 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -159,7 +159,11 @@ func (s *storageDir) GetContainerPoolInfo() (int64, string) {
 }
 
 func (s *storageDir) StoragePoolUpdate(writable *api.StoragePoolPut, changedConfig []string) error {
-	return fmt.Errorf("Dir storage properties cannot be changed.")
+	if shared.StringInSlice("rsync.bwlimit", changedConfig) {
+		return nil
+	}
+
+	return fmt.Errorf("Storage property cannot be changed.")
 }
 
 // Functions dealing with storage pools.
@@ -367,7 +371,8 @@ func (s *storageDir) copyContainer(target container, source container) error {
 		return err
 	}
 
-	output, err := storageRsyncCopy(sourceContainerMntPoint, targetContainerMntPoint)
+	bwlimit := s.pool.Config["rsync.bwlimit"]
+	output, err := rsyncLocalCopy(sourceContainerMntPoint, targetContainerMntPoint, bwlimit)
 	if err != nil {
 		return fmt.Errorf("Failed to rsync container: %s: %s.", string(output), err)
 	}
@@ -400,7 +405,8 @@ func (s *storageDir) copySnapshot(target container, source container) error {
 		return err
 	}
 
-	output, err := storageRsyncCopy(sourceContainerMntPoint, targetContainerMntPoint)
+	bwlimit := s.pool.Config["rsync.bwlimit"]
+	output, err := rsyncLocalCopy(sourceContainerMntPoint, targetContainerMntPoint, bwlimit)
 	if err != nil {
 		return fmt.Errorf("Failed to rsync container: %s: %s.", string(output), err)
 	}
@@ -533,7 +539,8 @@ func (s *storageDir) ContainerRestore(container container, sourceContainer conta
 	sourcePath := sourceContainer.Path()
 
 	// Restore using rsync
-	output, err := storageRsyncCopy(sourcePath, targetPath)
+	bwlimit := s.pool.Config["rsync.bwlimit"]
+	output, err := rsyncLocalCopy(sourcePath, targetPath, bwlimit)
 	if err != nil {
 		return fmt.Errorf("Failed to rsync container: %s: %s.", string(output), err)
 	}
@@ -566,8 +573,8 @@ func (s *storageDir) ContainerSnapshotCreate(snapshotContainer container, source
 		return err
 	}
 
-	rsync := func(snapshotContainer container, oldPath string, newPath string) error {
-		output, err := storageRsyncCopy(oldPath, newPath)
+	rsync := func(snapshotContainer container, oldPath string, newPath string, bwlimit string) error {
+		output, err := rsyncLocalCopy(oldPath, newPath, bwlimit)
 		if err != nil {
 			s.ContainerDelete(snapshotContainer)
 			return fmt.Errorf("Failed to rsync: %s: %s.", string(output), err)
@@ -586,7 +593,8 @@ func (s *storageDir) ContainerSnapshotCreate(snapshotContainer container, source
 	_, sourcePool := sourceContainer.Storage().GetContainerPoolInfo()
 	sourceContainerName := sourceContainer.Name()
 	sourceContainerMntPoint := getContainerMountPoint(sourcePool, sourceContainerName)
-	err = rsync(snapshotContainer, sourceContainerMntPoint, targetContainerMntPoint)
+	bwlimit := s.pool.Config["rsync.bwlimit"]
+	err = rsync(snapshotContainer, sourceContainerMntPoint, targetContainerMntPoint, bwlimit)
 	if err != nil {
 		return err
 	}
@@ -602,7 +610,7 @@ func (s *storageDir) ContainerSnapshotCreate(snapshotContainer container, source
 			return nil
 		}
 
-		err = rsync(snapshotContainer, sourceContainerMntPoint, targetContainerMntPoint)
+		err = rsync(snapshotContainer, sourceContainerMntPoint, targetContainerMntPoint, bwlimit)
 		if err != nil {
 			return err
 		}
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index f521ad2..22e65e3 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -915,19 +915,20 @@ func (s *storageLvm) StoragePoolUpdate(writable *api.StoragePoolPut, changedConf
 		return fmt.Errorf("The \"zfs.pool_name\" property does not apply to LVM drivers.")
 	}
 
+	if shared.StringInSlice("lvm.use_thinpool", changedConfig) {
+		return fmt.Errorf("The \"lvm.use_thinpool\" property cannot be changed.")
+	}
+
 	// "volume.block.mount_options" requires no on-disk modifications.
 	// "volume.block.filesystem" requires no on-disk modifications.
 	// "volume.size" requires no on-disk modifications.
+	// "rsync.bwlimit" requires no on-disk modifications.
 
 	// Given a set of changeable pool properties the change should be
 	// "transactional": either the whole update succeeds or none. So try to
 	// revert on error.
 	revert := true
 
-	if shared.StringInSlice("lvm.use_thinpool", changedConfig) {
-		return fmt.Errorf("The \"lvm.use_thinpool\" property cannot be changed.")
-	}
-
 	if shared.StringInSlice("lvm.thinpool_name", changedConfig) {
 		if !s.useThinpool {
 			return fmt.Errorf("The LVM storage pool \"%s\" does not use thin pools. The \"lvm.thinpool_name\" porperty cannot be set.", s.pool.Name)
@@ -1370,7 +1371,9 @@ func (s *storageLvm) copyContainerLv(target container, source container, readonl
 		}
 		defer source.Unfreeze()
 	}
-	output, err := storageRsyncCopy(sourceContainerMntPoint, targetContainerMntPoint)
+
+	bwlimit := s.pool.Config["rsync.bwlimit"]
+	output, err := rsyncLocalCopy(sourceContainerMntPoint, targetContainerMntPoint, bwlimit)
 	if err != nil {
 		return fmt.Errorf("Failed to rsync container: %s: %s.", string(output), err)
 	}
@@ -1722,7 +1725,9 @@ func (s *storageLvm) ContainerRestore(target container, source container) error
 		if err != nil {
 		}
 		defer target.Unfreeze()
-		output, err := storageRsyncCopy(sourceContainerMntPoint, targetContainerMntPoint)
+
+		bwlimit := s.pool.Config["rsync.bwlimit"]
+		output, err := rsyncLocalCopy(sourceContainerMntPoint, targetContainerMntPoint, bwlimit)
 		if err != nil {
 			return fmt.Errorf("Failed to rsync container: %s: %s.", string(output), err)
 		}
diff --git a/lxd/storage_migration.go b/lxd/storage_migration.go
index 67755b0..b207374 100644
--- a/lxd/storage_migration.go
+++ b/lxd/storage_migration.go
@@ -16,14 +16,14 @@ type MigrationStorageSourceDriver interface {
 	/* send any bits of the container/snapshots that are possible while the
 	 * container is still running.
 	 */
-	SendWhileRunning(conn *websocket.Conn, op *operation) error
+	SendWhileRunning(conn *websocket.Conn, op *operation, bwlimit string) error
 
 	/* send the final bits (e.g. a final delta snapshot for zfs, btrfs, or
 	 * do a final rsync) of the fs after the container has been
 	 * checkpointed. This will only be called when a container is actually
 	 * being live migrated.
 	 */
-	SendAfterCheckpoint(conn *websocket.Conn) error
+	SendAfterCheckpoint(conn *websocket.Conn, bwlimit string) error
 
 	/* Called after either success or failure of a migration, can be used
 	 * to clean up any temporary snapshots, etc.
@@ -40,7 +40,7 @@ func (s rsyncStorageSourceDriver) Snapshots() []container {
 	return s.snapshots
 }
 
-func (s rsyncStorageSourceDriver) SendWhileRunning(conn *websocket.Conn, op *operation) error {
+func (s rsyncStorageSourceDriver) SendWhileRunning(conn *websocket.Conn, op *operation, bwlimit string) error {
 	for _, send := range s.snapshots {
 		ourStart, err := send.StorageStart()
 		if err != nil {
@@ -52,18 +52,19 @@ func (s rsyncStorageSourceDriver) SendWhileRunning(conn *websocket.Conn, op *ope
 
 		path := send.Path()
 		wrapper := StorageProgressReader(op, "fs_progress", send.Name())
-		if err := RsyncSend(shared.AddSlash(path), conn, wrapper); err != nil {
+		err = RsyncSend(shared.AddSlash(path), conn, wrapper, bwlimit)
+		if err != nil {
 			return err
 		}
 	}
 
 	wrapper := StorageProgressReader(op, "fs_progress", s.container.Name())
-	return RsyncSend(shared.AddSlash(s.container.Path()), conn, wrapper)
+	return RsyncSend(shared.AddSlash(s.container.Path()), conn, wrapper, bwlimit)
 }
 
-func (s rsyncStorageSourceDriver) SendAfterCheckpoint(conn *websocket.Conn) error {
+func (s rsyncStorageSourceDriver) SendAfterCheckpoint(conn *websocket.Conn, bwlimit string) error {
 	// resync anything that changed between our first send and the checkpoint
-	return RsyncSend(shared.AddSlash(s.container.Path()), conn, nil)
+	return RsyncSend(shared.AddSlash(s.container.Path()), conn, nil, bwlimit)
 }
 
 func (s rsyncStorageSourceDriver) Cleanup() {
diff --git a/lxd/storage_pools_config.go b/lxd/storage_pools_config.go
index ace7122..2c97c07 100644
--- a/lxd/storage_pools_config.go
+++ b/lxd/storage_pools_config.go
@@ -51,6 +51,7 @@ var storagePoolConfigKeys = map[string]func(value string) error{
 	// valid drivers: zfs
 	"zfs.clone_copy": shared.IsBool,
 	"zfs.pool_name":  shared.IsAny,
+	"rsync.bwlimit":  shared.IsAny,
 }
 
 func storagePoolValidateConfig(name string, driver string, config map[string]string) error {
@@ -68,6 +69,14 @@ func storagePoolValidateConfig(name string, driver string, config map[string]str
 		}
 	}
 
+	v, ok := config["rsync.bwlimit"]
+	if ok && v != "" {
+		_, err := shared.ParseByteSizeString(v)
+		if err != nil {
+			return err
+		}
+	}
+
 	// Check whether the config properties for the driver container sane
 	// values.
 	for key, val := range config {
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index e56491f..bd02bb6 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -380,6 +380,8 @@ func (s *storageZfs) StoragePoolUpdate(writable *api.StoragePoolPut, changedConf
 		return fmt.Errorf("The \"zfs.pool_name\" property cannot be changed.")
 	}
 
+	// "rsync.bwlimit" requires no on-disk modifications.
+
 	logger.Infof("Updated ZFS storage pool \"%s\".", s.pool.Name)
 	return nil
 }
@@ -808,7 +810,8 @@ func (s *storageZfs) copyWithoutSnapshotsSparse(target container, source contain
 			s.ContainerDelete(target)
 		}()
 
-		output, err := storageRsyncCopy(sourceContainerPath, targetContainerPath)
+		bwlimit := s.pool.Config["rsync.bwlimit"]
+		output, err := rsyncLocalCopy(sourceContainerPath, targetContainerPath, bwlimit)
 		if err != nil {
 			return fmt.Errorf("rsync failed: %s", string(output))
 		}
@@ -2421,7 +2424,7 @@ func (s *zfsMigrationSourceDriver) send(conn *websocket.Conn, zfsName string, zf
 	return err
 }
 
-func (s *zfsMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn, op *operation) error {
+func (s *zfsMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn, op *operation, bwlimit string) error {
 	if s.container.IsSnapshot() {
 		fields := strings.SplitN(s.container.Name(), shared.SnapshotDelimiter, 2)
 		snapshotName := fmt.Sprintf("snapshot-%s", fields[1])
@@ -2458,7 +2461,7 @@ func (s *zfsMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn, op *op
 	return nil
 }
 
-func (s *zfsMigrationSourceDriver) SendAfterCheckpoint(conn *websocket.Conn) error {
+func (s *zfsMigrationSourceDriver) SendAfterCheckpoint(conn *websocket.Conn, bwlimit string) error {
 	s.stoppedSnapName = fmt.Sprintf("migration-send-%s", uuid.NewRandom().String())
 	if err := s.zfs.zfsPoolVolumeSnapshotCreate(fmt.Sprintf("containers/%s", s.container.Name()), s.stoppedSnapName); err != nil {
 		return err

From 4c27f85740541c14ec3dbbfb6485f1995e110c53 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 18 Apr 2017 15:21:23 +0200
Subject: [PATCH 2/2] test: test whether rsync.bwlimit can be set

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

diff --git a/test/suites/storage.sh b/test/suites/storage.sh
index 998d83f..3ddcc72 100644
--- a/test/suites/storage.sh
+++ b/test/suites/storage.sh
@@ -58,6 +58,9 @@ test_storage() {
 
       lxc storage create "lxdtest-$(basename "${LXD_DIR}")-valid-zfs-pool-config" zfs zfs.pool_name="lxdtest-$(basename "${LXD_DIR}")-valid-zfs-pool-config"
       lxc storage delete "lxdtest-$(basename "${LXD_DIR}")-valid-zfs-pool-config"
+
+      lxc storage create "lxdtest-$(basename "${LXD_DIR}")-valid-zfs-pool-config" zfs rsync.bwlimit=1024
+      lxc storage delete "lxdtest-$(basename "${LXD_DIR}")-valid-zfs-pool-config"
     fi
 
     if which btrfs >/dev/null 2>&1; then
@@ -83,6 +86,9 @@ test_storage() {
       ! lxc storage create "lxdtest-$(basename "${LXD_DIR}")-invalid-btrfs-pool-config" btrfs volume.zfs.use_refquota=true
       ! lxc storage create "lxdtest-$(basename "${LXD_DIR}")-invalid-btrfs-pool-config" btrfs zfs.clone_copy=true
       ! lxc storage create "lxdtest-$(basename "${LXD_DIR}")-invalid-btrfs-pool-config" btrfs zfs.pool_name=bla
+
+      lxc storage create "lxdtest-$(basename "${LXD_DIR}")-valid-btrfs-pool-config" btrfs rsync.bwlimit=1024
+      lxc storage delete "lxdtest-$(basename "${LXD_DIR}")-valid-btrfs-pool-config"
     fi
 
     # Create dir pool.
@@ -109,6 +115,9 @@ test_storage() {
     ! lxc storage create "lxdtest-$(basename "${LXD_DIR}")-invalid-dir-pool-config" dir zfs.clone_copy=true
     ! lxc storage create "lxdtest-$(basename "${LXD_DIR}")-invalid-dir-pool-config" dir zfs.pool_name=bla
 
+    lxc storage create "lxdtest-$(basename "${LXD_DIR}")-valid-dir-pool-config" dir rsync.bwlimit=1024
+    lxc storage delete "lxdtest-$(basename "${LXD_DIR}")-valid-dir-pool-config"
+
     if which lvdisplay >/dev/null 2>&1; then
       # Create lvm pool.
       configure_loop_device loop_file_3 loop_device_3
@@ -162,6 +171,7 @@ test_storage() {
       lxc storage create "lxdtest-$(basename "${LXD_DIR}")-valid-lvm-pool-config-pool21" lvm volume.size=2GB
       lxc storage create "lxdtest-$(basename "${LXD_DIR}")-valid-lvm-pool-config-pool22" lvm lvm.use_thinpool=true
       lxc storage create "lxdtest-$(basename "${LXD_DIR}")-valid-lvm-pool-config-pool23" lvm lvm.use_thinpool=true lvm.thinpool_name="lxdtest-$(basename "${LXD_DIR}")-valid-lvm-pool-config"
+      lxc storage create "lxdtest-$(basename "${LXD_DIR}")-valid-lvm-pool-config-pool24" lvm rsync.bwlimit=1024
     fi
 
     # Set default storage pool for image import.
@@ -641,6 +651,9 @@ test_storage() {
 
       lxc storage delete "lxdtest-$(basename "${LXD_DIR}")-valid-lvm-pool-config-pool23"
       vgremove -ff "lxdtest-$(basename "${LXD_DIR}")-non-thinpool-pool23" || true
+
+      lxc storage delete "lxdtest-$(basename "${LXD_DIR}")-valid-lvm-pool-config-pool24"
+      vgremove -ff "lxdtest-$(basename "${LXD_DIR}")-non-thinpool-pool24" || true
     fi
   )
 


More information about the lxc-devel mailing list