]> xenbits.xensource.com Git - libvirt.git/commitdiff
Support a new peer-to-peer migration mode & public API
authorDaniel P. Berrange <berrange@redhat.com>
Thu, 17 Sep 2009 17:10:04 +0000 (18:10 +0100)
committerDaniel P. Berrange <berrange@redhat.com>
Fri, 9 Oct 2009 11:35:07 +0000 (12:35 +0100)
Introduces several new public API options for migration

 - VIR_MIGRATE_PEER2PEER: With this flag the client only
   invokes the virDomainMigratePerform method, expecting
   the source host driver to do whatever is required to
   complete the entire migration process.
 - VIR_MIGRATE_TUNNELLED: With this flag the actual data
   for migration will be tunnelled over the libvirtd RPC
   channel. This requires that VIR_MIGRATE_PEER2PEER is
   also set.
 - virDomainMigrateToURI: This is variant of the existing
   virDomainMigrate method which does not require any
   virConnectPtr for the destination host. Given suitable
   driver support, this allows for all the same modes as
   virDomainMigrate()

The URI for VIR_MIGRATE_PEER2PEER must be a valid libvirt
URI. For non-p2p migration a hypervisor specific migration
URI is used.

virDomainMigrateToURI without a PEER2PEER flag is only
support for Xen currently, and it involves XenD talking
directly to XenD, no libvirtd involved at all.

* include/libvirt/libvirt.h.in: Add VIR_MIGRATE_PEER2PEER
  flag for migration
* src/libvirt_internal.h: Add feature flags for peer to
  peer migration (VIR_FEATURE_MIGRATE_P2P) and direct
  migration (VIR_MIGRATE_PEER2PEER mode)
* src/libvirt.c: Implement support for VIR_MIGRATE_PEER2PEER
  and virDomainMigrateToURI APIs.
* src/xen/xen_driver.c: Advertise support for DIRECT migration
* src/xen/xend_internal.c: Add TODO item for p2p migration
* src/libvirt_public.syms: Export virDomainMigrateToURI
  method
* src/qemu/qemu_driver.c: Add support for PEER2PEER and
  migration, and adapt TUNNELLED migration.
* tools/virsh.c: Add --p2p and --direct args and use the
  new virDomainMigrateToURI method where possible.

include/libvirt/libvirt.h.in
src/libvirt.c
src/libvirt_internal.h
src/libvirt_public.syms
src/qemu/qemu_driver.c
src/xen/xen_driver.c
src/xen/xend_internal.c
tools/virsh.c

index 60be41b4bca46b3d563b3b573844cfc757bfd30f..f51a565132b09a1c19ad53e0a79aed25c30754e3 100644 (file)
@@ -336,8 +336,9 @@ typedef virDomainInterfaceStatsStruct *virDomainInterfaceStatsPtr;
 
 /* Domain migration flags. */
 typedef enum {
-  VIR_MIGRATE_LIVE              = 1, /* live migration */
-  VIR_MIGRATE_TUNNELLED         = 2, /* tunnelled migration */
+    VIR_MIGRATE_LIVE              = (1 << 0), /* live migration */
+    VIR_MIGRATE_PEER2PEER         = (1 << 1), /* direct source -> dest host control channel */
+    VIR_MIGRATE_TUNNELLED         = (1 << 2), /* tunnel migration data over libvirtd connection */
 } virDomainMigrateFlags;
 
 /* Domain migration. */
@@ -345,6 +346,10 @@ virDomainPtr virDomainMigrate (virDomainPtr domain, virConnectPtr dconn,
                                unsigned long flags, const char *dname,
                                const char *uri, unsigned long bandwidth);
 
