From 858f3d18118d59490cf3c01c0ceba44bfb05b646 Mon Sep 17 00:00:00 2001
From: Iustin Pop <iustin@google.com>
Date: Fri, 21 Aug 2009 15:58:44 +0200
Subject: [PATCH] Add disk copy support at backend and the rpc level
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This uses a simple 'dd if=… | ssh $target dd of=…' method, like the
ExportSnapshot (which uses the OS export; here we want full disk-level
copy and not any FS-level changes).

Signed-off-by: Iustin Pop <iustin@google.com>
Reviewed-by: Michael Hanselmann <hansmi@google.com>
---
 daemons/ganeti-noded |  9 +++++++++
 lib/backend.py       | 48 ++++++++++++++++++++++++++++++++++++++++++++
 lib/rpc.py           | 11 ++++++++++
 3 files changed, 68 insertions(+)

diff --git a/daemons/ganeti-noded b/daemons/ganeti-noded
index 58d755e10..3e3d6a0c3 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 30d61ce58..bf2510eef 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 235754e21..98f0f6ed6 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.
 
-- 
GitLab