Signed-off-by: Jiri Denemark <jdenemar@xxxxxxxxxx> --- po/POTFILES.in | 1 + src/Makefile.am | 1 + src/qemu/qemu_migration.c | 1407 +------------------------------------- src/qemu/qemu_migration_cookie.c | 1340 ++++++++++++++++++++++++++++++++++++ src/qemu/qemu_migration_cookie.h | 153 +++++ 5 files changed, 1496 insertions(+), 1406 deletions(-) create mode 100644 src/qemu/qemu_migration_cookie.c create mode 100644 src/qemu/qemu_migration_cookie.h diff --git a/po/POTFILES.in b/po/POTFILES.in index 64cb88cfa..d9e9d0591 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -141,6 +141,7 @@ src/qemu/qemu_hostdev.c src/qemu/qemu_hotplug.c src/qemu/qemu_interface.c src/qemu/qemu_migration.c +src/qemu/qemu_migration_cookie.c src/qemu/qemu_monitor.c src/qemu/qemu_monitor_json.c src/qemu/qemu_monitor_text.c diff --git a/src/Makefile.am b/src/Makefile.am index 3b1bb1da3..3295bc2c7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -839,6 +839,7 @@ QEMU_DRIVER_SOURCES = \ qemu/qemu_process.c qemu/qemu_process.h \ qemu/qemu_processpriv.h \ qemu/qemu_migration.c qemu/qemu_migration.h \ + qemu/qemu_migration_cookie.c qemu/qemu_migration_cookie.h \ qemu/qemu_monitor.c qemu/qemu_monitor.h \ qemu/qemu_monitor_text.c \ qemu/qemu_monitor_text.h \ diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index f5711bcf7..b1d141755 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -24,14 +24,11 @@ #include <netdb.h> #include <sys/socket.h> #include <sys/time.h> -#ifdef WITH_GNUTLS -# include <gnutls/gnutls.h> -# include <gnutls/x509.h> -#endif #include <fcntl.h> #include <poll.h> #include "qemu_migration.h" +#include "qemu_migration_cookie.h" #include "qemu_monitor.h" #include "qemu_domain.h" #include "qemu_process.h" @@ -85,1408 +82,6 @@ VIR_ENUM_IMPL(qemuMigrationCompressMethod, QEMU_MIGRATION_COMPRESS_LAST, "mt", ); -enum qemuMigrationCookieFlags { - QEMU_MIGRATION_COOKIE_FLAG_GRAPHICS, - QEMU_MIGRATION_COOKIE_FLAG_LOCKSTATE, - QEMU_MIGRATION_COOKIE_FLAG_PERSISTENT, - QEMU_MIGRATION_COOKIE_FLAG_NETWORK, - QEMU_MIGRATION_COOKIE_FLAG_NBD, - QEMU_MIGRATION_COOKIE_FLAG_STATS, - QEMU_MIGRATION_COOKIE_FLAG_MEMORY_HOTPLUG, - QEMU_MIGRATION_COOKIE_FLAG_CPU_HOTPLUG, - - QEMU_MIGRATION_COOKIE_FLAG_LAST -}; - -VIR_ENUM_DECL(qemuMigrationCookieFlag); -VIR_ENUM_IMPL(qemuMigrationCookieFlag, - QEMU_MIGRATION_COOKIE_FLAG_LAST, - "graphics", - "lockstate", - "persistent", - "network", - "nbd", - "statistics", - "memory-hotplug", - "cpu-hotplug"); - -enum qemuMigrationCookieFeatures { - QEMU_MIGRATION_COOKIE_GRAPHICS = (1 << QEMU_MIGRATION_COOKIE_FLAG_GRAPHICS), - QEMU_MIGRATION_COOKIE_LOCKSTATE = (1 << QEMU_MIGRATION_COOKIE_FLAG_LOCKSTATE), - QEMU_MIGRATION_COOKIE_PERSISTENT = (1 << QEMU_MIGRATION_COOKIE_FLAG_PERSISTENT), - QEMU_MIGRATION_COOKIE_NETWORK = (1 << QEMU_MIGRATION_COOKIE_FLAG_NETWORK), - QEMU_MIGRATION_COOKIE_NBD = (1 << QEMU_MIGRATION_COOKIE_FLAG_NBD), - QEMU_MIGRATION_COOKIE_STATS = (1 << QEMU_MIGRATION_COOKIE_FLAG_STATS), - QEMU_MIGRATION_COOKIE_MEMORY_HOTPLUG = (1 << QEMU_MIGRATION_COOKIE_FLAG_MEMORY_HOTPLUG), - QEMU_MIGRATION_COOKIE_CPU_HOTPLUG = (1 << QEMU_MIGRATION_COOKIE_FLAG_CPU_HOTPLUG), -}; - -typedef struct _qemuMigrationCookieGraphics qemuMigrationCookieGraphics; -typedef qemuMigrationCookieGraphics *qemuMigrationCookieGraphicsPtr; -struct _qemuMigrationCookieGraphics { - int type; - int port; - int tlsPort; - char *listen; - char *tlsSubject; -}; - -typedef struct _qemuMigrationCookieNetData qemuMigrationCookieNetData; -typedef qemuMigrationCookieNetData *qemuMigrationCookieNetDataPtr; -struct _qemuMigrationCookieNetData { - int vporttype; /* enum virNetDevVPortProfile */ - - /* - * Array of pointers to saved data. Each VIF will have its own - * data to transfer. - */ - char *portdata; -}; - -typedef struct _qemuMigrationCookieNetwork qemuMigrationCookieNetwork; -typedef qemuMigrationCookieNetwork *qemuMigrationCookieNetworkPtr; -struct _qemuMigrationCookieNetwork { - /* How many virtual NICs are we saving data for? */ - int nnets; - - qemuMigrationCookieNetDataPtr net; -}; - -typedef struct _qemuMigrationCookieNBD qemuMigrationCookieNBD; -typedef qemuMigrationCookieNBD *qemuMigrationCookieNBDPtr; -struct _qemuMigrationCookieNBD { - int port; /* on which port does NBD server listen for incoming data */ - - size_t ndisks; /* Number of items in @disk array */ - struct { - char *target; /* Disk target */ - unsigned long long capacity; /* And its capacity */ - } *disks; -}; - -typedef struct _qemuMigrationCookie qemuMigrationCookie; -typedef qemuMigrationCookie *qemuMigrationCookiePtr; -struct _qemuMigrationCookie { - unsigned int flags; - unsigned int flagsMandatory; - - /* Host properties */ - unsigned char localHostuuid[VIR_UUID_BUFLEN]; - unsigned char remoteHostuuid[VIR_UUID_BUFLEN]; - char *localHostname; - char *remoteHostname; - - /* Guest properties */ - unsigned char uuid[VIR_UUID_BUFLEN]; - char *name; - - /* If (flags & QEMU_MIGRATION_COOKIE_LOCKSTATE) */ - char *lockState; - char *lockDriver; - - /* If (flags & QEMU_MIGRATION_COOKIE_GRAPHICS) */ - qemuMigrationCookieGraphicsPtr graphics; - - /* If (flags & QEMU_MIGRATION_COOKIE_PERSISTENT) */ - virDomainDefPtr persistent; - - /* If (flags & QEMU_MIGRATION_COOKIE_NETWORK) */ - qemuMigrationCookieNetworkPtr network; - - /* If (flags & QEMU_MIGRATION_COOKIE_NBD) */ - qemuMigrationCookieNBDPtr nbd; - - /* If (flags & QEMU_MIGRATION_COOKIE_STATS) */ - qemuDomainJobInfoPtr jobInfo; -}; - -static void qemuMigrationCookieGraphicsFree(qemuMigrationCookieGraphicsPtr grap) -{ - if (!grap) - return; - VIR_FREE(grap->listen); - VIR_FREE(grap->tlsSubject); - VIR_FREE(grap); -} - - -static void -qemuMigrationCookieNetworkFree(qemuMigrationCookieNetworkPtr network) -{ - size_t i; - - if (!network) - return; - - if (network->net) { - for (i = 0; i < network->nnets; i++) - VIR_FREE(network->net[i].portdata); - } - VIR_FREE(network->net); - VIR_FREE(network); -} - - -static void qemuMigrationCookieNBDFree(qemuMigrationCookieNBDPtr nbd) -{ - if (!nbd) - return; - - while (nbd->ndisks) - VIR_FREE(nbd->disks[--nbd->ndisks].target); - VIR_FREE(nbd->disks); - VIR_FREE(nbd); -} - - -static void qemuMigrationCookieFree(qemuMigrationCookiePtr mig) -{ - if (!mig) - return; - - qemuMigrationCookieGraphicsFree(mig->graphics); - qemuMigrationCookieNetworkFree(mig->network); - qemuMigrationCookieNBDFree(mig->nbd); - - VIR_FREE(mig->localHostname); - VIR_FREE(mig->remoteHostname); - VIR_FREE(mig->name); - VIR_FREE(mig->lockState); - VIR_FREE(mig->lockDriver); - VIR_FREE(mig->jobInfo); - VIR_FREE(mig); -} - - -#ifdef WITH_GNUTLS -static char * -qemuDomainExtractTLSSubject(const char *certdir) -{ - char *certfile = NULL; - char *subject = NULL; - char *pemdata = NULL; - gnutls_datum_t pemdatum; - gnutls_x509_crt_t cert; - int ret; - size_t subjectlen; - - if (virAsprintf(&certfile, "%s/server-cert.pem", certdir) < 0) - goto error; - - if (virFileReadAll(certfile, 8192, &pemdata) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("unable to read server cert %s"), certfile); - goto error; - } - - ret = gnutls_x509_crt_init(&cert); - if (ret < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("cannot initialize cert object: %s"), - gnutls_strerror(ret)); - goto error; - } - - 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)); - goto error; - } - - subjectlen = 1024; - if (VIR_ALLOC_N(subject, subjectlen+1) < 0) - goto error; - - gnutls_x509_crt_get_dn(cert, subject, &subjectlen); - subject[subjectlen] = '\0'; - - VIR_FREE(certfile); - VIR_FREE(pemdata); - - return subject; - - error: - VIR_FREE(certfile); - VIR_FREE(pemdata); - return NULL; -} -#endif - -static qemuMigrationCookieGraphicsPtr -qemuMigrationCookieGraphicsSpiceAlloc(virQEMUDriverPtr driver, - virDomainGraphicsDefPtr def, - virDomainGraphicsListenDefPtr glisten) -{ - qemuMigrationCookieGraphicsPtr mig = NULL; - const char *listenAddr; - virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); - - if (VIR_ALLOC(mig) < 0) - goto error; - - 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; - -#ifdef WITH_GNUTLS - if (cfg->spiceTLS && - !(mig->tlsSubject = qemuDomainExtractTLSSubject(cfg->spiceTLSx509certdir))) - goto error; -#endif - if (VIR_STRDUP(mig->listen, listenAddr) < 0) - goto error; - - virObjectUnref(cfg); - return mig; - - error: - qemuMigrationCookieGraphicsFree(mig); - virObjectUnref(cfg); - return NULL; -} - - -static qemuMigrationCookieNetworkPtr -qemuMigrationCookieNetworkAlloc(virQEMUDriverPtr driver ATTRIBUTE_UNUSED, - virDomainDefPtr def) -{ - qemuMigrationCookieNetworkPtr mig; - size_t i; - - if (VIR_ALLOC(mig) < 0) - goto error; - - mig->nnets = def->nnets; - - if (VIR_ALLOC_N(mig->net, def->nnets) <0) - goto error; - - for (i = 0; i < def->nnets; i++) { - virDomainNetDefPtr netptr; - virNetDevVPortProfilePtr 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); - goto error; - } - break; - default: - break; - } - } - } - return mig; - - error: - qemuMigrationCookieNetworkFree(mig); - return NULL; -} - -static qemuMigrationCookiePtr -qemuMigrationCookieNew(virDomainObjPtr dom) -{ - qemuDomainObjPrivatePtr priv = dom->privateData; - qemuMigrationCookiePtr mig = NULL; - const char *name; - - if (VIR_ALLOC(mig) < 0) - goto error; - - if (priv->origname) - name = priv->origname; - else - name = dom->def->name; - if (VIR_STRDUP(mig->name, name) < 0) - goto error; - memcpy(mig->uuid, dom->def->uuid, VIR_UUID_BUFLEN); - - if (!(mig->localHostname = virGetHostname())) - goto error; - if (virGetHostUUID(mig->localHostuuid) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("Unable to obtain host UUID")); - goto error; - } - - return mig; - - error: - qemuMigrationCookieFree(mig); - return NULL; -} - - -static int -qemuMigrationCookieAddGraphics(qemuMigrationCookiePtr mig, - virQEMUDriverPtr driver, - virDomainObjPtr dom) -{ - size_t i = 0; - - if (mig->flags & QEMU_MIGRATION_COOKIE_GRAPHICS) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("Migration graphics data already present")); - return -1; - } - - for (i = 0; i < dom->def->ngraphics; i++) { - if (dom->def->graphics[i]->type == VIR_DOMAIN_GRAPHICS_TYPE_SPICE) { - virDomainGraphicsListenDefPtr glisten = - virDomainGraphicsGetListen(dom->def->graphics[i], 0); - - if (!glisten) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("missing listen element")); - return -1; - } - - switch (glisten->type) { - case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_ADDRESS: - case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NETWORK: - /* Seamless migration is supported only for listen types - * 'address and 'network'. */ - if (!(mig->graphics = - qemuMigrationCookieGraphicsSpiceAlloc(driver, - dom->def->graphics[i], - glisten))) - return -1; - mig->flags |= QEMU_MIGRATION_COOKIE_GRAPHICS; - break; - - case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_SOCKET: - case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NONE: - case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_LAST: - break; - } - - /* Seamless migration is supported only for one graphics. */ - if (mig->graphics) - break; - } - } - - return 0; -} - - -static int -qemuMigrationCookieAddLockstate(qemuMigrationCookiePtr mig, - virQEMUDriverPtr driver, - virDomainObjPtr dom) -{ - qemuDomainObjPrivatePtr priv = dom->privateData; - - if (mig->flags & QEMU_MIGRATION_COOKIE_LOCKSTATE) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("Migration lockstate data already present")); - return -1; - } - - if (virDomainObjGetState(dom, NULL) == VIR_DOMAIN_PAUSED) { - if (VIR_STRDUP(mig->lockState, priv->lockState) < 0) - return -1; - } else { - if (virDomainLockProcessInquire(driver->lockManager, dom, &mig->lockState) < 0) - return -1; - } - - if (VIR_STRDUP(mig->lockDriver, virLockManagerPluginGetName(driver->lockManager)) < 0) { - VIR_FREE(mig->lockState); - return -1; - } - - mig->flags |= QEMU_MIGRATION_COOKIE_LOCKSTATE; - mig->flagsMandatory |= QEMU_MIGRATION_COOKIE_LOCKSTATE; - - return 0; -} - - -static int -qemuMigrationCookieAddPersistent(qemuMigrationCookiePtr mig, - virDomainDefPtr def) -{ - if (mig->flags & QEMU_MIGRATION_COOKIE_PERSISTENT) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("Migration persistent data already present")); - return -1; - } - - if (!def) - return 0; - - mig->persistent = def; - mig->flags |= QEMU_MIGRATION_COOKIE_PERSISTENT; - mig->flagsMandatory |= QEMU_MIGRATION_COOKIE_PERSISTENT; - return 0; -} - - -static virDomainDefPtr -qemuMigrationCookieGetPersistent(qemuMigrationCookiePtr mig) -{ - virDomainDefPtr def = mig->persistent; - - mig->persistent = NULL; - mig->flags &= ~QEMU_MIGRATION_COOKIE_PERSISTENT; - mig->flagsMandatory &= ~QEMU_MIGRATION_COOKIE_PERSISTENT; - - return def; -} - - -static int -qemuMigrationCookieAddNetwork(qemuMigrationCookiePtr mig, - virQEMUDriverPtr driver, - virDomainObjPtr dom) -{ - if (mig->flags & QEMU_MIGRATION_COOKIE_NETWORK) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("Network migration data already present")); - return -1; - } - - if (dom->def->nnets > 0) { - mig->network = qemuMigrationCookieNetworkAlloc(driver, dom->def); - if (!mig->network) - return -1; - mig->flags |= QEMU_MIGRATION_COOKIE_NETWORK; - } - - return 0; -} - - -static int -qemuMigrationCookieAddNBD(qemuMigrationCookiePtr mig, - virQEMUDriverPtr driver, - virDomainObjPtr vm) -{ - qemuDomainObjPrivatePtr priv = vm->privateData; - virHashTablePtr stats = NULL; - size_t i; - int ret = -1, rc; - - /* It is not a bug if there already is a NBD data */ - qemuMigrationCookieNBDFree(mig->nbd); - - if (VIR_ALLOC(mig->nbd) < 0) - return -1; - - if (vm->def->ndisks && - VIR_ALLOC_N(mig->nbd->disks, vm->def->ndisks) < 0) - return -1; - mig->nbd->ndisks = 0; - - for (i = 0; i < vm->def->ndisks; i++) { - virDomainDiskDefPtr disk = vm->def->disks[i]; - qemuBlockStats *entry; - - if (!stats) { - if (!(stats = virHashCreate(10, virHashValueFree))) - goto cleanup; - - if (qemuDomainObjEnterMonitorAsync(driver, vm, - priv->job.asyncJob) < 0) - goto cleanup; - rc = qemuMonitorBlockStatsUpdateCapacity(priv->mon, stats, false); - if (qemuDomainObjExitMonitor(driver, vm) < 0) - goto cleanup; - if (rc < 0) - goto cleanup; - } - - if (!disk->info.alias || - !(entry = virHashLookup(stats, disk->info.alias))) - continue; - - if (VIR_STRDUP(mig->nbd->disks[mig->nbd->ndisks].target, - disk->dst) < 0) - goto cleanup; - mig->nbd->disks[mig->nbd->ndisks].capacity = entry->capacity; - mig->nbd->ndisks++; - } - - mig->nbd->port = priv->nbdPort; - mig->flags |= QEMU_MIGRATION_COOKIE_NBD; - - ret = 0; - cleanup: - virHashFree(stats); - return ret; -} - - -static int -qemuMigrationCookieAddStatistics(qemuMigrationCookiePtr mig, - virDomainObjPtr vm) -{ - qemuDomainObjPrivatePtr priv = vm->privateData; - - if (!priv->job.completed) - return 0; - - if (!mig->jobInfo && VIR_ALLOC(mig->jobInfo) < 0) - return -1; - - *mig->jobInfo = *priv->job.completed; - mig->flags |= QEMU_MIGRATION_COOKIE_STATS; - - return 0; -} - - -static void qemuMigrationCookieGraphicsXMLFormat(virBufferPtr buf, - qemuMigrationCookieGraphicsPtr grap) -{ - virBufferAsprintf(buf, "<graphics type='%s' port='%d' listen='%s'", - virDomainGraphicsTypeToString(grap->type), - grap->port, grap->listen); - if (grap->type == VIR_DOMAIN_GRAPHICS_TYPE_SPICE) - virBufferAsprintf(buf, " tlsPort='%d'", grap->tlsPort); - if (grap->tlsSubject) { - virBufferAddLit(buf, ">\n"); - virBufferAdjustIndent(buf, 2); - virBufferEscapeString(buf, "<cert info='subject' value='%s'/>\n", grap->tlsSubject); - virBufferAdjustIndent(buf, -2); - virBufferAddLit(buf, "</graphics>\n"); - } else { - virBufferAddLit(buf, "/>\n"); - } -} - - -static void -qemuMigrationCookieNetworkXMLFormat(virBufferPtr buf, - qemuMigrationCookieNetworkPtr optr) -{ - size_t i; - bool empty = true; - - for (i = 0; i < optr->nnets; i++) { - /* If optr->net[i].vporttype is not set, there is nothing to transfer */ - if (optr->net[i].vporttype != VIR_NETDEV_VPORT_PROFILE_NONE) { - if (empty) { - virBufferAddLit(buf, "<network>\n"); - virBufferAdjustIndent(buf, 2); - empty = false; - } - virBufferAsprintf(buf, "<interface index='%zu' vporttype='%s'", - i, virNetDevVPortTypeToString(optr->net[i].vporttype)); - if (optr->net[i].portdata) { - virBufferAddLit(buf, ">\n"); - virBufferAdjustIndent(buf, 2); - virBufferEscapeString(buf, "<portdata>%s</portdata>\n", - optr->net[i].portdata); - virBufferAdjustIndent(buf, -2); - virBufferAddLit(buf, "</interface>\n"); - } else { - virBufferAddLit(buf, "/>\n"); - } - } - } - if (!empty) { - virBufferAdjustIndent(buf, -2); - virBufferAddLit(buf, "</network>\n"); - } -} - - -static void -qemuMigrationCookieStatisticsXMLFormat(virBufferPtr buf, - qemuDomainJobInfoPtr jobInfo) -{ - qemuMonitorMigrationStats *stats = &jobInfo->stats; - - 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); - virBufferAsprintf(buf, "<%1$s>%2$llu</%1$s>\n", - VIR_DOMAIN_JOB_TIME_REMAINING, - jobInfo->timeRemaining); - 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_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 int -qemuMigrationCookieXMLFormat(virQEMUDriverPtr driver, - virBufferPtr buf, - qemuMigrationCookiePtr mig) -{ - char uuidstr[VIR_UUID_STRING_BUFLEN]; - char hostuuidstr[VIR_UUID_STRING_BUFLEN]; - size_t i; - - virUUIDFormat(mig->uuid, uuidstr); - virUUIDFormat(mig->localHostuuid, hostuuidstr); - - virBufferAddLit(buf, "<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, - 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) { - virBufferAddLit(buf, "<nbd"); - if (mig->nbd->port) - virBufferAsprintf(buf, " port='%d'", mig->nbd->port); - if (mig->nbd->ndisks) { - virBufferAddLit(buf, ">\n"); - virBufferAdjustIndent(buf, 2); - for (i = 0; i < mig->nbd->ndisks; i++) { - virBufferEscapeString(buf, "<disk target='%s'", - mig->nbd->disks[i].target); - virBufferAsprintf(buf, " capacity='%llu'/>\n", - mig->nbd->disks[i].capacity); - } - virBufferAdjustIndent(buf, -2); - virBufferAddLit(buf, "</nbd>\n"); - } else { - virBufferAddLit(buf, "/>\n"); - } - } - - if (mig->flags & QEMU_MIGRATION_COOKIE_STATS && mig->jobInfo) - qemuMigrationCookieStatisticsXMLFormat(buf, mig->jobInfo); - - virBufferAdjustIndent(buf, -2); - virBufferAddLit(buf, "</qemu-migration>\n"); - return 0; -} - - -static char *qemuMigrationCookieXMLFormatStr(virQEMUDriverPtr driver, - qemuMigrationCookiePtr mig) -{ - virBuffer buf = VIR_BUFFER_INITIALIZER; - - if (qemuMigrationCookieXMLFormat(driver, &buf, mig) < 0) { - virBufferFreeAndReset(&buf); - return NULL; - } - - if (virBufferCheckError(&buf) < 0) - return NULL; - - return virBufferContentAndReset(&buf); -} - - -static qemuMigrationCookieGraphicsPtr -qemuMigrationCookieGraphicsXMLParse(xmlXPathContextPtr ctxt) -{ - qemuMigrationCookieGraphicsPtr grap; - char *tmp; - - if (VIR_ALLOC(grap) < 0) - goto error; - - if (!(tmp = virXPathString("string(./graphics/@type)", ctxt))) { - virReportError(VIR_ERR_INTERNAL_ERROR, - "%s", _("missing type attribute in migration data")); - goto error; - } - if ((grap->type = virDomainGraphicsTypeFromString(tmp)) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("unknown graphics type %s"), tmp); - VIR_FREE(tmp); - goto error; - } - VIR_FREE(tmp); - if (virXPathInt("string(./graphics/@port)", ctxt, &grap->port) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, - "%s", _("missing port attribute in migration data")); - goto error; - } - 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")); - goto error; - } - } - if (!(grap->listen = virXPathString("string(./graphics/@listen)", ctxt))) { - virReportError(VIR_ERR_INTERNAL_ERROR, - "%s", _("missing listen attribute in migration data")); - goto error; - } - /* Optional */ - grap->tlsSubject = virXPathString("string(./graphics/cert[@info='subject']/@value)", ctxt); - - return grap; - - error: - qemuMigrationCookieGraphicsFree(grap); - return NULL; -} - - -static qemuMigrationCookieNetworkPtr -qemuMigrationCookieNetworkXMLParse(xmlXPathContextPtr ctxt) -{ - qemuMigrationCookieNetworkPtr optr; - size_t i; - int n; - xmlNodePtr *interfaces = NULL; - char *vporttype; - xmlNodePtr save_ctxt = ctxt->node; - - if (VIR_ALLOC(optr) < 0) - goto error; - - if ((n = virXPathNodeSet("./network/interface", ctxt, &interfaces)) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, - "%s", _("missing interface information")); - goto error; - } - - optr->nnets = n; - if (VIR_ALLOC_N(optr->net, optr->nnets) < 0) - goto error; - - for (i = 0; i < n; i++) { - /* 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")); - goto error; - } - optr->net[i].vporttype = virNetDevVPortTypeFromString(vporttype); - } - - VIR_FREE(interfaces); - - cleanup: - ctxt->node = save_ctxt; - return optr; - - error: - VIR_FREE(interfaces); - qemuMigrationCookieNetworkFree(optr); - optr = NULL; - goto cleanup; -} - - -static qemuMigrationCookieNBDPtr -qemuMigrationCookieNBDXMLParse(xmlXPathContextPtr ctxt) -{ - qemuMigrationCookieNBDPtr ret = NULL; - char *port = NULL, *capacity = NULL; - size_t i; - int n; - xmlNodePtr *disks = NULL; - xmlNodePtr save_ctxt = ctxt->node; - - if (VIR_ALLOC(ret) < 0) - goto error; - - 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); - goto error; - } - - /* 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) { - if (VIR_ALLOC_N(ret->disks, n) < 0) - goto error; - ret->ndisks = n; - - for (i = 0; i < n; i++) { - ctxt->node = disks[i]; - VIR_FREE(capacity); - - if (!(ret->disks[i].target = virXPathString("string(./@target)", ctxt))) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("Malformed disk target")); - goto error; - } - - 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)); - goto error; - } - } - } - - cleanup: - VIR_FREE(port); - VIR_FREE(capacity); - VIR_FREE(disks); - ctxt->node = save_ctxt; - return ret; - error: - qemuMigrationCookieNBDFree(ret); - ret = NULL; - goto cleanup; -} - - -static qemuDomainJobInfoPtr -qemuMigrationCookieStatisticsXMLParse(xmlXPathContextPtr ctxt) -{ - qemuDomainJobInfoPtr jobInfo = NULL; - qemuMonitorMigrationStats *stats; - xmlNodePtr save_ctxt = ctxt->node; - - if (!(ctxt->node = virXPathNode("./statistics", ctxt))) - goto cleanup; - - if (VIR_ALLOC(jobInfo) < 0) - goto cleanup; - - stats = &jobInfo->stats; - jobInfo->type = VIR_DOMAIN_JOB_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); - virXPathULongLong("string(./" VIR_DOMAIN_JOB_TIME_REMAINING "[1])", - ctxt, &jobInfo->timeRemaining); - - 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_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); - cleanup: - ctxt->node = save_ctxt; - return jobInfo; -} - - -static int -qemuMigrationCookieXMLParse(qemuMigrationCookiePtr mig, - virQEMUDriverPtr driver, - xmlDocPtr doc, - xmlXPathContextPtr ctxt, - unsigned int flags) -{ - char uuidstr[VIR_UUID_STRING_BUFLEN]; - char *tmp = NULL; - xmlNodePtr *nodes = NULL; - size_t i; - int n; - virCapsPtr caps = NULL; - - if (!(caps = virQEMUDriverGetCapabilities(driver, false))) - goto error; - - /* 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 (!(tmp = virXPathString("string(./name[1])", ctxt))) { - virReportError(VIR_ERR_INTERNAL_ERROR, - "%s", _("missing name element in migration data")); - goto error; - } - if (STRNEQ(tmp, mig->name)) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Incoming cookie data had unexpected name %s vs %s"), - tmp, mig->name); - goto error; - } - VIR_FREE(tmp); - - /* Extract domain uuid */ - tmp = virXPathString("string(./uuid[1])", ctxt); - if (!tmp) { - virReportError(VIR_ERR_INTERNAL_ERROR, - "%s", _("missing uuid element in migration data")); - goto error; - } - virUUIDFormat(mig->uuid, uuidstr); - if (STRNEQ(tmp, uuidstr)) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Incoming cookie data had unexpected UUID %s vs %s"), - tmp, uuidstr); - goto error; - } - VIR_FREE(tmp); - - /* Check & forbid "localhost" migration */ - if (!(mig->remoteHostname = virXPathString("string(./hostname[1])", ctxt))) { - virReportError(VIR_ERR_INTERNAL_ERROR, - "%s", _("missing hostname element in migration data")); - goto error; - } - if (STREQ(mig->remoteHostname, mig->localHostname)) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Attempt to migrate guest to the same host %s"), - mig->remoteHostname); - goto error; - } - - if (!(tmp = virXPathString("string(./hostuuid[1])", ctxt))) { - virReportError(VIR_ERR_INTERNAL_ERROR, - "%s", _("missing hostuuid element in migration data")); - goto error; - } - if (virUUIDParse(tmp, mig->remoteHostuuid) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, - "%s", _("malformed hostuuid element in migration data")); - goto error; - } - if (memcmp(mig->remoteHostuuid, mig->localHostuuid, VIR_UUID_BUFLEN) == 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Attempt to migrate guest to the same host %s"), - tmp); - goto error; - } - VIR_FREE(tmp); - - /* Check to ensure all mandatory features from XML are also - * present in 'flags' */ - if ((n = virXPathNodeSet("./feature", ctxt, &nodes)) < 0) - goto error; - - for (i = 0; i < n; i++) { - int val; - char *str = virXMLPropString(nodes[i], "name"); - if (!str) { - virReportError(VIR_ERR_INTERNAL_ERROR, - "%s", _("missing feature name")); - goto error; - } - - if ((val = qemuMigrationCookieFlagTypeFromString(str)) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Unknown migration cookie feature %s"), - str); - VIR_FREE(str); - goto error; - } - - if ((flags & (1 << val)) == 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Unsupported migration cookie feature %s"), - str); - VIR_FREE(str); - goto error; - } - VIR_FREE(str); - } - VIR_FREE(nodes); - - if ((flags & QEMU_MIGRATION_COOKIE_GRAPHICS) && - virXPathBoolean("count(./graphics) > 0", ctxt) && - (!(mig->graphics = qemuMigrationCookieGraphicsXMLParse(ctxt)))) - goto error; - - if ((flags & QEMU_MIGRATION_COOKIE_LOCKSTATE) && - virXPathBoolean("count(./lockstate) > 0", ctxt)) { - mig->lockDriver = virXPathString("string(./lockstate[1]/@driver)", ctxt); - if (!mig->lockDriver) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("Missing lock driver name in migration cookie")); - goto error; - } - mig->lockState = virXPathString("string(./lockstate[1]/leases[1])", ctxt); - if (mig->lockState && STREQ(mig->lockState, "")) - VIR_FREE(mig->lockState); - } - - if ((flags & QEMU_MIGRATION_COOKIE_PERSISTENT) && - virXPathBoolean("count(./domain) > 0", ctxt)) { - if ((n = virXPathNodeSet("./domain", ctxt, &nodes)) > 1) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Too many domain elements in " - "migration cookie: %d"), - n); - goto error; - } - mig->persistent = virDomainDefParseNode(doc, nodes[0], - caps, driver->xmlopt, NULL, - VIR_DOMAIN_DEF_PARSE_INACTIVE | - VIR_DOMAIN_DEF_PARSE_ABI_UPDATE | - VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE); - if (!mig->persistent) { - /* virDomainDefParseNode already reported - * an error for us */ - goto error; - } - VIR_FREE(nodes); - } - - if ((flags & QEMU_MIGRATION_COOKIE_NETWORK) && - virXPathBoolean("count(./network) > 0", ctxt) && - (!(mig->network = qemuMigrationCookieNetworkXMLParse(ctxt)))) - goto error; - - if (flags & QEMU_MIGRATION_COOKIE_NBD && - virXPathBoolean("boolean(./nbd)", ctxt) && - (!(mig->nbd = qemuMigrationCookieNBDXMLParse(ctxt)))) - goto error; - - if (flags & QEMU_MIGRATION_COOKIE_STATS && - virXPathBoolean("boolean(./statistics)", ctxt) && - (!(mig->jobInfo = qemuMigrationCookieStatisticsXMLParse(ctxt)))) - goto error; - - virObjectUnref(caps); - return 0; - - error: - VIR_FREE(tmp); - VIR_FREE(nodes); - virObjectUnref(caps); - return -1; -} - - -static int -qemuMigrationCookieXMLParseStr(qemuMigrationCookiePtr mig, - virQEMUDriverPtr driver, - 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, doc, ctxt, flags); - - cleanup: - xmlXPathFreeContext(ctxt); - xmlFreeDoc(doc); - - return ret; -} - - -static int -qemuMigrationBakeCookie(qemuMigrationCookiePtr mig, - virQEMUDriverPtr driver, - virDomainObjPtr dom, - char **cookieout, - int *cookieoutlen, - unsigned int flags) -{ - 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 (!(*cookieout = qemuMigrationCookieXMLFormatStr(driver, mig))) - return -1; - - *cookieoutlen = strlen(*cookieout) + 1; - - VIR_DEBUG("cookielen=%d cookie=%s", *cookieoutlen, *cookieout); - - return 0; -} - - -static qemuMigrationCookiePtr -qemuMigrationEatCookie(virQEMUDriverPtr driver, - virDomainObjPtr dom, - const char *cookiein, - int cookieinlen, - unsigned int flags) -{ - qemuMigrationCookiePtr 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")); - goto error; - } - - VIR_DEBUG("cookielen=%d cookie='%s'", cookieinlen, NULLSTR(cookiein)); - - if (!(mig = qemuMigrationCookieNew(dom))) - return NULL; - - if (cookiein && cookieinlen && - qemuMigrationCookieXMLParseStr(mig, - driver, - cookiein, - flags) < 0) - goto error; - - if (flags & QEMU_MIGRATION_COOKIE_PERSISTENT && - mig->persistent && - STRNEQ(dom->def->name, mig->persistent->name)) { - VIR_FREE(mig->persistent->name); - if (VIR_STRDUP(mig->persistent->name, dom->def->name) < 0) - goto error; - } - - 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)); - goto error; - } - } 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)); - goto error; - } - } - - return mig; - - error: - qemuMigrationCookieFree(mig); - return NULL; -} static void qemuMigrationStoreDomainState(virDomainObjPtr vm) diff --git a/src/qemu/qemu_migration_cookie.c b/src/qemu/qemu_migration_cookie.c new file mode 100644 index 000000000..7b237c546 --- /dev/null +++ b/src/qemu/qemu_migration_cookie.c @@ -0,0 +1,1340 @@ +/* + * 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> + +#ifdef WITH_GNUTLS +# include <gnutls/gnutls.h> +# include <gnutls/x509.h> +#endif + +#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" + + +#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"); + + +static void qemuMigrationCookieGraphicsFree(qemuMigrationCookieGraphicsPtr grap) +{ + if (!grap) + return; + VIR_FREE(grap->listen); + VIR_FREE(grap->tlsSubject); + VIR_FREE(grap); +} + + +static void +qemuMigrationCookieNetworkFree(qemuMigrationCookieNetworkPtr network) +{ + size_t i; + + if (!network) + return; + + if (network->net) { + for (i = 0; i < network->nnets; i++) + VIR_FREE(network->net[i].portdata); + } + VIR_FREE(network->net); + VIR_FREE(network); +} + + +static void qemuMigrationCookieNBDFree(qemuMigrationCookieNBDPtr nbd) +{ + if (!nbd) + return; + + while (nbd->ndisks) + VIR_FREE(nbd->disks[--nbd->ndisks].target); + VIR_FREE(nbd->disks); + VIR_FREE(nbd); +} + + +void qemuMigrationCookieFree(qemuMigrationCookiePtr mig) +{ + if (!mig) + return; + + qemuMigrationCookieGraphicsFree(mig->graphics); + qemuMigrationCookieNetworkFree(mig->network); + qemuMigrationCookieNBDFree(mig->nbd); + + VIR_FREE(mig->localHostname); + VIR_FREE(mig->remoteHostname); + VIR_FREE(mig->name); + VIR_FREE(mig->lockState); + VIR_FREE(mig->lockDriver); + VIR_FREE(mig->jobInfo); + VIR_FREE(mig); +} + + +#ifdef WITH_GNUTLS +static char * +qemuDomainExtractTLSSubject(const char *certdir) +{ + char *certfile = NULL; + char *subject = NULL; + char *pemdata = NULL; + gnutls_datum_t pemdatum; + gnutls_x509_crt_t cert; + int ret; + size_t subjectlen; + + if (virAsprintf(&certfile, "%s/server-cert.pem", certdir) < 0) + goto error; + + if (virFileReadAll(certfile, 8192, &pemdata) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unable to read server cert %s"), certfile); + goto error; + } + + ret = gnutls_x509_crt_init(&cert); + if (ret < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("cannot initialize cert object: %s"), + gnutls_strerror(ret)); + goto error; + } + + 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)); + goto error; + } + + subjectlen = 1024; + if (VIR_ALLOC_N(subject, subjectlen+1) < 0) + goto error; + + gnutls_x509_crt_get_dn(cert, subject, &subjectlen); + subject[subjectlen] = '\0'; + + VIR_FREE(certfile); + VIR_FREE(pemdata); + + return subject; + + error: + VIR_FREE(certfile); + VIR_FREE(pemdata); + return NULL; +} +#endif + +static qemuMigrationCookieGraphicsPtr +qemuMigrationCookieGraphicsSpiceAlloc(virQEMUDriverPtr driver, + virDomainGraphicsDefPtr def, + virDomainGraphicsListenDefPtr glisten) +{ + qemuMigrationCookieGraphicsPtr mig = NULL; + const char *listenAddr; + virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); + + if (VIR_ALLOC(mig) < 0) + goto error; + + 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; + +#ifdef WITH_GNUTLS + if (cfg->spiceTLS && + !(mig->tlsSubject = qemuDomainExtractTLSSubject(cfg->spiceTLSx509certdir))) + goto error; +#endif + if (VIR_STRDUP(mig->listen, listenAddr) < 0) + goto error; + + virObjectUnref(cfg); + return mig; + + error: + qemuMigrationCookieGraphicsFree(mig); + virObjectUnref(cfg); + return NULL; +} + + +static qemuMigrationCookieNetworkPtr +qemuMigrationCookieNetworkAlloc(virQEMUDriverPtr driver ATTRIBUTE_UNUSED, + virDomainDefPtr def) +{ + qemuMigrationCookieNetworkPtr mig; + size_t i; + + if (VIR_ALLOC(mig) < 0) + goto error; + + mig->nnets = def->nnets; + + if (VIR_ALLOC_N(mig->net, def->nnets) <0) + goto error; + + for (i = 0; i < def->nnets; i++) { + virDomainNetDefPtr netptr; + virNetDevVPortProfilePtr 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); + goto error; + } + break; + default: + break; + } + } + } + return mig; + + error: + qemuMigrationCookieNetworkFree(mig); + return NULL; +} + +static qemuMigrationCookiePtr +qemuMigrationCookieNew(virDomainObjPtr dom) +{ + qemuDomainObjPrivatePtr priv = dom->privateData; + qemuMigrationCookiePtr mig = NULL; + const char *name; + + if (VIR_ALLOC(mig) < 0) + goto error; + + if (priv->origname) + name = priv->origname; + else + name = dom->def->name; + if (VIR_STRDUP(mig->name, name) < 0) + goto error; + memcpy(mig->uuid, dom->def->uuid, VIR_UUID_BUFLEN); + + if (!(mig->localHostname = virGetHostname())) + goto error; + if (virGetHostUUID(mig->localHostuuid) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Unable to obtain host UUID")); + goto error; + } + + return mig; + + error: + qemuMigrationCookieFree(mig); + return NULL; +} + + +static int +qemuMigrationCookieAddGraphics(qemuMigrationCookiePtr mig, + virQEMUDriverPtr driver, + virDomainObjPtr dom) +{ + size_t i = 0; + + if (mig->flags & QEMU_MIGRATION_COOKIE_GRAPHICS) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Migration graphics data already present")); + return -1; + } + + for (i = 0; i < dom->def->ngraphics; i++) { + if (dom->def->graphics[i]->type == VIR_DOMAIN_GRAPHICS_TYPE_SPICE) { + virDomainGraphicsListenDefPtr glisten = + virDomainGraphicsGetListen(dom->def->graphics[i], 0); + + if (!glisten) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing listen element")); + return -1; + } + + switch (glisten->type) { + case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_ADDRESS: + case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NETWORK: + /* Seamless migration is supported only for listen types + * 'address and 'network'. */ + if (!(mig->graphics = + qemuMigrationCookieGraphicsSpiceAlloc(driver, + dom->def->graphics[i], + glisten))) + return -1; + mig->flags |= QEMU_MIGRATION_COOKIE_GRAPHICS; + break; + + case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_SOCKET: + case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NONE: + case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_LAST: + break; + } + + /* Seamless migration is supported only for one graphics. */ + if (mig->graphics) + break; + } + } + + return 0; +} + + +static int +qemuMigrationCookieAddLockstate(qemuMigrationCookiePtr mig, + virQEMUDriverPtr driver, + virDomainObjPtr dom) +{ + qemuDomainObjPrivatePtr priv = dom->privateData; + + if (mig->flags & QEMU_MIGRATION_COOKIE_LOCKSTATE) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Migration lockstate data already present")); + return -1; + } + + if (virDomainObjGetState(dom, NULL) == VIR_DOMAIN_PAUSED) { + if (VIR_STRDUP(mig->lockState, priv->lockState) < 0) + return -1; + } else { + if (virDomainLockProcessInquire(driver->lockManager, dom, &mig->lockState) < 0) + return -1; + } + + if (VIR_STRDUP(mig->lockDriver, virLockManagerPluginGetName(driver->lockManager)) < 0) { + VIR_FREE(mig->lockState); + return -1; + } + + mig->flags |= QEMU_MIGRATION_COOKIE_LOCKSTATE; + mig->flagsMandatory |= QEMU_MIGRATION_COOKIE_LOCKSTATE; + + return 0; +} + + +int +qemuMigrationCookieAddPersistent(qemuMigrationCookiePtr mig, + virDomainDefPtr def) +{ + if (mig->flags & QEMU_MIGRATION_COOKIE_PERSISTENT) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Migration persistent data already present")); + return -1; + } + + if (!def) + return 0; + + mig->persistent = def; + mig->flags |= QEMU_MIGRATION_COOKIE_PERSISTENT; + mig->flagsMandatory |= QEMU_MIGRATION_COOKIE_PERSISTENT; + return 0; +} + + +virDomainDefPtr +qemuMigrationCookieGetPersistent(qemuMigrationCookiePtr mig) +{ + virDomainDefPtr def = mig->persistent; + + mig->persistent = NULL; + mig->flags &= ~QEMU_MIGRATION_COOKIE_PERSISTENT; + mig->flagsMandatory &= ~QEMU_MIGRATION_COOKIE_PERSISTENT; + + return def; +} + + +static int +qemuMigrationCookieAddNetwork(qemuMigrationCookiePtr mig, + virQEMUDriverPtr driver, + virDomainObjPtr dom) +{ + if (mig->flags & QEMU_MIGRATION_COOKIE_NETWORK) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Network migration data already present")); + return -1; + } + + if (dom->def->nnets > 0) { + mig->network = qemuMigrationCookieNetworkAlloc(driver, dom->def); + if (!mig->network) + return -1; + mig->flags |= QEMU_MIGRATION_COOKIE_NETWORK; + } + + return 0; +} + + +static int +qemuMigrationCookieAddNBD(qemuMigrationCookiePtr mig, + virQEMUDriverPtr driver, + virDomainObjPtr vm) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + virHashTablePtr stats = NULL; + size_t i; + int ret = -1, rc; + + /* It is not a bug if there already is a NBD data */ + qemuMigrationCookieNBDFree(mig->nbd); + + if (VIR_ALLOC(mig->nbd) < 0) + return -1; + + if (vm->def->ndisks && + VIR_ALLOC_N(mig->nbd->disks, vm->def->ndisks) < 0) + return -1; + mig->nbd->ndisks = 0; + + for (i = 0; i < vm->def->ndisks; i++) { + virDomainDiskDefPtr disk = vm->def->disks[i]; + qemuBlockStats *entry; + + if (!stats) { + if (!(stats = virHashCreate(10, virHashValueFree))) + goto cleanup; + + if (qemuDomainObjEnterMonitorAsync(driver, vm, + priv->job.asyncJob) < 0) + goto cleanup; + rc = qemuMonitorBlockStatsUpdateCapacity(priv->mon, stats, false); + if (qemuDomainObjExitMonitor(driver, vm) < 0) + goto cleanup; + if (rc < 0) + goto cleanup; + } + + if (!disk->info.alias || + !(entry = virHashLookup(stats, disk->info.alias))) + continue; + + if (VIR_STRDUP(mig->nbd->disks[mig->nbd->ndisks].target, + disk->dst) < 0) + goto cleanup; + mig->nbd->disks[mig->nbd->ndisks].capacity = entry->capacity; + mig->nbd->ndisks++; + } + + mig->nbd->port = priv->nbdPort; + mig->flags |= QEMU_MIGRATION_COOKIE_NBD; + + ret = 0; + cleanup: + virHashFree(stats); + return ret; +} + + +static int +qemuMigrationCookieAddStatistics(qemuMigrationCookiePtr mig, + virDomainObjPtr vm) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + + if (!priv->job.completed) + return 0; + + if (!mig->jobInfo && VIR_ALLOC(mig->jobInfo) < 0) + return -1; + + *mig->jobInfo = *priv->job.completed; + mig->flags |= QEMU_MIGRATION_COOKIE_STATS; + + return 0; +} + + +static void qemuMigrationCookieGraphicsXMLFormat(virBufferPtr buf, + qemuMigrationCookieGraphicsPtr grap) +{ + virBufferAsprintf(buf, "<graphics type='%s' port='%d' listen='%s'", + virDomainGraphicsTypeToString(grap->type), + grap->port, grap->listen); + if (grap->type == VIR_DOMAIN_GRAPHICS_TYPE_SPICE) + virBufferAsprintf(buf, " tlsPort='%d'", grap->tlsPort); + if (grap->tlsSubject) { + virBufferAddLit(buf, ">\n"); + virBufferAdjustIndent(buf, 2); + virBufferEscapeString(buf, "<cert info='subject' value='%s'/>\n", grap->tlsSubject); + virBufferAdjustIndent(buf, -2); + virBufferAddLit(buf, "</graphics>\n"); + } else { + virBufferAddLit(buf, "/>\n"); + } +} + + +static void +qemuMigrationCookieNetworkXMLFormat(virBufferPtr buf, + qemuMigrationCookieNetworkPtr optr) +{ + size_t i; + bool empty = true; + + for (i = 0; i < optr->nnets; i++) { + /* If optr->net[i].vporttype is not set, there is nothing to transfer */ + if (optr->net[i].vporttype != VIR_NETDEV_VPORT_PROFILE_NONE) { + if (empty) { + virBufferAddLit(buf, "<network>\n"); + virBufferAdjustIndent(buf, 2); + empty = false; + } + virBufferAsprintf(buf, "<interface index='%zu' vporttype='%s'", + i, virNetDevVPortTypeToString(optr->net[i].vporttype)); + if (optr->net[i].portdata) { + virBufferAddLit(buf, ">\n"); + virBufferAdjustIndent(buf, 2); + virBufferEscapeString(buf, "<portdata>%s</portdata>\n", + optr->net[i].portdata); + virBufferAdjustIndent(buf, -2); + virBufferAddLit(buf, "</interface>\n"); + } else { + virBufferAddLit(buf, "/>\n"); + } + } + } + if (!empty) { + virBufferAdjustIndent(buf, -2); + virBufferAddLit(buf, "</network>\n"); + } +} + + +static void +qemuMigrationCookieStatisticsXMLFormat(virBufferPtr buf, + qemuDomainJobInfoPtr jobInfo) +{ + qemuMonitorMigrationStats *stats = &jobInfo->stats; + + 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); + virBufferAsprintf(buf, "<%1$s>%2$llu</%1$s>\n", + VIR_DOMAIN_JOB_TIME_REMAINING, + jobInfo->timeRemaining); + 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_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 int +qemuMigrationCookieXMLFormat(virQEMUDriverPtr driver, + virBufferPtr buf, + qemuMigrationCookiePtr mig) +{ + char uuidstr[VIR_UUID_STRING_BUFLEN]; + char hostuuidstr[VIR_UUID_STRING_BUFLEN]; + size_t i; + + virUUIDFormat(mig->uuid, uuidstr); + virUUIDFormat(mig->localHostuuid, hostuuidstr); + + virBufferAddLit(buf, "<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, + 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) { + virBufferAddLit(buf, "<nbd"); + if (mig->nbd->port) + virBufferAsprintf(buf, " port='%d'", mig->nbd->port); + if (mig->nbd->ndisks) { + virBufferAddLit(buf, ">\n"); + virBufferAdjustIndent(buf, 2); + for (i = 0; i < mig->nbd->ndisks; i++) { + virBufferEscapeString(buf, "<disk target='%s'", + mig->nbd->disks[i].target); + virBufferAsprintf(buf, " capacity='%llu'/>\n", + mig->nbd->disks[i].capacity); + } + virBufferAdjustIndent(buf, -2); + virBufferAddLit(buf, "</nbd>\n"); + } else { + virBufferAddLit(buf, "/>\n"); + } + } + + if (mig->flags & QEMU_MIGRATION_COOKIE_STATS && mig->jobInfo) + qemuMigrationCookieStatisticsXMLFormat(buf, mig->jobInfo); + + virBufferAdjustIndent(buf, -2); + virBufferAddLit(buf, "</qemu-migration>\n"); + return 0; +} + + +static char *qemuMigrationCookieXMLFormatStr(virQEMUDriverPtr driver, + qemuMigrationCookiePtr mig) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + + if (qemuMigrationCookieXMLFormat(driver, &buf, mig) < 0) { + virBufferFreeAndReset(&buf); + return NULL; + } + + if (virBufferCheckError(&buf) < 0) + return NULL; + + return virBufferContentAndReset(&buf); +} + + +static qemuMigrationCookieGraphicsPtr +qemuMigrationCookieGraphicsXMLParse(xmlXPathContextPtr ctxt) +{ + qemuMigrationCookieGraphicsPtr grap; + char *tmp; + + if (VIR_ALLOC(grap) < 0) + goto error; + + if (!(tmp = virXPathString("string(./graphics/@type)", ctxt))) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("missing type attribute in migration data")); + goto error; + } + if ((grap->type = virDomainGraphicsTypeFromString(tmp)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unknown graphics type %s"), tmp); + VIR_FREE(tmp); + goto error; + } + VIR_FREE(tmp); + if (virXPathInt("string(./graphics/@port)", ctxt, &grap->port) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("missing port attribute in migration data")); + goto error; + } + 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")); + goto error; + } + } + if (!(grap->listen = virXPathString("string(./graphics/@listen)", ctxt))) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("missing listen attribute in migration data")); + goto error; + } + /* Optional */ + grap->tlsSubject = virXPathString("string(./graphics/cert[@info='subject']/@value)", ctxt); + + return grap; + + error: + qemuMigrationCookieGraphicsFree(grap); + return NULL; +} + + +static qemuMigrationCookieNetworkPtr +qemuMigrationCookieNetworkXMLParse(xmlXPathContextPtr ctxt) +{ + qemuMigrationCookieNetworkPtr optr; + size_t i; + int n; + xmlNodePtr *interfaces = NULL; + char *vporttype; + xmlNodePtr save_ctxt = ctxt->node; + + if (VIR_ALLOC(optr) < 0) + goto error; + + if ((n = virXPathNodeSet("./network/interface", ctxt, &interfaces)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("missing interface information")); + goto error; + } + + optr->nnets = n; + if (VIR_ALLOC_N(optr->net, optr->nnets) < 0) + goto error; + + for (i = 0; i < n; i++) { + /* 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")); + goto error; + } + optr->net[i].vporttype = virNetDevVPortTypeFromString(vporttype); + } + + VIR_FREE(interfaces); + + cleanup: + ctxt->node = save_ctxt; + return optr; + + error: + VIR_FREE(interfaces); + qemuMigrationCookieNetworkFree(optr); + optr = NULL; + goto cleanup; +} + + +static qemuMigrationCookieNBDPtr +qemuMigrationCookieNBDXMLParse(xmlXPathContextPtr ctxt) +{ + qemuMigrationCookieNBDPtr ret = NULL; + char *port = NULL, *capacity = NULL; + size_t i; + int n; + xmlNodePtr *disks = NULL; + xmlNodePtr save_ctxt = ctxt->node; + + if (VIR_ALLOC(ret) < 0) + goto error; + + 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); + goto error; + } + + /* 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) { + if (VIR_ALLOC_N(ret->disks, n) < 0) + goto error; + ret->ndisks = n; + + for (i = 0; i < n; i++) { + ctxt->node = disks[i]; + VIR_FREE(capacity); + + if (!(ret->disks[i].target = virXPathString("string(./@target)", ctxt))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Malformed disk target")); + goto error; + } + + 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)); + goto error; + } + } + } + + cleanup: + VIR_FREE(port); + VIR_FREE(capacity); + VIR_FREE(disks); + ctxt->node = save_ctxt; + return ret; + error: + qemuMigrationCookieNBDFree(ret); + ret = NULL; + goto cleanup; +} + + +static qemuDomainJobInfoPtr +qemuMigrationCookieStatisticsXMLParse(xmlXPathContextPtr ctxt) +{ + qemuDomainJobInfoPtr jobInfo = NULL; + qemuMonitorMigrationStats *stats; + xmlNodePtr save_ctxt = ctxt->node; + + if (!(ctxt->node = virXPathNode("./statistics", ctxt))) + goto cleanup; + + if (VIR_ALLOC(jobInfo) < 0) + goto cleanup; + + stats = &jobInfo->stats; + jobInfo->type = VIR_DOMAIN_JOB_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); + virXPathULongLong("string(./" VIR_DOMAIN_JOB_TIME_REMAINING "[1])", + ctxt, &jobInfo->timeRemaining); + + 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_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); + cleanup: + ctxt->node = save_ctxt; + return jobInfo; +} + + +static int +qemuMigrationCookieXMLParse(qemuMigrationCookiePtr mig, + virQEMUDriverPtr driver, + xmlDocPtr doc, + xmlXPathContextPtr ctxt, + unsigned int flags) +{ + char uuidstr[VIR_UUID_STRING_BUFLEN]; + char *tmp = NULL; + xmlNodePtr *nodes = NULL; + size_t i; + int n; + virCapsPtr caps = NULL; + + if (!(caps = virQEMUDriverGetCapabilities(driver, false))) + goto error; + + /* 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 (!(tmp = virXPathString("string(./name[1])", ctxt))) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("missing name element in migration data")); + goto error; + } + if (STRNEQ(tmp, mig->name)) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Incoming cookie data had unexpected name %s vs %s"), + tmp, mig->name); + goto error; + } + VIR_FREE(tmp); + + /* Extract domain uuid */ + tmp = virXPathString("string(./uuid[1])", ctxt); + if (!tmp) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("missing uuid element in migration data")); + goto error; + } + virUUIDFormat(mig->uuid, uuidstr); + if (STRNEQ(tmp, uuidstr)) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Incoming cookie data had unexpected UUID %s vs %s"), + tmp, uuidstr); + goto error; + } + VIR_FREE(tmp); + + /* Check & forbid "localhost" migration */ + if (!(mig->remoteHostname = virXPathString("string(./hostname[1])", ctxt))) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("missing hostname element in migration data")); + goto error; + } + if (STREQ(mig->remoteHostname, mig->localHostname)) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Attempt to migrate guest to the same host %s"), + mig->remoteHostname); + goto error; + } + + if (!(tmp = virXPathString("string(./hostuuid[1])", ctxt))) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("missing hostuuid element in migration data")); + goto error; + } + if (virUUIDParse(tmp, mig->remoteHostuuid) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("malformed hostuuid element in migration data")); + goto error; + } + if (memcmp(mig->remoteHostuuid, mig->localHostuuid, VIR_UUID_BUFLEN) == 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Attempt to migrate guest to the same host %s"), + tmp); + goto error; + } + VIR_FREE(tmp); + + /* Check to ensure all mandatory features from XML are also + * present in 'flags' */ + if ((n = virXPathNodeSet("./feature", ctxt, &nodes)) < 0) + goto error; + + for (i = 0; i < n; i++) { + int val; + char *str = virXMLPropString(nodes[i], "name"); + if (!str) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("missing feature name")); + goto error; + } + + if ((val = qemuMigrationCookieFlagTypeFromString(str)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unknown migration cookie feature %s"), + str); + VIR_FREE(str); + goto error; + } + + if ((flags & (1 << val)) == 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unsupported migration cookie feature %s"), + str); + VIR_FREE(str); + goto error; + } + VIR_FREE(str); + } + VIR_FREE(nodes); + + if ((flags & QEMU_MIGRATION_COOKIE_GRAPHICS) && + virXPathBoolean("count(./graphics) > 0", ctxt) && + (!(mig->graphics = qemuMigrationCookieGraphicsXMLParse(ctxt)))) + goto error; + + if ((flags & QEMU_MIGRATION_COOKIE_LOCKSTATE) && + virXPathBoolean("count(./lockstate) > 0", ctxt)) { + mig->lockDriver = virXPathString("string(./lockstate[1]/@driver)", ctxt); + if (!mig->lockDriver) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing lock driver name in migration cookie")); + goto error; + } + mig->lockState = virXPathString("string(./lockstate[1]/leases[1])", ctxt); + if (mig->lockState && STREQ(mig->lockState, "")) + VIR_FREE(mig->lockState); + } + + if ((flags & QEMU_MIGRATION_COOKIE_PERSISTENT) && + virXPathBoolean("count(./domain) > 0", ctxt)) { + if ((n = virXPathNodeSet("./domain", ctxt, &nodes)) > 1) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Too many domain elements in " + "migration cookie: %d"), + n); + goto error; + } + mig->persistent = virDomainDefParseNode(doc, nodes[0], + caps, driver->xmlopt, NULL, + VIR_DOMAIN_DEF_PARSE_INACTIVE | + VIR_DOMAIN_DEF_PARSE_ABI_UPDATE | + VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE); + if (!mig->persistent) { + /* virDomainDefParseNode already reported + * an error for us */ + goto error; + } + VIR_FREE(nodes); + } + + if ((flags & QEMU_MIGRATION_COOKIE_NETWORK) && + virXPathBoolean("count(./network) > 0", ctxt) && + (!(mig->network = qemuMigrationCookieNetworkXMLParse(ctxt)))) + goto error; + + if (flags & QEMU_MIGRATION_COOKIE_NBD && + virXPathBoolean("boolean(./nbd)", ctxt) && + (!(mig->nbd = qemuMigrationCookieNBDXMLParse(ctxt)))) + goto error; + + if (flags & QEMU_MIGRATION_COOKIE_STATS && + virXPathBoolean("boolean(./statistics)", ctxt) && + (!(mig->jobInfo = qemuMigrationCookieStatisticsXMLParse(ctxt)))) + goto error; + + virObjectUnref(caps); + return 0; + + error: + VIR_FREE(tmp); + VIR_FREE(nodes); + virObjectUnref(caps); + return -1; +} + + +static int +qemuMigrationCookieXMLParseStr(qemuMigrationCookiePtr mig, + virQEMUDriverPtr driver, + 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, doc, ctxt, flags); + + cleanup: + xmlXPathFreeContext(ctxt); + xmlFreeDoc(doc); + + return ret; +} + + +int +qemuMigrationBakeCookie(qemuMigrationCookiePtr mig, + virQEMUDriverPtr driver, + virDomainObjPtr dom, + char **cookieout, + int *cookieoutlen, + unsigned int flags) +{ + 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 (!(*cookieout = qemuMigrationCookieXMLFormatStr(driver, mig))) + return -1; + + *cookieoutlen = strlen(*cookieout) + 1; + + VIR_DEBUG("cookielen=%d cookie=%s", *cookieoutlen, *cookieout); + + return 0; +} + + +qemuMigrationCookiePtr +qemuMigrationEatCookie(virQEMUDriverPtr driver, + virDomainObjPtr dom, + const char *cookiein, + int cookieinlen, + unsigned int flags) +{ + qemuMigrationCookiePtr 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")); + goto error; + } + + VIR_DEBUG("cookielen=%d cookie='%s'", cookieinlen, NULLSTR(cookiein)); + + if (!(mig = qemuMigrationCookieNew(dom))) + return NULL; + + if (cookiein && cookieinlen && + qemuMigrationCookieXMLParseStr(mig, + driver, + cookiein, + flags) < 0) + goto error; + + if (flags & QEMU_MIGRATION_COOKIE_PERSISTENT && + mig->persistent && + STRNEQ(dom->def->name, mig->persistent->name)) { + VIR_FREE(mig->persistent->name); + if (VIR_STRDUP(mig->persistent->name, dom->def->name) < 0) + goto error; + } + + 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)); + goto error; + } + } 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)); + goto error; + } + } + + return mig; + + error: + qemuMigrationCookieFree(mig); + return NULL; +} diff --git a/src/qemu/qemu_migration_cookie.h b/src/qemu/qemu_migration_cookie.h new file mode 100644 index 000000000..ca3d639a3 --- /dev/null +++ b/src/qemu/qemu_migration_cookie.h @@ -0,0 +1,153 @@ +/* + * qemu_migration_cookie.h: 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/>. + */ + +#ifndef __QEMU_MIGRATION_COOKIE_H__ +# define __QEMU_MIGRATION_COOKIE_H__ + +enum qemuMigrationCookieFlags { + QEMU_MIGRATION_COOKIE_FLAG_GRAPHICS, + QEMU_MIGRATION_COOKIE_FLAG_LOCKSTATE, + QEMU_MIGRATION_COOKIE_FLAG_PERSISTENT, + QEMU_MIGRATION_COOKIE_FLAG_NETWORK, + QEMU_MIGRATION_COOKIE_FLAG_NBD, + QEMU_MIGRATION_COOKIE_FLAG_STATS, + QEMU_MIGRATION_COOKIE_FLAG_MEMORY_HOTPLUG, + QEMU_MIGRATION_COOKIE_FLAG_CPU_HOTPLUG, + + QEMU_MIGRATION_COOKIE_FLAG_LAST +}; + +VIR_ENUM_DECL(qemuMigrationCookieFlag); + +enum qemuMigrationCookieFeatures { + QEMU_MIGRATION_COOKIE_GRAPHICS = (1 << QEMU_MIGRATION_COOKIE_FLAG_GRAPHICS), + QEMU_MIGRATION_COOKIE_LOCKSTATE = (1 << QEMU_MIGRATION_COOKIE_FLAG_LOCKSTATE), + QEMU_MIGRATION_COOKIE_PERSISTENT = (1 << QEMU_MIGRATION_COOKIE_FLAG_PERSISTENT), + QEMU_MIGRATION_COOKIE_NETWORK = (1 << QEMU_MIGRATION_COOKIE_FLAG_NETWORK), + QEMU_MIGRATION_COOKIE_NBD = (1 << QEMU_MIGRATION_COOKIE_FLAG_NBD), + QEMU_MIGRATION_COOKIE_STATS = (1 << QEMU_MIGRATION_COOKIE_FLAG_STATS), + QEMU_MIGRATION_COOKIE_MEMORY_HOTPLUG = (1 << QEMU_MIGRATION_COOKIE_FLAG_MEMORY_HOTPLUG), + QEMU_MIGRATION_COOKIE_CPU_HOTPLUG = (1 << QEMU_MIGRATION_COOKIE_FLAG_CPU_HOTPLUG), +}; + +typedef struct _qemuMigrationCookieGraphics qemuMigrationCookieGraphics; +typedef qemuMigrationCookieGraphics *qemuMigrationCookieGraphicsPtr; +struct _qemuMigrationCookieGraphics { + int type; + int port; + int tlsPort; + char *listen; + char *tlsSubject; +}; + +typedef struct _qemuMigrationCookieNetData qemuMigrationCookieNetData; +typedef qemuMigrationCookieNetData *qemuMigrationCookieNetDataPtr; +struct _qemuMigrationCookieNetData { + int vporttype; /* enum virNetDevVPortProfile */ + + /* + * Array of pointers to saved data. Each VIF will have its own + * data to transfer. + */ + char *portdata; +}; + +typedef struct _qemuMigrationCookieNetwork qemuMigrationCookieNetwork; +typedef qemuMigrationCookieNetwork *qemuMigrationCookieNetworkPtr; +struct _qemuMigrationCookieNetwork { + /* How many virtual NICs are we saving data for? */ + int nnets; + + qemuMigrationCookieNetDataPtr net; +}; + +typedef struct _qemuMigrationCookieNBD qemuMigrationCookieNBD; +typedef qemuMigrationCookieNBD *qemuMigrationCookieNBDPtr; +struct _qemuMigrationCookieNBD { + int port; /* on which port does NBD server listen for incoming data */ + + size_t ndisks; /* Number of items in @disk array */ + struct { + char *target; /* Disk target */ + unsigned long long capacity; /* And its capacity */ + } *disks; +}; + +typedef struct _qemuMigrationCookie qemuMigrationCookie; +typedef qemuMigrationCookie *qemuMigrationCookiePtr; +struct _qemuMigrationCookie { + unsigned int flags; + unsigned int flagsMandatory; + + /* Host properties */ + unsigned char localHostuuid[VIR_UUID_BUFLEN]; + unsigned char remoteHostuuid[VIR_UUID_BUFLEN]; + char *localHostname; + char *remoteHostname; + + /* Guest properties */ + unsigned char uuid[VIR_UUID_BUFLEN]; + char *name; + + /* If (flags & QEMU_MIGRATION_COOKIE_LOCKSTATE) */ + char *lockState; + char *lockDriver; + + /* If (flags & QEMU_MIGRATION_COOKIE_GRAPHICS) */ + qemuMigrationCookieGraphicsPtr graphics; + + /* If (flags & QEMU_MIGRATION_COOKIE_PERSISTENT) */ + virDomainDefPtr persistent; + + /* If (flags & QEMU_MIGRATION_COOKIE_NETWORK) */ + qemuMigrationCookieNetworkPtr network; + + /* If (flags & QEMU_MIGRATION_COOKIE_NBD) */ + qemuMigrationCookieNBDPtr nbd; + + /* If (flags & QEMU_MIGRATION_COOKIE_STATS) */ + qemuDomainJobInfoPtr jobInfo; +}; + + +int +qemuMigrationBakeCookie(qemuMigrationCookiePtr mig, + virQEMUDriverPtr driver, + virDomainObjPtr dom, + char **cookieout, + int *cookieoutlen, + unsigned int flags); + +qemuMigrationCookiePtr +qemuMigrationEatCookie(virQEMUDriverPtr driver, + virDomainObjPtr dom, + const char *cookiein, + int cookieinlen, + unsigned int flags); + +void +qemuMigrationCookieFree(qemuMigrationCookiePtr mig); + +int +qemuMigrationCookieAddPersistent(qemuMigrationCookiePtr mig, + virDomainDefPtr def); + +virDomainDefPtr +qemuMigrationCookieGetPersistent(qemuMigrationCookiePtr mig); + +#endif -- 2.12.1 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list