mirror of https://gitee.com/openkylin/libvirt.git
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 <clalance@redhat.com>
This commit is contained in:
parent
3bd4c7ba16
commit
6e16575a37
|
@ -1,4 +1,5 @@
|
|||
^src/libvirt\.c$
|
||||
^src/qemu/qemu_driver\.c$
|
||||
^src/util/util\.c$
|
||||
^src/xen/xend_internal\.c$
|
||||
^daemon/libvirtd.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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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",
|
||||
}
|
||||
|
|
|
@ -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. */
|
||||
|
|
11
src/driver.h
11
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
|
||||
|
|
|
@ -3274,6 +3274,7 @@ static virDriver esxDriver = {
|
|||
NULL, /* nodeDeviceDettach */
|
||||
NULL, /* nodeDeviceReAttach */
|
||||
NULL, /* nodeDeviceReset */
|
||||
NULL, /* domainMigratePrepareTunnel */
|
||||
};
|
||||
|
||||
|
||||
|
|
181
src/libvirt.c
181
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -239,6 +239,7 @@ virDomainMigratePerform;
|
|||
virDomainMigrateFinish;
|
||||
virDomainMigratePrepare2;
|
||||
virDomainMigrateFinish2;
|
||||
virDomainMigratePrepareTunnel;
|
||||
virRegisterDriver;
|
||||
virRegisterInterfaceDriver;
|
||||
virRegisterNetworkDriver;
|
||||
|
|
|
@ -2147,6 +2147,7 @@ static virDriver lxcDriver = {
|
|||
NULL, /* nodeDeviceDettach */
|
||||
NULL, /* nodeDeviceReAttach */
|
||||
NULL, /* nodeDeviceReset */
|
||||
NULL, /* domainMigratePrepareTunnel */
|
||||
};
|
||||
|
||||
static virStateDriver lxcStateDriver = {
|
||||
|
|
|
@ -787,6 +787,7 @@ static virDriver oneDriver = {
|
|||
NULL, /* nodeDeviceDettach; */
|
||||
NULL, /* nodeDeviceReAttach; */
|
||||
NULL, /* nodeDeviceReset; */
|
||||
NULL, /* domainMigratePrepareTunnel */
|
||||
};
|
||||
|
||||
static virStateDriver oneStateDriver = {
|
||||
|
|
|
@ -1432,6 +1432,7 @@ static virDriver openvzDriver = {
|
|||
NULL, /* nodeDeviceDettach */
|
||||
NULL, /* nodeDeviceReAttach */
|
||||
NULL, /* nodeDeviceReset */
|
||||
NULL, /* domainMigratePrepareTunnel */
|
||||
};
|
||||
|
||||
int openvzRegister(void) {
|
||||
|
|
|
@ -1377,6 +1377,7 @@ virDriver phypDriver = {
|
|||
NULL, /* nodeDeviceDettach */
|
||||
NULL, /* nodeDeviceReAttach */
|
||||
NULL, /* nodeDeviceReset */
|
||||
NULL, /* domainMigratePrepareTunnel */
|
||||
};
|
||||
|
||||
int
|
||||
|
|
|
@ -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 */
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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 ();
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -4267,6 +4267,7 @@ static virDriver testDriver = {
|
|||
NULL, /* nodeDeviceDettach */
|
||||
NULL, /* nodeDeviceReAttach */
|
||||
NULL, /* nodeDeviceReset */
|
||||
NULL, /* domainMigratePrepareTunnel */
|
||||
};
|
||||
|
||||
static virNetworkDriver testNetworkDriver = {
|
||||
|
|
|
@ -1861,6 +1861,7 @@ static virDriver umlDriver = {
|
|||
NULL, /* nodeDeviceDettach */
|
||||
NULL, /* nodeDeviceReAttach */
|
||||
NULL, /* nodeDeviceReset */
|
||||
NULL, /* domainMigratePrepareTunnel */
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -6467,7 +6467,7 @@ virDriver NAME(Driver) = {
|
|||
NULL, /* nodeDeviceDettach */
|
||||
NULL, /* nodeDeviceReAttach */
|
||||
NULL, /* nodeDeviceReset */
|
||||
|
||||
NULL, /* domainMigratePrepareTunnel */
|
||||
};
|
||||
|
||||
virNetworkDriver NAME(NetworkDriver) = {
|
||||
|
|
|
@ -1722,6 +1722,7 @@ static virDriver xenUnifiedDriver = {
|
|||
xenUnifiedNodeDeviceDettach, /* nodeDeviceDettach */
|
||||
xenUnifiedNodeDeviceReAttach, /* nodeDeviceReAttach */
|
||||
xenUnifiedNodeDeviceReset, /* nodeDeviceReset */
|
||||
NULL, /* domainMigratePrepareTunnel */
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue