[libvirt] [PATCH] Tunnelled migration.

Chris Lalancette clalance at redhat.com
Wed Sep 30 11:27:21 UTC 2009


Implementation of tunnelled migration, using a Unix Domain Socket
on the qemu backend.  Note that this requires very new versions of
qemu (0.10.7 at least) in order to get the appropriate bugfixes.

Signed-off-by: Chris Lalancette <clalance at redhat.com>
---
 daemon/remote.c                     |   45 +++
 daemon/remote_dispatch_args.h       |    1 +
 daemon/remote_dispatch_prototypes.h |    8 +
 daemon/remote_dispatch_table.h      |    5 +
 docs/apibuild.py                    |    1 +
 include/libvirt/libvirt.h.in        |    1 +
 src/driver.h                        |   11 +
 src/esx/esx_driver.c                |    1 +
 src/libvirt.c                       |  181 ++++++++--
 src/libvirt_internal.h              |    8 +-
 src/libvirt_private.syms            |    1 +
 src/lxc/lxc_driver.c                |    1 +
 src/opennebula/one_driver.c         |    1 +
 src/openvz/openvz_driver.c          |    1 +
 src/phyp/phyp_driver.c              |    1 +
 src/qemu/qemu_driver.c              |  725 +++++++++++++++++++++++++++++++++--
 src/remote/remote_driver.c          |   46 +++-
 src/remote/remote_protocol.c        |   17 +
 src/remote/remote_protocol.h        |   13 +
 src/remote/remote_protocol.x        |   12 +-
 src/test/test_driver.c              |    1 +
 src/uml/uml_driver.c                |    1 +
 src/vbox/vbox_tmpl.c                |    2 +-
 src/xen/xen_driver.c                |    1 +
 tools/virsh.c                       |   28 ++-
 25 files changed, 1049 insertions(+), 64 deletions(-)

diff --git a/daemon/remote.c b/daemon/remote.c
index ba97379..4e02850 100644
--- a/daemon/remote.c
+++ b/daemon/remote.c
@@ -55,6 +55,7 @@
 #include "datatypes.h"
 #include "memory.h"
 #include "util.h"
+#include "stream.h"
 
 #define VIR_FROM_THIS VIR_FROM_REMOTE
 #define REMOTE_DEBUG(fmt, ...) DEBUG(fmt, __VA_ARGS__)
@@ -1493,6 +1494,50 @@ remoteDispatchDomainMigrateFinish2 (struct qemud_server *server ATTRIBUTE_UNUSED
 }
 
 static int
+remoteDispatchDomainMigratePrepareTunnel(struct qemud_server *server ATTRIBUTE_UNUSED,
+                                         struct qemud_client *client,
+                                         virConnectPtr conn,
+                                         remote_message_header *hdr,
+                                         remote_error *rerr,
+                                         remote_domain_migrate_prepare_tunnel_args *args,
+                                         void *ret ATTRIBUTE_UNUSED)
+{
+    int r;
+    char *uri_in;
+    char *dname;
+    struct qemud_client_stream *stream;
+    CHECK_CONN (client);
+
+    uri_in = args->uri_in == NULL ? NULL : *args->uri_in;
+    dname = args->dname == NULL ? NULL : *args->dname;
+
+    stream = remoteCreateClientStream(conn, hdr);
+    if (!stream) {
+        remoteDispatchOOMError(rerr);
+        return -1;
+    }
+
+    fprintf(stderr, "dom_xml is %p\n",args->dom_xml);
+    r = virDomainMigratePrepareTunnel(conn, stream->st, uri_in,
+                                      args->flags, dname, args->resource,
+                                      args->dom_xml);
+    if (r == -1) {
+        remoteFreeClientStream(client, stream);
+        remoteDispatchConnError(rerr, conn);
+        return -1;
+    }
+
+    if (remoteAddClientStream(client, stream, 0) < 0) {
+        remoteDispatchConnError(rerr, conn);
+        virStreamAbort(stream->st);
+        remoteFreeClientStream(client, stream);
+        return -1;
+    }
+
+    return 0;
+}
+
+static int
 remoteDispatchListDefinedDomains (struct qemud_server *server ATTRIBUTE_UNUSED,
                                   struct qemud_client *client ATTRIBUTE_UNUSED,
                                   virConnectPtr conn,
diff --git a/daemon/remote_dispatch_args.h b/daemon/remote_dispatch_args.h
index 95f668a..aceead1 100644
--- a/daemon/remote_dispatch_args.h
+++ b/daemon/remote_dispatch_args.h
@@ -125,3 +125,4 @@
     remote_secret_get_value_args val_remote_secret_get_value_args;
     remote_secret_undefine_args val_remote_secret_undefine_args;
     remote_secret_lookup_by_usage_args val_remote_secret_lookup_by_usage_args;
+    remote_domain_migrate_prepare_tunnel_args val_remote_domain_migrate_prepare_tunnel_args;
diff --git a/daemon/remote_dispatch_prototypes.h b/daemon/remote_dispatch_prototypes.h
index 16e8bb0..9afd2c7 100644
--- a/daemon/remote_dispatch_prototypes.h
+++ b/daemon/remote_dispatch_prototypes.h
@@ -298,6 +298,14 @@ static int remoteDispatchDomainMigratePrepare2(
     remote_error *err,
     remote_domain_migrate_prepare2_args *args,
     remote_domain_migrate_prepare2_ret *ret);
+static int remoteDispatchDomainMigratePrepareTunnel(
+    struct qemud_server *server,
+    struct qemud_client *client,
+    virConnectPtr conn,
+    remote_message_header *hdr,
+    remote_error *err,
+    remote_domain_migrate_prepare_tunnel_args *args,
+    void *ret);
 static int remoteDispatchDomainPinVcpu(
     struct qemud_server *server,
     struct qemud_client *client,
diff --git a/daemon/remote_dispatch_table.h b/daemon/remote_dispatch_table.h
index 6b5df80..bb13f4c 100644
--- a/daemon/remote_dispatch_table.h
+++ b/daemon/remote_dispatch_table.h
@@ -742,3 +742,8 @@
     .args_filter = (xdrproc_t) xdr_remote_secret_lookup_by_usage_args,
     .ret_filter = (xdrproc_t) xdr_remote_secret_lookup_by_usage_ret,
 },
+{   /* DomainMigratePrepareTunnel => 148 */
+    .fn = (dispatch_fn) remoteDispatchDomainMigratePrepareTunnel,
+    .args_filter = (xdrproc_t) xdr_remote_domain_migrate_prepare_tunnel_args,
+    .ret_filter = (xdrproc_t) xdr_void,
+},
diff --git a/docs/apibuild.py b/docs/apibuild.py
index 70a7efc..b00619f 100755
--- a/docs/apibuild.py
+++ b/docs/apibuild.py
@@ -38,6 +38,7 @@ ignored_functions = {
   "virDomainMigratePerform": "private function for migration",
   "virDomainMigratePrepare": "private function for migration",
   "virDomainMigratePrepare2": "private function for migration",
+  "virDomainMigratePrepareTunnel": "private function for tunnelled migration",
   "virDrvSupportsFeature": "private function for remote access",
   "DllMain": "specific function for Win32",
 }
diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in
index 4e63e48..60be41b 100644
--- a/include/libvirt/libvirt.h.in
+++ b/include/libvirt/libvirt.h.in
@@ -337,6 +337,7 @@ typedef virDomainInterfaceStatsStruct *virDomainInterfaceStatsPtr;
 /* Domain migration flags. */
 typedef enum {
   VIR_MIGRATE_LIVE              = 1, /* live migration */
+  VIR_MIGRATE_TUNNELLED         = 2, /* tunnelled migration */
 } virDomainMigrateFlags;
 
 /* Domain migration. */
diff --git a/src/driver.h b/src/driver.h
index 6a3dcc2..c926614 100644
--- a/src/driver.h
+++ b/src/driver.h
@@ -347,6 +347,16 @@ typedef int
     (*virDrvNodeDeviceReset)
                     (virNodeDevicePtr dev);
 
+typedef int
+    (*virDrvDomainMigratePrepareTunnel)
+                    (virConnectPtr conn,
+                     virStreamPtr st,
+                     const char *uri_in,
+                     unsigned long flags,
+                     const char *dname,
+                     unsigned long resource,
+                     const char *dom_xml);
+
 /**
  * _virDriver:
  *
@@ -427,6 +437,7 @@ struct _virDriver {
     virDrvNodeDeviceDettach     nodeDeviceDettach;
     virDrvNodeDeviceReAttach    nodeDeviceReAttach;
     virDrvNodeDeviceReset       nodeDeviceReset;
+    virDrvDomainMigratePrepareTunnel domainMigratePrepareTunnel;
 };
 
 typedef int
diff --git a/src/esx/esx_driver.c b/src/esx/esx_driver.c
index ec0cc14..e063b46 100644
--- a/src/esx/esx_driver.c
+++ b/src/esx/esx_driver.c
@@ -3274,6 +3274,7 @@ static virDriver esxDriver = {
     NULL,                            /* nodeDeviceDettach */
     NULL,                            /* nodeDeviceReAttach */
     NULL,                            /* nodeDeviceReset */
+    NULL,                            /* domainMigratePrepareTunnel */
 };
 
 
