This patch introduces a peer-to-peer migration protocol where the client
only invokes the virDomainMigratePerform method, expecting the source
host driver to do whatever is required to complete the entire migration
process.
This method is used by virsh whenever the hypervisor supports it and the
destination URI is not given.
In addition, this patch makes the dconn parameter to virDomainMigrate
optional. In this case, libvirt will try to use peer-to-peer migration
or, alternatively, will fall back to opening a temporary connection.
* src/driver.h: Remove feature flags.
* src/libvirt_internal.h: Add feature flags, and include
new VIR_DRV_FEATURE_MIGRATION_P2P indicating support for the
new migration protocol. Add VIR_MIGRATE_PEER2PEER private
flag passed to virMigratePerform.
* src/libvirt.c: Implement support for peer-to-peer migration.
* src/virsh.c: Let libvirt create the dconn where possible.
* src/xen/xen_driver.c: Advertise support for P2P migration
* src/xen/xend_internal.c: Accept VIR_MIGRATE_PEER2PEER.
---
Is there a reason why you shouldn't try a VIR_MIGRATE_PEER2PEER
if dconn is NULL, rather than having a separate flag?
Well it wasn't that simple, anyway this is what I meant.
I splitted out the non-fleshed-out tunnelled migration parts to
ease committing. However I've not tested this beyond compiling yet.
docs/libvirt-api.xml | 23 +++---
docs/libvirt-refs.xml | 120 +++++++++++++++++++-------------
src/driver.h | 19 -----
src/internal.h | 2 +
src/libvirt.c | 177 +++++++++++++++++++++++++++++++++++------------
src/libvirt_internal.h | 36 ++++++++++
src/xen/xen_driver.c | 7 ++-
src/xen/xend_internal.c | 5 ++
tools/virsh.c | 28 ++++---
9 files changed, 279 insertions(+), 138 deletions(-)
diff --git a/src/driver.h b/src/driver.h
index 6a3dcc2..893e98b 100644
--- a/src/driver.h
+++ b/src/driver.h
@@ -44,25 +44,6 @@ typedef enum {
VIR_DRV_OPEN_ERROR = -2,
} virDrvOpenStatus;
-/* Feature detection. This is a libvirt-private interface for determining
- * what features are supported by the driver.
- *
- * The remote driver passes features through to the real driver at the
- * remote end unmodified, except if you query a VIR_DRV_FEATURE_REMOTE*
- * feature.
- */
- /* Driver supports V1-style virDomainMigrate, ie. domainMigratePrepare/
- * domainMigratePerform/domainMigrateFinish.
- */
-#define VIR_DRV_FEATURE_MIGRATION_V1 1
-
- /* Driver is not local. */
-#define VIR_DRV_FEATURE_REMOTE 2
-
- /* Driver supports V2-style virDomainMigrate, ie. domainMigratePrepare2/
- * domainMigratePerform/domainMigrateFinish2.
- */
-#define VIR_DRV_FEATURE_MIGRATION_V2 3
/* Internal feature-detection macro. Don't call drv->supports_feature
* directly, because it may be NULL, use this macro instead.
diff --git a/src/internal.h b/src/internal.h
index 8fa579c..bd1cfe6 100644
--- a/src/internal.h
+++ b/src/internal.h
@@ -24,6 +24,8 @@
#include "libvirt/libvirt.h"
#include "libvirt/virterror.h"
+#include "libvirt_internal.h"
+
/* On architectures which lack these limits, define them (ie. Cygwin).
* Note that the libvirt code should be robust enough to handle the
* case where actual value is longer than these limits (eg. by setting
diff --git a/src/libvirt.c b/src/libvirt.c
index dacf8c4..f0ec640 100644
--- a/src/libvirt.c
+++ b/src/libvirt.c
@@ -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,6 +3043,39 @@ virDomainMigrateVersion2 (virDomainPtr domain,
return ddomain;
}
+
+ /*
+ * This is sort of a migration v3
+ *
+ * This performs a peer-to-peer migration where source host
+ * does all the communication with the destination host.
+ */
+static int
+virDomainMigrateP2P (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.
+ */
+ flags |= VIR_MIGRATE_PEER2PEER;
+ return domain->conn->driver->domainMigratePerform(domain,
+ NULL, /* cookie */
+ 0, /* cookielen */
+ uri,
+ flags,
+ dname,
+ bandwidth);
+}
+
+
/**
* virDomainMigrate:
* @domain: a domain object
@@ -3057,22 +3089,23 @@ virDomainMigrateVersion2 (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_LIVE Do not pause the VM during migration
*
* 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.
+ * dconn can be NULL. Then the uri parameter will then be a
+ * valid libvirt connection URI, by which the source libvirt
+ * driver can connect to the destination libvirt.
+ *
+ * If the dconn argument is 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. It is typically only necessary
+ * 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,
@@ -3089,8 +3122,10 @@ virDomainMigrateVersion2 (virDomainPtr domain,
* different types of hypervisor.
*
* Returns the new domain object if the migration was successful,
- * or NULL in case of error. Note that the new domain object
- * exists in the scope of the destination connection (dconn).
+ * or NULL in case of error. Note that the new domain object
+ * exists in the scope of the destination connection (dconn) if
+ * one was provided; else the migrated domain is returned
+ * with an added reference.
*/
virDomainPtr
virDomainMigrate (virDomainPtr domain,
@@ -3101,6 +3136,9 @@ virDomainMigrate (virDomainPtr domain,
unsigned long bandwidth)
{
virDomainPtr ddomain = NULL;
+ int protocol = 0;
+ int free_conn = 0;
+
DEBUG("domain=%p, dconn=%p, flags=%lu, dname=%s, uri=%s, bandwidth=%lu",
domain, dconn, flags, NULLSTR(dname), NULLSTR(uri), bandwidth);
@@ -3109,51 +3147,103 @@ virDomainMigrate (virDomainPtr domain,
/* First checkout the source */
if (!VIR_IS_CONNECTED_DOMAIN (domain)) {
virLibDomainError(NULL, VIR_ERR_INVALID_DOMAIN, __FUNCTION__);
- return NULL;
+ goto cleanup;
}
if (domain->conn->flags & VIR_CONNECT_RO) {
virLibDomainError(domain, VIR_ERR_OPERATION_DENIED, __FUNCTION__);
- goto error;
+ goto cleanup;
}
- /* 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;
- }
+ /* Now see if we have to open the destination ourselves... */
+ if (!dconn) {
+ if (!uri) {
+ virLibConnError (domain->conn, VIR_ERR_INVALID_CONN, __FUNCTION__);
+ goto cleanup;
+ }
+
+ if (VIR_DRV_SUPPORTS_FEATURE (domain->conn->driver, domain->conn,
+ VIR_DRV_FEATURE_MIGRATION_P2P))
+ protocol = VIR_DRV_FEATURE_MIGRATION_P2P;
+ else {
+ dconn = virConnectOpenAuth (uri, virConnectAuthPtrDefault, 0);
+ uri = NULL;
+ if (!dconn)
+ goto cleanup;
+ free_conn = 1;
+ }
+ }
+
+ /* ... and check it out. */
+ if (dconn) {
+ if (!VIR_IS_CONNECT (dconn)) {
+ virLibConnError (domain->conn, VIR_ERR_INVALID_CONN, __FUNCTION__);
+ goto cleanup;
+ }
+ if (dconn->flags & VIR_CONNECT_RO) {
+ /* NB, deliberately report error against source object, not dest */
+ virLibDomainError (domain, VIR_ERR_OPERATION_DENIED, __FUNCTION__);
+ goto cleanup;
+ }
- /* 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))
+ /* Choose a protocol. Some features are not supported by
+ * all protocols. */
+ if (!uri &&
+ VIR_DRV_SUPPORTS_FEATURE (domain->conn->driver, domain->conn,
+ VIR_DRV_FEATURE_MIGRATION_P2P))
+ protocol = VIR_DRV_FEATURE_MIGRATION_P2P;
+
+ /* For these, migration has to be supported by both drivers. */
+ else 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))
+ protocol = VIR_DRV_FEATURE_MIGRATION_V1;
+
+ 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))
+ protocol = VIR_DRV_FEATURE_MIGRATION_V2;
+ }
+
+ if (protocol == VIR_DRV_FEATURE_MIGRATION_P2P) {
+ char *duri = NULL;
+ if (!uri)
+ uri = duri = virConnectGetURI(dconn);
+
+ if (virDomainMigrateP2P (domain, flags, dname, uri, bandwidth) < 0) {
+ VIR_FREE(duri);
+ goto cleanup;
+ }
+ VIR_FREE(duri);
+
+ if (dconn)
+ ddomain = virDomainLookupByName (dconn, dname ? dname : domain->name);
+ else {
+ ddomain = domain;
+ virDomainRef (domain);
+ }
+ } else if (protocol == VIR_DRV_FEATURE_MIGRATION_V1)
+ ddomain = virDomainMigrateVersion1 (domain, dconn, flags, dname, uri,
bandwidth);
+ else if (protocol == VIR_DRV_FEATURE_MIGRATION_V2)
ddomain = virDomainMigrateVersion2 (domain, dconn, flags, dname, uri,
bandwidth);
else {
virLibConnError (domain->conn, VIR_ERR_NO_SUPPORT, __FUNCTION__);
- goto error;
+ goto cleanup;
}
- if (ddomain == NULL)
- goto error;
-
- return ddomain;
-error:
+cleanup:
/* Copy to connection error object for back compatability */
- virSetConnError(domain->conn);
- return NULL;
+ if (ddomain == NULL)
+ virSetConnError(domain->conn);
+ if (free_conn)
+ virConnectClose(dconn);
+ return ddomain;
}
+
+
/*
* Not for public use. This function is part of the internal
* implementation of migration in the remote case.
diff --git a/src/libvirt_internal.h b/src/libvirt_internal.h
index 5913798..024c9dd 100644
--- a/src/libvirt_internal.h
+++ b/src/libvirt_internal.h
@@ -17,6 +17,7 @@
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
+ * NB: This file is private, but still contributes to the ABI. Append only.
*/
#ifndef __LIBVIRT_H_
@@ -31,6 +32,41 @@ int virStateReload(void);
int virStateActive(void);
#endif
+/* Feature detection. This is a libvirt-private interface for determining
+ * what features are supported by the driver.
+ *
+ * The remote driver passes features through to the real driver at the
+ * remote end unmodified, except if you query a VIR_DRV_FEATURE_REMOTE*
+ * feature.
+ *
+ */
+enum {
+ /* Driver supports V1-style virDomainMigrate, ie. domainMigratePrepare/
+ * domainMigratePerform/domainMigrateFinish.
+ */
+ VIR_DRV_FEATURE_MIGRATION_V1 = 1,
+
+ /* Driver is not local. */
+ VIR_DRV_FEATURE_REMOTE = 2,
+
+ /* Driver supports V2-style virDomainMigrate, ie. domainMigratePrepare2/
+ * domainMigratePerform/domainMigrateFinish2.
+ */
+ VIR_DRV_FEATURE_MIGRATION_V2 = 3,
+
+ /* Driver supports peer-to-peer virDomainMigrate ie. the source host
+ * will do all the prepare/perform/finish steps directly
+ */
+ VIR_DRV_FEATURE_MIGRATION_P2P = 4,
+};
+
+enum {
+ /* Driver was requested peer-to-peer virDomainMigrate. This is
+ * needed because the perform callback is shared.
+ */
+ VIR_MIGRATE_PEER2PEER = (1 << 16),
+} virDomainInternalMigrateFlags;
+
int virDrvSupportsFeature (virConnectPtr conn, int feature);
int virDomainMigratePrepare (virConnectPtr dconn,
diff --git a/src/xen/xen_driver.c b/src/xen/xen_driver.c
index 9e1bc32..bc6fdfd 100644
--- a/src/xen/xen_driver.c
+++ b/src/xen/xen_driver.c
@@ -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_P2P:
+ return 1;
+ default:
+ return 0;
}
}
diff --git a/src/xen/xend_internal.c b/src/xen/xend_internal.c
index 49bdba9..e9b1de6 100644
--- a/src/xen/xend_internal.c
+++ b/src/xen/xend_internal.c
@@ -4409,6 +4409,11 @@ xenDaemonDomainMigratePerform (virDomainPtr domain,
strcpy (live, "1");
flags &= ~VIR_MIGRATE_LIVE;
}
+ /* Trivially support this in Xen, since XenD on dest is always
+ * ready to accept incoming migration */
+ if ((flags & VIR_MIGRATE_PEER2PEER)) {
+ flags &= ~VIR_MIGRATE_PEER2PEER;
+ }
if (flags != 0) {
virXendError (conn, VIR_ERR_NO_SUPPORT,
"%s", _("xenDaemonDomainMigrate: unsupported
flag"));
diff --git a/tools/virsh.c b/tools/virsh.c
index 3482389..a3d92df 100644
--- a/tools/virsh.c
+++ b/tools/virsh.c
@@ -2472,13 +2472,13 @@ static const vshCmdOptDef opts_migrate[] = {
static int
cmdMigrate (vshControl *ctl, const vshCmd *cmd)
{
+ virConnectPtr dconn = NULL;
virDomainPtr dom = NULL;
+ virDomainPtr ddom = NULL;
const char *desturi;
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,20 +2499,24 @@ 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;
-
- /* Migrate. */
- ddom = virDomainMigrate (dom, dconn, flags, dname, migrateuri, 0);
- if (!ddom) goto done;
+ if (migrateuri == NULL) {
+ /* Let libvirt handle opening the connection if necessary. */
+ ddom = virDomainMigrate (dom, NULL, flags, dname, desturi, 0);
+ } else {
+ dconn = virConnectOpenAuth (desturi, virConnectAuthPtrDefault, 0);
+ if (!dconn) goto done;
+ ddom = virDomainMigrate (dom, dconn, flags, dname, migrateuri, 0);
+ }
- ret = TRUE;
+ if (ddom) {
+ virDomainFree(ddom);
+ ret = TRUE;
+ }
+ if (dconn)
+ virConnectClose (dconn);
done:
if (dom) virDomainFree (dom);
- if (ddom) virDomainFree (ddom);
- if (dconn) virConnectClose (dconn);
return ret;
}
--
1.6.2.5