[Libguestfs] [PATCH 2/3] p2v: Allow nbdkit as an alternative NBD server to qemu-nbd.

Richard W.M. Jones rjones at redhat.com
Thu Jan 26 15:53:05 UTC 2017


Add code in virt-p2v so that it can use nbdkit (with the file plugin)
as an alternative to qemu-nbd.

This is controlled through the virt-p2v --nbd command line option,
allowing you to select which server (out of qemu-nbd or nbdkit) you
prefer (with a fallback).  The default is: --nbd=qemu-nbd,nbdkit so
qemu-nbd will be used preferentially.
---
 p2v/conversion.c | 135 ++++++++++++++++++++++++++++++++++++++++++++++---------
 p2v/main.c       |  92 ++++++++++++++++++++++++++++++++++++-
 p2v/p2v.h        |   9 ++++
 p2v/ssh.c        |   8 ++--
 p2v/virt-p2v.pod |  39 ++++++++++++----
 5 files changed, 248 insertions(+), 35 deletions(-)

diff --git a/p2v/conversion.c b/p2v/conversion.c
index b25f272..aa9cd4c 100644
--- a/p2v/conversion.c
+++ b/p2v/conversion.c
@@ -82,8 +82,8 @@ xmlBufferDetach (xmlBufferPtr buf)
 }
 #endif
 