+int virDomainMigrateToURI (virDomainPtr domain, const char *duri,
+                           unsigned long flags, const char *dname,
+                           unsigned long bandwidth);
+
 /**
  * VIR_NODEINFO_MAXCPUS:
  * @nodeinfo: virNodeInfo instance
index 952b69980326a3c03479f0142941413f7c3f8678..b14942da2360ab1c16ac34001b3cd54fcc910eba 100644 (file)
@@ -35,7 +35,6 @@
 #include "virterror_internal.h"
 #include "logging.h"
 #include "datatypes.h"
-#include "libvirt_internal.h"
 #include "driver.h"
 
 #include "uuid.h"
@@ -3044,36 +3043,75 @@ virDomainMigrateVersion2 (virDomainPtr domain,
     return ddomain;
 }
 
+
+ /*
+  * This is sort of a migration v3
+  *
+  * In this version, the client does not talk to the destination
+  * libvirtd. The source libvirtd will still try to talk to the
+  * destination libvirtd though, and will do the prepare/perform/finish
+  * steps.
+  */
+static int
+virDomainMigratePeer2Peer (virDomainPtr domain,
+                           unsigned long flags,
+                           const char *dname,
+                           const char *uri,
+                           unsigned long bandwidth)
+{
+    if (!domain->conn->driver->domainMigratePerform) {
+        virLibConnError (domain->conn, VIR_ERR_NO_SUPPORT, __FUNCTION__);
+        return -1;
+    }
+
+    /* Perform the migration.  The driver isn't supposed to return
+     * until the migration is complete.
+     */
+    return domain->conn->driver->domainMigratePerform(domain,
+                                                      NULL, /* cookie */
+                                                      0,    /* cookielen */
+                                                      uri,
+                                                      flags,
+                                                      dname,
+                                                      bandwidth);
+}
+
+
 /*
- * Tunnelled migration has the following flow:
+ * This is a variation on v1 & 2  migration
+ *
+ * This is for hypervisors which can directly handshake
+ * without any libvirtd involvement on destination either
+ * from client, or source libvirt.
  *
- * virDomainMigrate(src, uri)
- *   - virDomainMigratePerform(src, uri)
- *      - dst = virConnectOpen(uri)
- *      - virDomainMigratePrepareTunnel(dst)
- *      - while (1)
- *         - virStreamSend(dst, data)
- *      - virDomainMigrateFinish(dst)
- *      - virConnectClose(dst)
+ * eg, XenD can talk direct to XenD, so libvirtd on dest
+ * does not need to be involved at all, or even running
  */
-static virDomainPtr
-virDomainMigrateTunnelled(virDomainPtr domain,
-                          virConnectPtr dconn,
-                          unsigned long flags,
-                          const char *dname,
-                          const char *uri,
-                          unsigned long bandwidth)
-{
+static int
+virDomainMigrateDirect (virDomainPtr domain,
+                        unsigned long flags,
+                        const char *dname,
+                        const char *uri,
+                        unsigned long bandwidth)
+{
+    if (!domain->conn->driver->domainMigratePerform) {
+        virLibConnError (domain->conn, VIR_ERR_NO_SUPPORT, __FUNCTION__);
+        return -1;
+    }
+
     /* 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;
-
-    return virDomainLookupByName(dconn, dname ? dname : domain->name);
+    return domain->conn->driver->domainMigratePerform(domain,
+                                                      NULL, /* cookie */
+                                                      0,    /* cookielen */
+                                                      uri,
+                                                      flags,
+                                                      dname,
+                                                      bandwidth);
 }
 
