diff --git a/daemons/ganeti-noded b/daemons/ganeti-noded index 58d755e10f9260b7d30b421be5f9028c814af8f1..3e3d6a0c3b36500f9f97b0d9ce103873d459b5c1 100755 --- a/daemons/ganeti-noded +++ b/daemons/ganeti-noded @@ -256,6 +256,15 @@ class NodeHttpServer(http.server.HttpServer): disks = [objects.Disk.FromDict(cf) for cf in params[0]] return backend.BlockdevGetsize(disks) + @staticmethod + def perspective_blockdev_export(params): + """Compute the sizes of the given block devices. + + """ + disk = objects.Disk.FromDict(params[0]) + dest_node, dest_path, cluster_name = params[1:] + return backend.BlockdevExport(disk, dest_node, dest_path, cluster_name) + # blockdev/drbd specific methods ---------- @staticmethod diff --git a/lib/backend.py b/lib/backend.py index 30d61ce58e1fa2ef5bc83ea2c3a9de19696d9c9d..bf2510eeff546f1a4ae2498a3ef9fc6304f017fe 100644 --- a/lib/backend.py +++ b/lib/backend.py @@ -1459,6 +1459,54 @@ def BlockdevGetsize(disks): return result +def BlockdevExport(disk, dest_node, dest_path, cluster_name): + """Export a block device to a remote node. + + @type disk: L{objects.Disk} + @param disk: the description of the disk to export + @type dest_node: str + @param dest_node: the destination node to export to + @type dest_path: str + @param dest_path: the destination path on the target node + @type cluster_name: str + @param cluster_name: the cluster name, needed for SSH hostalias + @rtype: None + + """ + real_disk = _RecursiveFindBD(disk) + if real_disk is None: + _Fail("Block device '%s' is not set up", disk) + + real_disk.Open() + + # the block size on the read dd is 1MiB to match our units + expcmd = utils.BuildShellCmd("set -e; set -o pipefail; " + "dd if=%s bs=1048576 count=%s", + real_disk.dev_path, str(disk.size)) + + # we set here a smaller block size as, due to ssh buffering, more + # than 64-128k will mostly ignored; we use nocreat to fail if the + # device is not already there or we pass a wrong path; we use + # notrunc to no attempt truncate on an LV device; we use oflag=dsync + # to not buffer too much memory; this means that at best, we flush + # every 64k, which will not be very fast + destcmd = utils.BuildShellCmd("dd of=%s conv=nocreat,notrunc bs=65536" + " oflag=dsync", dest_path) + + remotecmd = _GetSshRunner(cluster_name).BuildCmd(dest_node, + constants.GANETI_RUNAS, + destcmd) + + # all commands have been checked, so we're safe to combine them + command = '|'.join([expcmd, utils.ShellQuoteArgs(remotecmd)]) + + result = utils.RunCmd(["bash", "-c", command]) + + if result.failed: + _Fail("Disk copy command '%s' returned error: %s" + " output: %s", command, result.fail_reason, result.output) + + def UploadFile(file_name, data, mode, uid, gid, atime, mtime): """Write a file to the filesystem. diff --git a/lib/rpc.py b/lib/rpc.py index 235754e2183d4785be4cac3e5dd8abd5d613c8e9..98f0f6ed6bc69becc913a3fe98ee218dd65f1ff0 100644 --- a/lib/rpc.py +++ b/lib/rpc.py @@ -961,6 +961,17 @@ class RpcRunner(object): return self._SingleNodeCall(node, "blockdev_grow", [cf_bdev.ToDict(), amount]) + def call_blockdev_export(self, node, cf_bdev, + dest_node, dest_path, cluster_name): + """Export a given disk to another node. + + This is a single-node call. + + """ + return self._SingleNodeCall(node, "blockdev_export", + [cf_bdev.ToDict(), dest_node, dest_path, + cluster_name]) + def call_blockdev_snapshot(self, node, cf_bdev): """Request a snapshot of the given block device.