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 @@
+
+
+
+