diff --git a/src/libvirt.c b/src/libvirt.c
index ee5fbfd..bdb8023 100644
--- a/src/libvirt.c
+++ b/src/libvirt.c
@@ -3044,6 +3044,70 @@ virDomainMigrateVersion2 (virDomainPtr domain,
     return ddomain;
 }
 
+/*
+ * Tunnelled migration has the following flow:
+ *
+ * virDomainMigrate(src, uri)
+ *   - virDomainMigratePerform(src, uri)
+ *      - dst = virConnectOpen(uri)
+ *      - virDomainMigratePrepareTunnel(dst)
+ *      - while (1)
+ *         - virStreamSend(dst, data)
+ *      - virDomainMigrateFinish(dst)
+ *      - virConnectClose(dst)
+ */
+static virDomainPtr
+virDomainMigrateTunnelled(virDomainPtr domain,
+                          unsigned long flags,
+                          const char *dname,
+                          const char *uri,
+                          unsigned long bandwidth)
+{
+    virConnectPtr dconn;
+    virDomainPtr ddomain = NULL;
+
+    if (uri == NULL) {
+        /* if you are doing a secure migration, you *must* also pass a uri */
+        virLibConnError(domain->conn, VIR_ERR_INVALID_ARG,
+                        _("requested TUNNELLED migration, but no URI passed"));
+        return NULL;
+    }
+
+    if (domain->conn->flags & VIR_CONNECT_RO) {
+        virLibDomainError(domain, VIR_ERR_OPERATION_DENIED, __FUNCTION__);
+        return NULL;
+    }
+
+    /* FIXME: do we even need this check?  In theory, V1 of the protocol
+     * should be able to do tunnelled migration as well
+     */
+    if (!VIR_DRV_SUPPORTS_FEATURE(domain->conn->driver, domain->conn,
+                                  VIR_DRV_FEATURE_MIGRATION_V2)) {
+        virLibConnError(domain->conn, VIR_ERR_NO_SUPPORT, __FUNCTION__);
+        return NULL;
+    }
+
+    /* Perform the migration.  The driver isn't supposed to return
+     * until the migration is complete.
+     */
+    if (domain->conn->driver->domainMigratePerform
+        (domain, NULL, 0, uri, flags, dname, bandwidth) == -1)
+        return NULL;
+
+    dconn = virConnectOpen(uri);
+    if (dconn == NULL)
+        /* FIXME: this is pretty crappy; as far as we know, the migration has
+         * now succeeded, but we can't connect back to the other side
+         */
+        return NULL;
+
+    ddomain = virDomainLookupByName(dconn, dname ? dname : domain->name);
+
+    virConnectClose(dconn);
+
+    return ddomain;
+}
+
 /**
  * virDomainMigrate:
  * @domain: a domain object
@@ -3058,6 +3122,8 @@ virDomainMigrateVersion2 (virDomainPtr domain,
  *
  * Flags may be one of more of the following:
  *   VIR_MIGRATE_LIVE   Attempt a live migration.
+ *   VIR_MIGRATE_TUNNELLED Attempt to do a migration tunnelled through the
+ *                         libvirt RPC mechanism
  *
  * If a hypervisor supports renaming domains during migration,
  * then you may set the dname parameter to the new name (otherwise
@@ -3116,31 +3182,47 @@ virDomainMigrate (virDomainPtr domain,
         goto error;
     }
 
-    /* Now checkout the destination */
-    if (!VIR_IS_CONNECT (dconn)) {
-        virLibConnError (domain->conn, VIR_ERR_INVALID_CONN, __FUNCTION__);
-        goto error;
+    if (flags & VIR_MIGRATE_TUNNELLED) {
+        /* tunnelled migration is more or less a completely different migration
+         * protocol.  dconn has to be NULL, uri has to be set, and the flow
+         * of logic is completely different.  Hence, here we split off from
+         * the main migration flow and use a separate function.
+         */
+        if (dconn != NULL) {
+            virLibConnError(domain->conn, VIR_ERR_INVALID_ARG,
+                            _("requested TUNNELLED migration, but non-NULL dconn"));
+            goto error;
+        }
+
+        ddomain = virDomainMigrateTunnelled(domain, flags, dname, uri, bandwidth);
     }
-    if (dconn->flags & VIR_CONNECT_RO) {
-        /* NB, deliberately report error against source object, not dest */
-        virLibDomainError (domain, VIR_ERR_OPERATION_DENIED, __FUNCTION__);
-        goto error;
-    }
-
-    /* Check that migration is supported by both drivers. */
-    if (VIR_DRV_SUPPORTS_FEATURE (domain->conn->driver, domain->conn,
-                                  VIR_DRV_FEATURE_MIGRATION_V1) &&
-        VIR_DRV_SUPPORTS_FEATURE (dconn->driver, dconn,
-                                  VIR_DRV_FEATURE_MIGRATION_V1))
-        ddomain = virDomainMigrateVersion1 (domain, dconn, flags, dname, uri, bandwidth);
-    else if (VIR_DRV_SUPPORTS_FEATURE (domain->conn->driver, domain->conn,
-                                       VIR_DRV_FEATURE_MIGRATION_V2) &&
-             VIR_DRV_SUPPORTS_FEATURE (dconn->driver, dconn,
-                                       VIR_DRV_FEATURE_MIGRATION_V2))
-        ddomain = virDomainMigrateVersion2 (domain, dconn, flags, dname, uri, bandwidth);
     else {
-        virLibConnError (domain->conn, VIR_ERR_NO_SUPPORT, __FUNCTION__);
-        goto error;
+        /* Now checkout the destination */
+        if (!VIR_IS_CONNECT(dconn)) {
+            virLibConnError(domain->conn, VIR_ERR_INVALID_CONN, __FUNCTION__);
+            goto error;
+        }
+        if (dconn->flags & VIR_CONNECT_RO) {
+            /* NB, deliberately report error against source object, not dest */
+            virLibDomainError(domain, VIR_ERR_OPERATION_DENIED, __FUNCTION__);
+            goto error;
+        }
+
+        /* Check that migration is supported by both drivers. */
+        if (VIR_DRV_SUPPORTS_FEATURE(domain->conn->driver, domain->conn,
+                                     VIR_DRV_FEATURE_MIGRATION_V1) &&
+            VIR_DRV_SUPPORTS_FEATURE(dconn->driver, dconn,
+                                     VIR_DRV_FEATURE_MIGRATION_V1))
+            ddomain = virDomainMigrateVersion1(domain, dconn, flags, dname, uri, bandwidth);
+        else if (VIR_DRV_SUPPORTS_FEATURE(domain->conn->driver, domain->conn,
+                                          VIR_DRV_FEATURE_MIGRATION_V2) &&
+                 VIR_DRV_SUPPORTS_FEATURE(dconn->driver, dconn,
+                                          VIR_DRV_FEATURE_MIGRATION_V2))
+            ddomain = virDomainMigrateVersion2(domain, dconn, flags, dname, uri, bandwidth);
+        else {
+            virLibConnError(domain->conn, VIR_ERR_NO_SUPPORT, __FUNCTION__);
+            goto error;
+        }
     }
 
      if (ddomain == NULL)
@@ -3398,6 +3480,59 @@ error:
 }
 
 
