mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2025-02-13 23:22:01 +00:00
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>
1695 lines
55 KiB
C
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;
|
|
}
|