Implement job handling for the block copy job (drive/blockdev-mirror)
when using -blockdev. In contrast to the previously implemented
blockjobs the block copy job introduces new images to the running qemu
instance, thus requires a bit more handling.
When copying to new images the code now makes use of blockdev-create to
format the images explicitly rather than depending on automagic qemu
behaviour.
Signed-off-by: Peter Krempa <pkrempa(a)redhat.com>
---
src/qemu/qemu_blockjob.c | 87 +++++++++++++++++
src/qemu/qemu_blockjob.h | 16 +++
src/qemu/qemu_domain.c | 13 +++
src/qemu/qemu_driver.c | 97 ++++++++++++++++---
.../blockjob-blockdev-in.xml | 14 +++
5 files changed, 216 insertions(+), 11 deletions(-)
diff --git a/src/qemu/qemu_blockjob.c b/src/qemu/qemu_blockjob.c
index 70550d17e7..3003e9c518 100644
--- a/src/qemu/qemu_blockjob.c
+++ b/src/qemu/qemu_blockjob.c
@@ -309,6 +309,40 @@ qemuBlockJobNewCreate(virDomainObjPtr vm,
}
+qemuBlockJobDataPtr
+qemuBlockJobDiskNewCopy(virDomainObjPtr vm,
+ virDomainDiskDefPtr disk,
+ virStorageSourcePtr mirror,
+ bool shallow,
+ bool reuse)
+{
+ qemuDomainObjPrivatePtr priv = vm->privateData;
+ VIR_AUTOUNREF(qemuBlockJobDataPtr) job = NULL;
+ VIR_AUTOFREE(char *) jobname = NULL;
+
+ if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV)) {
+ if (virAsprintf(&jobname, "copy-%s-%s", disk->dst,
disk->src->nodeformat) < 0)
+ return NULL;
+ } else {
+ if (!(jobname = qemuAliasDiskDriveFromDisk(disk)))
+ return NULL;
+ }
+
+ if (!(job = qemuBlockJobDataNew(QEMU_BLOCKJOB_TYPE_COPY, jobname)))
+ return NULL;
+
+ job->mirrorChain = virObjectRef(mirror);
+
+ if (shallow && !reuse)
+ job->data.copy.shallownew = true;
+
+ if (qemuBlockJobRegister(job, vm, disk, true) < 0)
+ return NULL;
+
+ VIR_RETURN_PTR(job);
+}
+
+
/**
* qemuBlockJobDiskGetJob:
* @disk: disk definition
@@ -1043,6 +1077,50 @@ qemuBlockJobProcessEventCompletedActiveCommit(virQEMUDriverPtr
driver,
}
+static void
+qemuBlockJobProcessEventConcludedCopyPivot(virQEMUDriverPtr driver,
+ virDomainObjPtr vm,
+ qemuBlockJobDataPtr job,
+ qemuDomainAsyncJob asyncJob)
+{
+ VIR_DEBUG("copy job '%s' on VM '%s' pivoted", job->name,
vm->def->name);
+
+ if (!job->disk)
+ return;
+
+ /* for shallow copy without reusing external image the user can either not
+ * specify the backing chain in which case libvirt will open and use the
+ * chain the user provided or not specify a chain in which case we'll
+ * inherit the rest of the chain */
+ if (job->data.copy.shallownew &&
+ !virStorageSourceIsBacking(job->disk->mirror->backingStore))
+ VIR_STEAL_PTR(job->disk->mirror->backingStore,
job->disk->src->backingStore);
+
+ qemuBlockJobRewriteConfigDiskSource(vm, job->disk, job->disk->mirror);
+
+ qemuBlockJobEventProcessConcludedRemoveChain(driver, vm, asyncJob,
job->disk->src);
+ virObjectUnref(job->disk->src);
+ VIR_STEAL_PTR(job->disk->src, job->disk->mirror);
+}
+
+
+static void
+qemuBlockJobProcessEventConcludedCopyAbort(virQEMUDriverPtr driver,
+ virDomainObjPtr vm,
+ qemuBlockJobDataPtr job,
+ qemuDomainAsyncJob asyncJob)
+{
+ VIR_DEBUG("copy job '%s' on VM '%s' aborted", job->name,
vm->def->name);
+
+ if (!job->disk)
+ return;
+
+ qemuBlockJobEventProcessConcludedRemoveChain(driver, vm, asyncJob,
job->disk->mirror);
+ virObjectUnref(job->disk->mirror);
+ job->disk->mirror = NULL;
+}
+
+
static void
qemuBlockJobProcessEventConcludedCreate(virQEMUDriverPtr driver,
virDomainObjPtr vm,
@@ -1111,6 +1189,12 @@ qemuBlockJobEventProcessConcludedTransition(qemuBlockJobDataPtr
job,
break;
case QEMU_BLOCKJOB_TYPE_COPY:
+ if (job->state == QEMU_BLOCKJOB_STATE_PIVOTING)
+ qemuBlockJobProcessEventConcludedCopyPivot(driver, vm, job, asyncJob);
+ else
+ qemuBlockJobProcessEventConcludedCopyAbort(driver, vm, job, asyncJob);
+ break;
+
case QEMU_BLOCKJOB_TYPE_NONE:
case QEMU_BLOCKJOB_TYPE_INTERNAL:
case QEMU_BLOCKJOB_TYPE_LAST:
@@ -1138,6 +1222,9 @@ qemuBlockJobEventProcessConcludedTransition(qemuBlockJobDataPtr
job,
break;
case QEMU_BLOCKJOB_TYPE_COPY:
+ qemuBlockJobProcessEventConcludedCopyAbort(driver, vm, job, asyncJob);
+ break;
+
case QEMU_BLOCKJOB_TYPE_NONE:
case QEMU_BLOCKJOB_TYPE_INTERNAL:
case QEMU_BLOCKJOB_TYPE_LAST:
diff --git a/src/qemu/qemu_blockjob.h b/src/qemu/qemu_blockjob.h
index ff3c4a9eb7..41a5cd91f8 100644
--- a/src/qemu/qemu_blockjob.h
+++ b/src/qemu/qemu_blockjob.h
@@ -97,6 +97,14 @@ struct _qemuBlockJobCreateData {
};
+typedef struct _qemuBlockJobCopyData qemuBlockJobCopyData;
+typedef qemuBlockJobCopyData *qemuBlockJobDataCopyPtr;
+
+struct _qemuBlockJobCopyData {
+ bool shallownew;
+};
+
+
typedef struct _qemuBlockJobData qemuBlockJobData;
typedef qemuBlockJobData *qemuBlockJobDataPtr;
@@ -113,6 +121,7 @@ struct _qemuBlockJobData {
qemuBlockJobPullData pull;
qemuBlockJobCommitData commit;
qemuBlockJobCreateData create;
+ qemuBlockJobCopyData copy;
} data;
int type; /* qemuBlockJobType */
@@ -163,6 +172,13 @@ qemuBlockJobNewCreate(virDomainObjPtr vm,
virStorageSourcePtr chain,
bool storage);
+qemuBlockJobDataPtr
+qemuBlockJobDiskNewCopy(virDomainObjPtr vm,
+ virDomainDiskDefPtr disk,
+ virStorageSourcePtr mirror,
+ bool shallow,
+ bool reuse);
+
qemuBlockJobDataPtr
qemuBlockJobDiskGetJob(virDomainDiskDefPtr disk)
ATTRIBUTE_NONNULL(1);
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
index 8f32f8a035..364046a456 100644
--- a/src/qemu/qemu_domain.c
+++ b/src/qemu/qemu_domain.c
@@ -2426,6 +2426,10 @@ qemuDomainObjPrivateXMLFormatBlockjobIterator(void *payload,
break;
case QEMU_BLOCKJOB_TYPE_COPY:
+ if (job->data.copy.shallownew)
+ virBufferAddLit(&attrBuf, " shallownew='yes'");
+ break;
+
case QEMU_BLOCKJOB_TYPE_NONE:
case QEMU_BLOCKJOB_TYPE_INTERNAL:
case QEMU_BLOCKJOB_TYPE_LAST:
@@ -2873,6 +2877,7 @@ qemuDomainObjPrivateXMLParseBlockjobDataSpecific(qemuBlockJobDataPtr
job,
virDomainXMLOptionPtr xmlopt)
{
VIR_AUTOFREE(char *) createmode = NULL;
+ VIR_AUTOFREE(char *) shallownew = NULL;
xmlNodePtr tmp;
switch ((qemuBlockJobType) job->type) {
@@ -2922,6 +2927,14 @@
qemuDomainObjPrivateXMLParseBlockjobDataSpecific(qemuBlockJobDataPtr job,
break;
case QEMU_BLOCKJOB_TYPE_COPY:
+ if ((shallownew = virXPathString("string(./@shallownew)", ctxt)))
{
+ if (STRNEQ(shallownew, "yes"))
+ goto broken;
+
+ job->data.copy.shallownew = true;
+ }
+ break;
+
case QEMU_BLOCKJOB_TYPE_NONE:
case QEMU_BLOCKJOB_TYPE_INTERNAL:
case QEMU_BLOCKJOB_TYPE_LAST:
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index e358c6a1c4..261a4167b5 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -18310,7 +18310,6 @@ qemuDomainBlockCopyCommon(virDomainObjPtr vm,
{
virQEMUDriverPtr driver = conn->privateData;
qemuDomainObjPrivatePtr priv = vm->privateData;
- VIR_AUTOFREE(char *) device = NULL;
virDomainDiskDefPtr disk = NULL;
int ret = -1;
bool need_unlink = false;
@@ -18322,6 +18321,11 @@ qemuDomainBlockCopyCommon(virDomainObjPtr vm,
qemuBlockJobDataPtr job = NULL;
VIR_AUTOUNREF(virStorageSourcePtr) mirror = mirrorsrc;
bool blockdev = virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV);
+ VIR_AUTOPTR(qemuBlockStorageSourceChainData) data = NULL;
+ VIR_AUTOPTR(qemuBlockStorageSourceChainData) crdata = NULL;
+ virStorageSourcePtr n;
+ virStorageSourcePtr mirrorBacking = NULL;
+ int rc = 0;
/* Preliminaries: find the disk we are editing, sanity checks */
virCheckFlags(VIR_DOMAIN_BLOCK_COPY_SHALLOW |
@@ -18351,9 +18355,6 @@ qemuDomainBlockCopyCommon(virDomainObjPtr vm,
if (!(disk = qemuDomainDiskByName(vm->def, path)))
goto endjob;
- if (!(device = qemuAliasDiskDriveFromDisk(disk)))
- goto endjob;
-
if (qemuDomainDiskBlockJobIsActive(disk))
goto endjob;
@@ -18428,7 +18429,8 @@ qemuDomainBlockCopyCommon(virDomainObjPtr vm,
goto endjob;
}
- /* pre-create the image file */
+ /* pre-create the image file. In case when 'blockdev' is used this is
+ * required so that libvirt can properly label the image for access by qemu */
if (!existing) {
if (virStorageFileCreate(mirror) < 0) {
virReportSystemError(errno, "%s", _("failed to create copy
target"));
@@ -18445,6 +18447,15 @@ qemuDomainBlockCopyCommon(virDomainObjPtr vm,
keepParentLabel) < 0)
goto endjob;
+ /* we must initialize XML-provided chain prior to detecting to keep semantics
+ * with VM startup */
+ if (blockdev) {
+ for (n = mirror; virStorageSourceIsBacking(n); n = n->backingStore) {
+ if (qemuDomainPrepareStorageSourceBlockdev(disk, n, priv, cfg) < 0)
+ goto endjob;
+ }
+ }
+
/* If reusing an external image that includes a backing file but the user
* did not enumerate the chain in the XML we need to detect the chain */
if (mirror_reuse &&
@@ -18456,18 +18467,72 @@ qemuDomainBlockCopyCommon(virDomainObjPtr vm,
if (qemuDomainStorageSourceChainAccessAllow(driver, vm, mirror) < 0)
goto endjob;
- if (!(job = qemuBlockJobDiskNew(vm, disk, QEMU_BLOCKJOB_TYPE_COPY, device)))
+ if (blockdev) {
+ if (mirror_reuse) {
+ if (!(data = qemuBuildStorageSourceChainAttachPrepareBlockdev(mirror,
+
priv->qemuCaps)))
+ goto endjob;
+ } else {
+ if (qemuBlockStorageSourceCreateDetectSize(vm, mirror, disk->src,
QEMU_ASYNC_JOB_NONE) < 0)
+ goto endjob;
+
+ if (mirror_shallow) {
+ /* if external backing store is populated we'll need to open it */
+ if (virStorageSourceHasBacking(mirror)) {
+ if (!(data =
qemuBuildStorageSourceChainAttachPrepareBlockdev(mirror->backingStore,
+
priv->qemuCaps)))
+ goto endjob;
+
+ mirrorBacking = mirror->backingStore;
+ } else {
+ /* backing store of original image will be reused, but the
+ * new image must refer to it in the metadata */
+ mirrorBacking = disk->src->backingStore;
+ }
+ }
+
+ if (!(crdata = qemuBuildStorageSourceChainAttachPrepareBlockdevTop(mirror,
+
priv->qemuCaps)))
+ goto endjob;
+ }
+
+ if (data) {
+ qemuDomainObjEnterMonitor(driver, vm);
+ rc = qemuBlockStorageSourceChainAttach(priv->mon, data);
+ if (qemuDomainObjExitMonitor(driver, vm) < 0)
+ goto endjob;
+
+ if (rc < 0)
+ goto endjob;
+ }
+
+ if (crdata &&
+ qemuBlockStorageSourceCreate(vm, mirror, mirrorBacking,
mirror->backingStore,
+ crdata->srcdata[0], QEMU_ASYNC_JOB_NONE) <
0)
+ goto endjob;
+ }
+
+ if (!(job = qemuBlockJobDiskNewCopy(vm, disk, mirror, mirror_shallow,
mirror_reuse)))
goto endjob;
disk->mirrorState = VIR_DOMAIN_DISK_MIRROR_STATE_NONE;
/* Actually start the mirroring */
qemuDomainObjEnterMonitor(driver, vm);
- /* qemuMonitorDriveMirror needs to honor the REUSE_EXT flag as specified
- * by the user */
- ret = qemuMonitorDriveMirror(priv->mon, device, mirror->path, format,
- bandwidth, granularity, buf_size,
- mirror_shallow, mirror_reuse);
+
+ if (blockdev) {
+ ret = qemuMonitorBlockdevMirror(priv->mon, job->name, true,
+ disk->src->nodeformat,
+ mirror->nodeformat, bandwidth,
+ granularity, buf_size, mirror_shallow);
+ } else {
+ /* qemuMonitorDriveMirror needs to honor the REUSE_EXT flag as specified
+ * by the user */
+ ret = qemuMonitorDriveMirror(priv->mon, job->name, mirror->path,
format,
+ bandwidth, granularity, buf_size,
+ mirror_shallow, mirror_reuse);
+ }
+
virDomainAuditDisk(vm, NULL, mirror, "mirror", ret >= 0);
if (qemuDomainObjExitMonitor(driver, vm) < 0)
ret = -1;
@@ -18484,6 +18549,16 @@ qemuDomainBlockCopyCommon(virDomainObjPtr vm,
qemuBlockJobStarted(job, vm);
endjob:
+ if (rc < 0 &&
+ virDomainObjIsActive(vm) &&
+ (data || crdata)) {
+ qemuDomainObjEnterMonitor(driver, vm);
+ if (data)
+ qemuBlockStorageSourceChainDetach(priv->mon, data);
+ if (crdata)
+ qemuBlockStorageSourceAttachRollback(priv->mon, crdata->srcdata[0]);
+ ignore_value(qemuDomainObjExitMonitor(driver, vm));
+ }
if (need_unlink && virStorageFileUnlink(mirror) < 0)
VIR_WARN("%s", _("unable to remove just-created copy
target"));
virStorageFileDeinit(mirror);
diff --git a/tests/qemustatusxml2xmldata/blockjob-blockdev-in.xml
b/tests/qemustatusxml2xmldata/blockjob-blockdev-in.xml
index 408e9269db..67252a3729 100644
--- a/tests/qemustatusxml2xmldata/blockjob-blockdev-in.xml
+++ b/tests/qemustatusxml2xmldata/blockjob-blockdev-in.xml
@@ -260,6 +260,9 @@
</source>
</src>
</blockjob>
+ <blockjob name='pull-vdc-libvirt-4321-format' type='copy'
state='ready' shallownew='yes'>
+ <disk dst='vdc'/>
+ </blockjob>
<blockjob name='pull-vdc-libvirt-9-format' type='commit'
state='running'>
<disk dst='vdc'/>
<base node='libvirt-11-format'/>
@@ -571,6 +574,17 @@
</backingStore>
</backingStore>
</backingStore>
+ <mirror type='file' file='/tmp/copy2' format='qcow2'
job='copy' ready='yes'>
+ <format type='qcow2'/>
+ <source file='/tmp/copy2' index='4321'>
+ <privateData>
+ <nodenames>
+ <nodename type='storage'
name='libvirt-4321-storage'/>
+ <nodename type='format'
name='libvirt-4321-format'/>
+ </nodenames>
+ </privateData>
+ </source>
+ </mirror>
<target dev='vdc' bus='virtio'/>
<alias name='virtio-disk3'/>
<address type='pci' domain='0x0000' bus='0x00'
slot='0x0d' function='0x0'/>
--
2.21.0