From 6e16575a3729691b2a60f5efa81ad44851f20fcf Mon Sep 17 00:00:00 2001 From: Chris Lalancette Date: Wed, 30 Sep 2009 12:51:54 +0200 Subject: [PATCH] Tunnelled migration. 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 --- .x-sc_avoid_write | 1 + 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 +- 26 files changed, 1035 insertions(+), 64 deletions(-) diff --git a/.x-sc_avoid_write b/.x-sc_avoid_write index 9e37248b24..111fb28598 100644 --- a/.x-sc_avoid_write +++ b/.x-sc_avoid_write @@ -1,4 +1,5 @@ ^src/libvirt\.c$ +^src/qemu/qemu_driver\.c$ ^src/util/util\.c$ ^src/xen/xend_internal\.c$ ^daemon/libvirtd.c$ diff --git a/daemon/remote.c b/daemon/remote.c index ba97379ee1..5369d0a8ae 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__) @@ -1492,6 +1493,49 @@ remoteDispatchDomainMigrateFinish2 (struct qemud_server *server ATTRIBUTE_UNUSED return 0; } +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, diff --git a/daemon/remote_dispatch_args.h b/daemon/remote_dispatch_args.h index 95f668ad00..aceead10f9 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 16e8bb097c..9afd2c715b 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 6b5df80657..bb13f4c276 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 70a7efc5a9..b00619fc26 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 4e63e48980..60be41b4bc 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 6a3dcc2891..c926614942 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 ec0cc146a9..e063b46318 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 dacf8c4ed4..1ed4804651 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 (dconn->flags & VIR_CONNECT_RO) { - /* NB, deliberately report error against source object, not dest */ - virLibDomainError (domain, VIR_ERR_OPERATION_DENIED, __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; + } - /* 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); + ddomain = virDomainMigrateTunnelled(domain, 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 5913798737..8f1ac3d259 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 b699fb2187..49bbf9660a 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 0a9cc285ec..5fb4105a0b 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 0ca1e9b0e9..9bcd5c3b5a 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 d577be1d2e..f64ad1e54b 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 fbb8982ed8..ef465ed374 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 70e9c70272..95e672bd7c 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 731b2139ec..25aaf329e1 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 1d2d242148..8c61712d73 100644 --- a/src/remote/remote_protocol.c +++ b/src/remote/remote_protocol.c @@ -2697,6 +2697,23 @@ xdr_remote_secret_lookup_by_usage_ret (XDR *xdrs, remote_secret_lookup_by_usage_ return TRUE; } +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 64da9fa88b..245f41132e 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 6b0a784287..537a838360 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 cb48f648a8..f57c92a4e8 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 f0d5fd4640..9a7fe42f26 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 72220e10fd..4f439012ee 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 9e1bc32a1b..76b896a3be 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 3482389e72..2222269b97 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;