/*
* qemu_migration_cookie.c: QEMU migration cookie handling
*
* 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
* .
*/
#include
#include
#include
#include "locking/domain_lock.h"
#include "viralloc.h"
#include "virerror.h"
#include "virlog.h"
#include "virnetdevopenvswitch.h"
#include "virstring.h"
#include "virutil.h"
#include "qemu_domain.h"
#include "qemu_migration_cookie.h"
#include "qemu_migration_params.h"
#define VIR_FROM_THIS VIR_FROM_QEMU
VIR_LOG_INIT("qemu.qemu_migration_cookie");
VIR_ENUM_IMPL(qemuMigrationCookieFlag,
QEMU_MIGRATION_COOKIE_FLAG_LAST,
"graphics",
"lockstate",
"persistent",
"network",
"nbd",
"statistics",
"memory-hotplug",
"cpu-hotplug",
"cpu",
"allowReboot",
"capabilities",
"block-dirty-bitmaps",
);
static void
qemuMigrationCookieGraphicsFree(qemuMigrationCookieGraphicsPtr grap)
{
if (!grap)
return;
g_free(grap->listen);
g_free(grap->tlsSubject);
g_free(grap);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuMigrationCookieGraphics,
qemuMigrationCookieGraphicsFree);
static void
qemuMigrationCookieNetworkFree(qemuMigrationCookieNetworkPtr network)
{
size_t i;
if (!network)
return;
if (network->net) {
for (i = 0; i < network->nnets; i++)
g_free(network->net[i].portdata);
}
g_free(network->net);
g_free(network);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuMigrationCookieNetwork,
qemuMigrationCookieNetworkFree);
static void
qemuMigrationCookieNBDFree(qemuMigrationCookieNBDPtr nbd)
{
if (!nbd)
return;
while (nbd->ndisks)
g_free(nbd->disks[--nbd->ndisks].target);
g_free(nbd->disks);
g_free(nbd);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuMigrationCookieNBD,
qemuMigrationCookieNBDFree);
static void
qemuMigrationCookieCapsFree(qemuMigrationCookieCapsPtr caps)
{
if (!caps)
return;
virBitmapFree(caps->supported);
virBitmapFree(caps->automatic);
g_free(caps);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuMigrationCookieCaps,
qemuMigrationCookieCapsFree);
static void
qemuMigrationBlockDirtyBitmapsDiskBitmapFree(qemuMigrationBlockDirtyBitmapsDiskBitmapPtr bmp)
{
if (!bmp)
return;
g_free(bmp->bitmapname);
g_free(bmp->alias);
g_free(bmp->sourcebitmap);
g_free(bmp);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuMigrationBlockDirtyBitmapsDiskBitmap,
qemuMigrationBlockDirtyBitmapsDiskBitmapFree);
static void
qemuMigrationBlockDirtyBitmapsDiskFree(qemuMigrationBlockDirtyBitmapsDiskPtr dsk)
{
if (!dsk)
return;
g_free(dsk->target);
if (dsk->bitmaps)
g_slist_free_full(dsk->bitmaps,
(GDestroyNotify) qemuMigrationBlockDirtyBitmapsDiskBitmapFree);
g_free(dsk);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuMigrationBlockDirtyBitmapsDisk,
qemuMigrationBlockDirtyBitmapsDiskFree);
void
qemuMigrationCookieFree(qemuMigrationCookiePtr mig)
{
if (!mig)
return;
qemuMigrationCookieGraphicsFree(mig->graphics);
virDomainDefFree(mig->persistent);
qemuMigrationCookieNetworkFree(mig->network);
qemuMigrationCookieNBDFree(mig->nbd);
g_free(mig->localHostname);
g_free(mig->remoteHostname);
g_free(mig->name);
g_free(mig->lockState);
g_free(mig->lockDriver);
g_clear_pointer(&mig->jobInfo, qemuDomainJobInfoFree);
virCPUDefFree(mig->cpu);
qemuMigrationCookieCapsFree(mig->caps);
if (mig->blockDirtyBitmaps)
g_slist_free_full(mig->blockDirtyBitmaps,
(GDestroyNotify) qemuMigrationBlockDirtyBitmapsDiskFree);
g_free(mig);
}
static char *
qemuDomainExtractTLSSubject(const char *certdir)
{
g_autofree char *certfile = NULL;
char *subject = NULL;
g_autofree char *pemdata = NULL;
gnutls_datum_t pemdatum;
gnutls_x509_crt_t cert;
int ret;
size_t subjectlen;
certfile = g_strdup_printf("%s/server-cert.pem", certdir);
if (virFileReadAll(certfile, 8192, &pemdata) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unable to read server cert %s"), certfile);
return NULL;
}
ret = gnutls_x509_crt_init(&cert);
if (ret < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot initialize cert object: %s"),
gnutls_strerror(ret));
return NULL;
}
pemdatum.data = (unsigned char *)pemdata;
pemdatum.size = strlen(pemdata);
ret = gnutls_x509_crt_import(cert, &pemdatum, GNUTLS_X509_FMT_PEM);
if (ret < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot load cert data from %s: %s"),
certfile, gnutls_strerror(ret));
return NULL;
}
subjectlen = 1024;
subject = g_new0(char, subjectlen + 1);
gnutls_x509_crt_get_dn(cert, subject, &subjectlen);
subject[subjectlen] = '\0';
return subject;
}
static qemuMigrationCookieGraphicsPtr
qemuMigrationCookieGraphicsSpiceAlloc(virQEMUDriverPtr driver,
virDomainGraphicsDefPtr def,
virDomainGraphicsListenDefPtr glisten)
{
g_autoptr(qemuMigrationCookieGraphics) mig = g_new0(qemuMigrationCookieGraphics, 1);
const char *listenAddr;
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
mig->type = VIR_DOMAIN_GRAPHICS_TYPE_SPICE;
mig->port = def->data.spice.port;
if (cfg->spiceTLS)
mig->tlsPort = def->data.spice.tlsPort;
else
mig->tlsPort = -1;
if (!glisten || !(listenAddr = glisten->address))
listenAddr = cfg->spiceListen;
if (cfg->spiceTLS &&
!(mig->tlsSubject = qemuDomainExtractTLSSubject(cfg->spiceTLSx509certdir)))
return NULL;
mig->listen = g_strdup(listenAddr);
return g_steal_pointer(&mig);
}
static qemuMigrationCookieNetworkPtr
qemuMigrationCookieNetworkAlloc(virQEMUDriverPtr driver G_GNUC_UNUSED,
virDomainDefPtr def)
{
g_autoptr(qemuMigrationCookieNetwork) mig = g_new0(qemuMigrationCookieNetwork, 1);
size_t i;
mig->nnets = def->nnets;
mig->net = g_new0(qemuMigrationCookieNetData, def->nnets);
for (i = 0; i < def->nnets; i++) {
virDomainNetDefPtr netptr;
const virNetDevVPortProfile *vport;
netptr = def->nets[i];
vport = virDomainNetGetActualVirtPortProfile(netptr);
if (vport) {
mig->net[i].vporttype = vport->virtPortType;
switch (vport->virtPortType) {
case VIR_NETDEV_VPORT_PROFILE_NONE:
case VIR_NETDEV_VPORT_PROFILE_8021QBG:
case VIR_NETDEV_VPORT_PROFILE_8021QBH:
break;
case VIR_NETDEV_VPORT_PROFILE_OPENVSWITCH:
if (virNetDevOpenvswitchGetMigrateData(&mig->net[i].portdata,
netptr->ifname) != 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unable to run command to get OVS port data for "
"interface %s"), netptr->ifname);
return NULL;
}
break;
default:
break;
}
}
}
return g_steal_pointer(&mig);
}
qemuMigrationCookiePtr
qemuMigrationCookieNew(const virDomainDef *def,
const char *origname)
{
qemuMigrationCookiePtr mig = NULL;
unsigned char localHostUUID[VIR_UUID_BUFLEN];
g_autofree char *localHostname = NULL;
if (!(localHostname = virGetHostname()))
return NULL;
if (virGetHostUUID(localHostUUID) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Unable to obtain host UUID"));
return NULL;
}
mig = g_new0(qemuMigrationCookie, 1);
if (origname)
mig->name = g_strdup(origname);
else
mig->name = g_strdup(def->name);
memcpy(mig->uuid, def->uuid, VIR_UUID_BUFLEN);
memcpy(mig->localHostuuid, localHostUUID, VIR_UUID_BUFLEN);
mig->localHostname = g_steal_pointer(&localHostname);
return mig;
}
static int
qemuMigrationCookieAddGraphics(qemuMigrationCookiePtr mig,
virQEMUDriverPtr driver,
virDomainObjPtr dom)
{
size_t i = 0;
if (mig->flags & QEMU_MIGRATION_COOKIE_GRAPHICS) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Migration graphics data already present"));
return -1;
}
for (i = 0; i < dom->def->ngraphics; i++) {
if (dom->def->graphics[i]->type == VIR_DOMAIN_GRAPHICS_TYPE_SPICE) {
virDomainGraphicsListenDefPtr glisten =
virDomainGraphicsGetListen(dom->def->graphics[i], 0);
if (!glisten) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("missing listen element"));
return -1;
}
switch (glisten->type) {
case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_ADDRESS:
case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NETWORK:
/* Seamless migration is supported only for listen types
* 'address and 'network'. */
if (!(mig->graphics =
qemuMigrationCookieGraphicsSpiceAlloc(driver,
dom->def->graphics[i],
glisten)))
return -1;
mig->flags |= QEMU_MIGRATION_COOKIE_GRAPHICS;
break;
case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_SOCKET:
case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NONE:
case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_LAST:
break;
}
/* Seamless migration is supported only for one graphics. */
if (mig->graphics)
break;
}
}
return 0;
}
static int
qemuMigrationCookieAddLockstate(qemuMigrationCookiePtr mig,
virQEMUDriverPtr driver,
virDomainObjPtr dom)
{
qemuDomainObjPrivatePtr priv = dom->privateData;
if (mig->flags & QEMU_MIGRATION_COOKIE_LOCKSTATE) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Migration lockstate data already present"));
return -1;
}
if (virDomainObjGetState(dom, NULL) == VIR_DOMAIN_PAUSED) {
mig->lockState = g_strdup(priv->lockState);
} else {
if (virDomainLockProcessInquire(driver->lockManager, dom, &mig->lockState) < 0)
return -1;
}
mig->lockDriver = g_strdup(virLockManagerPluginGetName(driver->lockManager));
mig->flags |= QEMU_MIGRATION_COOKIE_LOCKSTATE;
mig->flagsMandatory |= QEMU_MIGRATION_COOKIE_LOCKSTATE;
return 0;
}
int
qemuMigrationCookieAddPersistent(qemuMigrationCookiePtr mig,
virDomainDefPtr *def)
{
if (mig->flags & QEMU_MIGRATION_COOKIE_PERSISTENT) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Migration persistent data already present"));
return -1;
}
if (!def || !*def)
return 0;
mig->persistent = *def;
*def = NULL;
mig->flags |= QEMU_MIGRATION_COOKIE_PERSISTENT;
mig->flagsMandatory |= QEMU_MIGRATION_COOKIE_PERSISTENT;
return 0;
}
virDomainDefPtr
qemuMigrationCookieGetPersistent(qemuMigrationCookiePtr mig)
{
virDomainDefPtr def = mig->persistent;
mig->persistent = NULL;
mig->flags &= ~QEMU_MIGRATION_COOKIE_PERSISTENT;
mig->flagsMandatory &= ~QEMU_MIGRATION_COOKIE_PERSISTENT;
return def;
}
static int
qemuMigrationCookieAddNetwork(qemuMigrationCookiePtr mig,
virQEMUDriverPtr driver,
virDomainObjPtr dom)
{
if (mig->flags & QEMU_MIGRATION_COOKIE_NETWORK) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Network migration data already present"));
return -1;
}
if (dom->def->nnets > 0) {
mig->network = qemuMigrationCookieNetworkAlloc(driver, dom->def);
if (!mig->network)
return -1;
mig->flags |= QEMU_MIGRATION_COOKIE_NETWORK;
}
return 0;
}
static int
qemuMigrationCookieAddNBD(qemuMigrationCookiePtr mig,
virQEMUDriverPtr driver,
virDomainObjPtr vm)
{
qemuDomainObjPrivatePtr priv = vm->privateData;
g_autoptr(GHashTable) stats = virHashNew(g_free);
bool blockdev = virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV);
size_t i;
int rc;
/* It is not a bug if there already is a NBD data */
qemuMigrationCookieNBDFree(mig->nbd);
mig->nbd = g_new0(qemuMigrationCookieNBD, 1);
mig->nbd->port = priv->nbdPort;
mig->flags |= QEMU_MIGRATION_COOKIE_NBD;
if (vm->def->ndisks == 0)
return 0;
mig->nbd->disks = g_new0(struct qemuMigrationCookieNBDDisk, vm->def->ndisks);
mig->nbd->ndisks = 0;
if (qemuDomainObjEnterMonitorAsync(driver, vm, priv->job.asyncJob) < 0)
return -1;
if (blockdev)
rc = qemuMonitorBlockStatsUpdateCapacityBlockdev(priv->mon, stats);
else
rc = qemuMonitorBlockStatsUpdateCapacity(priv->mon, stats, false);
if (qemuDomainObjExitMonitor(driver, vm) < 0 || rc < 0)
return -1;
for (i = 0; i < vm->def->ndisks; i++) {
virDomainDiskDefPtr disk = vm->def->disks[i];
qemuBlockStats *entry;
if (blockdev) {
if (!(entry = virHashLookup(stats, disk->src->nodeformat)))
continue;
} else {
if (!disk->info.alias ||
!(entry = virHashLookup(stats, disk->info.alias)))
continue;
}
mig->nbd->disks[mig->nbd->ndisks].target = g_strdup(disk->dst);
mig->nbd->disks[mig->nbd->ndisks].capacity = entry->capacity;
mig->nbd->ndisks++;
}
return 0;
}
static int
qemuMigrationCookieAddStatistics(qemuMigrationCookiePtr mig,
virDomainObjPtr vm)
{
qemuDomainObjPrivatePtr priv = vm->privateData;
if (!priv->job.completed)
return 0;
g_clear_pointer(&mig->jobInfo, qemuDomainJobInfoFree);
mig->jobInfo = qemuDomainJobInfoCopy(priv->job.completed);
mig->flags |= QEMU_MIGRATION_COOKIE_STATS;
return 0;
}
static int
qemuMigrationCookieAddCPU(qemuMigrationCookiePtr mig,
virDomainObjPtr vm)
{
if (mig->cpu)
return 0;
if (!(mig->cpu = virCPUDefCopy(vm->def->cpu)))
return -1;
if (qemuDomainMakeCPUMigratable(mig->cpu) < 0)
return -1;
mig->flags |= QEMU_MIGRATION_COOKIE_CPU;
return 0;
}
static void
qemuMigrationCookieAddAllowReboot(qemuMigrationCookiePtr mig,
virDomainObjPtr vm)
{
qemuDomainObjPrivatePtr priv = vm->privateData;
mig->allowReboot = priv->allowReboot;
mig->flags |= QEMU_MIGRATION_COOKIE_ALLOW_REBOOT;
}
static int
qemuMigrationCookieAddCaps(qemuMigrationCookiePtr mig,
virDomainObjPtr vm,
qemuMigrationParty party)
{
qemuDomainObjPrivatePtr priv = vm->privateData;
qemuMigrationCookieCapsFree(mig->caps);
mig->caps = g_new0(qemuMigrationCookieCaps, 1);
if (priv->migrationCaps)
mig->caps->supported = virBitmapNewCopy(priv->migrationCaps);
else
mig->caps->supported = virBitmapNew(0);
mig->caps->automatic = qemuMigrationParamsGetAlwaysOnCaps(party);
mig->flags |= QEMU_MIGRATION_COOKIE_CAPS;
return 0;
}
static void
qemuMigrationCookieGraphicsXMLFormat(virBufferPtr buf,
qemuMigrationCookieGraphicsPtr grap)
{
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
virBufferAsprintf(&attrBuf, " type='%s' port='%d' listen='%s'",
virDomainGraphicsTypeToString(grap->type),
grap->port, grap->listen);
if (grap->type == VIR_DOMAIN_GRAPHICS_TYPE_SPICE)
virBufferAsprintf(&attrBuf, " tlsPort='%d'", grap->tlsPort);
virBufferEscapeString(&childBuf, "\n", grap->tlsSubject);
virXMLFormatElement(buf, "graphics", &attrBuf, &childBuf);
}
static void
qemuMigrationCookieNetworkXMLFormat(virBufferPtr buf,
qemuMigrationCookieNetworkPtr optr)
{
g_auto(virBuffer) interfaceBuf = VIR_BUFFER_INIT_CHILD(buf);
size_t i;
for (i = 0; i < optr->nnets; i++) {
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(&interfaceBuf);
/* If optr->net[i].vporttype is not set, there is nothing to transfer */
if (optr->net[i].vporttype == VIR_NETDEV_VPORT_PROFILE_NONE)
continue;
virBufferAsprintf(&attrBuf, " index='%zu' vporttype='%s'",
i, virNetDevVPortTypeToString(optr->net[i].vporttype));
virBufferEscapeString(&childBuf, "%s\n",
optr->net[i].portdata);
virXMLFormatElement(&interfaceBuf, "interface", &attrBuf, &childBuf);
}
virXMLFormatElement(buf, "network", NULL, &interfaceBuf);
}
static void
qemuMigrationCookieStatisticsXMLFormat(virBufferPtr buf,
qemuDomainJobInfoPtr jobInfo)
{
qemuMonitorMigrationStats *stats = &jobInfo->stats.mig;
virBufferAddLit(buf, "\n");
virBufferAdjustIndent(buf, 2);
virBufferAsprintf(buf, "%llu\n", jobInfo->started);
virBufferAsprintf(buf, "%llu\n", jobInfo->stopped);
virBufferAsprintf(buf, "%llu\n", jobInfo->sent);
if (jobInfo->timeDeltaSet)
virBufferAsprintf(buf, "%lld\n", jobInfo->timeDelta);
virBufferAsprintf(buf, "<%1$s>%2$llu%1$s>\n",
VIR_DOMAIN_JOB_TIME_ELAPSED,
jobInfo->timeElapsed);
if (stats->downtime_set)
virBufferAsprintf(buf, "<%1$s>%2$llu%1$s>\n",
VIR_DOMAIN_JOB_DOWNTIME,
stats->downtime);
if (stats->setup_time_set)
virBufferAsprintf(buf, "<%1$s>%2$llu%1$s>\n",
VIR_DOMAIN_JOB_SETUP_TIME,
stats->setup_time);
virBufferAsprintf(buf, "<%1$s>%2$llu%1$s>\n",
VIR_DOMAIN_JOB_MEMORY_TOTAL,
stats->ram_total);
virBufferAsprintf(buf, "<%1$s>%2$llu%1$s>\n",
VIR_DOMAIN_JOB_MEMORY_PROCESSED,
stats->ram_transferred);
virBufferAsprintf(buf, "<%1$s>%2$llu%1$s>\n",
VIR_DOMAIN_JOB_MEMORY_REMAINING,
stats->ram_remaining);
virBufferAsprintf(buf, "<%1$s>%2$llu%1$s>\n",
VIR_DOMAIN_JOB_MEMORY_BPS,
stats->ram_bps);
if (stats->ram_duplicate_set) {
virBufferAsprintf(buf, "<%1$s>%2$llu%1$s>\n",
VIR_DOMAIN_JOB_MEMORY_CONSTANT,
stats->ram_duplicate);
virBufferAsprintf(buf, "<%1$s>%2$llu%1$s>\n",
VIR_DOMAIN_JOB_MEMORY_NORMAL,
stats->ram_normal);
virBufferAsprintf(buf, "<%1$s>%2$llu%1$s>\n",
VIR_DOMAIN_JOB_MEMORY_NORMAL_BYTES,
stats->ram_normal_bytes);
}
virBufferAsprintf(buf, "<%1$s>%2$llu%1$s>\n",
VIR_DOMAIN_JOB_MEMORY_DIRTY_RATE,
stats->ram_dirty_rate);
virBufferAsprintf(buf, "<%1$s>%2$llu%1$s>\n",
VIR_DOMAIN_JOB_MEMORY_ITERATION,
stats->ram_iteration);
virBufferAsprintf(buf, "<%1$s>%2$llu%1$s>\n",
VIR_DOMAIN_JOB_MEMORY_POSTCOPY_REQS,
stats->ram_postcopy_reqs);
virBufferAsprintf(buf, "<%1$s>%2$llu%1$s>\n",
VIR_DOMAIN_JOB_MEMORY_PAGE_SIZE,
stats->ram_page_size);
virBufferAsprintf(buf, "<%1$s>%2$llu%1$s>\n",
VIR_DOMAIN_JOB_DISK_TOTAL,
stats->disk_total);
virBufferAsprintf(buf, "<%1$s>%2$llu%1$s>\n",
VIR_DOMAIN_JOB_DISK_PROCESSED,
stats->disk_transferred);
virBufferAsprintf(buf, "<%1$s>%2$llu%1$s>\n",
VIR_DOMAIN_JOB_DISK_REMAINING,
stats->disk_remaining);
virBufferAsprintf(buf, "<%1$s>%2$llu%1$s>\n",
VIR_DOMAIN_JOB_DISK_BPS,
stats->disk_bps);
if (stats->xbzrle_set) {
virBufferAsprintf(buf, "<%1$s>%2$llu%1$s>\n",
VIR_DOMAIN_JOB_COMPRESSION_CACHE,
stats->xbzrle_cache_size);
virBufferAsprintf(buf, "<%1$s>%2$llu%1$s>\n",
VIR_DOMAIN_JOB_COMPRESSION_BYTES,
stats->xbzrle_bytes);
virBufferAsprintf(buf, "<%1$s>%2$llu%1$s>\n",
VIR_DOMAIN_JOB_COMPRESSION_PAGES,
stats->xbzrle_pages);
virBufferAsprintf(buf, "<%1$s>%2$llu%1$s>\n",
VIR_DOMAIN_JOB_COMPRESSION_CACHE_MISSES,
stats->xbzrle_cache_miss);
virBufferAsprintf(buf, "<%1$s>%2$llu%1$s>\n",
VIR_DOMAIN_JOB_COMPRESSION_OVERFLOW,
stats->xbzrle_overflow);
}
virBufferAsprintf(buf, "<%1$s>%2$d%1$s>\n",
VIR_DOMAIN_JOB_AUTO_CONVERGE_THROTTLE,
stats->cpu_throttle_percentage);
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "\n");
}
static void
qemuMigrationCookieCapsXMLFormat(virBufferPtr buf,
qemuMigrationCookieCapsPtr caps)
{
qemuMigrationCapability cap;
virBufferAddLit(buf, "\n");
virBufferAdjustIndent(buf, 2);
for (cap = 0; cap < QEMU_MIGRATION_CAP_LAST; cap++) {
bool supported = false;
bool automatic = false;
ignore_value(virBitmapGetBit(caps->supported, cap, &supported));
ignore_value(virBitmapGetBit(caps->automatic, cap, &automatic));
if (supported) {
virBufferAsprintf(buf, "\n",
qemuMigrationCapabilityTypeToString(cap),
automatic ? "yes" : "no");
}
}
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "\n");
}
static void
qemuMigrationCookieNBDXMLFormat(qemuMigrationCookieNBDPtr nbd,
virBufferPtr buf)
{
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
size_t i;
if (nbd->port)
virBufferAsprintf(&attrBuf, " port='%d'", nbd->port);
for (i = 0; i < nbd->ndisks; i++) {
virBufferEscapeString(&childBuf, "disks[i].target);
virBufferAsprintf(&childBuf, " capacity='%llu'/>\n", nbd->disks[i].capacity);
}
virXMLFormatElement(buf, "nbd", &attrBuf, &childBuf);
}
static void
qemuMigrationCookieBlockDirtyBitmapsFormat(virBufferPtr buf,
GSList *bitmaps)
{
g_auto(virBuffer) disksBuf = VIR_BUFFER_INIT_CHILD(buf);
GSList *nextdisk;
for (nextdisk = bitmaps; nextdisk; nextdisk = nextdisk->next) {
qemuMigrationBlockDirtyBitmapsDiskPtr disk = nextdisk->data;
g_auto(virBuffer) diskAttrBuf = VIR_BUFFER_INITIALIZER;
g_auto(virBuffer) diskChildBuf = VIR_BUFFER_INIT_CHILD(&disksBuf);
bool hasBitmaps = false;
GSList *nextbitmap;
if (disk->skip || !disk->bitmaps)
continue;
for (nextbitmap = disk->bitmaps; nextbitmap; nextbitmap = nextbitmap->next) {
qemuMigrationBlockDirtyBitmapsDiskBitmapPtr bitmap = nextbitmap->data;
if (bitmap->skip)
continue;
virBufferAsprintf(&diskChildBuf,
"\n",
bitmap->bitmapname, bitmap->alias);
hasBitmaps = true;
}
if (!hasBitmaps)
continue;
virBufferAsprintf(&diskAttrBuf, " target='%s'", disk->target);
virXMLFormatElement(&disksBuf, "disk", &diskAttrBuf, &diskChildBuf);
}
virXMLFormatElement(buf, "blockDirtyBitmaps", NULL, &disksBuf);
}
int
qemuMigrationCookieXMLFormat(virQEMUDriverPtr driver,
virQEMUCapsPtr qemuCaps,
virBufferPtr buf,
qemuMigrationCookiePtr mig)
{
char uuidstr[VIR_UUID_STRING_BUFLEN];
char hostuuidstr[VIR_UUID_STRING_BUFLEN];
size_t i;
virUUIDFormat(mig->uuid, uuidstr);
virUUIDFormat(mig->localHostuuid, hostuuidstr);
virBufferAddLit(buf, "\n");
virBufferAdjustIndent(buf, 2);
virBufferEscapeString(buf, "%s\n", mig->name);
virBufferAsprintf(buf, "%s\n", uuidstr);
virBufferEscapeString(buf, "%s\n", mig->localHostname);
virBufferAsprintf(buf, "%s\n", hostuuidstr);
for (i = 0; i < QEMU_MIGRATION_COOKIE_FLAG_LAST; i++) {
if (mig->flagsMandatory & (1 << i))
virBufferAsprintf(buf, "\n",
qemuMigrationCookieFlagTypeToString(i));
}
if ((mig->flags & QEMU_MIGRATION_COOKIE_GRAPHICS) &&
mig->graphics)
qemuMigrationCookieGraphicsXMLFormat(buf, mig->graphics);
if ((mig->flags & QEMU_MIGRATION_COOKIE_LOCKSTATE) &&
mig->lockState) {
virBufferAsprintf(buf, "\n",
mig->lockDriver);
virBufferAdjustIndent(buf, 2);
virBufferAsprintf(buf, "%s\n",
mig->lockState);
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "\n");
}
if ((mig->flags & QEMU_MIGRATION_COOKIE_PERSISTENT) &&
mig->persistent) {
if (qemuDomainDefFormatBuf(driver,
qemuCaps,
mig->persistent,
VIR_DOMAIN_XML_INACTIVE |
VIR_DOMAIN_XML_SECURE |
VIR_DOMAIN_XML_MIGRATABLE,
buf) < 0)
return -1;
}
if ((mig->flags & QEMU_MIGRATION_COOKIE_NETWORK) && mig->network)
qemuMigrationCookieNetworkXMLFormat(buf, mig->network);
if ((mig->flags & QEMU_MIGRATION_COOKIE_NBD) && mig->nbd)
qemuMigrationCookieNBDXMLFormat(mig->nbd, buf);
if (mig->flags & QEMU_MIGRATION_COOKIE_STATS && mig->jobInfo)
qemuMigrationCookieStatisticsXMLFormat(buf, mig->jobInfo);
if (mig->flags & QEMU_MIGRATION_COOKIE_CPU && mig->cpu)
virCPUDefFormatBufFull(buf, mig->cpu, NULL);
if (mig->flags & QEMU_MIGRATION_COOKIE_ALLOW_REBOOT)
qemuDomainObjPrivateXMLFormatAllowReboot(buf, mig->allowReboot);
if (mig->flags & QEMU_MIGRATION_COOKIE_CAPS)
qemuMigrationCookieCapsXMLFormat(buf, mig->caps);
if (mig->flags & QEMU_MIGRATION_COOKIE_BLOCK_DIRTY_BITMAPS)
qemuMigrationCookieBlockDirtyBitmapsFormat(buf, mig->blockDirtyBitmaps);
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "\n");
return 0;
}
static qemuMigrationCookieGraphicsPtr
qemuMigrationCookieGraphicsXMLParse(xmlXPathContextPtr ctxt)
{
g_autoptr(qemuMigrationCookieGraphics) grap = g_new0(qemuMigrationCookieGraphics, 1);
g_autofree char *graphicstype = NULL;
if (!(graphicstype = virXPathString("string(./graphics/@type)", ctxt))) {
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("missing type attribute in migration data"));
return NULL;
}
if ((grap->type = virDomainGraphicsTypeFromString(graphicstype)) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unknown graphics type %s"), graphicstype);
return NULL;
}
if (virXPathInt("string(./graphics/@port)", ctxt, &grap->port) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("missing port attribute in migration data"));
return NULL;
}
if (grap->type == VIR_DOMAIN_GRAPHICS_TYPE_SPICE) {
if (virXPathInt("string(./graphics/@tlsPort)", ctxt, &grap->tlsPort) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("missing tlsPort attribute in migration data"));
return NULL;
}
}
if (!(grap->listen = virXPathString("string(./graphics/@listen)", ctxt))) {
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("missing listen attribute in migration data"));
return NULL;
}
/* Optional */
grap->tlsSubject = virXPathString("string(./graphics/cert[@info='subject']/@value)", ctxt);
return g_steal_pointer(&grap);
}
static qemuMigrationCookieNetworkPtr
qemuMigrationCookieNetworkXMLParse(xmlXPathContextPtr ctxt)
{
g_autoptr(qemuMigrationCookieNetwork) optr = g_new0(qemuMigrationCookieNetwork, 1);
size_t i;
int n;
g_autofree xmlNodePtr *interfaces = NULL;
VIR_XPATH_NODE_AUTORESTORE(ctxt)
if ((n = virXPathNodeSet("./network/interface", ctxt, &interfaces)) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("missing interface information"));
return NULL;
}
optr->nnets = n;
optr->net = g_new0(qemuMigrationCookieNetData, optr->nnets);
for (i = 0; i < n; i++) {
g_autofree char *vporttype = NULL;
/* portdata is optional, and may not exist */
ctxt->node = interfaces[i];
optr->net[i].portdata = virXPathString("string(./portdata[1])", ctxt);
if (!(vporttype = virXMLPropString(interfaces[i], "vporttype"))) {
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("missing vporttype attribute in migration data"));
return NULL;
}
optr->net[i].vporttype = virNetDevVPortTypeFromString(vporttype);
}
return g_steal_pointer(&optr);
}
static qemuMigrationCookieNBDPtr
qemuMigrationCookieNBDXMLParse(xmlXPathContextPtr ctxt)
{
g_autoptr(qemuMigrationCookieNBD) ret = g_new0(qemuMigrationCookieNBD, 1);
g_autofree char *port = NULL;
size_t i;
int n;
g_autofree xmlNodePtr *disks = NULL;
VIR_XPATH_NODE_AUTORESTORE(ctxt)
port = virXPathString("string(./nbd/@port)", ctxt);
if (port && virStrToLong_i(port, NULL, 10, &ret->port) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Malformed nbd port '%s'"),
port);
return NULL;
}
/* Now check if source sent a list of disks to prealloc. We might be
* talking to an older server, so it's not an error if the list is
* missing. */
if ((n = virXPathNodeSet("./nbd/disk", ctxt, &disks)) > 0) {
ret->disks = g_new0(struct qemuMigrationCookieNBDDisk, n);
ret->ndisks = n;
for (i = 0; i < n; i++) {
g_autofree char *capacity = NULL;
ctxt->node = disks[i];
if (!(ret->disks[i].target = virXPathString("string(./@target)", ctxt))) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Malformed disk target"));
return NULL;
}
capacity = virXPathString("string(./@capacity)", ctxt);
if (!capacity ||
virStrToLong_ull(capacity, NULL, 10,
&ret->disks[i].capacity) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Malformed disk capacity: '%s'"),
NULLSTR(capacity));
return NULL;
}
}
}
return g_steal_pointer(&ret);
}
static qemuDomainJobInfoPtr
qemuMigrationCookieStatisticsXMLParse(xmlXPathContextPtr ctxt)
{
qemuDomainJobInfoPtr jobInfo = NULL;
qemuMonitorMigrationStats *stats;
VIR_XPATH_NODE_AUTORESTORE(ctxt)
if (!(ctxt->node = virXPathNode("./statistics", ctxt)))
return NULL;
jobInfo = g_new0(qemuDomainJobInfo, 1);
stats = &jobInfo->stats.mig;
jobInfo->status = QEMU_DOMAIN_JOB_STATUS_COMPLETED;
virXPathULongLong("string(./started[1])", ctxt, &jobInfo->started);
virXPathULongLong("string(./stopped[1])", ctxt, &jobInfo->stopped);
virXPathULongLong("string(./sent[1])", ctxt, &jobInfo->sent);
if (virXPathLongLong("string(./delta[1])", ctxt, &jobInfo->timeDelta) == 0)
jobInfo->timeDeltaSet = true;
virXPathULongLong("string(./" VIR_DOMAIN_JOB_TIME_ELAPSED "[1])",
ctxt, &jobInfo->timeElapsed);
if (virXPathULongLong("string(./" VIR_DOMAIN_JOB_DOWNTIME "[1])",
ctxt, &stats->downtime) == 0)
stats->downtime_set = true;
if (virXPathULongLong("string(./" VIR_DOMAIN_JOB_SETUP_TIME "[1])",
ctxt, &stats->setup_time) == 0)
stats->setup_time_set = true;
virXPathULongLong("string(./" VIR_DOMAIN_JOB_MEMORY_TOTAL "[1])",
ctxt, &stats->ram_total);
virXPathULongLong("string(./" VIR_DOMAIN_JOB_MEMORY_PROCESSED "[1])",
ctxt, &stats->ram_transferred);
virXPathULongLong("string(./" VIR_DOMAIN_JOB_MEMORY_REMAINING "[1])",
ctxt, &stats->ram_remaining);
virXPathULongLong("string(./" VIR_DOMAIN_JOB_MEMORY_BPS "[1])",
ctxt, &stats->ram_bps);
if (virXPathULongLong("string(./" VIR_DOMAIN_JOB_MEMORY_CONSTANT "[1])",
ctxt, &stats->ram_duplicate) == 0)
stats->ram_duplicate_set = true;
virXPathULongLong("string(./" VIR_DOMAIN_JOB_MEMORY_NORMAL "[1])",
ctxt, &stats->ram_normal);
virXPathULongLong("string(./" VIR_DOMAIN_JOB_MEMORY_NORMAL_BYTES "[1])",
ctxt, &stats->ram_normal_bytes);
virXPathULongLong("string(./" VIR_DOMAIN_JOB_MEMORY_DIRTY_RATE "[1])",
ctxt, &stats->ram_dirty_rate);
virXPathULongLong("string(./" VIR_DOMAIN_JOB_MEMORY_ITERATION "[1])",
ctxt, &stats->ram_iteration);
virXPathULongLong("string(./" VIR_DOMAIN_JOB_MEMORY_POSTCOPY_REQS "[1])",
ctxt, &stats->ram_postcopy_reqs);
virXPathULongLong("string(./" VIR_DOMAIN_JOB_MEMORY_PAGE_SIZE "[1])",
ctxt, &stats->ram_page_size);
virXPathULongLong("string(./" VIR_DOMAIN_JOB_DISK_TOTAL "[1])",
ctxt, &stats->disk_total);
virXPathULongLong("string(./" VIR_DOMAIN_JOB_DISK_PROCESSED "[1])",
ctxt, &stats->disk_transferred);
virXPathULongLong("string(./" VIR_DOMAIN_JOB_DISK_REMAINING "[1])",
ctxt, &stats->disk_remaining);
virXPathULongLong("string(./" VIR_DOMAIN_JOB_DISK_BPS "[1])",
ctxt, &stats->disk_bps);
if (virXPathULongLong("string(./" VIR_DOMAIN_JOB_COMPRESSION_CACHE "[1])",
ctxt, &stats->xbzrle_cache_size) == 0)
stats->xbzrle_set = true;
virXPathULongLong("string(./" VIR_DOMAIN_JOB_COMPRESSION_BYTES "[1])",
ctxt, &stats->xbzrle_bytes);
virXPathULongLong("string(./" VIR_DOMAIN_JOB_COMPRESSION_PAGES "[1])",
ctxt, &stats->xbzrle_pages);
virXPathULongLong("string(./" VIR_DOMAIN_JOB_COMPRESSION_CACHE_MISSES "[1])",
ctxt, &stats->xbzrle_cache_miss);
virXPathULongLong("string(./" VIR_DOMAIN_JOB_COMPRESSION_OVERFLOW "[1])",
ctxt, &stats->xbzrle_overflow);
virXPathInt("string(./" VIR_DOMAIN_JOB_AUTO_CONVERGE_THROTTLE "[1])",
ctxt, &stats->cpu_throttle_percentage);
return jobInfo;
}
static qemuMigrationCookieCapsPtr
qemuMigrationCookieCapsXMLParse(xmlXPathContextPtr ctxt)
{
g_autoptr(qemuMigrationCookieCaps) caps = g_new0(qemuMigrationCookieCaps, 1);
g_autofree xmlNodePtr *nodes = NULL;
size_t i;
int n;
caps->supported = virBitmapNew(QEMU_MIGRATION_CAP_LAST);
caps->automatic = virBitmapNew(QEMU_MIGRATION_CAP_LAST);
if ((n = virXPathNodeSet("./capabilities[1]/cap", ctxt, &nodes)) < 0)
return NULL;
for (i = 0; i < n; i++) {
g_autofree char *name = NULL;
g_autofree char *automatic = NULL;
int cap;
if (!(name = virXMLPropString(nodes[i], "name"))) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("missing migration capability name"));
return NULL;
}
if ((cap = qemuMigrationCapabilityTypeFromString(name)) < 0)
VIR_DEBUG("unknown migration capability '%s'", name);
else
ignore_value(virBitmapSetBit(caps->supported, cap));
if ((automatic = virXMLPropString(nodes[i], "auto")) &&
STREQ(automatic, "yes"))
ignore_value(virBitmapSetBit(caps->automatic, cap));
}
return g_steal_pointer(&caps);
}
/**
* qemuMigrationCookieXMLParseMandatoryFeatures:
*
* Check to ensure all mandatory features from XML are also present in 'flags'.
*/
static int
qemuMigrationCookieXMLParseMandatoryFeatures(xmlXPathContextPtr ctxt,
unsigned int flags)
{
g_autofree xmlNodePtr *nodes = NULL;
size_t i;
ssize_t n;
if ((n = virXPathNodeSet("./feature", ctxt, &nodes)) < 0)
return -1;
for (i = 0; i < n; i++) {
int val;
g_autofree char *str = virXMLPropString(nodes[i], "name");
if (!str) {
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("missing feature name"));
return -1;
}
if ((val = qemuMigrationCookieFlagTypeFromString(str)) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unknown migration cookie feature %s"), str);
return -1;
}
if ((flags & (1 << val)) == 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unsupported migration cookie feature %s"), str);
return -1;
}
}
return 0;
}
static int
qemuMigrationCookieBlockDirtyBitmapsParse(xmlXPathContextPtr ctxt,
qemuMigrationCookiePtr mig)
{
g_autoslist(qemuMigrationBlockDirtyBitmapsDisk) disks = NULL;
g_autofree xmlNodePtr *disknodes = NULL;
int ndisknodes;
size_t i;
VIR_XPATH_NODE_AUTORESTORE(ctxt)
if ((ndisknodes = virXPathNodeSet("./blockDirtyBitmaps/disk", ctxt, &disknodes)) < 0)
return -1;
for (i = 0; i < ndisknodes; i++) {
g_autoslist(qemuMigrationBlockDirtyBitmapsDiskBitmap) bitmaps = NULL;
qemuMigrationBlockDirtyBitmapsDiskPtr disk;
g_autofree xmlNodePtr *bitmapnodes = NULL;
int nbitmapnodes;
size_t j;
ctxt->node = disknodes[i];
if ((nbitmapnodes = virXPathNodeSet("./bitmap", ctxt, &bitmapnodes)) < 0)
return -1;
for (j = 0; j < nbitmapnodes; j++) {
qemuMigrationBlockDirtyBitmapsDiskBitmapPtr bitmap;
bitmap = g_new0(qemuMigrationBlockDirtyBitmapsDiskBitmap, 1);
bitmap->bitmapname = virXMLPropString(bitmapnodes[j], "name");
bitmap->alias = virXMLPropString(bitmapnodes[j], "alias");
bitmaps = g_slist_prepend(bitmaps, bitmap);
if (!bitmap->bitmapname || !bitmap->alias) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("malformed in migration cookie"));
return -1;
}
}
disk = g_new0(qemuMigrationBlockDirtyBitmapsDisk, 1);
disk->target = virXMLPropString(disknodes[i], "target");
disk->bitmaps = g_slist_reverse(g_steal_pointer(&bitmaps));
disks = g_slist_prepend(disks, disk);
if (!disk->target) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("malformed in migration cookie"));
return -1;
}
}
mig->blockDirtyBitmaps = g_slist_reverse(g_steal_pointer(&disks));
return 0;
}
static int
qemuMigrationCookieXMLParse(qemuMigrationCookiePtr mig,
virQEMUDriverPtr driver,
virQEMUCapsPtr qemuCaps,
xmlDocPtr doc,
xmlXPathContextPtr ctxt,
unsigned int flags)
{
g_autofree char *name = NULL;
g_autofree char *uuid = NULL;
g_autofree char *hostuuid = NULL;
char localdomuuid[VIR_UUID_STRING_BUFLEN];
/* We don't store the uuid, name, hostname, or hostuuid
* values. We just compare them to local data to do some
* sanity checking on migration operation
*/
/* Extract domain name */
if (!(name = virXPathString("string(./name[1])", ctxt))) {
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("missing name element in migration data"));
return -1;
}
if (STRNEQ(name, mig->name)) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Incoming cookie data had unexpected name %s vs %s"),
name, mig->name);
return -1;
}
/* Extract domain uuid */
if (!(uuid = virXPathString("string(./uuid[1])", ctxt))) {
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("missing uuid element in migration data"));
return -1;
}
virUUIDFormat(mig->uuid, localdomuuid);
if (STRNEQ(uuid, localdomuuid)) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Incoming cookie data had unexpected UUID %s vs %s"),
uuid, localdomuuid);
return -1;
}
if (!(mig->remoteHostname = virXPathString("string(./hostname[1])", ctxt))) {
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("missing hostname element in migration data"));
return -1;
}
/* Historically, this is the place where we checked whether remoteHostname
* and localHostname are the same. But even if they were, it doesn't mean
* the domain is migrating onto the same host. Rely on UUID which can tell
* for sure. */
/* Check & forbid localhost migration */
if (!(hostuuid = virXPathString("string(./hostuuid[1])", ctxt))) {
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("missing hostuuid element in migration data"));
return -1;
}
if (virUUIDParse(hostuuid, mig->remoteHostuuid) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("malformed hostuuid element in migration data"));
return -1;
}
if (memcmp(mig->remoteHostuuid, mig->localHostuuid, VIR_UUID_BUFLEN) == 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Attempt to migrate guest to the same host %s"),
hostuuid);
return -1;
}
if (qemuMigrationCookieXMLParseMandatoryFeatures(ctxt, flags) < 0)
return -1;
if ((flags & QEMU_MIGRATION_COOKIE_GRAPHICS) &&
virXPathBoolean("count(./graphics) > 0", ctxt) &&
(!(mig->graphics = qemuMigrationCookieGraphicsXMLParse(ctxt))))
return -1;
if ((flags & QEMU_MIGRATION_COOKIE_LOCKSTATE) &&
virXPathBoolean("count(./lockstate) > 0", ctxt)) {
g_autofree char *lockState = NULL;
mig->lockDriver = virXPathString("string(./lockstate[1]/@driver)", ctxt);
if (!mig->lockDriver) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Missing lock driver name in migration cookie"));
return -1;
}
lockState = virXPathString("string(./lockstate[1]/leases[1])", ctxt);
if (STRNEQ_NULLABLE(lockState, ""))
mig->lockState = g_steal_pointer(&lockState);
}
if ((flags & QEMU_MIGRATION_COOKIE_PERSISTENT) &&
virXPathBoolean("count(./domain) > 0", ctxt)) {
g_autofree xmlNodePtr *nodes = NULL;
if ((virXPathNodeSet("./domain", ctxt, &nodes)) != 1) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Too many domain elements in migration cookie"));
return -1;
}
mig->persistent = virDomainDefParseNode(doc, nodes[0],
driver->xmlopt, qemuCaps,
VIR_DOMAIN_DEF_PARSE_INACTIVE |
VIR_DOMAIN_DEF_PARSE_ABI_UPDATE_MIGRATION |
VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE);
if (!mig->persistent)
return -1;
}
if ((flags & QEMU_MIGRATION_COOKIE_NETWORK) &&
virXPathBoolean("count(./network) > 0", ctxt) &&
(!(mig->network = qemuMigrationCookieNetworkXMLParse(ctxt))))
return -1;
if (flags & QEMU_MIGRATION_COOKIE_NBD &&
virXPathBoolean("boolean(./nbd)", ctxt) &&
(!(mig->nbd = qemuMigrationCookieNBDXMLParse(ctxt))))
return -1;
if (flags & QEMU_MIGRATION_COOKIE_STATS &&
virXPathBoolean("boolean(./statistics)", ctxt) &&
(!(mig->jobInfo = qemuMigrationCookieStatisticsXMLParse(ctxt))))
return -1;
if (flags & QEMU_MIGRATION_COOKIE_CPU &&
virCPUDefParseXML(ctxt, "./cpu[1]", VIR_CPU_TYPE_GUEST, &mig->cpu,
false) < 0)
return -1;
if (flags & QEMU_MIGRATION_COOKIE_ALLOW_REBOOT &&
qemuDomainObjPrivateXMLParseAllowReboot(ctxt, &mig->allowReboot) < 0)
return -1;
if (flags & QEMU_MIGRATION_COOKIE_CAPS &&
!(mig->caps = qemuMigrationCookieCapsXMLParse(ctxt)))
return -1;
if (flags & QEMU_MIGRATION_COOKIE_BLOCK_DIRTY_BITMAPS &&
virXPathBoolean("boolean(./blockDirtyBitmaps)", ctxt) &&
qemuMigrationCookieBlockDirtyBitmapsParse(ctxt, mig) < 0)
return -1;
return 0;
}
static int
qemuMigrationCookieXMLParseStr(qemuMigrationCookiePtr mig,
virQEMUDriverPtr driver,
virQEMUCapsPtr qemuCaps,
const char *xml,
unsigned int flags)
{
xmlDocPtr doc = NULL;
xmlXPathContextPtr ctxt = NULL;
int ret = -1;
VIR_DEBUG("xml=%s", NULLSTR(xml));
if (!(doc = virXMLParseStringCtxt(xml, _("(qemu_migration_cookie)"), &ctxt)))
goto cleanup;
ret = qemuMigrationCookieXMLParse(mig, driver, qemuCaps, doc, ctxt, flags);
cleanup:
xmlXPathFreeContext(ctxt);
xmlFreeDoc(doc);
return ret;
}
int
qemuMigrationCookieFormat(qemuMigrationCookiePtr mig,
virQEMUDriverPtr driver,
virDomainObjPtr dom,
qemuMigrationParty party,
char **cookieout,
int *cookieoutlen,
unsigned int flags)
{
qemuDomainObjPrivatePtr priv = dom->privateData;
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
if (!cookieout || !cookieoutlen)
return 0;
*cookieoutlen = 0;
if (flags & QEMU_MIGRATION_COOKIE_GRAPHICS &&
qemuMigrationCookieAddGraphics(mig, driver, dom) < 0)
return -1;
if (flags & QEMU_MIGRATION_COOKIE_LOCKSTATE &&
qemuMigrationCookieAddLockstate(mig, driver, dom) < 0)
return -1;
if (flags & QEMU_MIGRATION_COOKIE_NETWORK &&
qemuMigrationCookieAddNetwork(mig, driver, dom) < 0) {
return -1;
}
if ((flags & QEMU_MIGRATION_COOKIE_NBD) &&
qemuMigrationCookieAddNBD(mig, driver, dom) < 0)
return -1;
if (flags & QEMU_MIGRATION_COOKIE_STATS &&
qemuMigrationCookieAddStatistics(mig, dom) < 0)
return -1;
if (flags & QEMU_MIGRATION_COOKIE_MEMORY_HOTPLUG)
mig->flagsMandatory |= QEMU_MIGRATION_COOKIE_MEMORY_HOTPLUG;
if (flags & QEMU_MIGRATION_COOKIE_CPU_HOTPLUG)
mig->flagsMandatory |= QEMU_MIGRATION_COOKIE_CPU_HOTPLUG;
if (flags & QEMU_MIGRATION_COOKIE_CPU &&
qemuMigrationCookieAddCPU(mig, dom) < 0)
return -1;
if (flags & QEMU_MIGRATION_COOKIE_ALLOW_REBOOT)
qemuMigrationCookieAddAllowReboot(mig, dom);
if (flags & QEMU_MIGRATION_COOKIE_CAPS &&
qemuMigrationCookieAddCaps(mig, dom, party) < 0)
return -1;
if (qemuMigrationCookieXMLFormat(driver, priv->qemuCaps, &buf, mig) < 0)
return -1;
*cookieoutlen = virBufferUse(&buf) + 1;
*cookieout = virBufferContentAndReset(&buf);
VIR_DEBUG("cookielen=%d cookie=%s", *cookieoutlen, *cookieout);
return 0;
}
qemuMigrationCookiePtr
qemuMigrationCookieParse(virQEMUDriverPtr driver,
const virDomainDef *def,
const char *origname,
qemuDomainObjPrivatePtr priv,
const char *cookiein,
int cookieinlen,
unsigned int flags)
{
g_autoptr(qemuMigrationCookie) mig = NULL;
/* Parse & validate incoming cookie (if any) */
if (cookiein && cookieinlen &&
cookiein[cookieinlen-1] != '\0') {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Migration cookie was not NULL terminated"));
return NULL;
}
VIR_DEBUG("cookielen=%d cookie='%s'", cookieinlen, NULLSTR(cookiein));
if (!(mig = qemuMigrationCookieNew(def, origname)))
return NULL;
if (cookiein && cookieinlen &&
qemuMigrationCookieXMLParseStr(mig,
driver,
priv ? priv->qemuCaps : NULL,
cookiein,
flags) < 0)
return NULL;
if (flags & QEMU_MIGRATION_COOKIE_PERSISTENT &&
mig->persistent &&
STRNEQ(def->name, mig->persistent->name)) {
g_free(mig->persistent->name);
mig->persistent->name = g_strdup(def->name);
}
if (mig->flags & QEMU_MIGRATION_COOKIE_LOCKSTATE) {
if (!mig->lockDriver) {
if (virLockManagerPluginUsesState(driver->lockManager)) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Missing %s lock state for migration cookie"),
virLockManagerPluginGetName(driver->lockManager));
return NULL;
}
} else if (STRNEQ(mig->lockDriver,
virLockManagerPluginGetName(driver->lockManager))) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Source host lock driver %s different from target %s"),
mig->lockDriver,
virLockManagerPluginGetName(driver->lockManager));
return NULL;
}
}
if (flags & QEMU_MIGRATION_COOKIE_STATS && mig->jobInfo && priv->job.current)
mig->jobInfo->operation = priv->job.current->operation;
return g_steal_pointer(&mig);
}
/**
* qemuMigrationCookieBlockDirtyBitmapsMatchDisks:
* @def: domain definition
* @disks: list of qemuMigrationBlockDirtyBitmapsDiskPtr
*
* Matches all of the @disks to the actual domain disk definition objects
* by looking up the target.
*/
int
qemuMigrationCookieBlockDirtyBitmapsMatchDisks(virDomainDefPtr def,
GSList *disks)
{
GSList *next;
for (next = disks; next; next = next->next) {
qemuMigrationBlockDirtyBitmapsDiskPtr disk = next->data;
if (!(disk->disk = virDomainDiskByTarget(def, disk->target))) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Can't find disk '%s' in domain definition"),
disk->target);
return -1;
}
disk->nodename = disk->disk->src->nodeformat;
}
return 0;
}
/**
* qemuMigrationCookieBlockDirtyBitmapsToParams:
* @disks: list of qemuMigrationBlockDirtyBitmapsDisk
* @mapping: filled with resulting mapping
*
* Converts @disks into the arguments for 'block-bitmap-mapping' migration
* parameter.
*/
int
qemuMigrationCookieBlockDirtyBitmapsToParams(GSList *disks,
virJSONValuePtr *mapping)
{
g_autoptr(virJSONValue) map = virJSONValueNewArray();
bool hasDisks = false;
GSList *nextdisk;
for (nextdisk = disks; nextdisk; nextdisk = nextdisk->next) {
qemuMigrationBlockDirtyBitmapsDiskPtr disk = nextdisk->data;
g_autoptr(virJSONValue) jsondisk = NULL;
g_autoptr(virJSONValue) jsonbitmaps = virJSONValueNewArray();
bool hasBitmaps = false;
GSList *nextbitmap;
if (disk->skip || !disk->bitmaps)
continue;
for (nextbitmap = disk->bitmaps; nextbitmap; nextbitmap = nextbitmap->next) {
qemuMigrationBlockDirtyBitmapsDiskBitmapPtr bitmap = nextbitmap->data;
g_autoptr(virJSONValue) jsonbitmap = NULL;
g_autoptr(virJSONValue) transform = NULL;
const char *bitmapname = bitmap->sourcebitmap;
if (bitmap->skip)
continue;
/* if there isn't an override, use the real name */
if (!bitmapname)
bitmapname = bitmap->bitmapname;
if (bitmap->persistent == VIR_TRISTATE_BOOL_YES) {
if (virJSONValueObjectCreate(&transform,
"b:persistent", true, NULL) < 0)
return -1;
}
if (virJSONValueObjectCreate(&jsonbitmap,
"s:name", bitmapname,
"s:alias", bitmap->alias,
"A:transform", &transform,
NULL) < 0)
return -1;
if (virJSONValueArrayAppend(jsonbitmaps, &jsonbitmap) < 0)
return -1;
hasBitmaps = true;
}
if (!hasBitmaps)
continue;
if (virJSONValueObjectCreate(&jsondisk,
"s:node-name", disk->nodename,
"s:alias", disk->target,
"a:bitmaps", &jsonbitmaps,
NULL) < 0)
return -1;
if (virJSONValueArrayAppend(map, &jsondisk) < 0)
return -1;
hasDisks = true;
}
if (!hasDisks)
return 0;
*mapping = g_steal_pointer(&map);
return 0;
}