[PATCH v1 4/4] qemu: add support for fd based live migrations

Tejus GK posted 4 patches 2 weeks, 4 days ago
[PATCH v1 4/4] qemu: add support for fd based live migrations
Posted by Tejus GK 2 weeks, 4 days ago
QEMU already supports migration using a file descriptor that is passed
to QEMU. As of now, libvirt, uses this path via the
tunnelled migration approach, where libvirt creates a set of fds from
a pipe, passes those fds to QEMU, and migrates the VM.

This patch introduces supports for fd based live migrations in libvirt,
where the fds are opened and given to libvirt by the client. Clients are
expected to pass the FDs on both source and destination libvirt, via the
virDomainFDStore API, with the "fd" key being the domain UUID. If the
URI of the migration has been set to "fd", libvirt will try to look for
an FD passed to it via the client meant for migrating the VM, and will
pass on that FD to QEMU and trigger the migration.

Signed-off-by: Tejus GK <tejus.gk@nutanix.com>
---
 src/qemu/qemu_migration.c | 114 +++++++++++++++++++++++++++++++++++++-
 1 file changed, 112 insertions(+), 2 deletions(-)

diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c
index 0cd417c15d..66e1236e6c 100644
--- a/src/qemu/qemu_migration.c
+++ b/src/qemu/qemu_migration.c
@@ -38,6 +38,7 @@
 #include "qemu_security.h"
 #include "qemu_slirp.h"
 #include "qemu_block.h"
+#include "qemu_fd.h"
 #include "qemu_tpm.h"
 #include "qemu_vhost_user.h"
 
