diff --git a/src/qemu/qemu_blockjob.c b/src/qemu/qemu_blockjob.c index 1e19347b03..1d1b46d9bf 100644 --- a/src/qemu/qemu_blockjob.c +++ b/src/qemu/qemu_blockjob.c @@ -26,6 +26,7 @@ #include "qemu_blockjob.h" #include "qemu_block.h" #include "qemu_domain.h" +#include "qemu_alias.h" #include "conf/domain_conf.h" #include "conf/domain_event.h" @@ -207,6 +208,35 @@ qemuBlockJobDiskNew(virDomainObjPtr vm, } +qemuBlockJobDataPtr +qemuBlockJobDiskNewPull(virDomainObjPtr vm, + virDomainDiskDefPtr disk, + virStorageSourcePtr base) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + VIR_AUTOUNREF(qemuBlockJobDataPtr) job = NULL; + VIR_AUTOFREE(char *) jobname = NULL; + + if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV)) { + if (virAsprintf(&jobname, "pull-%s-%s", disk->dst, disk->src->nodeformat) < 0) + return NULL; + } else { + if (!(jobname = qemuAliasDiskDriveFromDisk(disk))) + return NULL; + } + + if (!(job = qemuBlockJobDataNew(QEMU_BLOCKJOB_TYPE_PULL, jobname))) + return NULL; + + job->data.pull.base = base; + + if (qemuBlockJobRegister(job, vm, disk, true) < 0) + return NULL; + + VIR_RETURN_PTR(job); +} + + /** * qemuBlockJobDiskRegisterMirror: * @job: block job to register 'mirror' chain on @@ -630,16 +660,175 @@ qemuBlockJobEventProcessConcludedRemoveChain(virQEMUDriverPtr driver, } +/** + * qemuBlockJobGetConfigDisk: + * @vm: domain object + * @disk: disk from the running definition + * @diskChainBottom: the last element of backing chain of @disk which is relevant + * + * Finds and returns the disk corresponding to @disk in the inactive definition. + * The inactive disk must have the backing chain starting from the source until + * @@diskChainBottom identical. If @diskChainBottom is NULL the whole backing + * chains of both @disk and the persistent config definition equivalent must + * be identical. + */ +static virDomainDiskDefPtr +qemuBlockJobGetConfigDisk(virDomainObjPtr vm, + virDomainDiskDefPtr disk, + virStorageSourcePtr diskChainBottom) +{ + virStorageSourcePtr disksrc = NULL; + virStorageSourcePtr cfgsrc = NULL; + virDomainDiskDefPtr ret = NULL; + + if (!vm->newDef || !disk) + return NULL; + + disksrc = disk->src; + + if (!(ret = virDomainDiskByName(vm->newDef, disk->dst, false))) + return NULL; + + cfgsrc = ret->src; + + while (disksrc && cfgsrc) { + if (!virStorageSourceIsSameLocation(disksrc, cfgsrc)) + return NULL; + + if (diskChainBottom && diskChainBottom == disksrc) + return ret; + + disksrc = disksrc->backingStore; + cfgsrc = cfgsrc->backingStore; + } + + if (disksrc || cfgsrc) + return NULL; + + return ret; +} + + +/** + * qemuBlockJobClearConfigChain: + * @vm: domain object + * @disk: disk object from running definition of @vm + * + * In cases when the backing chain definitions of the live disk differ from + * the definition for the next start config and the backing chain would touch + * it we'd not be able to restore the chain in the next start config properly. + * + * This function checks that the source of the running disk definition and the + * config disk definition are the same and if such it clears the backing chain + * data. + */ +static void +qemuBlockJobClearConfigChain(virDomainObjPtr vm, + virDomainDiskDefPtr disk) +{ + virDomainDiskDefPtr cfgdisk = NULL; + + if (!vm->newDef || !disk) + return; + + if (!(cfgdisk = virDomainDiskByName(vm->newDef, disk->dst, false))) + return; + + if (!virStorageSourceIsSameLocation(disk->src, cfgdisk->src)) + return; + + virObjectUnref(cfgdisk->src->backingStore); + cfgdisk->src->backingStore = NULL; +} + + +/** + * qemuBlockJobProcessEventCompletedPull: + * @driver: qemu driver object + * @vm: domain object + * @job: job data + * @asyncJob: qemu asynchronous job type (for monitor interaction) + * + * This function executes the finalizing steps after a successful block pull job + * (block-stream in qemu terminology. The pull job copies all the data from the + * images in the backing chain up to the 'base' image. The 'base' image becomes + * the backing store of the active top level image. If 'base' was not used + * everything is pulled into the top level image and the top level image will + * cease to have backing store. All intermediate images between the active image + * and base image are no longer required and can be unplugged. + */ +static void +qemuBlockJobProcessEventCompletedPull(virQEMUDriverPtr driver, + virDomainObjPtr vm, + qemuBlockJobDataPtr job, + qemuDomainAsyncJob asyncJob) +{ + virStorageSourcePtr baseparent = NULL; + virDomainDiskDefPtr cfgdisk = NULL; + virStorageSourcePtr cfgbase = NULL; + virStorageSourcePtr cfgbaseparent = NULL; + virStorageSourcePtr n; + virStorageSourcePtr tmp; + + VIR_DEBUG("pull job '%s' on VM '%s' completed", job->name, vm->def->name); + + /* if the job isn't associated with a disk there's nothing to do */ + if (!job->disk) + return; + + if ((cfgdisk = qemuBlockJobGetConfigDisk(vm, job->disk, job->data.pull.base))) + cfgbase = cfgdisk->src->backingStore; + + if (!cfgdisk) + qemuBlockJobClearConfigChain(vm, job->disk); + + /* when pulling if 'base' is right below the top image we don't have to modify it */ + if (job->disk->src->backingStore == job->data.pull.base) + return; + + if (job->data.pull.base) { + for (n = job->disk->src->backingStore; n && n != job->data.pull.base; n = n->backingStore) { + /* find the image on top of 'base' */ + + if (cfgbase) { + cfgbaseparent = cfgbase; + cfgbase = cfgbase->backingStore; + } + + baseparent = n; + } + } + + tmp = job->disk->src->backingStore; + job->disk->src->backingStore = job->data.pull.base; + if (baseparent) + baseparent->backingStore = NULL; + qemuBlockJobEventProcessConcludedRemoveChain(driver, vm, asyncJob, tmp); + virObjectUnref(tmp); + + if (cfgdisk) { + tmp = cfgdisk->src->backingStore; + cfgdisk->src->backingStore = cfgbase; + if (cfgbaseparent) + cfgbaseparent->backingStore = NULL; + virObjectUnref(tmp); + } +} + + static void qemuBlockJobEventProcessConcludedTransition(qemuBlockJobDataPtr job, virQEMUDriverPtr driver, virDomainObjPtr vm, - qemuDomainAsyncJob asyncJob ATTRIBUTE_UNUSED) + qemuDomainAsyncJob asyncJob) { switch ((qemuBlockjobState) job->newstate) { case QEMU_BLOCKJOB_STATE_COMPLETED: switch ((qemuBlockJobType) job->type) { case QEMU_BLOCKJOB_TYPE_PULL: + qemuBlockJobProcessEventCompletedPull(driver, vm, job, asyncJob); + break; + case QEMU_BLOCKJOB_TYPE_COMMIT: case QEMU_BLOCKJOB_TYPE_ACTIVE_COMMIT: case QEMU_BLOCKJOB_TYPE_COPY: diff --git a/src/qemu/qemu_blockjob.h b/src/qemu/qemu_blockjob.h index 3299207610..d5848fb72c 100644 --- a/src/qemu/qemu_blockjob.h +++ b/src/qemu/qemu_blockjob.h @@ -68,6 +68,15 @@ verify((int)QEMU_BLOCKJOB_TYPE_INTERNAL == VIR_DOMAIN_BLOCK_JOB_TYPE_LAST); VIR_ENUM_DECL(qemuBlockjob); + +typedef struct _qemuBlockJobPullData qemuBlockJobPullData; +typedef qemuBlockJobPullData *qemuBlockJobDataPullPtr; + +struct _qemuBlockJobPullData { + virStorageSourcePtr base; +}; + + typedef struct _qemuBlockJobData qemuBlockJobData; typedef qemuBlockJobData *qemuBlockJobDataPtr; @@ -80,6 +89,10 @@ struct _qemuBlockJobData { virStorageSourcePtr chain; /* Reference to the chain the job operates on. */ virStorageSourcePtr mirrorChain; /* reference to 'mirror' part of the job */ + union { + qemuBlockJobPullData pull; + } data; + int type; /* qemuBlockJobType */ int state; /* qemuBlockjobState */ char *errmsg; @@ -114,6 +127,11 @@ void qemuBlockJobDiskRegisterMirror(qemuBlockJobDataPtr job) ATTRIBUTE_NONNULL(1); +qemuBlockJobDataPtr +qemuBlockJobDiskNewPull(virDomainObjPtr vm, + virDomainDiskDefPtr disk, + virStorageSourcePtr base); + qemuBlockJobDataPtr qemuBlockJobDiskGetJob(virDomainDiskDefPtr disk) ATTRIBUTE_NONNULL(1); diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index c508f55287..b638d096e3 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -2390,6 +2390,21 @@ qemuDomainObjPrivateXMLFormatBlockjobIterator(void *payload, return -1; } + switch ((qemuBlockJobType) job->type) { + case QEMU_BLOCKJOB_TYPE_PULL: + if (job->data.pull.base) + virBufferAsprintf(&childBuf, "\n", job->data.pull.base->nodeformat); + break; + + case QEMU_BLOCKJOB_TYPE_COMMIT: + case QEMU_BLOCKJOB_TYPE_ACTIVE_COMMIT: + case QEMU_BLOCKJOB_TYPE_COPY: + case QEMU_BLOCKJOB_TYPE_NONE: + case QEMU_BLOCKJOB_TYPE_INTERNAL: + case QEMU_BLOCKJOB_TYPE_LAST: + break; + } + return virXMLFormatElement(data->buf, "blockjob", &attrBuf, &childBuf); } @@ -2793,6 +2808,64 @@ qemuDomainObjPrivateXMLParseBlockjobChain(xmlNodePtr node, } +static void +qemuDomainObjPrivateXMLParseBlockjobNodename(qemuBlockJobDataPtr job, + const char *xpath, + virStorageSourcePtr *src, + xmlXPathContextPtr ctxt) +{ + VIR_AUTOFREE(char *) nodename = NULL; + + *src = NULL; + + if (!(nodename = virXPathString(xpath, ctxt))) + return; + + if (job->disk && + (*src = virStorageSourceFindByNodeName(job->disk->src, nodename, NULL))) + return; + + if (job->chain && + (*src = virStorageSourceFindByNodeName(job->chain, nodename, NULL))) + return; + + if (job->mirrorChain && + (*src = virStorageSourceFindByNodeName(job->mirrorChain, nodename, NULL))) + return; + + /* the node was in the XML but was not found in the job definitions */ + VIR_DEBUG("marking block job '%s' as invalid: node name '%s' missing", + job->name, nodename); + job->invalidData = true; +} + + +static void +qemuDomainObjPrivateXMLParseBlockjobDataSpecific(qemuBlockJobDataPtr job, + xmlXPathContextPtr ctxt) +{ + switch ((qemuBlockJobType) job->type) { + case QEMU_BLOCKJOB_TYPE_PULL: + qemuDomainObjPrivateXMLParseBlockjobNodename(job, + "string(./base/@node)", + &job->data.pull.base, + ctxt); + /* base is not present if pulling everything */ + break; + + case QEMU_BLOCKJOB_TYPE_COMMIT: + case QEMU_BLOCKJOB_TYPE_ACTIVE_COMMIT: + case QEMU_BLOCKJOB_TYPE_COPY: + case QEMU_BLOCKJOB_TYPE_NONE: + case QEMU_BLOCKJOB_TYPE_INTERNAL: + case QEMU_BLOCKJOB_TYPE_LAST: + break; + } + + return; +} + + static int qemuDomainObjPrivateXMLParseBlockjobData(virDomainObjPtr vm, xmlNodePtr node, @@ -2867,6 +2940,8 @@ qemuDomainObjPrivateXMLParseBlockjobData(virDomainObjPtr vm, if (mirror) qemuBlockJobDiskRegisterMirror(job); + qemuDomainObjPrivateXMLParseBlockjobDataSpecific(job, ctxt); + if (qemuBlockJobRegister(job, vm, disk, false) < 0) return -1; diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 9110d15cca..6c332359cd 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -17069,7 +17069,8 @@ qemuDomainBlockPullCommon(virQEMUDriverPtr driver, unsigned int flags) { qemuDomainObjPrivatePtr priv = vm->privateData; - VIR_AUTOFREE(char *) device = NULL; + const char *device = NULL; + const char *jobname = NULL; virDomainDiskDefPtr disk; virStorageSourcePtr baseSource = NULL; unsigned int baseIndex = 0; @@ -17077,6 +17078,9 @@ qemuDomainBlockPullCommon(virQEMUDriverPtr driver, VIR_AUTOFREE(char *) backingPath = NULL; unsigned long long speed = bandwidth; qemuBlockJobDataPtr job = NULL; + bool persistjob = false; + const char *nodebase = NULL; + bool blockdev = virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV); int ret = -1; if (flags & VIR_DOMAIN_BLOCK_REBASE_RELATIVE && !base) { @@ -17095,9 +17099,6 @@ qemuDomainBlockPullCommon(virQEMUDriverPtr driver, if (!(disk = qemuDomainDiskByName(vm->def, path))) goto endjob; - if (!(device = qemuAliasDiskDriveFromDisk(disk))) - goto endjob; - if (qemuDomainDiskBlockJobIsActive(disk)) goto endjob; @@ -17140,16 +17141,32 @@ qemuDomainBlockPullCommon(virQEMUDriverPtr driver, speed <<= 20; } - if (!(job = qemuBlockJobDiskNew(vm, disk, QEMU_BLOCKJOB_TYPE_PULL, device))) + if (!(job = qemuBlockJobDiskNewPull(vm, disk, baseSource))) goto endjob; + if (blockdev) { + jobname = job->name; + persistjob = true; + if (baseSource) { + nodebase = baseSource->nodeformat; + if (!backingPath && + !(backingPath = qemuBlockGetBackingStoreString(baseSource))) + goto endjob; + } + device = disk->src->nodeformat; + } else { + device = job->name; + } + qemuDomainObjEnterMonitor(driver, vm); - if (baseSource) + if (!blockdev && baseSource) basePath = qemuMonitorDiskNameLookup(priv->mon, device, disk->src, baseSource); - if (!baseSource || basePath) - ret = qemuMonitorBlockStream(priv->mon, device, NULL, false, basePath, - NULL, backingPath, speed); + + if (blockdev || + (!baseSource || basePath)) + ret = qemuMonitorBlockStream(priv->mon, device, jobname, persistjob, basePath, + nodebase, backingPath, speed); if (qemuDomainObjExitMonitor(driver, vm) < 0) ret = -1; diff --git a/tests/qemustatusxml2xmldata/blockjob-blockdev-in.xml b/tests/qemustatusxml2xmldata/blockjob-blockdev-in.xml index 7b9282d059..e962b837ac 100644 --- a/tests/qemustatusxml2xmldata/blockjob-blockdev-in.xml +++ b/tests/qemustatusxml2xmldata/blockjob-blockdev-in.xml @@ -234,6 +234,10 @@ + + + +