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