@@ -3137,6 +3138,8 @@ qemuMigrationDstPrepare(virQEMUDriver *driver,
 
     if (tunnel) {
         migrateFrom = g_strdup("stdio");
+    } else if (g_strcmp0(protocol, "fd") == 0) {
+        migrateFrom = g_strdup("fd");
     } else if (g_strcmp0(protocol, "unix") == 0) {
         migrateFrom = g_strdup_printf("%s:%s", protocol, listenAddress);
     } else {
@@ -3301,6 +3304,8 @@ qemuMigrationDstPrepareActive(virQEMUDriver *driver,
     unsigned int startFlags;
     bool relabel = false;
     bool tunnel = !!st;
+    bool useDestFD = STREQ_NULLABLE(protocol, "fd");
+    int destfd = -1;
     int ret = -1;
     int rv;
 
@@ -3319,6 +3324,37 @@ qemuMigrationDstPrepareActive(virQEMUDriver *driver,
         virPipe(dataFD) < 0)
         goto error;
 
+    if (useDestFD) {
+        qemuFDTuple *fdtuple = NULL;
+        const char *fdName = vm->def->uuid;
+
+        VIR_WITH_MUTEX_LOCK_GUARD(&driver->lock) {
+            fdtuple = g_hash_table_lookup(driver->domainFDs, fdName);
+            if (!fdtuple) {
+                virReportError(VIR_ERR_OPERATION_INVALID,
+                               _("no file descriptor stored for migration of domain '%1$s'"),
+                               fdName);
+                goto error;
+            }
+
+            if (fdtuple->nfds != 1) {
+                virReportError(VIR_ERR_OPERATION_INVALID,
+                               _("expected a single file descriptor for migration of domain '%1$s'"),
+                               fdName);
+                goto error;
+            }
+
+            if ((destfd = dup(fdtuple->fds[0])) < 0) {
+                virReportSystemError(errno,
+                                     _("failed to duplicate migration FD for domain '%1$s'"),
+                                     fdName);
+                goto error;
+            }
+
+            g_hash_table_remove(driver->domainFDs, fdName);
+        }
+    }
+
     startFlags = VIR_QEMU_PROCESS_START_AUTODESTROY;
 
     if (qemuProcessInit(driver, vm, mig->cpu, VIR_ASYNC_JOB_MIGRATION_IN,
@@ -3328,7 +3364,7 @@ qemuMigrationDstPrepareActive(virQEMUDriver *driver,
 
     if (!(incoming = qemuMigrationDstPrepare(driver, vm, tunnel, protocol,
                                              listenAddress, port,
-                                             &dataFD[0])))
+                                             useDestFD ? destfd : dataFD[0])))
         goto error;
 
     qemuMigrationDstPrepareDiskSeclabels(vm, migrate_disks, flags);
@@ -3437,6 +3473,7 @@ qemuMigrationDstPrepareActive(virQEMUDriver *driver,
 
  cleanup:
     qemuProcessIncomingDefFree(incoming);
+    VIR_FORCE_CLOSE(destfd);
     VIR_FORCE_CLOSE(dataFD[0]);
     VIR_FORCE_CLOSE(dataFD[1]);
     virObjectEventStateQueue(driver->domainEventState, event);
@@ -3974,7 +4011,8 @@ qemuMigrationDstPrepareDirect(virQEMUDriver *driver,
 
         if (STRNEQ(uri->scheme, "tcp") &&
             STRNEQ(uri->scheme, "rdma") &&
-            STRNEQ(uri->scheme, "unix")) {
+            STRNEQ(uri->scheme, "unix") &&
+            STRNEQ(uri->scheme, "fd")) {
             virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED,
                            _("unsupported scheme %1$s in migration URI %2$s"),
                            uri->scheme, uri_in);
@@ -3984,6 +4022,9 @@ qemuMigrationDstPrepareDirect(virQEMUDriver *driver,
         if (STREQ(uri->scheme, "unix")) {
             autoPort = false;
             listenAddress = uri->path;
+        } else if (STREQ(uri->scheme, "fd")) {
+            autoPort = false;
+            listenAddress = NULL;
         } else {
             if (uri->server == NULL) {
                 virReportError(VIR_ERR_INVALID_ARG,
@@ -5411,6 +5452,75 @@ qemuMigrationSrcPerformNative(virQEMUDriver *driver,
             spec.destType = MIGRATION_DEST_CONNECT_SOCKET;
 
         spec.dest.socket.path = uribits->path;
+    } else if (STREQ(uribits->scheme, "fd")) {
+
+        if (flags & VIR_MIGRATE_PARALLEL) {
+            virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s",
+                           _("FD-based migration is not supported with multi-fd migrations"));
+            return -1;
+        }
+
+        if (flags & VIR_MIGRATE_POSTCOPY) {
+            virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s",
+                           _("FD-based migration is not supported with post-copy migration"));
+            return -1;
+        }
+        if (flags & VIR_MIGRATE_POSTCOPY_RESUME) {
+            virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s",
+                           _("FD-based migration is not supported with post-copy resume"));
+            return -1;
+        }
+        if (flags & VIR_MIGRATE_ZEROCOPY) {
+            virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s",
+                           _("FD-based migration is not supported with zero-copy migration"));
+            return -1;
+        }
+
+        if (flags & VIR_MIGRATE_TLS) {
+            virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s",
+                           _("FD-based migration is not supported with TLS migration"));
+            return -1;
+        }
+
+        qemuFDTuple *fdtuple = NULL;
+        const char *fdName = vm->def->uuid;
+        int srcfd = -1;
+
+        VIR_WITH_MUTEX_LOCK_GUARD(&driver->lock) {
+            fdtuple = g_hash_table_lookup(driver->domainFDs, fdName);
+            if (!fdtuple) {
+                virReportError(VIR_ERR_OPERATION_INVALID,
+                                _("no file descriptor stored for migration of domain '%1$s'"),
+                                fdName);
+                return -1;
+            }
+
+            if (fdtuple->nfds != 1) {
+                virReportError(VIR_ERR_OPERATION_INVALID,
+                                _("expected a single file descriptor for migration of domain '%1$s'"),
+                                fdName);
+                return -1;
+            }
+
+            if ((srcfd = dup(fdtuple->fds[0])) < 0) {
+                virReportSystemError(errno,
+                                        _("failed to duplicate migration FD for domain '%1$s'"),
+                                        fdName);
+                return -1;
+            }
+
+            g_hash_table_remove(driver->domainFDs, fdName);
+        }
+
+        if (qemuSecuritySetImageFDLabel(driver->securityManager, vm->def, srcfd) < 0) {
+            VIR_FORCE_CLOSE(srcfd);
+            return -1;
+        }
+
+        spec.destType = MIGRATION_DEST_FD;
+        spec.dest.fd.qemu = srcfd;
+        spec.dest.fd.local = -1;
+
     } else {
         /* RDMA, multi-fd, and postcopy-preempt migration require QEMU to
          * connect to the destination itself.
-- 
2.43.7