-/* How long to wait for qemu-nbd to start (seconds). */
-#define WAIT_QEMU_NBD_TIMEOUT 10
+/* How long to wait for the NBD server to start (seconds). */
+#define WAIT_NBD_TIMEOUT 10
 
 /* Data per NBD connection / physical disk. */
 struct data_conn {
@@ -93,8 +93,10 @@ struct data_conn {
   int nbd_remote_port;      /* remote NBD port on conversion server */
 };
 
+static pid_t start_nbd (int nbd_local_port, const char *device);
 static pid_t start_qemu_nbd (int nbd_local_port, const char *device);
-static int wait_qemu_nbd (int nbd_local_port, int timeout_seconds);
+static pid_t start_nbdkit (int nbd_local_port, const char *device);
+static int wait_nbd (int nbd_local_port, int timeout_seconds);
 static void cleanup_data_conns (struct data_conn *data_conns, size_t nr);
 static void generate_name (struct config *, const char *filename);
 static void generate_libvirt_xml (struct config *, struct data_conn *, const char *filename);
@@ -240,7 +242,7 @@ start_conversion (struct config *config,
     data_conns[i].nbd_remote_port = -1;
   }
 
-  /* Start the data connections and qemu-nbd processes, one per disk. */
+  /* Start the data connections and NBD server processes, one per disk. */
   for (i = 0; config->disks[i] != NULL; ++i) {
     CLEANUP_FREE char *device = NULL;
 
@@ -284,15 +286,15 @@ start_conversion (struct config *config,
              data_conns[i].nbd_remote_port, data_conns[i].nbd_local_port);
 #endif
 
-    /* Start qemu-nbd listening on the given port number. */
+    /* Start NBD server listening on the given port number. */
     data_conns[i].nbd_pid =
-      start_qemu_nbd (data_conns[i].nbd_local_port, device);
+      start_nbd (data_conns[i].nbd_local_port, device);
     if (data_conns[i].nbd_pid == 0)
       goto out;
 
-    /* Wait for qemu-nbd to listen */
-    if (wait_qemu_nbd (data_conns[i].nbd_local_port,
-                       WAIT_QEMU_NBD_TIMEOUT) == -1)
+    /* Wait for NBD server to listen */
+    if (wait_nbd (data_conns[i].nbd_local_port,
+                       WAIT_NBD_TIMEOUT) == -1)
       goto out;
   }
 
@@ -464,6 +466,42 @@ cancel_conversion (void)
 }
 
 /**
+ * Start the first server found in the C<nbd_servers> list.
+ *
+ * We previously tested all NBD servers (see C<test_nbd_servers>) so
+ * we only need to run the first server in the list.
+ *
+ * Returns the process ID (E<gt> 0) or C<0> if there is an error.
+ */
+static pid_t
+start_nbd (int port, const char *device)
+{
+  size_t i;
+
+  for (i = 0; i < NR_NBD_SERVERS; ++i) {
+    switch (nbd_servers[i]) {
+    case NO_SERVER:
+      /* ignore */
+      break;
+
+    case QEMU_NBD:
+      return start_qemu_nbd (port, device);
+
+    case NBDKIT:
+      return start_nbdkit (port, device);
+
+    default:
+      abort ();
+    }
+  }
+
+  /* This should never happen because of the checks in
+   * test_nbd_servers.
+   */
+  abort ();
+}
+
+/**
  * Start a local L<qemu-nbd(1)> process.
  *
  * Returns the process ID (E<gt> 0) or C<0> if there is an error.
@@ -474,6 +512,10 @@ start_qemu_nbd (int port, const char *device)
   pid_t pid;
   char port_str[64];
 
+#if DEBUG_STDERR
+  fprintf (stderr, "starting qemu-nbd for %s on port %d\n", device, port);
+#endif
+
   snprintf (port_str, sizeof port_str, "%d", port);
 
   pid = fork ();
@@ -504,6 +546,55 @@ start_qemu_nbd (int port, const char *device)
   return pid;
 }
 
+/**
+ * Start a local L<nbdkit(1)> process using the
+ * L<nbdkit-file-plugin(1)>.
+ *
+ * Returns the process ID (E<gt> 0) or C<0> if there is an error.
+ */
+static pid_t
+start_nbdkit (int port, const char *device)
+{
+  pid_t pid;
+  char port_str[64];
+  CLEANUP_FREE char *file_str = NULL;
+
+#if DEBUG_STDERR
+  fprintf (stderr, "starting nbdkit for %s on port %d\n", device, port);
+#endif
+
+  snprintf (port_str, sizeof port_str, "%d", port);
+
+  if (asprintf (&file_str, "file=%s", device) == -1)
+    error (EXIT_FAILURE, errno, "asprintf");
+
+  pid = fork ();
+  if (pid == -1) {
+    set_conversion_error ("fork: %m");
+    return 0;
+  }
+
+  if (pid == 0) {               /* Child. */
+    close (0);
+    open ("/dev/null", O_RDONLY);
+
+    execlp ("nbdkit",
+            "nbdkit",
+            "-r",               /* readonly (vital!) */
+            "-p", port_str,     /* listening port */
+            "-i", "localhost",  /* listen only on loopback interface */
+            "-f",               /* don't fork */
+            "file",             /* file plugin */
+            file_str,           /* a device like file=/dev/sda */
+            NULL);
+    perror ("nbdkit");
+    _exit (EXIT_FAILURE);
+  }
+
+  /* Parent. */
+  return pid;
+}
+
 static int bind_source_port (int sockfd, int family, int source_port);
 
 /**
@@ -563,7 +654,7 @@ connect_with_source_port (const char *hostname, int dest_port, int source_port)
 
     /* Connect. */
     if (connect (sockfd, rp->ai_addr, rp->ai_addrlen) == -1) {
-      set_conversion_error ("waiting for qemu-nbd to start: "
+      set_conversion_error ("waiting for NBD server to start: "
                             "connect to %s/%s: %m",
                             hostname, dest_port_str);
       close (sockfd);
@@ -606,7 +697,7 @@ bind_source_port (int sockfd, int family, int source_port)
       goto bound;
   }
 
-  set_conversion_error ("waiting for qemu-nbd to start: "
+  set_conversion_error ("waiting for NBD server to start: "
                         "bind to source port %d: %m",
                         source_port);
   freeaddrinfo (results);
@@ -618,7 +709,7 @@ bind_source_port (int sockfd, int family, int source_port)
 }
 
 static int
-wait_qemu_nbd (int nbd_local_port, int timeout_seconds)
+wait_nbd (int nbd_local_port, int timeout_seconds)
 {
   int sockfd = -1;
   int result = -1;
@@ -637,12 +728,12 @@ wait_qemu_nbd (int nbd_local_port, int timeout_seconds)
     if (now_t - start_t >= timeout_seconds)
       goto cleanup;
 
-    /* Source port for probing qemu-nbd should be one greater than
-     * nbd_local_port.  It's not guaranteed to always bind to this port,
-     * but it will hint the kernel to start there and try incrementally
-     * higher ports if needed.  This avoids the case where the kernel
-     * selects nbd_local_port as our source port, and we immediately
-     * connect to ourself.  See:
+    /* Source port for probing NBD server should be one greater than
+     * nbd_local_port.  It's not guaranteed to always bind to this
+     * port, but it will hint the kernel to start there and try
+     * incrementally higher ports if needed.  This avoids the case
+     * where the kernel selects nbd_local_port as our source port, and
+     * we immediately connect to ourself.  See:
      * https://bugzilla.redhat.com/show_bug.cgi?id=1167774#c9
      */
     sockfd = connect_with_source_port ("localhost", nbd_local_port,
@@ -661,7 +752,7 @@ wait_qemu_nbd (int nbd_local_port, int timeout_seconds)
     recvd = recv (sockfd, magic, sizeof magic - bytes_read, 0);
 
     if (recvd == -1) {
-      set_conversion_error ("waiting for qemu-nbd to start: recv: %m");
+      set_conversion_error ("waiting for NBD server to start: recv: %m");
       goto cleanup;
     }
 
@@ -669,8 +760,8 @@ wait_qemu_nbd (int nbd_local_port, int timeout_seconds)
   } while (bytes_read < sizeof magic);
 
   if (memcmp (magic, "NBDMAGIC", sizeof magic) != 0) {
-    set_conversion_error ("waiting for qemu-nbd to start: "
-                          "'NBDMAGIC' was not received from qemu-nbd");
+    set_conversion_error ("waiting for NBD server to start: "
+                          "'NBDMAGIC' was not received from NBD server");
     goto cleanup;
   }
 
@@ -697,7 +788,7 @@ cleanup_data_conns (struct data_conn *data_conns, size_t nr)
     }
 
     if (data_conns[i].nbd_pid > 0) {
-      /* Kill qemu-nbd process and clean up. */
+      /* Kill NBD process and clean up. */
       kill (data_conns[i].nbd_pid, SIGTERM);
       waitpid (data_conns[i].nbd_pid, NULL, 0);
     }
diff --git a/p2v/main.c b/p2v/main.c
index 8f8955c..f616b4f 100644
--- a/p2v/main.c
+++ b/p2v/main.c
@@ -54,7 +54,7 @@ int is_iso_environment = 0;
 int nbd_local_port;
 int feature_colours_option = 0;
 int force_colour = 0;
-
+enum nbd_server nbd_servers[NR_NBD_SERVERS] = { QEMU_NBD, NBDKIT };
 static const char *test_disk = NULL;
 
 static void udevadm_settle (void);
@@ -62,6 +62,7 @@ static void set_config_defaults (struct config *config);
 static void find_all_disks (void);
 static void find_all_interfaces (void);
 static int cpuinfo_flags (void);
+static void test_nbd_servers (void);
 
 enum { HELP_OPTION = CHAR_MAX + 1 };
 static const char options[] = "Vv";
@@ -73,6 +74,7 @@ static const struct option long_options[] = {
   { "colour", 0, 0, 0 },
   { "colours", 0, 0, 0 },
   { "iso", 0, 0, 0 },
+  { "nbd", 1, 0, 0 },
   { "long-options", 0, 0, 0 },
   { "short-options", 0, 0, 0 },
   { "test-disk", 1, 0, 0 },
@@ -97,6 +99,7 @@ usage (int status)
               " --cmdline=CMDLINE       Used to debug command line parsing\n"
               " --colors|--colours      Use ANSI colour sequences even if not tty\n"
               " --iso                   Running in the ISO environment\n"
+              " --nbd=qemu-nbd,nbdkit   Search order for NBD servers\n"
               " --test-disk=DISK.IMG    For testing, use disk as /dev/sda\n"
               "  -v|--verbose           Verbose messages\n"
               "  -V|--version           Display version and exit\n"
@@ -130,6 +133,35 @@ display_long_options (const struct option *long_options)
   exit (EXIT_SUCCESS);
 }
 
+static void
+set_nbd_servers (const char *opt)
+{
+  size_t i;
+  CLEANUP_FREE_STRING_LIST char **strs
+    = guestfs_int_split_string (',', opt);
+
+  if (strs == NULL)
+    error (EXIT_FAILURE, errno, _("malloc"));
+
+  if (strs[0] == NULL)
+    error (EXIT_FAILURE, 0, _("--nbd option cannot be empty"));
+
+  for (i = 0; strs[i] != NULL; ++i) {
+    if (i >= NR_NBD_SERVERS)
+      error (EXIT_FAILURE, 0, _("too many --nbd servers"));
+
+    if (STREQ (strs[i], "qemu-nbd") || STREQ (strs[i], "qemu"))
+      nbd_servers[i] = QEMU_NBD;
+    else if (STREQ (strs[i], "nbdkit"))
+      nbd_servers[i] = NBDKIT;
+    else
+      error (EXIT_FAILURE, 0, _("--nbd: unknown server: %s"), strs[i]);
+  }
+
+  for (; i < NR_NBD_SERVERS; ++i)
+    nbd_servers[i] = NO_SERVER;
+}
+
 int
 main (int argc, char *argv[])
 {
@@ -181,6 +213,9 @@ main (int argc, char *argv[])
       else if (STREQ (long_options[option_index].name, "iso")) {
         is_iso_environment = 1;
       }
+      else if (STREQ (long_options[option_index].name, "nbd")) {
+        set_nbd_servers (optarg);
+      }
       else if (STREQ (long_options[option_index].name, "test-disk")) {
         if (test_disk != NULL)
           error (EXIT_FAILURE, 0,
@@ -232,6 +267,8 @@ main (int argc, char *argv[])
     /* When testing on the local machine, choose a random port. */
     nbd_local_port = 50000 + (random () % 10000);
 
+  test_nbd_servers ();
+
   set_config_defaults (config);
 
   /* Parse /proc/cmdline (if it exists) or use the --cmdline parameter
@@ -272,6 +309,59 @@ udevadm_settle (void)
   ignore_value (system ("udevadm settle"));
 }
 
+/* Test the nbd_servers[] array to see which servers are actually
+ * installed and appear to be working.  If a server is not installed
+ * we set the corresponding nbd_servers[i] = NO_SERVER.
+ */
+static void
+test_nbd_servers (void)
+{
+  size_t i;
+  int r;
+
+  for (i = 0; i < NR_NBD_SERVERS; ++i) {
+    switch (nbd_servers[i]) {
+    case NO_SERVER:
+      /* ignore entry */
+      break;
+
+    case QEMU_NBD:
+      r = system ("qemu-nbd --version"
+#ifndef DEBUG_STDERR
+                  " >/dev/null 2>&1"
+#endif
+                  );
+      if (r != 0)
+        nbd_servers[i] = NO_SERVER;
+      break;
+
+    case NBDKIT:
+      r = system ("nbdkit file --version"
+#ifndef DEBUG_STDERR
+                  " >/dev/null 2>&1"
+#endif
+                  );
+      if (r != 0)
+        nbd_servers[i] = NO_SERVER;
+      break;
+
+    default:
+      abort ();
+    }
+  }
+
+  for (i = 0; i < NR_NBD_SERVERS; ++i)
+    if (nbd_servers[i] != NO_SERVER)
+      return;
+
+  /* If we reach here, there are no servers left, so we have to exit. */
+  fprintf (stderr,
+           _("%s: no working NBD server was found, cannot continue.\n"
+             "Please check the --nbd option in the virt-p2v(1) man page.\n"),
+           getprogname ());
+  exit (EXIT_FAILURE);
+}
+
 static void
 set_config_defaults (struct config *config)
 {
diff --git a/p2v/p2v.h b/p2v/p2v.h
index df23898..d805aaf 100644
--- a/p2v/p2v.h
+++ b/p2v/p2v.h
@@ -63,6 +63,15 @@ extern int feature_colours_option;
 /* virt-p2v --colours option (used by ansi_* macros). */
 extern int force_colour;
 
+/* virt-p2v --nbd option. */
+enum nbd_server {
+  NO_SERVER = 0,
+  QEMU_NBD = 1,
+  NBDKIT = 2,
+#define NR_NBD_SERVERS 2
+};
+extern enum nbd_server nbd_servers[NR_NBD_SERVERS];
+
 /* config.c */
 struct config {
   char *server;
diff --git a/p2v/ssh.c b/p2v/ssh.c
index 74ae126..4c1d64c 100644
--- a/p2v/ssh.c
+++ b/p2v/ssh.c
@@ -31,10 +31,10 @@
  * Once we start conversion, we will open a control connection to send
  * the libvirt configuration data and to start up virt-v2v, and we
  * will open up one data connection per local hard disk.  The data
- * connection(s) have a reverse port forward to the local
- * L<qemu-nbd(8)> server which is serving the content of that hard
- * disk.  The remote port for each data connection is assigned by ssh.
- * See C<open_data_connection> and C<start_remote_conversion>.
+ * connection(s) have a reverse port forward to the local NBD server
+ * which is serving the content of that hard disk.  The remote port
+ * for each data connection is assigned by ssh.  See
+ * C<open_data_connection> and C<start_remote_conversion>.
  */
 
 #include <config.h>
diff --git a/p2v/virt-p2v.pod b/p2v/virt-p2v.pod
index ec8eff2..e04c1f1 100644
--- a/p2v/virt-p2v.pod
+++ b/p2v/virt-p2v.pod
@@ -597,6 +597,24 @@ virt-p2v ISO environment, ie. when it is running on a real physical
 machine (and thus not when testing).  It enables various dangerous
 features such as the Reboot button.
 
+=item B<--nbd=server[,server...]>
+
+Select which NBD server is used.  By default the following
+servers are checked and the first one found is used:
+I<--nbd=qemu-nbd,nbdkit>
+
+=over 4
+
+=item B<qemu-nbd>
+
+Use qemu-nbd.
+
+=item B<nbdkit>
+
+Use nbdkit with the file plugin (see: L<nbdkit-file-plugin(1)>).
+
+=back
+
 =item B<--test-disk=/PATH/TO/DISK.IMG>
 
 For testing or debugging purposes, replace F</dev/sda> with a local
@@ -731,11 +749,15 @@ interest only, do not attempt to run this script yourself.
 =back
 
 Before conversion actually begins, virt-p2v then makes one or more
-further ssh connections to the server for data transfer.  The transfer
-protocol used currently is NBD (Network Block Device), which is
-proxied over ssh.  The server is L<qemu-nbd(1)>.  There is one ssh
-connection per physical hard disk on the source machine (the common
-case — a single hard disk — is shown below):
+further ssh connections to the server for data transfer.
+
+The transfer protocol used currently is NBD (Network Block Device),
+which is proxied over ssh.  The NBD server is L<qemu-nbd(1)> by
+default but others can be selected using the I<--nbd> command line
+option.
+
+There is one ssh connection per physical hard disk on the source
+machine (the common case — a single hard disk — is shown below):
 
  ┌──────────────┐                      ┌─────────────────┐
  │ virt-p2v     │                      │ virt-v2v        │
@@ -754,9 +776,9 @@ server and terminates on the conversion server, in fact NBD requests
 flow in the opposite direction.  This is because the reverse port
 forward feature of ssh (C<ssh -R>) is used to open a port on the
 loopback interface of the conversion server which is proxied back by
-ssh to the qemu-nbd server running on the physical machine.  The
-effect is that virt-v2v via libguestfs can open nbd connections which
-directly read the hard disk(s) of the physical server.
+ssh to the NBD server running on the physical machine.  The effect is
+that virt-v2v via libguestfs can open nbd connections which directly
+read the hard disk(s) of the physical server.
 
 Two layers of protection are used to ensure that there are no writes
 to the hard disks: Firstly, the qemu-nbd I<-r> (readonly) option is
@@ -783,6 +805,7 @@ L<virt-p2v-make-kickstart(1)>,
 L<virt-p2v-make-kiwi(1)>,
 L<virt-v2v(1)>,
 L<qemu-nbd(1)>,
+L<nbdkit(1)>, L<nbdkit-file-plugin(1)>,
 L<ssh(1)>,
 L<sshd(8)>,
 L<sshd_config(5)>,
-- 
2.9.3




More information about the Libguestfs mailing list