+
 /**
  * virDomainMigrate:
  * @domain: a domain object
@@ -3087,24 +3125,34 @@ virDomainMigrateTunnelled(virDomainPtr domain,
  * host given by dconn (a connection to the destination host).
  *
  * 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
+ *   VIR_MIGRATE_LIVE      Do not pause the VM during migration
+ *   VIR_MIGRATE_PEER2PEER Direct connection between source & destination hosts
+ *   VIR_MIGRATE_TUNNELLED Tunnel migration data over the libvirt RPC channel
+ *
+ * VIR_MIGRATE_TUNNELLED requires that VIR_MIGRATE_PEER2PEER be set.
+ * Applications using the VIR_MIGRATE_PEER2PEER flag will probably
+ * prefer to invoke virDomainMigrateToURI, avoiding the need to
+ * open connection to the destination host themselves.
  *
  * If a hypervisor supports renaming domains during migration,
  * then you may set the dname parameter to the new name (otherwise
  * it keeps the same name).  If this is not supported by the
  * hypervisor, dname must be NULL or else you will get an error.
  *
- * Since typically the two hypervisors connect directly to each
- * other in order to perform the migration, you may need to specify
- * a path from the source to the destination.  This is the purpose
- * of the uri parameter.  If uri is NULL, then libvirt will try to
- * find the best method.  Uri may specify the hostname or IP address
- * of the destination host as seen from the source.  Or uri may be
- * a URI giving transport, hostname, user, port, etc. in the usual
- * form.  Refer to driver documentation for the particular URIs
- * supported.
+ * If the VIR_MIGRATE_PEER2PEER flag is set, the uri parameter
+ * must be a valid libvirt connection URI, by which the source
+ * libvirt driver can connect to the destination libvirt. If
+ * omitted, the dconn connection object will be queried for its
+ * current URI.
+ *
+ * If the VIR_MIGRATE_PEER2PEER flag is NOT set, the URI parameter
+ * takes a hypervisor specific format. The hypervisor capabilities
+ * XML includes details of the support URI schemes. If omitted
+ * the dconn will be asked for a default URI.
+ *
+ * In either case it is typically only neccessary to specify a
+ * URI if the destination host has multiple interfaces and a
+ * specific interface is required to transmit migration data.
  *
  * The maximum bandwidth (in Mbps) that will be used to do migration
  * can be specified with the bandwidth parameter.  If set to 0,
@@ -3159,18 +3207,35 @@ virDomainMigrate (virDomainPtr domain,
         goto error;
     }
 
-    if (flags & VIR_MIGRATE_TUNNELLED) {
-        char *dstURI = NULL;
-        if (uri == NULL) {
-            dstURI = virConnectGetURI(dconn);
-            if (!uri)
-                return NULL;
-        }
+    if (flags & VIR_MIGRATE_PEER2PEER) {
+        if (VIR_DRV_SUPPORTS_FEATURE (domain->conn->driver, domain->conn,
+                                      VIR_DRV_FEATURE_MIGRATION_P2P)) {
+            char *dstURI = NULL;
+            if (uri == NULL) {
+                dstURI = virConnectGetURI(dconn);
+                if (!uri)
+                    return NULL;
+            }
 
-        ddomain = virDomainMigrateTunnelled(domain, dconn, flags, dname, uri ? uri : dstURI, bandwidth);
+            if (virDomainMigratePeer2Peer(domain, flags, dname, uri ? uri : dstURI, bandwidth) < 0) {
+                VIR_FREE(dstURI);
+                goto error;
+            }
+            VIR_FREE(dstURI);
 
-        VIR_FREE(dstURI);
+            ddomain = virDomainLookupByName (dconn, dname ? dname : domain->name);
+        } else {
+            /* This driver does not support peer to peer migration */
+            virLibConnError (domain->conn, VIR_ERR_NO_SUPPORT, __FUNCTION__);
+            goto error;
+        }
     } else {
+        if (flags & VIR_MIGRATE_TUNNELLED) {
+            virLibConnError(domain->conn, VIR_ERR_OPERATION_INVALID,
+                            _("cannot perform tunnelled migration without using peer2peer flag"));
+            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) &&
@@ -3183,13 +3248,14 @@ virDomainMigrate (virDomainPtr domain,
                                           VIR_DRV_FEATURE_MIGRATION_V2))
             ddomain = virDomainMigrateVersion2(domain, dconn, flags, dname, uri, bandwidth);
         else {
+            /* This driver does not support any migration method */
             virLibConnError(domain->conn, VIR_ERR_NO_SUPPORT, __FUNCTION__);
             goto error;
         }
     }
 
-     if (ddomain == NULL)
-         goto error;
+    if (ddomain == NULL)
+        goto error;
 
     return ddomain;
 
@@ -3199,6 +3265,122 @@ error:
     return NULL;
 }
 