+/*
+ * Not for public use.  This function is part of the internal
+ * implementation of migration in the remote case.
+ */
+int
+virDomainMigratePrepareTunnel(virConnectPtr conn,
+                              virStreamPtr st,
+                              const char *uri_in,
+                              unsigned long flags,
+                              const char *dname,
+                              unsigned long bandwidth,
+                              const char *dom_xml)
+
+{
+    VIR_DEBUG("conn=%p, stream=%p, uri_in=%s, flags=%lu, dname=%s, "
+              "bandwidth=%lu, dom_xml=%s", conn, st, uri_in, flags,
+              NULLSTR(dname), bandwidth, dom_xml);
+
+    virResetLastError();
+
+    if (!VIR_IS_CONNECT(conn)) {
+        virLibConnError(NULL, VIR_ERR_INVALID_CONN, __FUNCTION__);
+        return -1;
+    }
+
+    if (conn->flags & VIR_CONNECT_RO) {
+        virLibConnError(conn, VIR_ERR_OPERATION_DENIED, __FUNCTION__);
+        goto error;
+    }
+
+    if (conn != st->conn) {
+        virLibConnError(conn, VIR_ERR_INVALID_ARG, __FUNCTION__);
+        goto error;
+    }
+
+    if (conn->driver->domainMigratePrepareTunnel) {
+        int rv = conn->driver->domainMigratePrepareTunnel(conn, st, uri_in,
+                                                          flags, dname,
+                                                          bandwidth, dom_xml);
+        if (rv < 0)
+            goto error;
+        return rv;
+    }
+
+    virLibConnError(conn, VIR_ERR_NO_SUPPORT, __FUNCTION__);
+
+error:
+    /* Copy to connection error object for back compatability */
+    virSetConnError(conn);
+    return -1;
+}
+
+
 /**
  * virNodeGetInfo:
  * @conn: pointer to the hypervisor connection
diff --git a/src/libvirt_internal.h b/src/libvirt_internal.h
index 5913798..8f1ac3d 100644
--- a/src/libvirt_internal.h
+++ b/src/libvirt_internal.h
@@ -70,6 +70,12 @@ virDomainPtr virDomainMigrateFinish2 (virConnectPtr dconn,
                                       const char *uri,
                                       unsigned long flags,
                                       int retcode);
-
+int virDomainMigratePrepareTunnel(virConnectPtr conn,
+                                  virStreamPtr st,
+                                  const char *uri_in,
+                                  unsigned long flags,
+                                  const char *dname,
+                                  unsigned long resource,
+                                  const char *dom_xml);
 
 #endif
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index b699fb2..49bbf96 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -239,6 +239,7 @@ virDomainMigratePerform;
 virDomainMigrateFinish;
 virDomainMigratePrepare2;
 virDomainMigrateFinish2;
+virDomainMigratePrepareTunnel;
 virRegisterDriver;
 virRegisterInterfaceDriver;
 virRegisterNetworkDriver;
diff --git a/src/lxc/lxc_driver.c b/src/lxc/lxc_driver.c
index 0a9cc28..5fb4105 100644
--- a/src/lxc/lxc_driver.c
+++ b/src/lxc/lxc_driver.c
@@ -2147,6 +2147,7 @@ static virDriver lxcDriver = {
     NULL, /* nodeDeviceDettach */
     NULL, /* nodeDeviceReAttach */
     NULL, /* nodeDeviceReset */
