#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"
}
+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
}
+/**
+ * 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:
return -1;
}
+ switch ((qemuBlockJobType) job->type) {
+ case QEMU_BLOCKJOB_TYPE_PULL:
+ if (job->data.pull.base)
+ virBufferAsprintf(&childBuf, "<base node='%s'/>\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);
}
}
+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,
if (mirror)
qemuBlockJobDiskRegisterMirror(job);
+ qemuDomainObjPrivateXMLParseBlockjobDataSpecific(job, ctxt);
+
if (qemuBlockJobRegister(job, vm, disk, false) < 0)
return -1;
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;
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) {
if (!(disk = qemuDomainDiskByName(vm->def, path)))
goto endjob;
- if (!(device = qemuAliasDiskDriveFromDisk(disk)))
- goto endjob;
-
if (qemuDomainDiskBlockJobIsActive(disk))
goto endjob;
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;