+
+/**
+ * virDomainMigrateToURI:
+ * @domain: a domain object
+ * @duri: mandatory URI for the destination host
+ * @flags: flags
+ * @dname: (optional) rename domain to this at destination
+ * @bandwidth: (optional) specify migration bandwidth limit in Mbps
+ *
+ * Migrate the domain object from its current host to the destination
+ * host given by dconn (a connection to the destination host).
+ *
+ * Flags may be one of more of the following:
+ *   VIR_MIGRATE_LIVE      Do not pause the VM during migration
+ *   VIR_MIGRATE_PEER2PEER Direct connection between source & destination hosts
+ *   VIR_MIGRATE_TUNNELLED Tunnel migration data over the libvirt RPC channel
+ *
+ * VIR_MIGRATE_TUNNELLED requires that VIR_MIGRATE_PEER2PEER be set.
+ * Applications using the VIR_MIGRATE_PEER2PEER flag will probably
+ * prefer to invoke virDomainMigrateToURI, avoiding the need to
+ * open connection to the destination host themselves.
+ *
+ * If a hypervisor supports renaming domains during migration,
+ * then you may set the dname parameter to the new name (otherwise
+ * it keeps the same name).  If this is not supported by the
+ * hypervisor, dname must be NULL or else you will get an error.
+ *
+ * If the VIR_MIGRATE_PEER2PEER flag is set, the duri parameter
+ * must be a valid libvirt connection URI, by which the source
+ * libvirt driver can connect to the destination libvirt. If
+ * omitted, the dconn connection object will be queried for its
+ * current URI.
+ *
+ * If the VIR_MIGRATE_PEER2PEER flag is NOT set, the duri parameter
+ * takes a hypervisor specific format. The hypervisor capabilities
+ * XML includes details of the support URI schemes. If omitted
+ * the dconn will be asked for a default URI. Not all hypervisors
+ * will support this mode of migration, so if the VIR_MIGRATE_PEER2PEER
+ * flag is not set, then it may be neccessary to use the alternative
+ * virDomainMigrate API providing an explicit virConnectPtr for the
+ * destination host
+ *
+ * The maximum bandwidth (in Mbps) that will be used to do migration
+ * can be specified with the bandwidth parameter.  If set to 0,
+ * libvirt will choose a suitable default.  Some hypervisors do
+ * not support this feature and will return an error if bandwidth
+ * is not 0.
+ *
+ * To see which features are supported by the current hypervisor,
+ * see virConnectGetCapabilities, /capabilities/host/migration_features.
+ *
+ * There are many limitations on migration imposed by the underlying
+ * technology - for example it may not be possible to migrate between
+ * different processors even with the same architecture, or between
+ * different types of hypervisor.
+ *
+ * Returns 0 if the migration succeeded, -1 upon error.
+ */
+int
+virDomainMigrateToURI (virDomainPtr domain,
+                       const char *duri,
+                       unsigned long flags,
+                       const char *dname,
+                       unsigned long bandwidth)
+{
+    DEBUG("domain=%p, duri=%p, flags=%lu, dname=%s, bandwidth=%lu",
+          domain, NULLSTR(duri), flags, NULLSTR(dname), bandwidth);
+
+    virResetLastError();
+
+    /* First checkout the source */
+    if (!VIR_IS_CONNECTED_DOMAIN (domain)) {
+        virLibDomainError(NULL, VIR_ERR_INVALID_DOMAIN, __FUNCTION__);
+        return -1;
+    }
+    if (domain->conn->flags & VIR_CONNECT_RO) {
+        virLibDomainError(domain, VIR_ERR_OPERATION_DENIED, __FUNCTION__);
+        goto error;
+    }
+
+    if (duri == NULL) {
+        virLibConnError (domain->conn, VIR_ERR_INVALID_ARG, __FUNCTION__);
+        goto error;
+    }
+
+    if (flags & VIR_MIGRATE_PEER2PEER) {
+        if (VIR_DRV_SUPPORTS_FEATURE (domain->conn->driver, domain->conn,
+                                      VIR_DRV_FEATURE_MIGRATION_P2P)) {
+            if (virDomainMigratePeer2Peer (domain, flags, dname, duri, bandwidth) < 0)
+                goto error;
+        } else {
+            /* No peer to peer migration supported */
+            virLibConnError (domain->conn, VIR_ERR_NO_SUPPORT, __FUNCTION__);
+            goto error;
+        }
+    } else {
+        if (VIR_DRV_SUPPORTS_FEATURE (domain->conn->driver, domain->conn,
+                                      VIR_DRV_FEATURE_MIGRATION_DIRECT)) {
+            if (virDomainMigrateDirect (domain, flags, dname, duri, bandwidth) < 0)
+                goto error;
+        } else {
+            /* Cannot do a migration with only the perform step */
+            virLibConnError (domain->conn, VIR_ERR_NO_SUPPORT, __FUNCTION__);
+            goto error;
+        }
+    }
+
+    return 0;
+
+error:
+    /* Copy to connection error object for back compatability */
+    virSetConnError(domain->conn);
+    return -1;
+}
+
+
 /*
  * Not for public use.  This function is part of the internal
  * implementation of migration in the remote case.
index d7cfd958b6260d5150c7843cd8e8032ea3fc19a2..2ee09e93ed0f1f4c8ef2833df78974ed33730206 100644 (file)
@@ -55,6 +55,17 @@ enum {
      * domainMigratePerform/domainMigrateFinish2.
      */
     VIR_DRV_FEATURE_MIGRATION_V2 = 3,
+
+    /* Driver supports peer-2-peer virDomainMigrate ie source host
+     * does all the prepare/perform/finish steps directly
+     */
+    VIR_DRV_FEATURE_MIGRATION_P2P = 4,
+
+    /* Driver supports migration with only the source host involved,
+     * no libvirtd connetions on the destination at all, only the
+     * perform step is used.
+     */
+    VIR_DRV_FEATURE_MIGRATION_DIRECT = 5,
 };
 
 
