From ce7229a3b0d28479e0f123efce3fa73617889a50 Mon Sep 17 00:00:00 2001 From: Peter Krempa Date: Mon, 22 Jul 2019 13:59:01 +0200 Subject: [PATCH] qemu: Add blockdev support for the block copy job MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Reviewed-by: Ján Tomko --- 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 87c6353e5a..a06672333c 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 88536362b9..e01f2e1db5 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -18351,7 +18351,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; @@ -18363,6 +18362,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 | @@ -18392,9 +18396,6 @@ qemuDomainBlockCopyCommon(virDomainObjPtr vm, if (!(disk = qemuDomainDiskByName(vm->def, path))) goto endjob; - if (!(device = qemuAliasDiskDriveFromDisk(disk))) - goto endjob; - if (qemuDomainDiskBlockJobIsActive(disk)) goto endjob; @@ -18469,7 +18470,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")); @@ -18486,6 +18488,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 && @@ -18497,18 +18508,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; @@ -18525,6 +18590,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 @@ + + + @@ -571,6 +574,17 @@ + + + + + + + + + + +