mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-11-01 10:51:12 +00:00
f9d6b01262
Signed-off-by: Michal Privoznik <mprivozn@redhat.com> Reviewed-by: Ján Tomko <jtomko@redhat.com>
1424 lines
43 KiB
C
1424 lines
43 KiB
C
/*
|
|
* libxl_migration.c: methods for handling migration with libxenlight
|
|
*
|
|
* Copyright (C) 2014-2015 SUSE LINUX Products GmbH, Nuernberg, Germany.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see
|
|
* <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include "internal.h"
|
|
#include "virlog.h"
|
|
#include "virerror.h"
|
|
#include "virconf.h"
|
|
#include "datatypes.h"
|
|
#include "virfile.h"
|
|
#include "viralloc.h"
|
|
#include "viruuid.h"
|
|
#include "vircommand.h"
|
|
#include "virstring.h"
|
|
#include "virobject.h"
|
|
#include "virthread.h"
|
|
#include "virhook.h"
|
|
#include "rpc/virnetsocket.h"
|
|
#include "libxl_domain.h"
|
|
#include "libxl_driver.h"
|
|
#include "libxl_conf.h"
|
|
#include "libxl_migration.h"
|
|
#include "locking/domain_lock.h"
|
|
#include "virtypedparam.h"
|
|
#include "virfdstream.h"
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_LIBXL
|
|
|
|
VIR_LOG_INIT("libxl.libxl_migration");
|
|
|
|
typedef struct _libxlMigrationCookie libxlMigrationCookie;
|
|
typedef libxlMigrationCookie *libxlMigrationCookiePtr;
|
|
struct _libxlMigrationCookie {
|
|
/* Host properties */
|
|
char *srcHostname;
|
|
uint32_t xenMigStreamVer;
|
|
|
|
/* Guest properties */
|
|
unsigned char uuid[VIR_UUID_BUFLEN];
|
|
char *name;
|
|
};
|
|
|
|
typedef struct _libxlMigrationDstArgs {
|
|
virObject parent;
|
|
|
|
int recvfd;
|
|
virConnectPtr conn;
|
|
virDomainObjPtr vm;
|
|
unsigned int flags;
|
|
libxlMigrationCookiePtr migcookie;
|
|
|
|
/* for freeing listen sockets */
|
|
virNetSocketPtr *socks;
|
|
size_t nsocks;
|
|
} libxlMigrationDstArgs;
|
|
|
|
static virClassPtr libxlMigrationDstArgsClass;
|
|
|
|
|
|
static void
|
|
libxlMigrationCookieFree(libxlMigrationCookiePtr mig)
|
|
{
|
|
if (!mig)
|
|
return;
|
|
|
|
VIR_FREE(mig->srcHostname);
|
|
VIR_FREE(mig->name);
|
|
VIR_FREE(mig);
|
|
}
|
|
|
|
static libxlMigrationCookiePtr
|
|
libxlMigrationCookieNew(virDomainObjPtr dom)
|
|
{
|
|
libxlMigrationCookiePtr mig = NULL;
|
|
|
|
if (VIR_ALLOC(mig) < 0)
|
|
goto error;
|
|
|
|
mig->name = g_strdup(dom->def->name);
|
|
|
|
memcpy(mig->uuid, dom->def->uuid, VIR_UUID_BUFLEN);
|
|
|
|
if (!(mig->srcHostname = virGetHostname()))
|
|
goto error;
|
|
|
|
mig->xenMigStreamVer = LIBXL_SAVE_VERSION;
|
|
|
|
return mig;
|
|
|
|
error:
|
|
libxlMigrationCookieFree(mig);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static int
|
|
libxlMigrationBakeCookie(libxlMigrationCookiePtr mig,
|
|
char **cookieout,
|
|
int *cookieoutlen)
|
|
{
|
|
virBuffer buf = VIR_BUFFER_INITIALIZER;
|
|
char uuidstr[VIR_UUID_STRING_BUFLEN];
|
|
|
|
if (!cookieout || !cookieoutlen)
|
|
return 0;
|
|
|
|
*cookieoutlen = 0;
|
|
virUUIDFormat(mig->uuid, uuidstr);
|
|
|
|
virBufferAddLit(&buf, "<libxl-migration>\n");
|
|
virBufferAdjustIndent(&buf, 2);
|
|
virBufferEscapeString(&buf, "<name>%s</name>\n", mig->name);
|
|
virBufferAsprintf(&buf, "<uuid>%s</uuid>\n", uuidstr);
|
|
virBufferEscapeString(&buf, "<hostname>%s</hostname>\n", mig->srcHostname);
|
|
virBufferAsprintf(&buf, "<migration-stream-version>%u</migration-stream-version>\n", mig->xenMigStreamVer);
|
|
virBufferAdjustIndent(&buf, -2);
|
|
virBufferAddLit(&buf, "</libxl-migration>\n");
|
|
|
|
*cookieout = virBufferContentAndReset(&buf);
|
|
*cookieoutlen = strlen(*cookieout) + 1;
|
|
|
|
VIR_DEBUG("cookielen=%d cookie=%s", *cookieoutlen, *cookieout);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
libxlMigrationEatCookie(const char *cookiein,
|
|
int cookieinlen,
|
|
libxlMigrationCookiePtr *migout)
|
|
{
|
|
libxlMigrationCookiePtr mig = NULL;
|
|
xmlDocPtr doc = NULL;
|
|
xmlXPathContextPtr ctxt = NULL;
|
|
char *uuidstr = NULL;
|
|
int ret = -1;
|
|
|
|
/*
|
|
* Assume a legacy (V1) migration stream if request came from a
|
|
* source host without cookie support, and hence no way to
|
|
* specify a stream version.
|
|
*/
|
|
if (!cookiein || !cookieinlen) {
|
|
if (VIR_ALLOC(mig) < 0)
|
|
return -1;
|
|
|
|
mig->xenMigStreamVer = 1;
|
|
*migout = mig;
|
|
return 0;
|
|
}
|
|
|
|
if (cookiein[cookieinlen-1] != '\0') {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Migration cookie was not NULL terminated"));
|
|
return -1;
|
|
}
|
|
|
|
VIR_DEBUG("cookielen=%d cookie='%s'", cookieinlen, NULLSTR(cookiein));
|
|
|
|
if (VIR_ALLOC(mig) < 0)
|
|
return -1;
|
|
|
|
if (!(doc = virXMLParseStringCtxt(cookiein,
|
|
_("(libxl_migration_cookie)"),
|
|
&ctxt)))
|
|
goto error;
|
|
|
|
/* Extract domain name */
|
|
if (!(mig->name = virXPathString("string(./name[1])", ctxt))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("missing name element in migration data"));
|
|
goto error;
|
|
}
|
|
|
|
/* Extract domain uuid */
|
|
uuidstr = virXPathString("string(./uuid[1])", ctxt);
|
|
if (!uuidstr) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("missing uuid element in migration data"));
|
|
goto error;
|
|
}
|
|
if (virUUIDParse(uuidstr, mig->uuid) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("malformed uuid element"));
|
|
goto error;
|
|
}
|
|
|
|
if (virXPathUInt("string(./migration-stream-version[1])",
|
|
ctxt, &mig->xenMigStreamVer) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("missing Xen migration stream version"));
|
|
goto error;
|
|
}
|
|
|
|
*migout = mig;
|
|
ret = 0;
|
|
goto cleanup;
|
|
|
|
error:
|
|
libxlMigrationCookieFree(mig);
|
|
|
|
cleanup:
|
|
VIR_FREE(uuidstr);
|
|
xmlXPathFreeContext(ctxt);
|
|
xmlFreeDoc(doc);
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
libxlMigrationDstArgsDispose(void *obj)
|
|
{
|
|
libxlMigrationDstArgs *args = obj;
|
|
|
|
libxlMigrationCookieFree(args->migcookie);
|
|
VIR_FREE(args->socks);
|
|
virObjectUnref(args->conn);
|
|
virObjectUnref(args->vm);
|
|
}
|
|
|
|
static int
|
|
libxlMigrationDstArgsOnceInit(void)
|
|
{
|
|
if (!VIR_CLASS_NEW(libxlMigrationDstArgs, virClassForObject()))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
VIR_ONCE_GLOBAL_INIT(libxlMigrationDstArgs);
|
|
|
|
static void
|
|
libxlDoMigrateDstReceive(void *opaque)
|
|
{
|
|
libxlMigrationDstArgs *args = opaque;
|
|
virDomainObjPtr vm = args->vm;
|
|
virNetSocketPtr *socks = args->socks;
|
|
size_t nsocks = args->nsocks;
|
|
libxlDriverPrivatePtr driver = args->conn->privateData;
|
|
int recvfd = args->recvfd;
|
|
size_t i;
|
|
|
|
virObjectRef(vm);
|
|
|
|
/*
|
|
* Always start the domain paused. If needed, unpause in the
|
|
* finish phase, after transfer of the domain is complete.
|
|
* Errors and cleanup are also handled in the finish phase.
|
|
*/
|
|
libxlDomainStartRestore(driver, vm, true, recvfd,
|
|
args->migcookie->xenMigStreamVer);
|
|
|
|
/* Remove all listen socks from event handler, and close them. */
|
|
for (i = 0; i < nsocks; i++) {
|
|
virNetSocketRemoveIOCallback(socks[i]);
|
|
virNetSocketClose(socks[i]);
|
|
virObjectUnref(socks[i]);
|
|
socks[i] = NULL;
|
|
}
|
|
args->nsocks = 0;
|
|
VIR_FORCE_CLOSE(recvfd);
|
|
virObjectUnref(args);
|
|
virDomainObjEndAPI(&vm);
|
|
}
|
|
|
|
|
|
static void
|
|
libxlMigrateDstReceive(virNetSocketPtr sock,
|
|
int events G_GNUC_UNUSED,
|
|
void *opaque)
|
|
{
|
|
libxlMigrationDstArgs *args = opaque;
|
|
virNetSocketPtr *socks = args->socks;
|
|
size_t nsocks = args->nsocks;
|
|
libxlDomainObjPrivatePtr priv = args->vm->privateData;
|
|
virNetSocketPtr client_sock;
|
|
int recvfd = -1;
|
|
size_t i;
|
|
|
|
/* Accept migration connection */
|
|
if (virNetSocketAccept(sock, &client_sock) < 0 || !client_sock) {
|
|
virReportError(VIR_ERR_OPERATION_FAILED, "%s",
|
|
_("Failed to accept migration connection"));
|
|
goto fail;
|
|
}
|
|
VIR_DEBUG("Accepted migration connection."
|
|
" Spawning thread to process migration data");
|
|
recvfd = virNetSocketDupFD(client_sock, true);
|
|
virObjectUnref(client_sock);
|
|
|
|
/*
|
|
* Avoid blocking the event loop. Start a thread to receive
|
|
* the migration data
|
|
*/
|
|
args->recvfd = recvfd;
|
|
VIR_FREE(priv->migrationDstReceiveThr);
|
|
if (VIR_ALLOC(priv->migrationDstReceiveThr) < 0)
|
|
goto fail;
|
|
if (virThreadCreate(priv->migrationDstReceiveThr, true,
|
|
libxlDoMigrateDstReceive, args) < 0) {
|
|
virReportError(VIR_ERR_OPERATION_FAILED, "%s",
|
|
_("Failed to create thread for receiving migration data"));
|
|
goto fail;
|
|
}
|
|
|
|
return;
|
|
|
|
fail:
|
|
/* Remove all listen socks from event handler, and close them. */
|
|
for (i = 0; i < nsocks; i++) {
|
|
virNetSocketUpdateIOCallback(socks[i], 0);
|
|
virNetSocketRemoveIOCallback(socks[i]);
|
|
virNetSocketClose(socks[i]);
|
|
socks[i] = NULL;
|
|
}
|
|
args->nsocks = 0;
|
|
VIR_FORCE_CLOSE(recvfd);
|
|
virObjectUnref(args);
|
|
}
|
|
|
|
static int
|
|
libxlDoMigrateSrcSend(libxlDriverPrivatePtr driver,
|
|
virDomainObjPtr vm,
|
|
unsigned long flags,
|
|
int sockfd)
|
|
{
|
|
libxlDriverConfigPtr cfg = libxlDriverConfigGet(driver);
|
|
int xl_flags = 0;
|
|
int ret;
|
|
|
|
if (flags & VIR_MIGRATE_LIVE)
|
|
xl_flags = LIBXL_SUSPEND_LIVE;
|
|
|
|
ret = libxl_domain_suspend(cfg->ctx, vm->def->id, sockfd,
|
|
xl_flags, NULL);
|
|
if (ret != 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Failed to send migration data to destination host"));
|
|
ret = -1;
|
|
}
|
|
|
|
virObjectUnref(cfg);
|
|
return ret;
|
|
}
|
|
|
|
static bool
|
|
libxlDomainMigrationIsAllowed(virDomainDefPtr def)
|
|
{
|
|
/* Migration is not allowed if definition contains any hostdevs */
|
|
if (def->nhostdevs > 0) {
|
|
virReportError(VIR_ERR_OPERATION_INVALID, "%s",
|
|
_("domain has assigned host devices"));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
char *
|
|
libxlDomainMigrationSrcBegin(virConnectPtr conn,
|
|
virDomainObjPtr vm,
|
|
const char *xmlin,
|
|
char **cookieout,
|
|
int *cookieoutlen)
|
|
{
|
|
libxlDriverPrivatePtr driver = conn->privateData;
|
|
libxlDriverConfigPtr cfg = libxlDriverConfigGet(driver);
|
|
libxlMigrationCookiePtr mig = NULL;
|
|
virDomainDefPtr tmpdef = NULL;
|
|
virDomainDefPtr def;
|
|
char *xml = NULL;
|
|
|
|
/*
|
|
* In the case of successful migration, a job is started here and
|
|
* terminated in the confirm phase. Errors in the begin or perform
|
|
* phase will also terminate the job.
|
|
*/
|
|
if (libxlDomainObjBeginJob(driver, vm, LIBXL_JOB_MODIFY) < 0)
|
|
goto cleanup;
|
|
|
|
if (!(mig = libxlMigrationCookieNew(vm)))
|
|
goto endjob;
|
|
|
|
if (libxlMigrationBakeCookie(mig, cookieout, cookieoutlen) < 0)
|
|
goto endjob;
|
|
|
|
if (xmlin) {
|
|
if (!(tmpdef = virDomainDefParseString(xmlin, cfg->caps,
|
|
driver->xmlopt,
|
|
NULL,
|
|
VIR_DOMAIN_DEF_PARSE_INACTIVE |
|
|
VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE)))
|
|
goto endjob;
|
|
|
|
if (!libxlDomainDefCheckABIStability(driver, vm->def, tmpdef))
|
|
goto endjob;
|
|
|
|
def = tmpdef;
|
|
} else {
|
|
def = vm->def;
|
|
}
|
|
|
|
if (!libxlDomainMigrationIsAllowed(def))
|
|
goto endjob;
|
|
|
|
xml = virDomainDefFormat(def, cfg->caps, VIR_DOMAIN_DEF_FORMAT_SECURE);
|
|
/* Valid xml means success! EndJob in the confirm phase */
|
|
if (xml)
|
|
goto cleanup;
|
|
|
|
endjob:
|
|
libxlDomainObjEndJob(driver, vm);
|
|
|
|
cleanup:
|
|
libxlMigrationCookieFree(mig);
|
|
virDomainDefFree(tmpdef);
|
|
virObjectUnref(cfg);
|
|
return xml;
|
|
}
|
|
|
|
virDomainDefPtr
|
|
libxlDomainMigrationDstPrepareDef(libxlDriverPrivatePtr driver,
|
|
const char *dom_xml,
|
|
const char *dname)
|
|
{
|
|
libxlDriverConfigPtr cfg = libxlDriverConfigGet(driver);
|
|
virDomainDefPtr def;
|
|
char *name = NULL;
|
|
|
|
if (!dom_xml) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("no domain XML passed"));
|
|
return NULL;
|
|
}
|
|
|
|
if (!(def = virDomainDefParseString(dom_xml, cfg->caps, driver->xmlopt,
|
|
NULL,
|
|
VIR_DOMAIN_DEF_PARSE_INACTIVE |
|
|
VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE)))
|
|
goto cleanup;
|
|
|
|
if (dname) {
|
|
name = def->name;
|
|
def->name = g_strdup(dname);
|
|
}
|
|
|
|
cleanup:
|
|
virObjectUnref(cfg);
|
|
VIR_FREE(name);
|
|
return def;
|
|
}
|
|
|
|
static int
|
|
libxlDomainMigrationPrepareAny(virConnectPtr dconn,
|
|
virDomainDefPtr *def,
|
|
const char *cookiein,
|
|
int cookieinlen,
|
|
libxlMigrationCookiePtr *mig,
|
|
char **xmlout,
|
|
bool *taint_hook)
|
|
{
|
|
libxlDriverPrivatePtr driver = dconn->privateData;
|
|
libxlDriverConfigPtr cfg = libxlDriverConfigGet(driver);
|
|
|
|
if (libxlMigrationEatCookie(cookiein, cookieinlen, mig) < 0)
|
|
return -1;
|
|
|
|
if ((*mig)->xenMigStreamVer > LIBXL_SAVE_VERSION) {
|
|
virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
|
|
_("Xen migration stream version '%d' is not supported on this host"),
|
|
(*mig)->xenMigStreamVer);
|
|
return -1;
|
|
}
|
|
|
|
/* Let migration hook filter domain XML */
|
|
if (virHookPresent(VIR_HOOK_DRIVER_LIBXL)) {
|
|
char *xml;
|
|
int hookret;
|
|
|
|
if (!(xml = virDomainDefFormat(*def, cfg->caps,
|
|
VIR_DOMAIN_XML_SECURE |
|
|
VIR_DOMAIN_XML_MIGRATABLE)))
|
|
return -1;
|
|
|
|
hookret = virHookCall(VIR_HOOK_DRIVER_LIBXL, (*def)->name,
|
|
VIR_HOOK_LIBXL_OP_MIGRATE, VIR_HOOK_SUBOP_BEGIN,
|
|
NULL, xml, xmlout);
|
|
VIR_FREE(xml);
|
|
|
|
if (hookret < 0) {
|
|
return -1;
|
|
} else if (hookret == 0) {
|
|
if (virStringIsEmpty(*xmlout)) {
|
|
VIR_DEBUG("Migrate hook filter returned nothing; using the"
|
|
" original XML");
|
|
} else {
|
|
virDomainDefPtr newdef;
|
|
|
|
VIR_DEBUG("Using hook-filtered domain XML: %s", *xmlout);
|
|
newdef = virDomainDefParseString(*xmlout, cfg->caps, driver->xmlopt,
|
|
NULL,
|
|
VIR_DOMAIN_DEF_PARSE_INACTIVE |
|
|
VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE);
|
|
if (!newdef)
|
|
return -1;
|
|
|
|
/* TODO At some stage we will want to have some check of what the user
|
|
* did in the hook. */
|
|
|
|
virDomainDefFree(*def);
|
|
*def = newdef;
|
|
/* We should taint the domain here. However, @vm and therefore
|
|
* privateData too are still NULL, so just notice the fact and
|
|
* taint it later. */
|
|
*taint_hook = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
libxlDomainMigrationDstPrepareTunnel3(virConnectPtr dconn,
|
|
virStreamPtr st,
|
|
virDomainDefPtr *def,
|
|
const char *cookiein,
|
|
int cookieinlen,
|
|
unsigned int flags)
|
|
{
|
|
libxlMigrationCookiePtr mig = NULL;
|
|
libxlDriverPrivatePtr driver = dconn->privateData;
|
|
virDomainObjPtr vm = NULL;
|
|
libxlMigrationDstArgs *args = NULL;
|
|
bool taint_hook = false;
|
|
libxlDomainObjPrivatePtr priv = NULL;
|
|
char *xmlout = NULL;
|
|
int dataFD[2] = { -1, -1 };
|
|
int ret = -1;
|
|
|
|
if (libxlDomainMigrationPrepareAny(dconn, def, cookiein, cookieinlen,
|
|
&mig, &xmlout, &taint_hook) < 0)
|
|
goto error;
|
|
|
|
if (!(vm = virDomainObjListAdd(driver->domains, *def,
|
|
driver->xmlopt,
|
|
VIR_DOMAIN_OBJ_LIST_ADD_LIVE |
|
|
VIR_DOMAIN_OBJ_LIST_ADD_CHECK_LIVE,
|
|
NULL)))
|
|
goto error;
|
|
*def = NULL;
|
|
|
|
/*
|
|
* Unless an error is encountered in this function, the job will
|
|
* be terminated in the finish phase.
|
|
*/
|
|
if (libxlDomainObjBeginJob(driver, vm, LIBXL_JOB_MODIFY) < 0)
|
|
goto error;
|
|
|
|
priv = vm->privateData;
|
|
|
|
if (taint_hook) {
|
|
/* Domain XML has been altered by a hook script. */
|
|
priv->hookRun = true;
|
|
}
|
|
|
|
/*
|
|
* The data flow of tunnel3 migration in the dest side:
|
|
* stream -> pipe -> recvfd of libxlDomainStartRestore
|
|
*/
|
|
if (pipe(dataFD) < 0)
|
|
goto endjob;
|
|
|
|
/* Stream data will be written to pipeIn */
|
|
if (virFDStreamOpen(st, dataFD[1]) < 0)
|
|
goto endjob;
|
|
dataFD[1] = -1; /* 'st' owns the FD now & will close it */
|
|
|
|
if (libxlMigrationDstArgsInitialize() < 0)
|
|
goto endjob;
|
|
|
|
if (!(args = virObjectNew(libxlMigrationDstArgsClass)))
|
|
goto endjob;
|
|
|
|
args->conn = virObjectRef(dconn);
|
|
args->vm = virObjectRef(vm);
|
|
args->flags = flags;
|
|
args->migcookie = mig;
|
|
/* Receive from pipeOut */
|
|
args->recvfd = dataFD[0];
|
|
args->nsocks = 0;
|
|
mig = NULL;
|
|
|
|
VIR_FREE(priv->migrationDstReceiveThr);
|
|
if (VIR_ALLOC(priv->migrationDstReceiveThr) < 0)
|
|
goto error;
|
|
if (virThreadCreate(priv->migrationDstReceiveThr, true, libxlDoMigrateDstReceive, args) < 0) {
|
|
virReportError(VIR_ERR_OPERATION_FAILED, "%s",
|
|
_("Failed to create thread for receiving migration data"));
|
|
goto endjob;
|
|
}
|
|
|
|
ret = 0;
|
|
goto done;
|
|
|
|
endjob:
|
|
libxlDomainObjEndJob(driver, vm);
|
|
|
|
error:
|
|
libxlMigrationCookieFree(mig);
|
|
VIR_FORCE_CLOSE(dataFD[1]);
|
|
VIR_FORCE_CLOSE(dataFD[0]);
|
|
virObjectUnref(args);
|
|
/* Remove virDomainObj from domain list */
|
|
if (vm)
|
|
virDomainObjListRemove(driver->domains, vm);
|
|
|
|
done:
|
|
virDomainObjEndAPI(&vm);
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
libxlDomainMigrationDstPrepare(virConnectPtr dconn,
|
|
virDomainDefPtr *def,
|
|
const char *uri_in,
|
|
char **uri_out,
|
|
const char *cookiein,
|
|
int cookieinlen,
|
|
unsigned int flags)
|
|
{
|
|
libxlDriverPrivatePtr driver = dconn->privateData;
|
|
libxlDriverConfigPtr cfg = libxlDriverConfigGet(driver);
|
|
libxlMigrationCookiePtr mig = NULL;
|
|
virDomainObjPtr vm = NULL;
|
|
char *hostname = NULL;
|
|
char *xmlout = NULL;
|
|
unsigned short port;
|
|
char portstr[100];
|
|
virURIPtr uri = NULL;
|
|
virNetSocketPtr *socks = NULL;
|
|
size_t nsocks = 0;
|
|
int nsocks_listen = 0;
|
|
libxlMigrationDstArgs *args = NULL;
|
|
bool taint_hook = false;
|
|
libxlDomainObjPrivatePtr priv = NULL;
|
|
size_t i;
|
|
int ret = -1;
|
|
|
|
if (libxlDomainMigrationPrepareAny(dconn, def, cookiein, cookieinlen,
|
|
&mig, &xmlout, &taint_hook) < 0)
|
|
goto error;
|
|
|
|
if (!(vm = virDomainObjListAdd(driver->domains, *def,
|
|
driver->xmlopt,
|
|
VIR_DOMAIN_OBJ_LIST_ADD_LIVE |
|
|
VIR_DOMAIN_OBJ_LIST_ADD_CHECK_LIVE,
|
|
NULL)))
|
|
goto error;
|
|
*def = NULL;
|
|
|
|
/*
|
|
* Unless an error is encountered in this function, the job will
|
|
* be terminated in the finish phase.
|
|
*/
|
|
if (libxlDomainObjBeginJob(driver, vm, LIBXL_JOB_MODIFY) < 0)
|
|
goto error;
|
|
|
|
priv = vm->privateData;
|
|
|
|
if (taint_hook) {
|
|
/* Domain XML has been altered by a hook script. */
|
|
priv->hookRun = true;
|
|
}
|
|
|
|
/* Create socket connection to receive migration data */
|
|
if (!uri_in) {
|
|
if ((hostname = virGetHostname()) == NULL)
|
|
goto endjob;
|
|
|
|
if (STRPREFIX(hostname, "localhost")) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("hostname on destination resolved to localhost,"
|
|
" but migration requires an FQDN"));
|
|
goto endjob;
|
|
}
|
|
|
|
if (virPortAllocatorAcquire(driver->migrationPorts, &port) < 0)
|
|
goto endjob;
|
|
|
|
priv->migrationPort = port;
|
|
*uri_out = g_strdup_printf("tcp://%s:%d", hostname, port);
|
|
} else {
|
|
if (!(STRPREFIX(uri_in, "tcp://"))) {
|
|
/* not full URI, add prefix tcp:// */
|
|
char *tmp;
|
|
tmp = g_strdup_printf("tcp://%s", uri_in);
|
|
uri = virURIParse(tmp);
|
|
VIR_FREE(tmp);
|
|
} else {
|
|
uri = virURIParse(uri_in);
|
|
}
|
|
|
|
if (uri == NULL) {
|
|
virReportError(VIR_ERR_INVALID_ARG,
|
|
_("unable to parse URI: %s"),
|
|
uri_in);
|
|
goto endjob;
|
|
}
|
|
|
|
if (uri->server == NULL) {
|
|
virReportError(VIR_ERR_INVALID_ARG,
|
|
_("missing host in migration URI: %s"),
|
|
uri_in);
|
|
goto endjob;
|
|
}
|
|
hostname = uri->server;
|
|
|
|
if (uri->port == 0) {
|
|
if (virPortAllocatorAcquire(driver->migrationPorts, &port) < 0)
|
|
goto endjob;
|
|
|
|
priv->migrationPort = port;
|
|
} else {
|
|
port = uri->port;
|
|
}
|
|
|
|
*uri_out = g_strdup_printf("tcp://%s:%d", hostname, port);
|
|
}
|
|
|
|
snprintf(portstr, sizeof(portstr), "%d", port);
|
|
|
|
if (virNetSocketNewListenTCP(hostname, portstr,
|
|
AF_UNSPEC,
|
|
&socks, &nsocks) < 0) {
|
|
virReportError(VIR_ERR_OPERATION_FAILED, "%s",
|
|
_("Fail to create socket for incoming migration"));
|
|
goto endjob;
|
|
}
|
|
|
|
if (libxlMigrationDstArgsInitialize() < 0)
|
|
goto endjob;
|
|
|
|
if (!(args = virObjectNew(libxlMigrationDstArgsClass)))
|
|
goto endjob;
|
|
|
|
args->conn = virObjectRef(dconn);
|
|
args->vm = virObjectRef(vm);
|
|
args->flags = flags;
|
|
args->socks = socks;
|
|
args->nsocks = nsocks;
|
|
args->migcookie = mig;
|
|
mig = NULL;
|
|
|
|
for (i = 0; i < nsocks; i++) {
|
|
if (virNetSocketSetBlocking(socks[i], true) < 0)
|
|
continue;
|
|
|
|
if (virNetSocketListen(socks[i], 1) < 0)
|
|
continue;
|
|
|
|
if (virNetSocketAddIOCallback(socks[i],
|
|
VIR_EVENT_HANDLE_READABLE,
|
|
libxlMigrateDstReceive,
|
|
virObjectRef(args),
|
|
NULL) < 0)
|
|
continue;
|
|
|
|
nsocks_listen++;
|
|
}
|
|
|
|
if (!nsocks_listen)
|
|
goto endjob;
|
|
|
|
ret = 0;
|
|
goto done;
|
|
|
|
endjob:
|
|
libxlDomainObjEndJob(driver, vm);
|
|
|
|
error:
|
|
for (i = 0; i < nsocks; i++) {
|
|
virNetSocketClose(socks[i]);
|
|
virObjectUnref(socks[i]);
|
|
}
|
|
VIR_FREE(socks);
|
|
if (priv) {
|
|
virPortAllocatorRelease(priv->migrationPort);
|
|
priv->migrationPort = 0;
|
|
}
|
|
/* Remove virDomainObj from domain list */
|
|
if (vm)
|
|
virDomainObjListRemove(driver->domains, vm);
|
|
|
|
done:
|
|
VIR_FREE(xmlout);
|
|
libxlMigrationCookieFree(mig);
|
|
if (!uri_in)
|
|
VIR_FREE(hostname);
|
|
else
|
|
virURIFree(uri);
|
|
virObjectUnref(args);
|
|
virDomainObjEndAPI(&vm);
|
|
virObjectUnref(cfg);
|
|
return ret;
|
|
}
|
|
|
|
typedef struct _libxlTunnelMigrationThread libxlTunnelMigrationThread;
|
|
struct _libxlTunnelMigrationThread {
|
|
virStreamPtr st;
|
|
int srcFD;
|
|
};
|
|
#define TUNNEL_SEND_BUF_SIZE 65536
|
|
|
|
/*
|
|
* The data flow of tunnel3 migration in the src side:
|
|
* libxlDoMigrateSrcSend() -> pipe
|
|
* libxlTunnel3MigrationSrcFunc() polls pipe out and then write to dest stream.
|
|
*/
|
|
static void libxlTunnel3MigrationSrcFunc(void *arg)
|
|
{
|
|
libxlTunnelMigrationThread *data = (libxlTunnelMigrationThread *)arg;
|
|
char *buffer = NULL;
|
|
struct pollfd fds[1];
|
|
int timeout = -1;
|
|
|
|
if (VIR_ALLOC_N(buffer, TUNNEL_SEND_BUF_SIZE) < 0)
|
|
return;
|
|
|
|
fds[0].fd = data->srcFD;
|
|
for (;;) {
|
|
int ret;
|
|
|
|
fds[0].events = POLLIN;
|
|
fds[0].revents = 0;
|
|
ret = poll(fds, G_N_ELEMENTS(fds), timeout);
|
|
if (ret < 0) {
|
|
if (errno == EAGAIN || errno == EINTR)
|
|
continue;
|
|
virReportError(errno, "%s",
|
|
_("poll failed in libxlTunnel3MigrationSrcFunc"));
|
|
goto cleanup;
|
|
}
|
|
|
|
if (ret == 0) {
|
|
VIR_DEBUG("poll returned 0");
|
|
break;
|
|
}
|
|
|
|
if (fds[0].revents & (POLLIN | POLLERR | POLLHUP)) {
|
|
int nbytes;
|
|
|
|
nbytes = read(data->srcFD, buffer, TUNNEL_SEND_BUF_SIZE);
|
|
if (nbytes > 0) {
|
|
/* Write to dest stream */
|
|
if (virStreamSend(data->st, buffer, nbytes) < 0) {
|
|
virStreamAbort(data->st);
|
|
goto cleanup;
|
|
}
|
|
} else if (nbytes < 0) {
|
|
virReportError(errno, "%s",
|
|
_("tunnelled migration failed to read from xen side"));
|
|
virStreamAbort(data->st);
|
|
goto cleanup;
|
|
} else {
|
|
/* EOF; transferred all data */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
ignore_value(virStreamFinish(data->st));
|
|
|
|
cleanup:
|
|
VIR_FREE(buffer);
|
|
|
|
return;
|
|
}
|
|
|
|
struct libxlTunnelControl {
|
|
libxlTunnelMigrationThread tmThread;
|
|
virThread thread;
|
|
int dataFD[2];
|
|
};
|
|
|
|
static int
|
|
libxlMigrationSrcStartTunnel(libxlDriverPrivatePtr driver,
|
|
virDomainObjPtr vm,
|
|
unsigned long flags,
|
|
virStreamPtr st,
|
|
struct libxlTunnelControl **tnl)
|
|
{
|
|
struct libxlTunnelControl *tc = NULL;
|
|
libxlTunnelMigrationThread *arg = NULL;
|
|
int ret = -1;
|
|
|
|
if (VIR_ALLOC(tc) < 0)
|
|
goto out;
|
|
*tnl = tc;
|
|
|
|
tc->dataFD[0] = -1;
|
|
tc->dataFD[1] = -1;
|
|
if (pipe(tc->dataFD) < 0) {
|
|
virReportError(errno, "%s", _("Unable to make pipes"));
|
|
goto out;
|
|
}
|
|
|
|
arg = &tc->tmThread;
|
|
/* Read from pipe */
|
|
arg->srcFD = tc->dataFD[0];
|
|
/* Write to dest stream */
|
|
arg->st = st;
|
|
if (virThreadCreate(&tc->thread, true,
|
|
libxlTunnel3MigrationSrcFunc, arg) < 0) {
|
|
virReportError(errno, "%s",
|
|
_("Unable to create tunnel migration thread"));
|
|
goto out;
|
|
}
|
|
|
|
virObjectUnlock(vm);
|
|
/* Send data to pipe */
|
|
ret = libxlDoMigrateSrcSend(driver, vm, flags, tc->dataFD[1]);
|
|
virObjectLock(vm);
|
|
|
|
out:
|
|
/* libxlMigrationSrcStopTunnel will be called in libxlDoMigrateSrcP2P
|
|
* to free all resources for us.
|
|
*/
|
|
return ret;
|
|
}
|
|
|
|
static void libxlMigrationSrcStopTunnel(struct libxlTunnelControl *tc)
|
|
{
|
|
if (!tc)
|
|
return;
|
|
|
|
virThreadCancel(&tc->thread);
|
|
virThreadJoin(&tc->thread);
|
|
|
|
VIR_FORCE_CLOSE(tc->dataFD[0]);
|
|
VIR_FORCE_CLOSE(tc->dataFD[1]);
|
|
VIR_FREE(tc);
|
|
}
|
|
|
|
/* This function is a simplification of virDomainMigrateVersion3Full and
|
|
* restricting it to migration v3 with params since it was the first to be
|
|
* introduced in libxl.
|
|
*/
|
|
static int
|
|
libxlDoMigrateSrcP2P(libxlDriverPrivatePtr driver,
|
|
virDomainObjPtr vm,
|
|
virConnectPtr sconn,
|
|
const char *xmlin,
|
|
virConnectPtr dconn,
|
|
const char *dconnuri G_GNUC_UNUSED,
|
|
const char *dname,
|
|
const char *uri,
|
|
unsigned int flags)
|
|
{
|
|
virDomainPtr ddomain = NULL;
|
|
virTypedParameterPtr params = NULL;
|
|
int nparams = 0;
|
|
int maxparams = 0;
|
|
char *uri_out = NULL;
|
|
char *dom_xml = NULL;
|
|
unsigned long destflags;
|
|
char *cookieout = NULL;
|
|
int cookieoutlen;
|
|
bool cancelled = true;
|
|
bool notify_source = true;
|
|
virErrorPtr orig_err = NULL;
|
|
int ret = -1;
|
|
/* For tunnel migration */
|
|
virStreamPtr st = NULL;
|
|
struct libxlTunnelControl *tc = NULL;
|
|
|
|
if (dname &&
|
|
virTypedParamsAddString(¶ms, &nparams, &maxparams,
|
|
VIR_MIGRATE_PARAM_DEST_NAME, dname) < 0)
|
|
goto cleanup;
|
|
|
|
if (uri &&
|
|
virTypedParamsAddString(¶ms, &nparams, &maxparams,
|
|
VIR_MIGRATE_PARAM_URI, uri) < 0)
|
|
goto cleanup;
|
|
|
|
dom_xml = libxlDomainMigrationSrcBegin(sconn, vm, xmlin,
|
|
&cookieout, &cookieoutlen);
|
|
/*
|
|
* If dom_xml is non-NULL the begin phase has succeeded, and the
|
|
* confirm phase must be called to cleanup the migration operation.
|
|
*/
|
|
if (!dom_xml)
|
|
goto cleanup;
|
|
|
|
if (virTypedParamsAddString(¶ms, &nparams, &maxparams,
|
|
VIR_MIGRATE_PARAM_DEST_XML, dom_xml) < 0)
|
|
goto confirm;
|
|
|
|
/* We don't require the destination to have P2P support
|
|
* as it looks to be normal migration from the receiver perspective.
|
|
*/
|
|
destflags = flags & ~(VIR_MIGRATE_PEER2PEER);
|
|
|
|
VIR_DEBUG("Prepare3");
|
|
virObjectUnlock(vm);
|
|
if (flags & VIR_MIGRATE_TUNNELLED) {
|
|
if (!(st = virStreamNew(dconn, 0)))
|
|
goto confirm;
|
|
ret = dconn->driver->domainMigratePrepareTunnel3Params
|
|
(dconn, st, params, nparams, cookieout, cookieoutlen, NULL, NULL, destflags);
|
|
} else {
|
|
ret = dconn->driver->domainMigratePrepare3Params
|
|
(dconn, params, nparams, cookieout, cookieoutlen, NULL, NULL, &uri_out, destflags);
|
|
}
|
|
virObjectLock(vm);
|
|
|
|
if (ret == -1)
|
|
goto confirm;
|
|
|
|
if (!(flags & VIR_MIGRATE_TUNNELLED)) {
|
|
if (uri_out) {
|
|
if (virTypedParamsReplaceString(¶ms, &nparams,
|
|
VIR_MIGRATE_PARAM_URI, uri_out) < 0) {
|
|
virErrorPreserveLast(&orig_err);
|
|
goto finish;
|
|
}
|
|
} else {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("domainMigratePrepare3 did not set uri"));
|
|
goto finish;
|
|
}
|
|
}
|
|
|
|
VIR_DEBUG("Perform3 uri=%s", NULLSTR(uri_out));
|
|
if (flags & VIR_MIGRATE_TUNNELLED)
|
|
ret = libxlMigrationSrcStartTunnel(driver, vm, flags, st, &tc);
|
|
else
|
|
ret = libxlDomainMigrationSrcPerform(driver, vm, NULL, NULL,
|
|
uri_out, NULL, flags);
|
|
if (ret < 0) {
|
|
notify_source = false;
|
|
virErrorPreserveLast(&orig_err);
|
|
}
|
|
|
|
cancelled = (ret < 0);
|
|
|
|
finish:
|
|
VIR_DEBUG("Finish3 ret=%d", ret);
|
|
if (virTypedParamsGetString(params, nparams,
|
|
VIR_MIGRATE_PARAM_DEST_NAME, NULL) <= 0 &&
|
|
virTypedParamsReplaceString(¶ms, &nparams,
|
|
VIR_MIGRATE_PARAM_DEST_NAME,
|
|
vm->def->name) < 0) {
|
|
ddomain = NULL;
|
|
} else {
|
|
virObjectUnlock(vm);
|
|
ddomain = dconn->driver->domainMigrateFinish3Params
|
|
(dconn, params, nparams, NULL, 0, NULL, NULL,
|
|
destflags, cancelled);
|
|
virObjectLock(vm);
|
|
}
|
|
|
|
cancelled = (ddomain == NULL);
|
|
|
|
/* If Finish3Params set an error, and we don't have an earlier
|
|
* one we need to preserve it in case confirm3 overwrites
|
|
*/
|
|
if (!orig_err)
|
|
virErrorPreserveLast(&orig_err);
|
|
|
|
confirm:
|
|
if (notify_source) {
|
|
VIR_DEBUG("Confirm3 cancelled=%d vm=%p", cancelled, vm);
|
|
ret = libxlDomainMigrationSrcConfirm(driver, vm, flags, cancelled);
|
|
|
|
if (ret < 0)
|
|
VIR_WARN("Guest %s probably left in 'paused' state on source",
|
|
vm->def->name);
|
|
}
|
|
|
|
cleanup:
|
|
if (flags & VIR_MIGRATE_TUNNELLED) {
|
|
libxlMigrationSrcStopTunnel(tc);
|
|
virObjectUnref(st);
|
|
}
|
|
|
|
if (ddomain) {
|
|
virObjectUnref(ddomain);
|
|
ret = 0;
|
|
} else {
|
|
ret = -1;
|
|
}
|
|
|
|
virErrorRestore(&orig_err);
|
|
|
|
VIR_FREE(cookieout);
|
|
VIR_FREE(dom_xml);
|
|
VIR_FREE(uri_out);
|
|
virTypedParamsFree(params, nparams);
|
|
return ret;
|
|
}
|
|
|
|
static int virConnectCredType[] = {
|
|
VIR_CRED_AUTHNAME,
|
|
VIR_CRED_PASSPHRASE,
|
|
};
|
|
|
|
static virConnectAuth virConnectAuthConfig = {
|
|
.credtype = virConnectCredType,
|
|
.ncredtype = G_N_ELEMENTS(virConnectCredType),
|
|
};
|
|
|
|
/* On P2P mode there is only the Perform3 phase and we need to handle
|
|
* the connection with the destination libvirtd and perform the migration.
|
|
* Here we first tackle the first part of it, and libxlDoMigrationP2P handles
|
|
* the migration process with an established virConnectPtr to the destination.
|
|
*/
|
|
int
|
|
libxlDomainMigrationSrcPerformP2P(libxlDriverPrivatePtr driver,
|
|
virDomainObjPtr vm,
|
|
virConnectPtr sconn,
|
|
const char *xmlin,
|
|
const char *dconnuri,
|
|
const char *uri_str G_GNUC_UNUSED,
|
|
const char *dname,
|
|
unsigned int flags)
|
|
{
|
|
int ret = -1;
|
|
bool useParams;
|
|
virConnectPtr dconn = NULL;
|
|
virErrorPtr orig_err = NULL;
|
|
libxlDriverConfigPtr cfg = libxlDriverConfigGet(driver);
|
|
|
|
virObjectUnlock(vm);
|
|
dconn = virConnectOpenAuth(dconnuri, &virConnectAuthConfig, 0);
|
|
virObjectLock(vm);
|
|
|
|
if (dconn == NULL) {
|
|
virReportError(VIR_ERR_OPERATION_FAILED,
|
|
_("Failed to connect to remote libvirt URI %s: %s"),
|
|
dconnuri, virGetLastErrorMessage());
|
|
return ret;
|
|
}
|
|
|
|
if (virConnectSetKeepAlive(dconn, cfg->keepAliveInterval,
|
|
cfg->keepAliveCount) < 0)
|
|
goto cleanup;
|
|
|
|
virObjectUnlock(vm);
|
|
useParams = VIR_DRV_SUPPORTS_FEATURE(dconn->driver, dconn,
|
|
VIR_DRV_FEATURE_MIGRATION_PARAMS);
|
|
virObjectLock(vm);
|
|
|
|
if (!useParams) {
|
|
virReportError(VIR_ERR_OPERATION_FAILED, "%s",
|
|
_("Destination libvirt does not support migration with extensible parameters"));
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = libxlDoMigrateSrcP2P(driver, vm, sconn, xmlin, dconn, dconnuri,
|
|
dname, uri_str, flags);
|
|
|
|
if (ret < 0) {
|
|
/*
|
|
* Confirm phase will not be executed if perform fails. End the
|
|
* job started in begin phase.
|
|
*/
|
|
libxlDomainObjEndJob(driver, vm);
|
|
}
|
|
|
|
cleanup:
|
|
virErrorPreserveLast(&orig_err);
|
|
virObjectUnlock(vm);
|
|
virObjectUnref(dconn);
|
|
virObjectUnref(cfg);
|
|
virObjectLock(vm);
|
|
virErrorRestore(&orig_err);
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
libxlDomainMigrationSrcPerform(libxlDriverPrivatePtr driver,
|
|
virDomainObjPtr vm,
|
|
const char *dom_xml G_GNUC_UNUSED,
|
|
const char *dconnuri G_GNUC_UNUSED,
|
|
const char *uri_str,
|
|
const char *dname G_GNUC_UNUSED,
|
|
unsigned int flags)
|
|
{
|
|
libxlDomainObjPrivatePtr priv = vm->privateData;
|
|
char *hostname = NULL;
|
|
unsigned short port = 0;
|
|
char portstr[100];
|
|
virURIPtr uri = NULL;
|
|
virNetSocketPtr sock;
|
|
int sockfd = -1;
|
|
int ret = -1;
|
|
|
|
/* parse dst host:port from uri */
|
|
uri = virURIParse(uri_str);
|
|
if (uri == NULL || uri->server == NULL || uri->port == 0)
|
|
goto cleanup;
|
|
|
|
hostname = uri->server;
|
|
port = uri->port;
|
|
snprintf(portstr, sizeof(portstr), "%d", port);
|
|
|
|
/* socket connect to dst host:port */
|
|
if (virNetSocketNewConnectTCP(hostname, portstr,
|
|
AF_UNSPEC,
|
|
&sock) < 0)
|
|
goto cleanup;
|
|
|
|
if (virNetSocketSetBlocking(sock, true) < 0) {
|
|
virObjectUnref(sock);
|
|
goto cleanup;
|
|
}
|
|
|
|
sockfd = virNetSocketDupFD(sock, true);
|
|
virObjectUnref(sock);
|
|
|
|
if (virDomainLockProcessPause(driver->lockManager, vm, &priv->lockState) < 0)
|
|
VIR_WARN("Unable to release lease on %s", vm->def->name);
|
|
VIR_DEBUG("Preserving lock state '%s'", NULLSTR(priv->lockState));
|
|
|
|
/* suspend vm and send saved data to dst through socket fd */
|
|
virObjectUnlock(vm);
|
|
ret = libxlDoMigrateSrcSend(driver, vm, flags, sockfd);
|
|
virObjectLock(vm);
|
|
|
|
if (ret < 0) {
|
|
virDomainLockProcessResume(driver->lockManager,
|
|
"xen:///system",
|
|
vm,
|
|
priv->lockState);
|
|
/*
|
|
* Confirm phase will not be executed if perform fails. End the
|
|
* job started in begin phase.
|
|
*/
|
|
libxlDomainObjEndJob(driver, vm);
|
|
}
|
|
|
|
cleanup:
|
|
VIR_FORCE_CLOSE(sockfd);
|
|
virURIFree(uri);
|
|
return ret;
|
|
}
|
|
|
|
virDomainPtr
|
|
libxlDomainMigrationDstFinish(virConnectPtr dconn,
|
|
virDomainObjPtr vm,
|
|
unsigned int flags,
|
|
int cancelled)
|
|
{
|
|
libxlDriverPrivatePtr driver = dconn->privateData;
|
|
libxlDriverConfigPtr cfg = libxlDriverConfigGet(driver);
|
|
libxlDomainObjPrivatePtr priv = vm->privateData;
|
|
virObjectEventPtr event = NULL;
|
|
virDomainPtr dom = NULL;
|
|
|
|
if (priv->migrationDstReceiveThr) {
|
|
virObjectUnlock(vm);
|
|
virThreadJoin(priv->migrationDstReceiveThr);
|
|
virObjectLock(vm);
|
|
VIR_FREE(priv->migrationDstReceiveThr);
|
|
}
|
|
|
|
virPortAllocatorRelease(priv->migrationPort);
|
|
priv->migrationPort = 0;
|
|
|
|
if (cancelled)
|
|
goto cleanup;
|
|
|
|
/* Check if domain is alive */
|
|
if (!virDomainObjIsActive(vm)) {
|
|
/* Migration failed if domain is inactive */
|
|
virReportError(VIR_ERR_OPERATION_FAILED,
|
|
"%s", _("Migration failed. Domain is not running "
|
|
"on destination host"));
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Unpause if requested */
|
|
if (!(flags & VIR_MIGRATE_PAUSED)) {
|
|
if (libxl_domain_unpause(cfg->ctx, vm->def->id) != 0) {
|
|
virReportError(VIR_ERR_OPERATION_FAILED, "%s",
|
|
_("Failed to unpause domain"));
|
|
goto cleanup;
|
|
}
|
|
|
|
virDomainObjSetState(vm, VIR_DOMAIN_RUNNING,
|
|
VIR_DOMAIN_RUNNING_MIGRATED);
|
|
event = virDomainEventLifecycleNewFromObj(vm,
|
|
VIR_DOMAIN_EVENT_RESUMED,
|
|
VIR_DOMAIN_EVENT_RESUMED_MIGRATED);
|
|
} else {
|
|
virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, VIR_DOMAIN_PAUSED_USER);
|
|
event = virDomainEventLifecycleNewFromObj(vm,
|
|
VIR_DOMAIN_EVENT_SUSPENDED,
|
|
VIR_DOMAIN_EVENT_SUSPENDED_PAUSED);
|
|
}
|
|
|
|
virObjectEventStateQueue(driver->domainEventState, event);
|
|
event = NULL;
|
|
|
|
if (flags & VIR_MIGRATE_PERSIST_DEST) {
|
|
unsigned int oldPersist = vm->persistent;
|
|
virDomainDefPtr vmdef;
|
|
|
|
vm->persistent = 1;
|
|
if (!(vmdef = virDomainObjGetPersistentDef(cfg->caps,
|
|
driver->xmlopt, vm, NULL)))
|
|
goto cleanup;
|
|
|
|
if (virDomainSaveConfig(cfg->configDir, cfg->caps, vmdef) < 0)
|
|
goto cleanup;
|
|
|
|
event = virDomainEventLifecycleNewFromObj(vm,
|
|
VIR_DOMAIN_EVENT_DEFINED,
|
|
oldPersist ?
|
|
VIR_DOMAIN_EVENT_DEFINED_UPDATED :
|
|
VIR_DOMAIN_EVENT_DEFINED_ADDED);
|
|
virObjectEventStateQueue(driver->domainEventState, event);
|
|
event = NULL;
|
|
}
|
|
|
|
if (virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm, cfg->caps) < 0)
|
|
goto cleanup;
|
|
|
|
dom = virGetDomain(dconn, vm->def->name, vm->def->uuid, vm->def->id);
|
|
|
|
cleanup:
|
|
if (dom == NULL) {
|
|
libxlDomainDestroyInternal(driver, vm);
|
|
libxlDomainCleanup(driver, vm);
|
|
virDomainObjSetState(vm, VIR_DOMAIN_SHUTOFF,
|
|
VIR_DOMAIN_SHUTOFF_FAILED);
|
|
event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED,
|
|
VIR_DOMAIN_EVENT_STOPPED_FAILED);
|
|
if (!vm->persistent)
|
|
virDomainObjListRemove(driver->domains, vm);
|
|
}
|
|
|
|
/* EndJob for corresponding BeginJob in prepare phase */
|
|
libxlDomainObjEndJob(driver, vm);
|
|
virObjectEventStateQueue(driver->domainEventState, event);
|
|
virObjectUnref(cfg);
|
|
return dom;
|
|
}
|
|
|
|
int
|
|
libxlDomainMigrationSrcConfirm(libxlDriverPrivatePtr driver,
|
|
virDomainObjPtr vm,
|
|
unsigned int flags,
|
|
int cancelled)
|
|
{
|
|
libxlDriverConfigPtr cfg = libxlDriverConfigGet(driver);
|
|
libxlDomainObjPrivatePtr priv = vm->privateData;
|
|
virObjectEventPtr event = NULL;
|
|
int ret = -1;
|
|
|
|
if (cancelled) {
|
|
/* Resume lock process that was paused in MigrationSrcPerform */
|
|
virDomainLockProcessResume(driver->lockManager,
|
|
"xen:///system",
|
|
vm,
|
|
priv->lockState);
|
|
if (libxl_domain_resume(cfg->ctx, vm->def->id, 1, 0) == 0) {
|
|
ret = 0;
|
|
} else {
|
|
VIR_DEBUG("Unable to resume domain '%s' after failed migration",
|
|
vm->def->name);
|
|
virDomainObjSetState(vm, VIR_DOMAIN_PAUSED,
|
|
VIR_DOMAIN_PAUSED_MIGRATION);
|
|
event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_SUSPENDED,
|
|
VIR_DOMAIN_EVENT_SUSPENDED_MIGRATED);
|
|
ignore_value(virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm, cfg->caps));
|
|
}
|
|
goto cleanup;
|
|
}
|
|
|
|
libxlDomainDestroyInternal(driver, vm);
|
|
libxlDomainCleanup(driver, vm);
|
|
virDomainObjSetState(vm, VIR_DOMAIN_SHUTOFF,
|
|
VIR_DOMAIN_SHUTOFF_MIGRATED);
|
|
event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED,
|
|
VIR_DOMAIN_EVENT_STOPPED_MIGRATED);
|
|
|
|
VIR_DEBUG("Domain '%s' successfully migrated", vm->def->name);
|
|
|
|
if (flags & VIR_MIGRATE_UNDEFINE_SOURCE)
|
|
virDomainDeleteConfig(cfg->configDir, cfg->autostartDir, vm);
|
|
|
|
if (!vm->persistent || (flags & VIR_MIGRATE_UNDEFINE_SOURCE))
|
|
virDomainObjListRemove(driver->domains, vm);
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
/* EndJob for corresponding BeginJob in begin phase */
|
|
libxlDomainObjEndJob(driver, vm);
|
|
virObjectEventStateQueue(driver->domainEventState, event);
|
|
virObjectUnref(cfg);
|
|
return ret;
|
|
}
|