index 7226e88a0d00dd5ed1cd48a179a12ccd2473a1c3..8921c1a5d025471e19d808f0bdb8dc69cb8063cc 100644 (file)
@@ -326,6 +326,7 @@ LIBVIRT_0.7.2 {
        virStreamFinish;
        virStreamAbort;
        virStreamFree;
+       virDomainMigrateToURI;
 } LIBVIRT_0.7.1;
 
 # .... define new API here using predicted next version number ....
index 160e0ff878def57453cfdd17ed95112d2152def4..3812f9107721b784d8dec99b2aa696cdcb662e9d 100644 (file)
@@ -2382,8 +2382,11 @@ static int
 qemudSupportsFeature (virConnectPtr conn ATTRIBUTE_UNUSED, int feature)
 {
     switch (feature) {
-    case VIR_DRV_FEATURE_MIGRATION_V2: return 1;
-    default: return 0;
+    case VIR_DRV_FEATURE_MIGRATION_V2:
+    case VIR_DRV_FEATURE_MIGRATION_P2P:
+        return 1;
+    default:
+        return 0;
     }
 }
 
@@ -6415,7 +6418,7 @@ static int doNativeMigrate(virDomainPtr dom,
                            unsigned long resource)
 {
     int ret = -1;
-    xmlURIPtr uribits;
+    xmlURIPtr uribits = NULL;
     int status;
     unsigned long long transferred, remaining, total;
 
@@ -6683,6 +6686,57 @@ cleanup:
 }
 
 
+/* This is essentially a simplified re-impl of
+ * virDomainMigrateVersion2 from libvirt.c, but running in source
+ * libvirtd context, instead of client app context */
+static int doNonTunnelMigrate(virDomainPtr dom,
+                              virConnectPtr dconn,
+                              virDomainObjPtr vm,
+                              const char *dom_xml,
+                              const char *uri ATTRIBUTE_UNUSED,
+                              unsigned long flags,
+                              const char *dname,
+                              unsigned long resource)
+{
+    virDomainPtr ddomain = NULL;
+    int retval = -1;
+    char *uri_out = NULL;
+
+    /* NB we don't pass 'uri' into this, since that's the libvirtd
+     * URI in this context - so we let dest pick it */
+    if (dconn->driver->domainMigratePrepare2(dconn,
+                                             NULL, /* cookie */
+                                             0, /* cookielen */
+                                             NULL, /* uri */
+                                             &uri_out,
+                                             flags, dname,
+                                             resource, dom_xml) < 0)
+        /* domainMigratePrepare2 sets the error for us */
+        goto cleanup;
+
+    if (uri_out == NULL) {
+        qemudReportError(NULL, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+                         _("domainMigratePrepare2 did not set uri"));
+    }
+
+    if (doNativeMigrate(dom, vm, uri_out, flags, dname, resource) < 0)
+        goto finish;
+
+    retval = 0;
+
+finish:
+    dname = dname ? dname : dom->name;
+    ddomain = dconn->driver->domainMigrateFinish2
+        (dconn, dname, NULL, 0, uri_out, flags, retval);
+
+    if (ddomain)
+        virUnrefDomain(ddomain);
+
+cleanup:
+    return retval;
+}
+
+
 static int doPeer2PeerMigrate(virDomainPtr dom,
                               virDomainObjPtr vm,
                               const char *uri,
@@ -6705,9 +6759,9 @@ static int doPeer2PeerMigrate(virDomainPtr dom,
         return -1;
     }
     if (!VIR_DRV_SUPPORTS_FEATURE(dconn->driver, dconn,
-                                  VIR_DRV_FEATURE_MIGRATION_V2)) {
+                                  VIR_DRV_FEATURE_MIGRATION_P2P)) {
         qemudReportError(dom->conn, dom, NULL, VIR_ERR_OPERATION_FAILED, "%s",
-                         _("Destination libvirt does not support required migration protocol 2"));
+                         _("Destination libvirt does not support peer-to-peer migration protocol"));
         goto cleanup;
     }
 
@@ -6718,7 +6772,10 @@ static int doPeer2PeerMigrate(virDomainPtr dom,
         goto cleanup;
     }
 
-    ret = doTunnelMigrate(dom, dconn, vm, dom_xml, uri, flags, dname, resource);
+    if (flags & VIR_MIGRATE_TUNNELLED)
+        ret = doTunnelMigrate(dom, dconn, vm, dom_xml, uri, flags, dname, resource);
+    else
+        ret = doNonTunnelMigrate(dom, dconn, vm, dom_xml, uri, flags, dname, resource);
 
 cleanup:
     VIR_FREE(dom_xml);
index 76b896a3beb3fb9e08c7939410da9ebbf356fa6c..5273a115309234688aac51f7bb500c4ffd75989f 100644 (file)
@@ -455,8 +455,11 @@ static int
 xenUnifiedSupportsFeature (virConnectPtr conn ATTRIBUTE_UNUSED, int feature)
 {
     switch (feature) {
-    case VIR_DRV_FEATURE_MIGRATION_V1: return 1;
-    default: return 0;
+    case VIR_DRV_FEATURE_MIGRATION_V1:
+    case VIR_DRV_FEATURE_MIGRATION_DIRECT:
+        return 1;
+    default:
+        return 0;
     }
 }
 
index 49bdba9015622510999756b0439fc18ca55df520..27d215e40a507896ccf8100cce7a81a19ffb9da3 100644 (file)
@@ -4409,6 +4409,8 @@ xenDaemonDomainMigratePerform (virDomainPtr domain,
         strcpy (live, "1");
         flags &= ~VIR_MIGRATE_LIVE;
     }
+    /* XXX we could easily do tunnelled & peer2peer migration too
+       if we want to. support these... */
     if (flags != 0) {
         virXendError (conn, VIR_ERR_NO_SUPPORT,
                       "%s", _("xenDaemonDomainMigrate: unsupported flag"));
index 2222269b9735eccc7c0873f23633993ff54a4894..46c545469c86f66f4a3e72cb5d4657e3757b6fe1 100644 (file)
@@ -2462,6 +2462,8 @@ static const vshCmdInfo info_migrate[] = {
 
 static const vshCmdOptDef opts_migrate[] = {
     {"live", VSH_OT_BOOL, 0, gettext_noop("live migration")},
+    {"p2p", VSH_OT_BOOL, 0, gettext_noop("peer-2-peer migration")},
+    {"direct", VSH_OT_BOOL, 0, gettext_noop("direct 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")},
@@ -2478,8 +2480,6 @@ cmdMigrate (vshControl *ctl, const vshCmd *cmd)
     const char *migrateuri;
     const char *dname;
     int flags = 0, found, ret = FALSE;
-    virConnectPtr dconn = NULL;
-    virDomainPtr ddom = NULL;
 
     if (!vshConnectionUsability (ctl, ctl->conn, TRUE))
         return FALSE;
@@ -2499,40 +2499,41 @@ cmdMigrate (vshControl *ctl, const vshCmd *cmd)
 
     if (vshCommandOptBool (cmd, "live"))
         flags |= VIR_MIGRATE_LIVE;
-
+    if (vshCommandOptBool (cmd, "p2p"))
+        flags |= VIR_MIGRATE_PEER2PEER;
     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;
-    }
+    if ((flags & VIR_MIGRATE_PEER2PEER) ||
+        vshCommandOptBool (cmd, "direct")) {
+        /* For peer2peer migration or direct migration we only expect one URI
+         * a libvirt URI, or a hypervisor specific URI. */
 
-    /* Migrate. */
-    ddom = virDomainMigrate(dom, dconn, flags, dname, migrateuri, 0);
-    if (!ddom) goto done;
+        if (migrateuri != NULL) {
+            vshError(ctl, FALSE, "%s", _("migrate: Unexpected migrateuri for peer2peer/direct migration"));
+            goto done;
+        }
 
-    ret = TRUE;
+        if (virDomainMigrateToURI (dom, desturi, flags, dname, 0) == 0)
+            ret = TRUE;
+    } else {
+        /* For traditional live migration, connect to the destination host directly. */
+        virConnectPtr dconn = NULL;
+        virDomainPtr ddom = NULL;
+
+        dconn = virConnectOpenAuth (desturi, virConnectAuthPtrDefault, 0);
+        if (!dconn) goto done;
+
+        ddom = virDomainMigrate (dom, dconn, flags, dname, migrateuri, 0);
+        if (ddom) {
+            virDomainFree(ddom);
+            ret = TRUE;
+        }
+        virConnectClose (dconn);
+    }
 
  done:
     if (dom) virDomainFree (dom);
-    if (ddom) virDomainFree (ddom);
-    if (dconn) virConnectClose (dconn);
     return ret;
 }