libvirt/src/qemu/qemu_migration_cookie.c
Peter Krempa 7c6d462aae qemuMigrationCookieNBDXMLFormat: Format empty <nbd/> element
Commit 518be41aaa3 refactored qemuMigrationCookieNBDXMLFormat to use
virXMLFormatElement which in comparison to the previous code doesn't
format the element if it's empty.

Unfortunately some crusty bits of our migration code use questionable
logic to assert use of the old-style storage migration parameters which
breaks if no disks are being migrated and the <nbd/> element is not
present.

While later patches will fix the code, re-instate formatting of empty
<nbd/> for increased compatibility.

Signed-off-by: Peter Krempa <pkrempa@redhat.com>
Reviewed-by: Andrea Bolognani <abologna@redhat.com>
Tested-by: Andrea Bolognani <abologna@redhat.com>
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
2021-04-26 17:22:30 +02:00

1695 lines
55 KiB
C

/*
* 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
* <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#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(qemuMigrationCookieGraphics *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(qemuMigrationCookieNetwork *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(qemuMigrationCookieNBD *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(qemuMigrationCookieCaps *caps)
{
if (!caps)
return;
virBitmapFree(caps->supported);
virBitmapFree(caps->automatic);
g_free(caps);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuMigrationCookieCaps,
qemuMigrationCookieCapsFree);
static void
qemuMigrationBlockDirtyBitmapsDiskBitmapFree(qemuMigrationBlockDirtyBitmapsDiskBitmap *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(qemuMigrationBlockDirtyBitmapsDisk *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(qemuMigrationCookie *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 qemuMigrationCookieGraphics *
qemuMigrationCookieGraphicsSpiceAlloc(virQEMUDriver *driver,
virDomainGraphicsDef *def,
virDomainGraphicsListenDef *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 qemuMigrationCookieNetwork *
qemuMigrationCookieNetworkAlloc(virQEMUDriver *driver G_GNUC_UNUSED,
virDomainDef *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++) {
virDomainNetDef *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);
}
qemuMigrationCookie *
qemuMigrationCookieNew(const virDomainDef *def,
const char *origname)
{
qemuMigrationCookie *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(qemuMigrationCookie *mig,
virQEMUDriver *driver,
virDomainObj *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) {
virDomainGraphicsListenDef *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(qemuMigrationCookie *mig,
virQEMUDriver *driver,
virDomainObj *dom)
{
qemuDomainObjPrivate *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(qemuMigrationCookie *mig,
virDomainDef **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 = g_steal_pointer(&*def);
mig->flags |= QEMU_MIGRATION_COOKIE_PERSISTENT;
mig->flagsMandatory |= QEMU_MIGRATION_COOKIE_PERSISTENT;
return 0;
}
virDomainDef *
qemuMigrationCookieGetPersistent(qemuMigrationCookie *mig)
{
virDomainDef *def = mig->persistent;
mig->persistent = NULL;
mig->flags &= ~QEMU_MIGRATION_COOKIE_PERSISTENT;
mig->flagsMandatory &= ~QEMU_MIGRATION_COOKIE_PERSISTENT;
return def;
}
static int
qemuMigrationCookieAddNetwork(qemuMigrationCookie *mig,
virQEMUDriver *driver,
virDomainObj *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(qemuMigrationCookie *mig,
virQEMUDriver *driver,
virDomainObj *vm)
{
qemuDomainObjPrivate *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++) {
virDomainDiskDef *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(qemuMigrationCookie *mig,
virDomainObj *vm)
{
qemuDomainObjPrivate *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(qemuMigrationCookie *mig,
virDomainObj *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(qemuMigrationCookie *mig,
virDomainObj *vm)
{
qemuDomainObjPrivate *priv = vm->privateData;
mig->allowReboot = priv->allowReboot;
mig->flags |= QEMU_MIGRATION_COOKIE_ALLOW_REBOOT;
}
static int
qemuMigrationCookieAddCaps(qemuMigrationCookie *mig,
virDomainObj *vm,
qemuMigrationParty party)
{
qemuDomainObjPrivate *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(virBuffer *buf,
qemuMigrationCookieGraphics *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, "<cert info='subject' value='%s'/>\n", grap->tlsSubject);
virXMLFormatElement(buf, "graphics", &attrBuf, &childBuf);
}
static void
qemuMigrationCookieNetworkXMLFormat(virBuffer *buf,
qemuMigrationCookieNetwork *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, "<portdata>%s</portdata>\n",
optr->net[i].portdata);
virXMLFormatElement(&interfaceBuf, "interface", &attrBuf, &childBuf);
}
virXMLFormatElement(buf, "network", NULL, &interfaceBuf);
}
static void
qemuMigrationCookieStatisticsXMLFormat(virBuffer *buf,
qemuDomainJobInfo *jobInfo)
{
qemuMonitorMigrationStats *stats = &jobInfo->stats.mig;
virBufferAddLit(buf, "<statistics>\n");
virBufferAdjustIndent(buf, 2);
virBufferAsprintf(buf, "<started>%llu</started>\n", jobInfo->started);
virBufferAsprintf(buf, "<stopped>%llu</stopped>\n", jobInfo->stopped);
virBufferAsprintf(buf, "<sent>%llu</sent>\n", jobInfo->sent);
if (jobInfo->timeDeltaSet)
virBufferAsprintf(buf, "<delta>%lld</delta>\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, "</statistics>\n");
}
static void
qemuMigrationCookieCapsXMLFormat(virBuffer *buf,
qemuMigrationCookieCaps *caps)
{
qemuMigrationCapability cap;
virBufferAddLit(buf, "<capabilities>\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, "<cap name='%s' auto='%s'/>\n",
qemuMigrationCapabilityTypeToString(cap),
automatic ? "yes" : "no");
}
}
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "</capabilities>\n");
}
static void
qemuMigrationCookieNBDXMLFormat(qemuMigrationCookieNBD *nbd,
virBuffer *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, "<disk target='%s'", nbd->disks[i].target);
virBufferAsprintf(&childBuf, " capacity='%llu'/>\n", nbd->disks[i].capacity);
}
virXMLFormatElementEmpty(buf, "nbd", &attrBuf, &childBuf);
}
static void
qemuMigrationCookieBlockDirtyBitmapsFormat(virBuffer *buf,
GSList *bitmaps)
{
g_auto(virBuffer) disksBuf = VIR_BUFFER_INIT_CHILD(buf);
GSList *nextdisk;
for (nextdisk = bitmaps; nextdisk; nextdisk = nextdisk->next) {
qemuMigrationBlockDirtyBitmapsDisk *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) {
qemuMigrationBlockDirtyBitmapsDiskBitmap *bitmap = nextbitmap->data;
if (bitmap->skip)
continue;
virBufferAsprintf(&diskChildBuf,
"<bitmap name='%s' alias='%s'/>\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(virQEMUDriver *driver,
virQEMUCaps *qemuCaps,
virBuffer *buf,
qemuMigrationCookie *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, "<qemu-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->localHostname);
virBufferAsprintf(buf, "<hostuuid>%s</hostuuid>\n", hostuuidstr);
for (i = 0; i < QEMU_MIGRATION_COOKIE_FLAG_LAST; i++) {
if (mig->flagsMandatory & (1 << i))
virBufferAsprintf(buf, "<feature name='%s'/>\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, "<lockstate driver='%s'>\n",
mig->lockDriver);
virBufferAdjustIndent(buf, 2);
virBufferAsprintf(buf, "<leases>%s</leases>\n",
mig->lockState);
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "</lockstate>\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, "</qemu-migration>\n");
return 0;
}
static qemuMigrationCookieGraphics *
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 qemuMigrationCookieNetwork *
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 qemuMigrationCookieNBD *
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 qemuDomainJobInfo *
qemuMigrationCookieStatisticsXMLParse(xmlXPathContextPtr ctxt)
{
qemuDomainJobInfo *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 qemuMigrationCookieCaps *
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,
qemuMigrationCookie *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;
qemuMigrationBlockDirtyBitmapsDisk *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++) {
qemuMigrationBlockDirtyBitmapsDiskBitmap *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 <blockDirtyBitmaps> 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 <blockDirtyBitmaps> in migration cookie"));
return -1;
}
}
mig->blockDirtyBitmaps = g_slist_reverse(g_steal_pointer(&disks));
return 0;
}
static int
qemuMigrationCookieXMLParse(qemuMigrationCookie *mig,
virQEMUDriver *driver,
virQEMUCaps *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(qemuMigrationCookie *mig,
virQEMUDriver *driver,
virQEMUCaps *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(qemuMigrationCookie *mig,
virQEMUDriver *driver,
virDomainObj *dom,
qemuMigrationParty party,
char **cookieout,
int *cookieoutlen,
unsigned int flags)
{
qemuDomainObjPrivate *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;
}
qemuMigrationCookie *
qemuMigrationCookieParse(virQEMUDriver *driver,
const virDomainDef *def,
const char *origname,
qemuDomainObjPrivate *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 qemuMigrationBlockDirtyBitmapsDisk *
*
* Matches all of the @disks to the actual domain disk definition objects
* by looking up the target.
*/
int
qemuMigrationCookieBlockDirtyBitmapsMatchDisks(virDomainDef *def,
GSList *disks)
{
GSList *next;
for (next = disks; next; next = next->next) {
qemuMigrationBlockDirtyBitmapsDisk *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,
virJSONValue **mapping)
{
g_autoptr(virJSONValue) map = virJSONValueNewArray();
bool hasDisks = false;
GSList *nextdisk;
for (nextdisk = disks; nextdisk; nextdisk = nextdisk->next) {
qemuMigrationBlockDirtyBitmapsDisk *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) {
qemuMigrationBlockDirtyBitmapsDiskBitmap *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;
}