+    NULL, /* domainMigratePrepareTunnel */
 };
 
 static virStateDriver lxcStateDriver = {
diff --git a/src/opennebula/one_driver.c b/src/opennebula/one_driver.c
index 0ca1e9b..9bcd5c3 100644
--- a/src/opennebula/one_driver.c
+++ b/src/opennebula/one_driver.c
@@ -787,6 +787,7 @@ static virDriver oneDriver = {
     NULL, /* nodeDeviceDettach; */
     NULL, /* nodeDeviceReAttach; */
     NULL, /* nodeDeviceReset; */
+    NULL, /* domainMigratePrepareTunnel */
 };
 
 static virStateDriver oneStateDriver = {
diff --git a/src/openvz/openvz_driver.c b/src/openvz/openvz_driver.c
index d577be1..f64ad1e 100644
--- a/src/openvz/openvz_driver.c
+++ b/src/openvz/openvz_driver.c
@@ -1432,6 +1432,7 @@ static virDriver openvzDriver = {
     NULL, /* nodeDeviceDettach */
     NULL, /* nodeDeviceReAttach */
     NULL, /* nodeDeviceReset */
+    NULL, /* domainMigratePrepareTunnel */
 };
 
 int openvzRegister(void) {
diff --git a/src/phyp/phyp_driver.c b/src/phyp/phyp_driver.c
index fbb8982..ef465ed 100644
--- a/src/phyp/phyp_driver.c
+++ b/src/phyp/phyp_driver.c
@@ -1377,6 +1377,7 @@ virDriver phypDriver = {
     NULL,                       /* nodeDeviceDettach */
     NULL,                       /* nodeDeviceReAttach */
     NULL,                       /* nodeDeviceReset */
+    NULL,                       /* domainMigratePrepareTunnel */
 };
 
 int
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 155e4a3..01981bd 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -71,6 +71,7 @@
 #include "hostusb.h"
 #include "security/security_driver.h"
 #include "cgroup.h"
+#include "libvirt_internal.h"
 
 
 #define VIR_FROM_THIS VIR_FROM_QEMU
@@ -5796,6 +5797,432 @@ static void qemuDomainEventQueue(struct qemud_driver *driver,
 
 /* Migration support. */
 
+/* Tunnelled migration stream support */
+struct qemuStreamMigFile {
+    int fd;
+
+    int watch;
+    unsigned int cbRemoved;
+    unsigned int dispatching;
+    virStreamEventCallback cb;
+    void *opaque;
+    virFreeCallback ff;
+};
+
+static int qemuStreamMigRemoveCallback(virStreamPtr stream)
+{
+    struct qemud_driver *driver = stream->conn->privateData;
+    struct qemuStreamMigFile *qemust = stream->privateData;
+    int ret = -1;
+
+    if (!qemust) {
+        qemudReportError(stream->conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+                         "%s", _("stream is not open"));
+        return -1;
+    }
+
+    qemuDriverLock(driver);
+    if (qemust->watch == 0) {
+        qemudReportError(stream->conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+                         "%s", _("stream does not have a callback registered"));
+        goto cleanup;
+    }
+
+    virEventRemoveHandle(qemust->watch);
+    if (qemust->dispatching)
+        qemust->cbRemoved = 1;
+    else if (qemust->ff)
+        (qemust->ff)(qemust->opaque);
+
+    qemust->watch = 0;
+    qemust->ff = NULL;
+    qemust->cb = NULL;
+    qemust->opaque = NULL;
+
+    ret = 0;
+
+cleanup:
+    qemuDriverUnlock(driver);
+    return ret;
+}
+
+static int qemuStreamMigUpdateCallback(virStreamPtr stream, int events)
+{
+    struct qemud_driver *driver = stream->conn->privateData;
+    struct qemuStreamMigFile *qemust = stream->privateData;
+    int ret = -1;
+
+    if (!qemust) {
+        qemudReportError(stream->conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+                         "%s", _("stream is not open"));
+        return -1;
+    }
+
+    qemuDriverLock(driver);
+    if (qemust->watch == 0) {
+        qemudReportError(stream->conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+                         "%s", _("stream does not have a callback registered"));
+        goto cleanup;
+    }
+
+    virEventUpdateHandle(qemust->watch, events);
+
+    ret = 0;
+
+cleanup:
+    qemuDriverUnlock(driver);
+    return ret;
+}
+
+static void qemuStreamMigEvent(int watch ATTRIBUTE_UNUSED,
+                               int fd ATTRIBUTE_UNUSED,
+                               int events,
+                               void *opaque)
+{
+    virStreamPtr stream = opaque;
+    struct qemud_driver *driver = stream->conn->privateData;
+    struct qemuStreamMigFile *qemust = stream->privateData;
+    virStreamEventCallback cb;
+    void *cbopaque;
+    virFreeCallback ff;
+
+    qemuDriverLock(driver);
+    if (!qemust || !qemust->cb) {
+        qemuDriverUnlock(driver);
+        return;
+    }
+
+    cb = qemust->cb;
+    cbopaque = qemust->opaque;
+    ff = qemust->ff;
+    qemust->dispatching = 1;
+    qemuDriverUnlock(driver);
+
+    cb(stream, events, cbopaque);
+
+    qemuDriverLock(driver);
+    qemust->dispatching = 0;
+    if (qemust->cbRemoved && ff)
+        (ff)(cbopaque);
+    qemuDriverUnlock(driver);
+}
+
+static int
+qemuStreamMigAddCallback(virStreamPtr st,
+                         int events,
+                         virStreamEventCallback cb,
+                         void *opaque,
+                         virFreeCallback ff)
+{
+    struct qemud_driver *driver = st->conn->privateData;
+    struct qemuStreamMigFile *qemust = st->privateData;
+    int ret = -1;
+
+    if (!qemust) {
+        qemudReportError(st->conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+                         "%s", _("stream is not open"));
+        return -1;
+    }
+
+    qemuDriverLock(driver);
+    if (qemust->watch != 0) {
+        qemudReportError(st->conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+                         "%s", _("stream already has a callback registered"));
+        goto cleanup;
+    }
+
+    if ((qemust->watch = virEventAddHandle(qemust->fd,
+                                           events,
+                                           qemuStreamMigEvent,
+                                           st,
+                                           NULL)) < 0) {
+        qemudReportError(st->conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+                         "%s", _("cannot register file watch on stream"));
+        goto cleanup;
+    }
+
+    qemust->cbRemoved = 0;
+    qemust->cb = cb;
+    qemust->opaque = opaque;
+    qemust->ff = ff;
+    virStreamRef(st);
+
+    ret = 0;
+
+cleanup:
+    qemuDriverUnlock(driver);
+    return ret;
+}
+
+static void qemuStreamMigFree(struct qemuStreamMigFile *qemust)
+{
+    if (qemust->fd != -1)
+        close(qemust->fd);
+    VIR_FREE(qemust);
+}
+
+static struct qemuStreamMigFile *qemuStreamMigOpen(virStreamPtr st,
+                                                   const char *unixfile)
+{
+    struct qemuStreamMigFile *qemust = NULL;
+    struct sockaddr_un sa_qemu;
+    int i = 0;
+    int timeout = 3;
+    int ret;
+
+    if (VIR_ALLOC(qemust) < 0)
+        return NULL;
+
+    qemust->fd = socket(AF_UNIX, SOCK_STREAM, 0);
+    if (qemust->fd < 0)
+        goto cleanup;
+
+    memset(&sa_qemu, 0, sizeof(sa_qemu));
+    sa_qemu.sun_family = AF_UNIX;
+    if (virStrcpy(sa_qemu.sun_path, unixfile, sizeof(sa_qemu.sun_path)) == NULL)
+        goto cleanup;
+
+    do {
+        ret = connect(qemust->fd, (struct sockaddr *)&sa_qemu, sizeof(sa_qemu));
+        if (ret == 0)
+            break;
+
+        if (errno == ENOENT || errno == ECONNREFUSED) {
+            /* ENOENT       : Socket may not have shown up yet
+             * ECONNREFUSED : Leftover socket hasn't been removed yet */
+            continue;
+        }
+
+        goto cleanup;
+    } while ((++i <= timeout*5) && (usleep(.2 * 1000000) <= 0));
+
+    if ((st->flags & VIR_STREAM_NONBLOCK) && virSetNonBlock(qemust->fd) < 0)
+        goto cleanup;
+
+    return qemust;
+
+cleanup:
+    qemuStreamMigFree(qemust);
+    return NULL;
+}
+
+static int
+qemuStreamMigClose(virStreamPtr st)
+{
+    struct qemud_driver *driver = st->conn->privateData;
+    struct qemuStreamMigFile *qemust = st->privateData;
+
+    if (!qemust)
+        return 0;
+
+    qemuDriverLock(driver);
+
+    qemuStreamMigFree(qemust);
+
+    st->privateData = NULL;
+
+    qemuDriverUnlock(driver);
+
+    return 0;
+}
+
+static int qemuStreamMigWrite(virStreamPtr st, const char *bytes, size_t nbytes)
+{
+    struct qemud_driver *driver = st->conn->privateData;
+    struct qemuStreamMigFile *qemust = st->privateData;
+    int ret;
+
+    if (!qemust) {
+        qemudReportError(st->conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+                         "%s", _("stream is not open"));
+        return -1;
+    }
+
+    qemuDriverLock(driver);
+
+retry:
+    ret = write(qemust->fd, bytes, nbytes);
+    if (ret < 0) {
+        if (errno == EAGAIN || errno == EWOULDBLOCK) {
+            ret = -2;
+        } else if (errno == EINTR) {
+            goto retry;
+        } else {
+            ret = -1;
+            virReportSystemError(st->conn, errno, "%s",
+                                 _("cannot write to stream"));
+        }
+    }
+
+    qemuDriverUnlock(driver);
+    return ret;
+}
+
+static virStreamDriver qemuStreamMigDrv = {
+    .streamSend = qemuStreamMigWrite,
+    .streamFinish = qemuStreamMigClose,
+    .streamAbort = qemuStreamMigClose,
+    .streamAddCallback = qemuStreamMigAddCallback,
+    .streamUpdateCallback = qemuStreamMigUpdateCallback,
+    .streamRemoveCallback = qemuStreamMigRemoveCallback
+};
+
+/* Prepare is the first step, and it runs on the destination host.
+ *
+ * This version starts an empty VM listening on a localhost TCP port, and
+ * sets up the corresponding virStream to handle the incoming data.
+ */
+static int
+qemudDomainMigratePrepareTunnel(virConnectPtr dconn,
+                                virStreamPtr st,
+                                const char *uri_in,
+                                unsigned long flags,
+                                const char *dname,
+                                unsigned long resource ATTRIBUTE_UNUSED,
+                                const char *dom_xml)
+{
+    struct qemud_driver *driver = dconn->privateData;
+    virDomainDefPtr def = NULL;
+    virDomainObjPtr vm = NULL;
+    char *migrateFrom;
+    virDomainEventPtr event = NULL;
+    int ret = -1;
+    int internalret;
+    char *unixfile = NULL;
+    unsigned int qemuCmdFlags;
+    struct qemuStreamMigFile *qemust = NULL;
+
+    qemuDriverLock(driver);
+    if (!dom_xml) {
+        qemudReportError(dconn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+                         "%s", _("no domain XML passed"));
+        goto cleanup;
+    }
+    if (!uri_in) {
+        qemudReportError(dconn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+                         "%s", _("no URI passed"));
+        goto cleanup;
+    }
+    if (!(flags & VIR_MIGRATE_TUNNELLED)) {
+        qemudReportError(dconn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+                         "%s", _("PrepareTunnel called but no TUNNELLED flag set"));
+        goto cleanup;
+    }
+    if (st == NULL) {
+        qemudReportError(dconn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+                         "%s", _("tunnelled migration requested but NULL stream passed"));
+        goto cleanup;
+    }
+
+    /* Parse the domain XML. */
+    if (!(def = virDomainDefParseString(dconn, driver->caps, dom_xml,
+                                        VIR_DOMAIN_XML_INACTIVE))) {
+        qemudReportError(dconn, NULL, NULL, VIR_ERR_OPERATION_FAILED,
+                         "%s", _("failed to parse XML"));
+        goto cleanup;
+    }
+
+    /* Target domain name, maybe renamed. */
+    dname = dname ? dname : def->name;
+
+    /* Ensure the name and UUID don't already exist in an active VM */
+    vm = virDomainFindByUUID(&driver->domains, def->uuid);
+
+    if (!vm) vm = virDomainFindByName(&driver->domains, dname);
+    if (vm) {
+        if (virDomainIsActive(vm)) {
+            qemudReportError(dconn, NULL, NULL, VIR_ERR_OPERATION_FAILED,
+                             _("domain with the same name or UUID already exists as '%s'"),
+                             vm->def->name);
+            goto cleanup;
+        }
+        virDomainObjUnlock(vm);
+    }
+
+    if (!(vm = virDomainAssignDef(dconn,
+                                  &driver->domains,
+                                  def))) {
+        qemudReportError(dconn, NULL, NULL, VIR_ERR_OPERATION_FAILED,
+                         "%s", _("failed to assign new VM"));
+        goto cleanup;
+    }
+    def = NULL;
+
+    /* Domain starts inactive, even if the domain XML had an id field. */
+    vm->def->id = -1;
+
+    if (virAsprintf(&unixfile, "%s/qemu.tunnelmigrate.dest.%s",
+                    driver->stateDir, vm->def->name) < 0) {
+        virReportOOMError (dconn);
+        goto cleanup;
+    }
+    unlink(unixfile);
+
+    /* check that this qemu version supports the interactive exec */
+    if (qemudExtractVersionInfo(vm->def->emulator, NULL, &qemuCmdFlags) < 0) {
+        qemudReportError(dconn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+                         _("Cannot determine QEMU argv syntax %s"),
+                         vm->def->emulator);
+        goto cleanup;
+    }
+    if (qemuCmdFlags & QEMUD_CMD_FLAG_MIGRATE_QEMU_UNIX)
+        internalret = virAsprintf(&migrateFrom, "unix:%s", unixfile);
+    else if (qemuCmdFlags & QEMUD_CMD_FLAG_MIGRATE_QEMU_EXEC)
+        internalret = virAsprintf(&migrateFrom, "exec:nc -U -l %s", unixfile);
+    else {
+        qemudReportError(dconn, NULL, NULL, VIR_ERR_OPERATION_FAILED,
+                         "%s", _("Destination qemu is too old to support tunnelled migration"));
+        goto cleanup;
+    }
+    if (internalret < 0) {
+        virReportOOMError(dconn);
+        goto cleanup;
+    }
+    /* Start the QEMU daemon, with the same command-line arguments plus
+     * -incoming unix:/path/to/file or exec:nc -U /path/to/file
+     */
+    internalret = qemudStartVMDaemon(dconn, driver, vm, migrateFrom, -1);
+    VIR_FREE(migrateFrom);
+    if (internalret < 0) {
+        /* Note that we don't set an error here because qemudStartVMDaemon
+         * should have already done that.
+         */
+        if (!vm->persistent) {
+            virDomainRemoveInactive(&driver->domains, vm);
+            vm = NULL;
+        }
+        goto cleanup;
+    }
+
+    qemust = qemuStreamMigOpen(st, unixfile);
+    if (qemust == NULL) {
+        qemudShutdownVMDaemon(NULL, driver, vm);
+        virReportSystemError(dconn, errno,
+                             _("cannot open unix socket '%s' for tunnelled migration"),
+                             unixfile);
+        goto cleanup;
+    }
+
+    st->driver = &qemuStreamMigDrv;
+    st->privateData = qemust;
+
+    event = virDomainEventNewFromObj(vm,
+                                     VIR_DOMAIN_EVENT_STARTED,
+                                     VIR_DOMAIN_EVENT_STARTED_MIGRATED);
+    ret = 0;
+
+cleanup:
+    virDomainDefFree(def);
+    unlink(unixfile);
+    VIR_FREE(unixfile);
+    if (vm)
+        virDomainObjUnlock(vm);
+    if (event)
+        qemuDomainEventQueue(driver, event);
+    qemuDriverUnlock(driver);
+    return ret;
+}
+
 /* Prepare is the first step, and it runs on the destination host.
  *
  * This starts an empty VM listening on a TCP port.
@@ -5806,7 +6233,7 @@ qemudDomainMigratePrepare2 (virConnectPtr dconn,
                             int *cookielen ATTRIBUTE_UNUSED,
                             const char *uri_in,
                             char **uri_out,
-                            unsigned long flags ATTRIBUTE_UNUSED,
+                            unsigned long flags,
                             const char *dname,
                             unsigned long resource ATTRIBUTE_UNUSED,
                             const char *dom_xml)
@@ -5826,6 +6253,15 @@ qemudDomainMigratePrepare2 (virConnectPtr dconn,
     *uri_out = NULL;
 
     qemuDriverLock(driver);
+    if (flags & VIR_MIGRATE_TUNNELLED) {
+        /* this is a logical error; we never should have gotten here with
+         * VIR_MIGRATE_TUNNELLED set
+         */
+        qemudReportError(dconn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+                         "%s", _("Tunnelled migration requested but invalid RPC method called"));
+        goto cleanup;
+    }
+
     if (!dom_xml) {
         qemudReportError (dconn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
                           "%s", _("no domain XML passed"));
@@ -5967,6 +6403,223 @@ cleanup:
         qemuDomainEventQueue(driver, event);
     qemuDriverUnlock(driver);
     return ret;
+
+}
+
+static int doTunnelMigrate(virDomainPtr dom,
+                           virDomainObjPtr vm,
+                           const char *uri,
+                           unsigned long flags,
+                           const char *dname,
+                           unsigned long resource)
+{
+    struct qemud_driver *driver = dom->conn->privateData;
+    int client_sock, qemu_sock;
+    struct sockaddr_un sa_qemu, sa_client;
+    socklen_t addrlen;
+    virConnectPtr dconn;
+    virDomainPtr ddomain;
+    char *dest;
+    int retval = -1;
+    ssize_t bytes;
+    char buffer[65536];
+    char *safe_uri;
+    virStreamPtr st;
+    char *dom_xml = NULL;
+    char *unixfile;
+    int internalret;
+    unsigned int qemuCmdFlags;
+    int status;
+    unsigned long long transferred, remaining, total;
+
+    /* the order of operations is important here; we make sure the
+     * destination side is completely setup before we touch the source
+     */
+
+    safe_uri = qemuMonitorEscapeArg(uri);
+    if (!safe_uri) {
+        virReportOOMError(dom->conn);
+        return -1;
+    }
+
+    dconn = virConnectOpen(safe_uri);
+    VIR_FREE (safe_uri);
+    if (dconn == NULL) {
+        qemudReportError(dom->conn, dom, NULL, VIR_ERR_OPERATION_FAILED,
+                         _("Failed to connect to remote libvirt URI %s"), uri);
+        return -1;
+    }
+    if (!VIR_DRV_SUPPORTS_FEATURE(dconn->driver, dconn,
+                                  VIR_DRV_FEATURE_MIGRATION_V2)) {
+        qemudReportError(dom->conn, dom, NULL, VIR_ERR_OPERATION_FAILED, "%s",
+                         _("Destination libvirt does not support required migration protocol 2"));
+        goto close_dconn;
+    }
+
+    st = virStreamNew(dconn, 0);
+    if (st == NULL)
+        /* virStreamNew only fails on OOM, and it reports the error itself */
+        goto close_dconn;
+
+    dom_xml = virDomainDefFormat(dom->conn, vm->def, VIR_DOMAIN_XML_SECURE);
+    if (!dom_xml) {
+        qemudReportError(dom->conn, dom, NULL, VIR_ERR_OPERATION_FAILED,
+                         "%s", _("failed to get domain xml"));
+        goto close_stream;
+    }
+
+    internalret = dconn->driver->domainMigratePrepareTunnel(dconn, st, uri,
+                                                            flags, dname,
+                                                            resource, dom_xml);
+    VIR_FREE(dom_xml);
+    if (internalret < 0)
+        /* domainMigratePrepareTunnel sets the error for us */
+        goto close_stream;
+
+    if (virAsprintf(&unixfile, "%s/qemu.tunnelmigrate.src.%s",
+                    driver->stateDir, vm->def->name) < 0) {
+        virReportOOMError(dom->conn);
+        goto finish_migrate;
+    }
+
+    qemu_sock = socket(AF_UNIX, SOCK_STREAM, 0);
+    if (qemu_sock < 0) {
+        virReportSystemError(dom->conn, errno, "%s",
+                             _("cannot open tunnelled migration socket"));
+        goto free_unix_path;
+    }
+    memset(&sa_qemu, 0, sizeof(sa_qemu));
+    sa_qemu.sun_family = AF_UNIX;
+    if (virStrcpy(sa_qemu.sun_path, unixfile,
+                  sizeof(sa_qemu.sun_path)) == NULL) {
+        qemudReportError(dom->conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+                         _("Unix socket '%s' too big for destination"),
+                         unixfile);
+        goto close_qemu_sock;
+    }
+    unlink(unixfile);
+    if (bind(qemu_sock, (struct sockaddr *)&sa_qemu, sizeof(sa_qemu)) < 0) {
+        virReportSystemError(dom->conn, errno,
+                             _("Cannot bind to unix socket '%s' for tunnelled migration"),
+                             unixfile);
+        goto close_qemu_sock;
+    }
+    if (listen(qemu_sock, 1) < 0) {
+        virReportSystemError(dom->conn, errno,
+                             _("Cannot listen on unix socket '%s' for tunnelled migration"),
+                             unixfile);
+        goto close_qemu_sock;
+    }
+
+    /* check that this qemu version supports the unix migration */
+    if (qemudExtractVersionInfo(vm->def->emulator, NULL, &qemuCmdFlags) < 0) {
+        qemudReportError(dom->conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+                         _("Cannot extract Qemu version from '%s'"),
+                         vm->def->emulator);
+        goto close_qemu_sock;
+    }
+    if (qemuCmdFlags & QEMUD_CMD_FLAG_MIGRATE_QEMU_UNIX)
+        internalret = virAsprintf(&dest, "unix:%s", unixfile);
+    else if (qemuCmdFlags & QEMUD_CMD_FLAG_MIGRATE_QEMU_EXEC)
+        internalret = virAsprintf(&dest, "exec:nc -U %s", unixfile);
+    else {
+        qemudReportError(dom->conn, NULL, NULL, VIR_ERR_OPERATION_FAILED,
+                         "%s", _("Source qemu is too old to support tunnelled migration"));
+        goto close_qemu_sock;
+    }
+    if (internalret < 0) {
+        virReportOOMError(dom->conn);
+        goto close_qemu_sock;
+    }
+
+    internalret = qemuMonitorMigrate(vm, 1, dest);
+    VIR_FREE(dest);
+    if (internalret < 0) {
+        qemudReportError(dom->conn, dom, NULL, VIR_ERR_OPERATION_FAILED,
+                         "%s", _("tunnelled migration monitor command failed"));
+        goto close_qemu_sock;
+    }
+
+    /* it is also possible that the migrate didn't fail initially, but
+     * rather failed later on.  Check the output of "info migrate"
+     */
+    if (qemuMonitorGetMigrationStatus(vm, &status,
+                                      &transferred,
+                                      &remaining,
+                                      &total) < 0) {
+        goto qemu_cancel_migration;
+    }
+
+    if (status == QEMU_MONITOR_MIGRATION_STATUS_ERROR) {
+        qemudReportError(dom->conn, dom, NULL, VIR_ERR_OPERATION_FAILED,
+                         "%s",_("migrate failed"));
+        goto qemu_cancel_migration;
+    }
+
+    addrlen = sizeof(sa_client);
+    while ((client_sock = accept(qemu_sock, (struct sockaddr *)&sa_client, &addrlen)) < 0) {
+        if (errno == EAGAIN || errno == EINTR)
+            continue;
+        virReportSystemError(dom->conn, errno, "%s",
+                             _("tunnelled migration failed to accept from qemu"));
+        goto qemu_cancel_migration;
+    }
+
+    for (;;) {
+        bytes = saferead(client_sock, buffer, sizeof(buffer));
+        if (bytes < 0) {
+            virReportSystemError(dconn, errno, "%s",
+                                 _("tunnelled migration failed to read from qemu"));
+            goto close_client_sock;
+        }
+        else if (bytes == 0)
+            /* EOF; get out of here */
+            break;
+
+        if (virStreamSend(st, buffer, bytes) < 0) {
+            qemudReportError(dom->conn, dom, NULL, VIR_ERR_OPERATION_FAILED,
+                             _("Failed to write migration data to remote libvirtd"));
+            virStreamAbort(st);
+            goto close_client_sock;
+        }
+    }
+
+    if (virStreamFinish(st) < 0)
+        /* virStreamFinish set the error for us */
+        goto close_client_sock;
+
+    retval = 0;
+
+close_client_sock:
+    close(client_sock);
+
+qemu_cancel_migration:
+    if (retval != 0)
+        qemuMonitorMigrateCancel(vm);
+
+close_qemu_sock:
+    close(qemu_sock);
+
+free_unix_path:
+    unlink(unixfile);
+    VIR_FREE(unixfile);
+
+finish_migrate:
+    dname = dname ? dname : dom->name;
+    ddomain = dconn->driver->domainMigrateFinish2
+        (dconn, dname, NULL, 0, uri, flags, retval);
+    if (ddomain)
+        virUnrefDomain(ddomain);
+
+close_stream:
+    /* don't call virStreamFree(), because that resets any pending errors */
+    virUnrefStream(st);
+
+close_dconn:
+    /* don't call virConnectClose(), because that resets any pending errors */
+    virUnrefConnect(dconn);
+
+    return retval;
 }
 
 /* Perform is the second step, and it runs on the source host. */
@@ -6022,42 +6675,49 @@ qemudDomainMigratePerform (virDomainPtr dom,
         qemuMonitorSetMigrationSpeed(vm, resource) < 0)
         goto cleanup;
 
-    /* Issue the migrate command. */
-    if (STRPREFIX(uri, "tcp:") && !STRPREFIX(uri, "tcp://")) {
-        /* HACK: source host generates bogus URIs, so fix them up */
-        char *tmpuri;
-        if (virAsprintf(&tmpuri, "tcp://%s", uri + strlen("tcp:")) < 0) {
-            virReportOOMError(dom->conn);
+    if (!(flags & VIR_MIGRATE_TUNNELLED)) {
+        /* Issue the migrate command. */
+        if (STRPREFIX(uri, "tcp:") && !STRPREFIX(uri, "tcp://")) {
+            /* HACK: source host generates bogus URIs, so fix them up */
+            char *tmpuri;
+            if (virAsprintf(&tmpuri, "tcp://%s", uri + strlen("tcp:")) < 0) {
+                virReportOOMError(dom->conn);
+                goto cleanup;
+            }
+            uribits = xmlParseURI(tmpuri);
+            VIR_FREE(tmpuri);
+        } else {
+            uribits = xmlParseURI(uri);
+        }
+        if (!uribits) {
+            qemudReportError(dom->conn, dom, NULL, VIR_ERR_INTERNAL_ERROR,
+                             _("cannot parse URI %s"), uri);
             goto cleanup;
         }
-        uribits = xmlParseURI(tmpuri);
-        VIR_FREE(tmpuri);
-    } else {
-        uribits = xmlParseURI(uri);
-    }
-    if (!uribits) {
-        qemudReportError(dom->conn, dom, NULL, VIR_ERR_INTERNAL_ERROR,
-                         _("cannot parse URI %s"), uri);
-        goto cleanup;
-    }
 
-    if (qemuMonitorMigrateToHost(vm, uribits->server, uribits->port) < 0)
-        goto cleanup;
+        if (qemuMonitorMigrateToHost(vm, uribits->server, uribits->port) < 0)
+            goto cleanup;
 
-    /* it is also possible that the migrate didn't fail initially, but
-     * rather failed later on.  Check the output of "info migrate"
-     */
-    if (qemuMonitorGetMigrationStatus(vm, &status,
-                                      &transferred,
-                                      &remaining,
-                                      &total) < 0) {
-        goto cleanup;
-    }
+        /* it is also possible that the migrate didn't fail initially, but
+         * rather failed later on.  Check the output of "info migrate"
+         */
+        if (qemuMonitorGetMigrationStatus(vm, &status,
+                                          &transferred,
+                                          &remaining,
+                                          &total) < 0) {
+            goto cleanup;
+        }
 
-    if (status != QEMU_MONITOR_MIGRATION_STATUS_COMPLETED) {
-        qemudReportError (dom->conn, dom, NULL, VIR_ERR_OPERATION_FAILED,
-                          "%s", _("migrate did not successfully complete"));
-        goto cleanup;
+        if (status != QEMU_MONITOR_MIGRATION_STATUS_COMPLETED) {
+            qemudReportError (dom->conn, dom, NULL, VIR_ERR_OPERATION_FAILED,
+                              "%s", _("migrate did not successfully complete"));
+            goto cleanup;
+        }
+    }
+    else {
+        if (doTunnelMigrate(dom, vm, uri, flags, dname, resource) < 0)
+            /* doTunnelMigrate already set the error, so just get out */
+            goto cleanup;
     }
 
     /* Clean up the source domain. */
@@ -6357,6 +7017,7 @@ static virDriver qemuDriver = {
     qemudNodeDeviceDettach, /* nodeDeviceDettach */
     qemudNodeDeviceReAttach, /* nodeDeviceReAttach */
     qemudNodeDeviceReset, /* nodeDeviceReset */
+    qemudDomainMigratePrepareTunnel, /* domainMigratePrepareTunnel */
 };
 
 
diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c
index 731b213..25aaf32 100644
--- a/src/remote/remote_driver.c
+++ b/src/remote/remote_driver.c
@@ -6698,7 +6698,6 @@ done:
 }
 
 
-#if 0
 static struct private_stream_data *
 remoteStreamOpen(virStreamPtr st,
                  int output ATTRIBUTE_UNUSED,
@@ -7049,9 +7048,51 @@ static virStreamDriver remoteStreamDrv = {
     .streamUpdateCallback = remoteStreamEventUpdateCallback,
     .streamRemoveCallback = remoteStreamEventRemoveCallback,
 };
-#endif
 
 
+static int
+remoteDomainMigratePrepareTunnel(virConnectPtr conn,
+                                 virStreamPtr st,
+                                 const char *uri_in,
+                                 unsigned long flags,
+                                 const char *dname,
+                                 unsigned long resource,
+                                 const char *dom_xml)
+{
+    struct private_data *priv = conn->privateData;
+    struct private_stream_data *privst = NULL;
+    int rv = -1;
+    remote_domain_migrate_prepare_tunnel_args args;
+
+    remoteDriverLock(priv);
+
+    if (!(privst = remoteStreamOpen(st, 1, REMOTE_PROC_DOMAIN_MIGRATE_PREPARE_TUNNEL, priv->counter)))
+        goto done;
+
+    st->driver = &remoteStreamDrv;
+    st->privateData = privst;
+
+    args.uri_in = uri_in == NULL ? NULL : (char **) &uri_in;
+    args.flags = flags;
+    args.dname = dname == NULL ? NULL : (char **) &dname;
+    args.resource = resource;
+    args.dom_xml = (char *) dom_xml;
+
+    if (call(conn, priv, 0, REMOTE_PROC_DOMAIN_MIGRATE_PREPARE_TUNNEL,
+             (xdrproc_t) xdr_remote_domain_migrate_prepare_tunnel_args, (char *) &args,
+             (xdrproc_t) xdr_void, NULL) == -1) {
+        remoteStreamRelease(st);
+        goto done;
+    }
+
+    rv = 0;
+
+done:
+    remoteDriverUnlock(priv);
+
+    return rv;
+}
+
 /*----------------------------------------------------------------------*/
 
 
@@ -8410,6 +8451,7 @@ static virDriver remote_driver = {
     remoteNodeDeviceDettach, /* nodeDeviceDettach */
     remoteNodeDeviceReAttach, /* nodeDeviceReAttach */
     remoteNodeDeviceReset, /* nodeDeviceReset */
+    remoteDomainMigratePrepareTunnel, /* domainMigratePrepareTunnel */
 };
 
 static virNetworkDriver network_driver = {
diff --git a/src/remote/remote_protocol.c b/src/remote/remote_protocol.c
index 1d2d242..8c61712 100644
--- a/src/remote/remote_protocol.c
+++ b/src/remote/remote_protocol.c
@@ -2698,6 +2698,23 @@ xdr_remote_secret_lookup_by_usage_ret (XDR *xdrs, remote_secret_lookup_by_usage_
 }
 
 bool_t
+xdr_remote_domain_migrate_prepare_tunnel_args (XDR *xdrs, remote_domain_migrate_prepare_tunnel_args *objp)
+{
+
+         if (!xdr_remote_string (xdrs, &objp->uri_in))
+                 return FALSE;
+         if (!xdr_uint64_t (xdrs, &objp->flags))
+                 return FALSE;
+         if (!xdr_remote_string (xdrs, &objp->dname))
+                 return FALSE;
+         if (!xdr_uint64_t (xdrs, &objp->resource))
+                 return FALSE;
+         if (!xdr_remote_nonnull_string (xdrs, &objp->dom_xml))
+                 return FALSE;
+        return TRUE;
+}
+
+bool_t
 xdr_remote_procedure (XDR *xdrs, remote_procedure *objp)
 {
 
diff --git a/src/remote/remote_protocol.h b/src/remote/remote_protocol.h
index 64da9fa..245f411 100644
--- a/src/remote/remote_protocol.h
+++ b/src/remote/remote_protocol.h
@@ -1528,6 +1528,16 @@ struct remote_secret_lookup_by_usage_ret {
         remote_nonnull_secret secret;
 };
 typedef struct remote_secret_lookup_by_usage_ret remote_secret_lookup_by_usage_ret;
+
+struct remote_domain_migrate_prepare_tunnel_args {
+        remote_string uri_in;
+        uint64_t flags;
+        remote_string dname;
+        uint64_t resource;
+        remote_nonnull_string dom_xml;
+};
+typedef struct remote_domain_migrate_prepare_tunnel_args remote_domain_migrate_prepare_tunnel_args;
+
 #define REMOTE_PROGRAM 0x20008086
 #define REMOTE_PROTOCOL_VERSION 1
 
@@ -1679,6 +1689,7 @@ enum remote_procedure {
         REMOTE_PROC_SECRET_GET_VALUE = 145,
         REMOTE_PROC_SECRET_UNDEFINE = 146,
         REMOTE_PROC_SECRET_LOOKUP_BY_USAGE = 147,
+        REMOTE_PROC_DOMAIN_MIGRATE_PREPARE_TUNNEL = 148,
 };
 typedef enum remote_procedure remote_procedure;
 
@@ -1959,6 +1970,7 @@ extern  bool_t xdr_remote_secret_get_value_ret (XDR *, remote_secret_get_value_r
 extern  bool_t xdr_remote_secret_undefine_args (XDR *, remote_secret_undefine_args*);
 extern  bool_t xdr_remote_secret_lookup_by_usage_args (XDR *, remote_secret_lookup_by_usage_args*);
 extern  bool_t xdr_remote_secret_lookup_by_usage_ret (XDR *, remote_secret_lookup_by_usage_ret*);
+extern  bool_t xdr_remote_domain_migrate_prepare_tunnel_args (XDR *, remote_domain_migrate_prepare_tunnel_args*);
 extern  bool_t xdr_remote_procedure (XDR *, remote_procedure*);
 extern  bool_t xdr_remote_message_type (XDR *, remote_message_type*);
 extern  bool_t xdr_remote_message_status (XDR *, remote_message_status*);
@@ -2213,6 +2225,7 @@ extern bool_t xdr_remote_secret_get_value_ret ();
 extern bool_t xdr_remote_secret_undefine_args ();
 extern bool_t xdr_remote_secret_lookup_by_usage_args ();
 extern bool_t xdr_remote_secret_lookup_by_usage_ret ();
+extern bool_t xdr_remote_domain_migrate_prepare_tunnel_args ();
 extern bool_t xdr_remote_procedure ();
 extern bool_t xdr_remote_message_type ();
 extern bool_t xdr_remote_message_status ();
diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x
index 6b0a784..537a838 100644
--- a/src/remote/remote_protocol.x
+++ b/src/remote/remote_protocol.x
@@ -1355,6 +1355,14 @@ struct remote_secret_lookup_by_usage_ret {
     remote_nonnull_secret secret;
 };
 
+struct remote_domain_migrate_prepare_tunnel_args {
+    remote_string uri_in;
+    unsigned hyper flags;
+    remote_string dname;
+    unsigned hyper resource;
+    remote_nonnull_string dom_xml;
+};
+
 /*----- Protocol. -----*/
 
 /* Define the program number, protocol version and procedure numbers here. */
@@ -1523,7 +1531,9 @@ enum remote_procedure {
     REMOTE_PROC_SECRET_SET_VALUE = 144,
     REMOTE_PROC_SECRET_GET_VALUE = 145,
     REMOTE_PROC_SECRET_UNDEFINE = 146,
-    REMOTE_PROC_SECRET_LOOKUP_BY_USAGE = 147
+    REMOTE_PROC_SECRET_LOOKUP_BY_USAGE = 147,
+
+    REMOTE_PROC_DOMAIN_MIGRATE_PREPARE_TUNNEL = 148
 };
 
 
diff --git a/src/test/test_driver.c b/src/test/test_driver.c
index cb48f64..f57c92a 100644
--- a/src/test/test_driver.c
+++ b/src/test/test_driver.c
@@ -4267,6 +4267,7 @@ static virDriver testDriver = {
     NULL, /* nodeDeviceDettach */
     NULL, /* nodeDeviceReAttach */
     NULL, /* nodeDeviceReset */
+    NULL, /* domainMigratePrepareTunnel */
 };
 
 static virNetworkDriver testNetworkDriver = {
diff --git a/src/uml/uml_driver.c b/src/uml/uml_driver.c
index f0d5fd4..9a7fe42 100644
--- a/src/uml/uml_driver.c
+++ b/src/uml/uml_driver.c
@@ -1861,6 +1861,7 @@ static virDriver umlDriver = {
     NULL, /* nodeDeviceDettach */
     NULL, /* nodeDeviceReAttach */
     NULL, /* nodeDeviceReset */
+    NULL, /* domainMigratePrepareTunnel */
 };
 
 
diff --git a/src/vbox/vbox_tmpl.c b/src/vbox/vbox_tmpl.c
index 72220e1..4f43901 100644
--- a/src/vbox/vbox_tmpl.c
+++ b/src/vbox/vbox_tmpl.c
@@ -6467,7 +6467,7 @@ virDriver NAME(Driver) = {
     NULL, /* nodeDeviceDettach */
     NULL, /* nodeDeviceReAttach */
     NULL, /* nodeDeviceReset */
-
+    NULL, /* domainMigratePrepareTunnel */
 };
 
 virNetworkDriver NAME(NetworkDriver) = {
diff --git a/src/xen/xen_driver.c b/src/xen/xen_driver.c
index 9e1bc32..76b896a 100644
--- a/src/xen/xen_driver.c
+++ b/src/xen/xen_driver.c
@@ -1722,6 +1722,7 @@ static virDriver xenUnifiedDriver = {
     xenUnifiedNodeDeviceDettach, /* nodeDeviceDettach */
     xenUnifiedNodeDeviceReAttach, /* nodeDeviceReAttach */
     xenUnifiedNodeDeviceReset, /* nodeDeviceReset */
+    NULL, /* domainMigratePrepareTunnel */
 };
 
 /**
diff --git a/tools/virsh.c b/tools/virsh.c
index 3482389..2222269 100644
--- a/tools/virsh.c
+++ b/tools/virsh.c
@@ -2462,6 +2462,7 @@ static const vshCmdInfo info_migrate[] = {
 
 static const vshCmdOptDef opts_migrate[] = {
     {"live", VSH_OT_BOOL, 0, gettext_noop("live migration")},
+    {"tunnelled", VSH_OT_BOOL, 0, gettext_noop("tunnelled migration")},
     {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("domain name, id or uuid")},
     {"desturi", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("connection URI of the destination host")},
     {"migrateuri", VSH_OT_DATA, 0, gettext_noop("migration URI, usually can be omitted")},
@@ -2499,12 +2500,31 @@ cmdMigrate (vshControl *ctl, const vshCmd *cmd)
     if (vshCommandOptBool (cmd, "live"))
         flags |= VIR_MIGRATE_LIVE;
 
-    /* Temporarily connect to the destination host. */
-    dconn = virConnectOpenAuth (desturi, virConnectAuthPtrDefault, 0);
-    if (!dconn) goto done;
+    if (vshCommandOptBool (cmd, "tunnelled"))
+        flags |= VIR_MIGRATE_TUNNELLED;
+
+    if (!(flags & VIR_MIGRATE_TUNNELLED)) {
+        /* For regular live migration, temporarily connect to the destination
+         * host.  For tunnelled migration, that will be done by the remote
+         * libvirtd.
+         */
+        dconn = virConnectOpenAuth(desturi, virConnectAuthPtrDefault, 0);
+        if (!dconn) goto done;
+    }
+    else {
+        /* when doing tunnelled migration, use migrateuri if it's available,
+         * but if not, fall back to desturi.  This allows both of these
+         * to work:
+         *
+         * virsh migrate guest qemu+tls://dest/system
+         * virsh migrate guest qemu+tls://dest/system qemu+tls://dest-alt/system
+         */
+        if (migrateuri == NULL)
+            migrateuri = desturi;
+    }
 
     /* Migrate. */
-    ddom = virDomainMigrate (dom, dconn, flags, dname, migrateuri, 0);
+    ddom = virDomainMigrate(dom, dconn, flags, dname, migrateuri, 0);
     if (!ddom) goto done;
 
     ret = TRUE;
-- 
1.6.0.6




More information about the libvir-list mailing list