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(a)redhat.com>
---
daemon/remote.c | 44 +++
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 | 711 +++++++++++++++++++++++++++++++++--
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, 1034 insertions(+), 64 deletions(-)
diff --git a/daemon/remote.c b/daemon/remote.c
index ba97379..5369d0a 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,49 @@ 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;
+ }
+
+ 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 4cc19ec..25660f7 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 70e9c70..95e672b 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,209 @@ 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;
+ int retval = -1;
+ ssize_t bytes;
+ char buffer[65536];
+ virStreamPtr st;
+ char *dom_xml = NULL;
+ char *unixfile = NULL;
+ 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
+ */
+
+ dconn = virConnectOpen(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 = qemuMonitorMigrateToUnix(vm, 1, unixfile);
+ else if (qemuCmdFlags & QEMUD_CMD_FLAG_MIGRATE_QEMU_EXEC) {
+ const char *args[] = { "nc", "-U", unixfile, NULL };
+ internalret = qemuMonitorMigrateToCommand(vm, 1, args, "/dev/null");
+ }
+ 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) {
+ 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 +6661,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, 0, uribits->server, uribits->port) < 0)
- goto cleanup;
+ if (qemuMonitorMigrateToHost(vm, 0, 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 +7003,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