[libvirt] [PATCH 0/9] qemu: Support block pull/commit jobs with blockdev (blockdev-add saga)

One of the final chapters of the saga. At the end we finally support first blockjobs with blockdev. This series depends on some of the previous postings so for convenience it can be fetched at: git fetch https://gitlab.com/pipo.sk/libvirt.git blockjobs-send-partial The above branch also contains patches which enable blockdev support, which is ideal for testing. Peter Krempa (9): qemu: blockjob: Use proper value when setting disk's READY state tests: qemustatusxml: Add few disks with backing chains for blockdev job testing qemu: blockjob: Document qemuBlockJobRegister qemu: blockjob: Register disk->mirror with a job only when required qemu: blockjob: Split out update of persistent XML disk's source on mirror jobs qemu: blockjob: Esure that persistent definition source does not copy irrelevant data qemu: blockjob: Ensure that config disk source is identical when modifying it qemu: Add -blockdev support for block pull job qemu: Add -blockdev support for block commit job src/qemu/qemu_blockjob.c | 525 +++++++++++++++++- src/qemu/qemu_blockjob.h | 40 ++ src/qemu/qemu_domain.c | 123 +++- src/qemu/qemu_driver.c | 83 ++- .../blockjob-blockdev-in.xml | 285 +++++++++- 5 files changed, 1001 insertions(+), 55 deletions(-) -- 2.21.0

Commit c412383796c used value from wrong enum when setting the disk's mirrorState variable. This meant that a 'READY' job would show up as 'PIVOTING'. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_blockjob.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qemu/qemu_blockjob.c b/src/qemu/qemu_blockjob.c index da4f20929b..e63dc49e92 100644 --- a/src/qemu/qemu_blockjob.c +++ b/src/qemu/qemu_blockjob.c @@ -723,7 +723,7 @@ qemuBlockJobEventProcess(virQEMUDriverPtr driver, case QEMU_BLOCKJOB_STATE_READY: if (job->disk && job->disk->mirror) { - job->disk->mirrorState = VIR_DOMAIN_BLOCK_JOB_READY; + job->disk->mirrorState = VIR_DOMAIN_DISK_MIRROR_STATE_READY; qemuBlockJobEmitEvents(driver, vm, job->disk, job->type, job->newstate); } job->state = job->newstate; -- 2.21.0

On 7/24/19 4:07 PM, Peter Krempa wrote:
Commit c412383796c used value from wrong enum when setting the disk's
used a value from the wrong enum
mirrorState variable. This meant that a 'READY' job would show up as 'PIVOTING'.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_blockjob.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
Thankfully, not a released regression. ACK. -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3226 Virtualization: qemu.org | libvirt.org

Add 4 disks to the blockjob-blockdev-in.xml test case to allow adding data for block pull, block copy, block commit, and active block commit jobs. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- .../blockjob-blockdev-in.xml | 264 ++++++++++++++++++ 1 file changed, 264 insertions(+) diff --git a/tests/qemustatusxml2xmldata/blockjob-blockdev-in.xml b/tests/qemustatusxml2xmldata/blockjob-blockdev-in.xml index 43fe02137b..5b9777ca71 100644 --- a/tests/qemustatusxml2xmldata/blockjob-blockdev-in.xml +++ b/tests/qemustatusxml2xmldata/blockjob-blockdev-in.xml @@ -320,6 +320,270 @@ <alias name='virtio-disk0'/> <address type='pci' domain='0x0000' bus='0x00' slot='0x0a' function='0x0'/> </disk> + <disk type='file' device='disk'> + <driver name='qemu' type='qcow2'/> + <source file='/tmp/pull4.qcow2' index='3'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-3-storage'/> + <nodename type='format' name='libvirt-3-format'/> + </nodenames> + </privateData> + </source> + <backingStore type='file' index='13'> + <format type='qcow2'/> + <source file='/tmp/pull3.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-13-storage'/> + <nodename type='format' name='libvirt-13-format'/> + </nodenames> + <relPath>pull3.qcow2</relPath> + </privateData> + </source> + <backingStore type='file' index='14'> + <format type='qcow2'/> + <source file='/tmp/pull2.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-14-storage'/> + <nodename type='format' name='libvirt-14-format'/> + </nodenames> + <relPath>pull2.qcow2</relPath> + </privateData> + </source> + <backingStore type='file' index='15'> + <format type='qcow2'/> + <source file='/tmp/pull1.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-15-storage'/> + <nodename type='format' name='libvirt-15-format'/> + </nodenames> + <relPath>pull1.qcow2</relPath> + </privateData> + </source> + <backingStore type='file' index='16'> + <format type='qcow2'/> + <source file='/tmp/pull0.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-16-storage'/> + <nodename type='format' name='libvirt-16-format'/> + </nodenames> + <relPath>pull0.qcow2</relPath> + </privateData> + </source> + <backingStore/> + </backingStore> + </backingStore> + </backingStore> + </backingStore> + <target dev='vdb' bus='virtio'/> + <alias name='virtio-disk1'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x0b' function='0x0'/> + <privateData> + <qom name='/machine/peripheral/virtio-disk1/virtio-backend'/> + </privateData> + </disk> + <disk type='file' device='disk'> + <driver name='qemu' type='qcow2'/> + <source file='/tmp/commit4.qcow2' index='2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-2-storage'/> + <nodename type='format' name='libvirt-2-format'/> + </nodenames> + </privateData> + </source> + <backingStore type='file' index='9'> + <format type='qcow2'/> + <source file='/tmp/commit3.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-9-storage'/> + <nodename type='format' name='libvirt-9-format'/> + </nodenames> + <relPath>commit3.qcow2</relPath> + </privateData> + </source> + <backingStore type='file' index='10'> + <format type='qcow2'/> + <source file='/tmp/commit2.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-10-storage'/> + <nodename type='format' name='libvirt-10-format'/> + </nodenames> + <relPath>commit2.qcow2</relPath> + </privateData> + </source> + <backingStore type='file' index='11'> + <format type='qcow2'/> + <source file='/tmp/commit1.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-11-storage'/> + <nodename type='format' name='libvirt-11-format'/> + </nodenames> + <relPath>commit1.qcow2</relPath> + </privateData> + </source> + <backingStore type='file' index='12'> + <format type='qcow2'/> + <source file='/tmp/commit0.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-12-storage'/> + <nodename type='format' name='libvirt-12-format'/> + </nodenames> + <relPath>commit0.qcow2</relPath> + </privateData> + </source> + <backingStore/> + </backingStore> + </backingStore> + </backingStore> + </backingStore> + <target dev='vdc' bus='virtio'/> + <alias name='virtio-disk2'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x0c' function='0x0'/> + <privateData> + <qom name='/machine/peripheral/virtio-disk2/virtio-backend'/> + </privateData> + </disk> + <disk type='file' device='disk'> + <driver name='qemu' type='qcow2'/> + <source file='/tmp/copy4.qcow2' index='1'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-1-storage'/> + <nodename type='format' name='libvirt-1-format'/> + </nodenames> + </privateData> + </source> + <backingStore type='file' index='5'> + <format type='qcow2'/> + <source file='/tmp/copy3.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-5-storage'/> + <nodename type='format' name='libvirt-5-format'/> + </nodenames> + <relPath>copy3.qcow2</relPath> + </privateData> + </source> + <backingStore type='file' index='6'> + <format type='qcow2'/> + <source file='/tmp/copy2.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-6-storage'/> + <nodename type='format' name='libvirt-6-format'/> + </nodenames> + <relPath>copy2.qcow2</relPath> + </privateData> + </source> + <backingStore type='file' index='7'> + <format type='qcow2'/> + <source file='/tmp/copy1.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-7-storage'/> + <nodename type='format' name='libvirt-7-format'/> + </nodenames> + <relPath>copy1.qcow2</relPath> + </privateData> + </source> + <backingStore type='file' index='8'> + <format type='qcow2'/> + <source file='/tmp/copy0.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-8-storage'/> + <nodename type='format' name='libvirt-8-format'/> + </nodenames> + <relPath>copy0.qcow2</relPath> + </privateData> + </source> + <backingStore/> + </backingStore> + </backingStore> + </backingStore> + </backingStore> + <target dev='vdc' bus='virtio'/> + <alias name='virtio-disk3'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x0d' function='0x0'/> + <privateData> + <qom name='/machine/peripheral/virtio-disk3/virtio-backend'/> + </privateData> + </disk> + <disk type='file' device='disk'> + <driver name='qemu' type='qcow2'/> + <source file='/tmp/activecommit4.qcow2' index='17'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-17-storage'/> + <nodename type='format' name='libvirt-17-format'/> + </nodenames> + </privateData> + </source> + <backingStore type='file' index='18'> + <format type='qcow2'/> + <source file='/tmp/activecommit3.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-18-storage'/> + <nodename type='format' name='libvirt-18-format'/> + </nodenames> + <relPath>activecommit3.qcow2</relPath> + </privateData> + </source> + <backingStore type='file' index='19'> + <format type='qcow2'/> + <source file='/tmp/activecommit2.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-19-storage'/> + <nodename type='format' name='libvirt-19-format'/> + </nodenames> + <relPath>activecommit2.qcow2</relPath> + </privateData> + </source> + <backingStore type='file' index='20'> + <format type='qcow2'/> + <source file='/tmp/activecommit1.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-20-storage'/> + <nodename type='format' name='libvirt-20-format'/> + </nodenames> + <relPath>activecommit1.qcow2</relPath> + </privateData> + </source> + <backingStore type='file' index='21'> + <format type='qcow2'/> + <source file='/tmp/activecommit0.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-21-storage'/> + <nodename type='format' name='libvirt-21-format'/> + </nodenames> + <relPath>activecommit0.qcow2</relPath> + </privateData> + </source> + <backingStore/> + </backingStore> + </backingStore> + </backingStore> + </backingStore> + <target dev='vdd' bus='virtio'/> + <alias name='virtio-disk3'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x0e' function='0x0'/> + <privateData> + <qom name='/machine/peripheral/virtio-disk3/virtio-backend'/> + </privateData> + </disk> <controller type='usb' index='0' model='piix3-uhci'> <alias name='usb'/> <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/> -- 2.21.0

On 7/24/19 4:07 PM, Peter Krempa wrote:
Add 4 disks to the blockjob-blockdev-in.xml test case to allow adding data for block pull, block copy, block commit, and active block commit jobs.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- .../blockjob-blockdev-in.xml | 264 ++++++++++++++++++ 1 file changed, 264 insertions(+)
Lots of stuff, but looks sane. I'm guessing you actually built up a guest with this configuration, rather than typing it by hand :) ACK. -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3226 Virtualization: qemu.org | libvirt.org

Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_blockjob.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/qemu/qemu_blockjob.c b/src/qemu/qemu_blockjob.c index e63dc49e92..ac7b3a0aef 100644 --- a/src/qemu/qemu_blockjob.c +++ b/src/qemu/qemu_blockjob.c @@ -117,6 +117,16 @@ qemuBlockJobDataNew(qemuBlockJobType type, } +/** + * qemuBlockJobRegister: + * @job: job to register + * @vm: domain to register @job with + * @disk: disk to register @job with + * @savestatus: save the status XML after registering + * + * This function registers @job with @disk and @vm and records it into the status + * xml (if @savestatus is true). + */ int qemuBlockJobRegister(qemuBlockJobDataPtr job, virDomainObjPtr vm, -- 2.21.0

On 7/24/19 4:07 PM, Peter Krempa wrote:
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_blockjob.c | 10 ++++++++++ 1 file changed, 10 insertions(+)
ACK -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3226 Virtualization: qemu.org | libvirt.org

Commit job for example references existing storage in the <mirror> subelement thus tracking it separately could lead into problems. This patch introduces qemuBlockJobDiskRegisterMirror which registers the mirror chain separately only for job which require it. This also comes with remembering that in the status XML. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_blockjob.c | 22 ++++++++++++++++++- src/qemu/qemu_blockjob.h | 4 ++++ src/qemu/qemu_domain.c | 14 +++++++++++- .../blockjob-blockdev-in.xml | 2 +- 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/qemu/qemu_blockjob.c b/src/qemu/qemu_blockjob.c index ac7b3a0aef..a3109d3934 100644 --- a/src/qemu/qemu_blockjob.c +++ b/src/qemu/qemu_blockjob.c @@ -126,6 +126,9 @@ qemuBlockJobDataNew(qemuBlockJobType type, * * This function registers @job with @disk and @vm and records it into the status * xml (if @savestatus is true). + * + * Note that if @job also references a separate chain e.g. for disk mirroring + * qemuBlockJobDiskRegisterMirror should be used. */ int qemuBlockJobRegister(qemuBlockJobDataPtr job, @@ -143,7 +146,6 @@ qemuBlockJobRegister(qemuBlockJobDataPtr job, if (disk) { job->disk = disk; job->chain = virObjectRef(disk->src); - job->mirrorChain = virObjectRef(disk->mirror); QEMU_DOMAIN_DISK_PRIVATE(disk)->blockjob = virObjectRef(job); } @@ -205,6 +207,24 @@ qemuBlockJobDiskNew(virDomainObjPtr vm, } +/** + * qemuBlockJobDiskRegisterMirror: + * @job: block job to register 'mirror' chain on + * + * In cases when the disk->mirror attribute references a separate storage chain + * such as for block-copy, this function registers it with the job. Note + * that this function does not save the status XML and thus must be used before + * qemuBlockJobRegister or qemuBlockJobStarted to properly track the chain + * in the status XML. + */ +void +qemuBlockJobDiskRegisterMirror(qemuBlockJobDataPtr job) +{ + if (job->disk) + job->mirrorChain = virObjectRef(job->disk->mirror); +} + + /** * qemuBlockJobDiskGetJob: * @disk: disk definition diff --git a/src/qemu/qemu_blockjob.h b/src/qemu/qemu_blockjob.h index 47bdc54b2b..3299207610 100644 --- a/src/qemu/qemu_blockjob.h +++ b/src/qemu/qemu_blockjob.h @@ -110,6 +110,10 @@ qemuBlockJobDiskNew(virDomainObjPtr vm, const char *jobname) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(4); +void +qemuBlockJobDiskRegisterMirror(qemuBlockJobDataPtr job) + ATTRIBUTE_NONNULL(1); + qemuBlockJobDataPtr qemuBlockJobDiskGetJob(virDomainDiskDefPtr disk) ATTRIBUTE_NONNULL(1); diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index e7f28aa2b8..c508f55287 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -2367,7 +2367,10 @@ qemuDomainObjPrivateXMLFormatBlockjobIterator(void *payload, virBufferEscapeString(&childBuf, "<errmsg>%s</errmsg>", job->errmsg); if (job->disk) { - virBufferEscapeString(&childBuf, "<disk dst='%s'/>\n", job->disk->dst); + virBufferEscapeString(&childBuf, "<disk dst='%s'", job->disk->dst); + if (job->mirrorChain) + virBufferAddLit(&childBuf, " mirror='yes'"); + virBufferAddLit(&childBuf, "/>\n"); } else { if (job->chain && qemuDomainObjPrivateXMLFormatBlockjobFormatChain(&chainsBuf, @@ -2806,6 +2809,7 @@ qemuDomainObjPrivateXMLParseBlockjobData(virDomainObjPtr vm, int state = QEMU_BLOCKJOB_STATE_FAILED; VIR_AUTOFREE(char *) diskdst = NULL; VIR_AUTOFREE(char *) newstatestr = NULL; + VIR_AUTOFREE(char *) mirror = NULL; int newstate = -1; bool invalidData = false; xmlNodePtr tmp; @@ -2840,6 +2844,10 @@ qemuDomainObjPrivateXMLParseBlockjobData(virDomainObjPtr vm, !(disk = virDomainDiskByName(vm->def, diskdst, false))) invalidData = true; + if ((mirror = virXPathString("string(./disk/@mirror)", ctxt)) && + STRNEQ(mirror, "yes")) + invalidData = true; + if (!disk && !invalidData) { if ((tmp = virXPathNode("./chains/disk", ctxt)) && !(job->chain = qemuDomainObjPrivateXMLParseBlockjobChain(tmp, ctxt, xmlopt))) @@ -2854,6 +2862,10 @@ qemuDomainObjPrivateXMLParseBlockjobData(virDomainObjPtr vm, job->newstate = newstate; job->errmsg = virXPathString("string(./errmsg)", ctxt); job->invalidData = invalidData; + job->disk = disk; + + if (mirror) + qemuBlockJobDiskRegisterMirror(job); if (qemuBlockJobRegister(job, vm, disk, false) < 0) return -1; diff --git a/tests/qemustatusxml2xmldata/blockjob-blockdev-in.xml b/tests/qemustatusxml2xmldata/blockjob-blockdev-in.xml index 5b9777ca71..7b9282d059 100644 --- a/tests/qemustatusxml2xmldata/blockjob-blockdev-in.xml +++ b/tests/qemustatusxml2xmldata/blockjob-blockdev-in.xml @@ -235,7 +235,7 @@ <nodename index='0'/> <blockjobs active='yes'> <blockjob name='drive-virtio-disk0' type='copy' state='ready'> - <disk dst='vda'/> + <disk dst='vda' mirror='yes'/> </blockjob> <blockjob name='test-orphan-job0' type='copy' state='ready'> <chains> -- 2.21.0

On 7/24/19 4:07 PM, Peter Krempa wrote:
Commit job for example references existing storage in the <mirror> subelement thus tracking it separately could lead into problems.
Grammar is hard to parse, maybe: The <mirror> subelement is used in two ways: in a commit job to point to existing storage, and in a block-copy job to point to additional storage. We need a way to track only the distinct storage.
This patch introduces qemuBlockJobDiskRegisterMirror which registers the mirror chain separately only for job which require it. This also comes
for jobs which
with remembering that in the status XML.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> ---
+++ b/src/qemu/qemu_blockjob.c @@ -126,6 +126,9 @@ qemuBlockJobDataNew(qemuBlockJobType type, * * This function registers @job with @disk and @vm and records it into the status * xml (if @savestatus is true). + * + * Note that if @job also references a separate chain e.g. for disk mirroring + * qemuBlockJobDiskRegisterMirror should be used.
a separate chain, e.g. for disk mirroring, then qemuBlock...
+++ b/src/qemu/qemu_domain.c @@ -2367,7 +2367,10 @@ qemuDomainObjPrivateXMLFormatBlockjobIterator(void *payload, virBufferEscapeString(&childBuf, "<errmsg>%s</errmsg>", job->errmsg);
if (job->disk) { - virBufferEscapeString(&childBuf, "<disk dst='%s'/>\n", job->disk->dst); + virBufferEscapeString(&childBuf, "<disk dst='%s'", job->disk->dst); + if (job->mirrorChain) + virBufferAddLit(&childBuf, " mirror='yes'");
Is there any RNG grammar tweak needed to permit this? Or is the private XML block internal only (if the user can never see it via virXYZGetXMLDesc, then they have no reason to submit it during virXYZCreate, so we have no reason to teach the RNG validation to ignore it). Wording fixes are minor, so ACK -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3226 Virtualization: qemu.org | libvirt.org

On Wed, Jul 24, 2019 at 17:02:46 -0500, Eric Blake wrote:
On 7/24/19 4:07 PM, Peter Krempa wrote:
Commit job for example references existing storage in the <mirror> subelement thus tracking it separately could lead into problems.
Grammar is hard to parse, maybe:
The <mirror> subelement is used in two ways: in a commit job to point to existing storage, and in a block-copy job to point to additional storage. We need a way to track only the distinct storage.
This patch introduces qemuBlockJobDiskRegisterMirror which registers the mirror chain separately only for job which require it. This also comes
for jobs which
with remembering that in the status XML.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> ---
+++ b/src/qemu/qemu_blockjob.c @@ -126,6 +126,9 @@ qemuBlockJobDataNew(qemuBlockJobType type, * * This function registers @job with @disk and @vm and records it into the status * xml (if @savestatus is true). + * + * Note that if @job also references a separate chain e.g. for disk mirroring + * qemuBlockJobDiskRegisterMirror should be used.
a separate chain, e.g. for disk mirroring, then qemuBlock...
+++ b/src/qemu/qemu_domain.c @@ -2367,7 +2367,10 @@ qemuDomainObjPrivateXMLFormatBlockjobIterator(void *payload, virBufferEscapeString(&childBuf, "<errmsg>%s</errmsg>", job->errmsg);
if (job->disk) { - virBufferEscapeString(&childBuf, "<disk dst='%s'/>\n", job->disk->dst); + virBufferEscapeString(&childBuf, "<disk dst='%s'", job->disk->dst); + if (job->mirrorChain) + virBufferAddLit(&childBuf, " mirror='yes'");
Is there any RNG grammar tweak needed to permit this? Or is the private XML block internal only (if the user can never see it via virXYZGetXMLDesc, then they have no reason to submit it during virXYZCreate, so we have no reason to teach the RNG validation to ignore it).
The users can't see it (unless poking into internal files) and for some weird reason we never implemented RNG for the status XML part. I ran with it for consistency.

Both active block commit and block copy modify the disk source of the active definition and thus also must modify the corresponding inactive definition source so that the VM starts up later. This is currently implemented in the legacy block job handler but the logic will be ueful also for the new handlers. Split it out which also simplifies it. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_blockjob.c | 64 ++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/src/qemu/qemu_blockjob.c b/src/qemu/qemu_blockjob.c index a3109d3934..f9e79db131 100644 --- a/src/qemu/qemu_blockjob.c +++ b/src/qemu/qemu_blockjob.c @@ -434,6 +434,44 @@ qemuBlockJobEmitEvents(virQEMUDriverPtr driver, } +/** + * qemuBlockJobRewriteConfigDiskSource: + * @vm: domain object + * @disk: live definition disk + * @newsrc: new source which should be also considered for the new disk + * + * For block jobs which modify the running disk source it is required that we + * try our best to update the config XML's disk source as well in most cases. + * + * This helper finds the disk from the persistent definition corresponding to + * @disk and updates it's source to @newsrc. + */ +static void +qemuBlockJobRewriteConfigDiskSource(virDomainObjPtr vm, + virDomainDiskDefPtr disk, + virStorageSourcePtr newsrc) +{ + virDomainDiskDefPtr persistDisk = NULL; + VIR_AUTOUNREF(virStorageSourcePtr) copy = NULL; + + if (!vm->newDef) + return; + + if (!(persistDisk = virDomainDiskByName(vm->newDef, disk->dst, false))) + return; + + if (!(copy = virStorageSourceCopy(newsrc, false)) || + virStorageSourceInitChainElement(copy, persistDisk->src, true) < 0) { + VIR_WARN("Unable to update persistent definition on vm %s after block job", + vm->def->name); + return; + } + + virObjectUnref(persistDisk->src); + VIR_STEAL_PTR(persistDisk->src, copy); +} + + static void qemuBlockJobEventProcessLegacyCompleted(virQEMUDriverPtr driver, virDomainObjPtr vm, @@ -441,36 +479,12 @@ qemuBlockJobEventProcessLegacyCompleted(virQEMUDriverPtr driver, int asyncJob) { virDomainDiskDefPtr disk = job->disk; - virDomainDiskDefPtr persistDisk = NULL; if (!disk) return; if (disk->mirrorState == VIR_DOMAIN_DISK_MIRROR_STATE_PIVOT) { - if (vm->newDef) { - virStorageSourcePtr copy = NULL; - - if ((persistDisk = virDomainDiskByName(vm->newDef, - disk->dst, false))) { - copy = virStorageSourceCopy(disk->mirror, false); - if (!copy || - virStorageSourceInitChainElement(copy, - persistDisk->src, - true) < 0) { - VIR_WARN("Unable to update persistent definition " - "on vm %s after block job", - vm->def->name); - virObjectUnref(copy); - copy = NULL; - persistDisk = NULL; - } - } - if (copy) { - virObjectUnref(persistDisk->src); - persistDisk->src = copy; - } - } - + qemuBlockJobRewriteConfigDiskSource(vm, disk, disk->mirror); /* XXX We want to revoke security labels as well as audit that * revocation, before dropping the original source. But it gets * tricky if both source and mirror share common backing files (we -- 2.21.0

On 7/24/19 4:07 PM, Peter Krempa wrote:
Both active block commit and block copy modify the disk source of the active definition and thus also must modify the corresponding inactive definition source so that the VM starts up later. This is currently implemented in the legacy block job handler but the logic will be ueful
useful
also for the new handlers. Split it out which also simplifies it.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_blockjob.c | 64 ++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 25 deletions(-)
More lines is simpler? /me reads patch Aha, the inflation is caused by doc comments; while the code itself represents the same construct in fewer lines due to saner line wrapping :)
diff --git a/src/qemu/qemu_blockjob.c b/src/qemu/qemu_blockjob.c index a3109d3934..f9e79db131 100644 --- a/src/qemu/qemu_blockjob.c +++ b/src/qemu/qemu_blockjob.c @@ -434,6 +434,44 @@ qemuBlockJobEmitEvents(virQEMUDriverPtr driver, }
+/** + * qemuBlockJobRewriteConfigDiskSource: + * @vm: domain object + * @disk: live definition disk + * @newsrc: new source which should be also considered for the new disk + * + * For block jobs which modify the running disk source it is required that we + * try our best to update the config XML's disk source as well in most cases. + * + * This helper finds the disk from the persistent definition corresponding to + * @disk and updates it's source to @newsrc.
its (remember, "it's" is okay only if "it is" can be substituted) ACK -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3226 Virtualization: qemu.org | libvirt.org

Since we copy everything from the original storage source including some runtime data which are not relevant for the config we should clear them. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_blockjob.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/qemu/qemu_blockjob.c b/src/qemu/qemu_blockjob.c index f9e79db131..f5574b7e03 100644 --- a/src/qemu/qemu_blockjob.c +++ b/src/qemu/qemu_blockjob.c @@ -433,6 +433,25 @@ qemuBlockJobEmitEvents(virQEMUDriverPtr driver, virObjectEventStateQueue(driver->domainEventState, event2); } +/** + * qemuBlockJobCleanStorageSourceRuntime: + * @src: storage source to clean from runtime data + * + * Remove all runtime related data from the storage source. + */ +static void +qemuBlockJobCleanStorageSourceRuntime(virStorageSourcePtr src) +{ + src->id = 0; + src->detected = false; + VIR_FREE(src->relPath); + VIR_FREE(src->backingStoreRaw); + VIR_FREE(src->nodestorage); + VIR_FREE(src->nodeformat); + VIR_FREE(src->tlsAlias); + VIR_FREE(src->tlsCertdir); +} + /** * qemuBlockJobRewriteConfigDiskSource: @@ -467,6 +486,8 @@ qemuBlockJobRewriteConfigDiskSource(virDomainObjPtr vm, return; } + qemuBlockJobCleanStorageSourceRuntime(copy); + virObjectUnref(persistDisk->src); VIR_STEAL_PTR(persistDisk->src, copy); } -- 2.21.0

On 7/24/19 4:07 PM, Peter Krempa wrote: typo in subject: Ensure subject line is long, maybe: qemu: blockjob: Clear out any irrelevant data in copied source
Since we copy everything from the original storage source including some runtime data which are not relevant for the config we should clear them.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_blockjob.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+)
diff --git a/src/qemu/qemu_blockjob.c b/src/qemu/qemu_blockjob.c
ACK. -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3226 Virtualization: qemu.org | libvirt.org

qemuBlockJobRewriteConfigDiskSource rewrites the disk source only according to the 'target'. This means that if someone would change the inactive config of the VM to refer to a different disk a block job would rewrite it when finishing a job which modifies the disk source. Make sure that this does not happen by verifying that the source of the config disk is the same. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_blockjob.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/qemu/qemu_blockjob.c b/src/qemu/qemu_blockjob.c index f5574b7e03..0c0ae89f10 100644 --- a/src/qemu/qemu_blockjob.c +++ b/src/qemu/qemu_blockjob.c @@ -479,6 +479,9 @@ qemuBlockJobRewriteConfigDiskSource(virDomainObjPtr vm, if (!(persistDisk = virDomainDiskByName(vm->newDef, disk->dst, false))) return; + if (!virStorageSourceIsSameLocation(disk->src, persistDisk->src)) + return; + if (!(copy = virStorageSourceCopy(newsrc, false)) || virStorageSourceInitChainElement(copy, persistDisk->src, true) < 0) { VIR_WARN("Unable to update persistent definition on vm %s after block job", -- 2.21.0

On 7/24/19 4:07 PM, Peter Krempa wrote:
qemuBlockJobRewriteConfigDiskSource rewrites the disk source only according to the 'target'. This means that if someone would change the inactive config of the VM to refer to a different disk a block job would rewrite it when finishing a job which modifies the disk source.
Make sure that this does not happen by verifying that the source of the config disk is the same.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_blockjob.c | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/src/qemu/qemu_blockjob.c b/src/qemu/qemu_blockjob.c index f5574b7e03..0c0ae89f10 100644 --- a/src/qemu/qemu_blockjob.c +++ b/src/qemu/qemu_blockjob.c @@ -479,6 +479,9 @@ qemuBlockJobRewriteConfigDiskSource(virDomainObjPtr vm, if (!(persistDisk = virDomainDiskByName(vm->newDef, disk->dst, false))) return;
+ if (!virStorageSourceIsSameLocation(disk->src, persistDisk->src)) + return;
ACK. Especially useful if I hotunplug disk vdb pointing to file1 and then hotplug disk vdb pointing to file2, but without updating config in the meantime. -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3226 Virtualization: qemu.org | libvirt.org

Introduce the handler for finalizing a block pull job which will allow to use it with blockdev. This patch also contains some additional machinery which is required to store all the relevant job data in the status XML which will also be reused with other block job types. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_blockjob.c | 191 +++++++++++++++++- src/qemu/qemu_blockjob.h | 18 ++ src/qemu/qemu_domain.c | 77 +++++++ src/qemu/qemu_driver.c | 33 ++- .../blockjob-blockdev-in.xml | 4 + 5 files changed, 313 insertions(+), 10 deletions(-) diff --git a/src/qemu/qemu_blockjob.c b/src/qemu/qemu_blockjob.c index 0c0ae89f10..a29af7ec48 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..ec1dda4870 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, "<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); } @@ -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, @@ -2863,10 +2936,14 @@ qemuDomainObjPrivateXMLParseBlockjobData(virDomainObjPtr vm, job->errmsg = virXPathString("string(./errmsg)", ctxt); job->invalidData = invalidData; job->disk = disk; + if (invalidData) + VIR_DEBUG("marking block job '%s' as invalid: basic data broken", job->name); 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 b6d705b679..705c1a06c0 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -17040,7 +17040,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; @@ -17048,6 +17049,8 @@ qemuDomainBlockPullCommon(virQEMUDriverPtr driver, VIR_AUTOFREE(char *) backingPath = NULL; unsigned long long speed = bandwidth; qemuBlockJobDataPtr job = NULL; + bool persistjob = false; + const char *nodebase = NULL; int ret = -1; if (flags & VIR_DOMAIN_BLOCK_REBASE_RELATIVE && !base) { @@ -17066,9 +17069,6 @@ qemuDomainBlockPullCommon(virQEMUDriverPtr driver, if (!(disk = qemuDomainDiskByName(vm->def, path))) goto endjob; - if (!(device = qemuAliasDiskDriveFromDisk(disk))) - goto endjob; - if (qemuDomainDiskBlockJobIsActive(disk)) goto endjob; @@ -17111,16 +17111,31 @@ qemuDomainBlockPullCommon(virQEMUDriverPtr driver, speed <<= 20; } - if (!(job = qemuBlockJobDiskNew(vm, disk, QEMU_BLOCKJOB_TYPE_PULL, device))) + if (!(job = qemuBlockJobDiskNewPull(vm, disk, baseSource))) goto endjob; + if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_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 (baseSource && !jobname) basePath = qemuMonitorDiskNameLookup(priv->mon, device, disk->src, baseSource); - if (!baseSource || basePath) - ret = qemuMonitorBlockStream(priv->mon, device, NULL, false, basePath, - NULL, backingPath, speed); + + if (!baseSource || basePath || jobname) + 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 @@ <allowReboot value='yes'/> <nodename index='0'/> <blockjobs active='yes'> + <blockjob name='pull-vdb-libvirt-3-format' type='pull' state='running'> + <disk dst='vdb'/> + <base node='libvirt-14-format'/> + </blockjob> <blockjob name='drive-virtio-disk0' type='copy' state='ready'> <disk dst='vda' mirror='yes'/> </blockjob> -- 2.21.0

On Wed, Jul 24, 2019 at 11:07:35PM +0200, Peter Krempa wrote:
Introduce the handler for finalizing a block pull job which will allow to use it with blockdev.
This patch also contains some additional machinery which is required to store all the relevant job data in the status XML which will also be reused with other block job types.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_blockjob.c | 191 +++++++++++++++++- src/qemu/qemu_blockjob.h | 18 ++ src/qemu/qemu_domain.c | 77 +++++++ src/qemu/qemu_driver.c | 33 ++- .../blockjob-blockdev-in.xml | 4 + 5 files changed, 313 insertions(+), 10 deletions(-)
diff --git a/src/qemu/qemu_blockjob.c b/src/qemu/qemu_blockjob.c index 0c0ae89f10..a29af7ec48 100644 --- a/src/qemu/qemu_blockjob.c +++ b/src/qemu/qemu_blockjob.c +/** + * 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; +
Consider: cfgdisk = ...(); if (cfgdisk) cfgbase = ...; else ...();
+ 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;
You can unref tmp directly here
+ 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..ec1dda4870 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, "<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); }
@@ -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:
virReportEnumRangeError
+ break; + } + + return; +} + + static int qemuDomainObjPrivateXMLParseBlockjobData(virDomainObjPtr vm, xmlNodePtr node, @@ -2863,10 +2936,14 @@ qemuDomainObjPrivateXMLParseBlockjobData(virDomainObjPtr vm, job->errmsg = virXPathString("string(./errmsg)", ctxt); job->invalidData = invalidData; job->disk = disk; + if (invalidData) + VIR_DEBUG("marking block job '%s' as invalid: basic data broken", job->name);
This hunk belongs to a separate patch.
if (mirror) qemuBlockJobDiskRegisterMirror(job);
+ qemuDomainObjPrivateXMLParseBlockjobDataSpecific(job, ctxt); +
Possibly this one too.
if (qemuBlockJobRegister(job, vm, disk, false) < 0) return -1;
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index b6d705b679..705c1a06c0 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -17040,7 +17040,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; @@ -17048,6 +17049,8 @@ qemuDomainBlockPullCommon(virQEMUDriverPtr driver, VIR_AUTOFREE(char *) backingPath = NULL; unsigned long long speed = bandwidth; qemuBlockJobDataPtr job = NULL; + bool persistjob = false; + const char *nodebase = NULL; int ret = -1;
if (flags & VIR_DOMAIN_BLOCK_REBASE_RELATIVE && !base) { @@ -17066,9 +17069,6 @@ qemuDomainBlockPullCommon(virQEMUDriverPtr driver, if (!(disk = qemuDomainDiskByName(vm->def, path))) goto endjob;
- if (!(device = qemuAliasDiskDriveFromDisk(disk))) - goto endjob; - if (qemuDomainDiskBlockJobIsActive(disk)) goto endjob;
@@ -17111,16 +17111,31 @@ qemuDomainBlockPullCommon(virQEMUDriverPtr driver, speed <<= 20; }
- if (!(job = qemuBlockJobDiskNew(vm, disk, QEMU_BLOCKJOB_TYPE_PULL, device))) + if (!(job = qemuBlockJobDiskNewPull(vm, disk, baseSource))) goto endjob;
+ if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV)) {
Please put this capability in a 'blockdev' bool and use it instead of jobname below:
+ 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 (baseSource && !jobname)
if (!blockdev && baseSource)
basePath = qemuMonitorDiskNameLookup(priv->mon, device, disk->src, baseSource); - if (!baseSource || basePath)
Oh, this is a clever way to avoiding a 'exit_monitor' label and jump to it when the above command fails.
- ret = qemuMonitorBlockStream(priv->mon, device, NULL, false, basePath, - NULL, backingPath, speed); + + if (!baseSource || basePath || jobname)
Please either: * separate the conditions: if (blockdev || (the old one || )) grab a pint and hide in Winchester until we can get rid of the cleverness * introduce the exit_monitor label and avoid the need to extend the condition at all
+ ret = qemuMonitorBlockStream(priv->mon, device, jobname, persistjob, basePath, + nodebase, backingPath, speed); if (qemuDomainObjExitMonitor(driver, vm) < 0) ret = -1;
Reviewed-by: Ján Tomko <jtomko@redhat.com> Jano

On Fri, Jul 26, 2019 at 14:12:57 +0200, Ján Tomko wrote:
On Wed, Jul 24, 2019 at 11:07:35PM +0200, Peter Krempa wrote:
Introduce the handler for finalizing a block pull job which will allow to use it with blockdev.
This patch also contains some additional machinery which is required to store all the relevant job data in the status XML which will also be reused with other block job types.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_blockjob.c | 191 +++++++++++++++++- src/qemu/qemu_blockjob.h | 18 ++ src/qemu/qemu_domain.c | 77 +++++++ src/qemu/qemu_driver.c | 33 ++- .../blockjob-blockdev-in.xml | 4 + 5 files changed, 313 insertions(+), 10 deletions(-)
diff --git a/src/qemu/qemu_blockjob.c b/src/qemu/qemu_blockjob.c index 0c0ae89f10..a29af7ec48 100644 --- a/src/qemu/qemu_blockjob.c +++ b/src/qemu/qemu_blockjob.c +/** + * 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; +
Consider: cfgdisk = ...(); if (cfgdisk) cfgbase = ...; else ...();
There is no 'else' so this format does not make much sense.
+ 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;
You can unref tmp directly here
No I can't. 'cfgbaseparent' is in the chain of images below now 'tmp' and thus the undef would unref everything ...
+ cfgdisk->src->backingStore = cfgbase; + if (cfgbaseparent) + cfgbaseparent->backingStore = NULL;
... this inhibits deletion of the part of the chain we still want.
+ virObjectUnref(tmp); + } +}
[...]
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index c508f55287..ec1dda4870 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, "<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); }
@@ -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:
virReportEnumRangeError
In a void function?
+ break; + } + + return; +} + + static int qemuDomainObjPrivateXMLParseBlockjobData(virDomainObjPtr vm, xmlNodePtr node, @@ -2863,10 +2936,14 @@ qemuDomainObjPrivateXMLParseBlockjobData(virDomainObjPtr vm, job->errmsg = virXPathString("string(./errmsg)", ctxt); job->invalidData = invalidData; job->disk = disk; + if (invalidData) + VIR_DEBUG("marking block job '%s' as invalid: basic data broken", job->name);
This hunk belongs to a separate patch.
if (mirror) qemuBlockJobDiskRegisterMirror(job);
+ qemuDomainObjPrivateXMLParseBlockjobDataSpecific(job, ctxt); +
Possibly this one too.
Well I'd have to introduce a function which doesn't do anything here and then populate it later? Should I do it?
if (qemuBlockJobRegister(job, vm, disk, false) < 0) return -1;

On Mon, Jul 29, 2019 at 10:40:14AM +0200, Peter Krempa wrote:
On Fri, Jul 26, 2019 at 14:12:57 +0200, Ján Tomko wrote:
On Wed, Jul 24, 2019 at 11:07:35PM +0200, Peter Krempa wrote:
Introduce the handler for finalizing a block pull job which will allow to use it with blockdev.
This patch also contains some additional machinery which is required to store all the relevant job data in the status XML which will also be reused with other block job types.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_blockjob.c | 191 +++++++++++++++++- src/qemu/qemu_blockjob.h | 18 ++ src/qemu/qemu_domain.c | 77 +++++++ src/qemu/qemu_driver.c | 33 ++- .../blockjob-blockdev-in.xml | 4 + 5 files changed, 313 insertions(+), 10 deletions(-)
diff --git a/src/qemu/qemu_blockjob.c b/src/qemu/qemu_blockjob.c index 0c0ae89f10..a29af7ec48 100644 --- a/src/qemu/qemu_blockjob.c +++ b/src/qemu/qemu_blockjob.c +/** + * 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; +
Consider: cfgdisk = ...(); if (cfgdisk) cfgbase = ...; else ...();
There is no 'else' so this format does not make much sense.
+ if (!cfgdisk)
This is the 'else'
+ 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;
You can unref tmp directly here
No I can't. 'cfgbaseparent' is in the chain of images below now 'tmp' and thus the undef would unref everything ...
+ cfgdisk->src->backingStore = cfgbase; + if (cfgbaseparent) + cfgbaseparent->backingStore = NULL;
... this inhibits deletion of the part of the chain we still want.
+ virObjectUnref(tmp); + } +}
[...]
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index c508f55287..ec1dda4870 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, "<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); }
@@ -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:
virReportEnumRangeError
In a void function?
+ break; + } + + return; +} + + static int qemuDomainObjPrivateXMLParseBlockjobData(virDomainObjPtr vm, xmlNodePtr node, @@ -2863,10 +2936,14 @@ qemuDomainObjPrivateXMLParseBlockjobData(virDomainObjPtr vm, job->errmsg = virXPathString("string(./errmsg)", ctxt); job->invalidData = invalidData; job->disk = disk; + if (invalidData) + VIR_DEBUG("marking block job '%s' as invalid: basic data broken", job->name);
This hunk belongs to a separate patch.
if (mirror) qemuBlockJobDiskRegisterMirror(job);
+ qemuDomainObjPrivateXMLParseBlockjobDataSpecific(job, ctxt); +
Possibly this one too.
Well I'd have to introduce a function which doesn't do anything here and then populate it later? Should I do it?
Nah. Jano
if (qemuBlockJobRegister(job, vm, disk, false) < 0) return -1;

Introduce the handler for finalizing a block commit and active bloc commit job which will allow to use it with blockdev. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_blockjob.c | 212 ++++++++++++++++++ src/qemu/qemu_blockjob.h | 18 ++ src/qemu/qemu_domain.c | 32 +++ src/qemu/qemu_driver.c | 50 +++-- .../blockjob-blockdev-in.xml | 15 ++ 5 files changed, 311 insertions(+), 16 deletions(-) diff --git a/src/qemu/qemu_blockjob.c b/src/qemu/qemu_blockjob.c index a29af7ec48..4cbdc34b66 100644 --- a/src/qemu/qemu_blockjob.c +++ b/src/qemu/qemu_blockjob.c @@ -237,6 +237,43 @@ qemuBlockJobDiskNewPull(virDomainObjPtr vm, } +qemuBlockJobDataPtr +qemuBlockJobDiskNewCommit(virDomainObjPtr vm, + virDomainDiskDefPtr disk, + virStorageSourcePtr topparent, + virStorageSourcePtr top, + virStorageSourcePtr base) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + VIR_AUTOUNREF(qemuBlockJobDataPtr) job = NULL; + VIR_AUTOFREE(char *) jobname = NULL; + qemuBlockJobType jobtype = QEMU_BLOCKJOB_TYPE_COMMIT; + + if (topparent == NULL) + jobtype = QEMU_BLOCKJOB_TYPE_ACTIVE_COMMIT; + + if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV)) { + if (virAsprintf(&jobname, "commit-%s-%s", disk->dst, top->nodeformat) < 0) + return NULL; + } else { + if (!(jobname = qemuAliasDiskDriveFromDisk(disk))) + return NULL; + } + + if (!(job = qemuBlockJobDataNew(jobtype, jobname))) + return NULL; + + job->data.commit.topparent = topparent; + job->data.commit.top = top; + job->data.commit.base = base; + + if (qemuBlockJobRegister(job, vm, disk, true) < 0) + return NULL; + + VIR_RETURN_PTR(job); +} + + /** * qemuBlockJobDiskRegisterMirror: * @job: block job to register 'mirror' chain on @@ -816,6 +853,167 @@ qemuBlockJobProcessEventCompletedPull(virQEMUDriverPtr driver, } +/** + * qemuBlockJobProcessEventCompletedCommit: + * @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 commit + * job. The commit job moves the blocks from backing chain images starting from + * 'top' into the 'base' image. The overlay of the 'top' image ('topparent') + * then directly references the 'base' image. All intermediate images can be + * removed/deleted. + */ +static void +qemuBlockJobProcessEventCompletedCommit(virQEMUDriverPtr driver, + virDomainObjPtr vm, + qemuBlockJobDataPtr job, + qemuDomainAsyncJob asyncJob) +{ + virStorageSourcePtr baseparent = NULL; + virDomainDiskDefPtr cfgdisk = NULL; + virStorageSourcePtr cfgnext = NULL; + virStorageSourcePtr cfgtopparent = NULL; + virStorageSourcePtr cfgtop = NULL; + virStorageSourcePtr cfgbase = NULL; + virStorageSourcePtr cfgbaseparent = NULL; + virStorageSourcePtr n; + + VIR_DEBUG("commit 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.commit.base))) + cfgnext = cfgdisk->src; + + if (!cfgdisk) + qemuBlockJobClearConfigChain(vm, job->disk); + + for (n = job->disk->src; n && n != job->data.commit.base; n = n->backingStore) { + if (cfgnext) { + if (n == job->data.commit.topparent) + cfgtopparent = cfgnext; + + if (n == job->data.commit.top) + cfgtop = cfgnext; + + cfgbaseparent = cfgnext; + cfgnext = cfgnext->backingStore; + } + baseparent = n; + } + + if (!n) + return; + + /* revert access to images */ + qemuDomainStorageSourceAccessAllow(driver, vm, job->data.commit.base, true, false); + if (job->data.commit.topparent != job->disk->src) + qemuDomainStorageSourceAccessAllow(driver, vm, job->data.commit.topparent, true, false); + + baseparent->backingStore = NULL; + job->data.commit.topparent->backingStore = job->data.commit.base; + + qemuBlockJobEventProcessConcludedRemoveChain(driver, vm, asyncJob, job->data.commit.top); + virObjectUnref(job->data.commit.top); + job->data.commit.top = NULL; + + if (cfgbaseparent) { + cfgbase = cfgbaseparent->backingStore; + cfgbaseparent->backingStore = NULL; + + if (cfgtopparent) + cfgtopparent->backingStore = cfgbase; + else + cfgdisk->src = cfgbase; + + virObjectUnref(cfgtop); + } +} + + +/** + * qemuBlockJobProcessEventCompletedActiveCommit: + * @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 active layer + * block commit job. The commit job moves the blocks from backing chain images + * starting from the active disk source image into the 'base' image. The disk + * source then changes to the 'base' image. All intermediate images can be + * removed/deleted. + */ +static void +qemuBlockJobProcessEventCompletedActiveCommit(virQEMUDriverPtr driver, + virDomainObjPtr vm, + qemuBlockJobDataPtr job, + qemuDomainAsyncJob asyncJob) +{ + virStorageSourcePtr baseparent = NULL; + virDomainDiskDefPtr cfgdisk = NULL; + virStorageSourcePtr cfgnext = NULL; + virStorageSourcePtr cfgtop = NULL; + virStorageSourcePtr cfgbase = NULL; + virStorageSourcePtr cfgbaseparent = NULL; + virStorageSourcePtr n; + + VIR_DEBUG("active commit 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.commit.base))) + cfgnext = cfgdisk->src; + + for (n = job->disk->src; n && n != job->data.commit.base; n = n->backingStore) { + if (cfgnext) { + if (n == job->data.commit.top) + cfgtop = cfgnext; + + cfgbaseparent = cfgnext; + cfgnext = cfgnext->backingStore; + } + baseparent = n; + } + + if (!n) + return; + + if (!cfgdisk) { + /* in case when the config disk chain didn't match but the disk top seems + * to be identical we need to modify the disk source since the active + * commit makes the top level image invalid. + */ + qemuBlockJobRewriteConfigDiskSource(vm, job->disk, job->data.commit.base); + } else { + cfgbase = cfgbaseparent->backingStore; + cfgbaseparent->backingStore = NULL; + cfgdisk->src = cfgbase; + virObjectUnref(cfgtop); + } + + /* Move security driver metadata */ + if (qemuSecurityMoveImageMetadata(driver, vm, job->disk->src, job->data.commit.base) < 0) + VIR_WARN("Unable to move disk metadata on vm %s", vm->def->name); + + baseparent->backingStore = NULL; + job->disk->src = job->data.commit.base; + + qemuBlockJobEventProcessConcludedRemoveChain(driver, vm, asyncJob, job->data.commit.top); + virObjectUnref(job->data.commit.top); + job->data.commit.top = NULL; + /* the mirror element does not serve functional purpose for the commit job */ + virObjectUnref(job->disk->mirror); + job->disk->mirror = NULL; +} + static void qemuBlockJobEventProcessConcludedTransition(qemuBlockJobDataPtr job, virQEMUDriverPtr driver, @@ -830,7 +1028,13 @@ qemuBlockJobEventProcessConcludedTransition(qemuBlockJobDataPtr job, break; case QEMU_BLOCKJOB_TYPE_COMMIT: + qemuBlockJobProcessEventCompletedCommit(driver, vm, job, asyncJob); + break; + case QEMU_BLOCKJOB_TYPE_ACTIVE_COMMIT: + qemuBlockJobProcessEventCompletedActiveCommit(driver, vm, job, asyncJob); + break; + case QEMU_BLOCKJOB_TYPE_COPY: case QEMU_BLOCKJOB_TYPE_NONE: case QEMU_BLOCKJOB_TYPE_INTERNAL: @@ -845,7 +1049,15 @@ qemuBlockJobEventProcessConcludedTransition(qemuBlockJobDataPtr job, switch ((qemuBlockJobType) job->type) { case QEMU_BLOCKJOB_TYPE_PULL: case QEMU_BLOCKJOB_TYPE_COMMIT: + break; + case QEMU_BLOCKJOB_TYPE_ACTIVE_COMMIT: + if (job->disk) { + virObjectUnref(job->disk->mirror); + job->disk->mirror = NULL; + } + break; + case QEMU_BLOCKJOB_TYPE_COPY: case QEMU_BLOCKJOB_TYPE_NONE: case QEMU_BLOCKJOB_TYPE_INTERNAL: diff --git a/src/qemu/qemu_blockjob.h b/src/qemu/qemu_blockjob.h index d5848fb72c..8139a1a324 100644 --- a/src/qemu/qemu_blockjob.h +++ b/src/qemu/qemu_blockjob.h @@ -77,6 +77,16 @@ struct _qemuBlockJobPullData { }; +typedef struct _qemuBlockJobCommitData qemuBlockJobCommitData; +typedef qemuBlockJobCommitData *qemuBlockJobDataCommitPtr; + +struct _qemuBlockJobCommitData { + virStorageSourcePtr topparent; + virStorageSourcePtr top; + virStorageSourcePtr base; +}; + + typedef struct _qemuBlockJobData qemuBlockJobData; typedef qemuBlockJobData *qemuBlockJobDataPtr; @@ -91,6 +101,7 @@ struct _qemuBlockJobData { union { qemuBlockJobPullData pull; + qemuBlockJobCommitData commit; } data; int type; /* qemuBlockJobType */ @@ -132,6 +143,13 @@ qemuBlockJobDiskNewPull(virDomainObjPtr vm, virDomainDiskDefPtr disk, virStorageSourcePtr base); +qemuBlockJobDataPtr +qemuBlockJobDiskNewCommit(virDomainObjPtr vm, + virDomainDiskDefPtr disk, + virStorageSourcePtr topparent, + virStorageSourcePtr top, + virStorageSourcePtr base); + qemuBlockJobDataPtr qemuBlockJobDiskGetJob(virDomainDiskDefPtr disk) ATTRIBUTE_NONNULL(1); diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index ec1dda4870..26a951b6d3 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -2398,6 +2398,12 @@ qemuDomainObjPrivateXMLFormatBlockjobIterator(void *payload, case QEMU_BLOCKJOB_TYPE_COMMIT: case QEMU_BLOCKJOB_TYPE_ACTIVE_COMMIT: + if (job->data.commit.base) + virBufferAsprintf(&childBuf, "<base node='%s'/>\n", job->data.commit.base->nodeformat); + if (job->data.commit.top) + virBufferAsprintf(&childBuf, "<top node='%s'/>\n", job->data.commit.top->nodeformat); + if (job->data.commit.topparent) + virBufferAsprintf(&childBuf, "<topparent node='%s'/>\n", job->data.commit.topparent->nodeformat); case QEMU_BLOCKJOB_TYPE_COPY: case QEMU_BLOCKJOB_TYPE_NONE: case QEMU_BLOCKJOB_TYPE_INTERNAL: @@ -2854,7 +2860,29 @@ qemuDomainObjPrivateXMLParseBlockjobDataSpecific(qemuBlockJobDataPtr job, break; case QEMU_BLOCKJOB_TYPE_COMMIT: + qemuDomainObjPrivateXMLParseBlockjobNodename(job, + "string(./topparent/@node)", + &job->data.commit.topparent, + ctxt); + + if (!job->data.commit.topparent) + goto broken; + + ATTRIBUTE_FALLTHROUGH; case QEMU_BLOCKJOB_TYPE_ACTIVE_COMMIT: + qemuDomainObjPrivateXMLParseBlockjobNodename(job, + "string(./top/@node)", + &job->data.commit.top, + ctxt); + qemuDomainObjPrivateXMLParseBlockjobNodename(job, + "string(./base/@node)", + &job->data.commit.base, + ctxt); + if (!job->data.commit.top || + !job->data.commit.base) + goto broken; + break; + case QEMU_BLOCKJOB_TYPE_COPY: case QEMU_BLOCKJOB_TYPE_NONE: case QEMU_BLOCKJOB_TYPE_INTERNAL: @@ -2863,6 +2891,10 @@ qemuDomainObjPrivateXMLParseBlockjobDataSpecific(qemuBlockJobDataPtr job, } return; + + broken: + VIR_DEBUG("marking block job '%s' as invalid: malformed job data", job->name); + job->invalidData = true; } diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 705c1a06c0..79d13674c6 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -17939,7 +17939,8 @@ qemuDomainBlockCommit(virDomainPtr dom, virQEMUDriverPtr driver = dom->conn->privateData; qemuDomainObjPrivatePtr priv; virDomainObjPtr vm = NULL; - VIR_AUTOFREE(char *) device = NULL; + const char *device = NULL; + const char *jobname = NULL; int ret = -1; virDomainDiskDefPtr disk = NULL; virStorageSourcePtr topSource; @@ -17953,8 +17954,10 @@ qemuDomainBlockCommit(virDomainPtr dom, VIR_AUTOFREE(char *) backingPath = NULL; unsigned long long speed = bandwidth; qemuBlockJobDataPtr job = NULL; - qemuBlockJobType jobtype = QEMU_BLOCKJOB_TYPE_COMMIT; VIR_AUTOUNREF(virStorageSourcePtr) mirror = NULL; + const char *nodetop = NULL; + const char *nodebase = NULL; + bool persistjob = false; /* XXX Add support for COMMIT_DELETE */ virCheckFlags(VIR_DOMAIN_BLOCK_COMMIT_SHALLOW | @@ -17989,9 +17992,6 @@ qemuDomainBlockCommit(virDomainPtr dom, if (!(disk = qemuDomainDiskByName(vm->def, path))) goto endjob; - if (!(device = qemuAliasDiskDriveFromDisk(disk))) - goto endjob; - if (virStorageSourceIsEmpty(disk->src)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("disk %s has no source file to be committed"), @@ -18023,8 +18023,6 @@ qemuDomainBlockCommit(virDomainPtr dom, disk->dst); goto endjob; } - - jobtype = QEMU_BLOCKJOB_TYPE_ACTIVE_COMMIT; } else if (flags & VIR_DOMAIN_BLOCK_COMMIT_ACTIVE) { virReportError(VIR_ERR_INVALID_ARG, _("active commit requested but '%s' is not active"), @@ -18097,7 +18095,8 @@ qemuDomainBlockCommit(virDomainPtr dom, qemuDomainStorageSourceAccessAllow(driver, vm, top_parent, false, false) < 0)) goto endjob; - if (!(job = qemuBlockJobDiskNew(vm, disk, jobtype, device))) + if (!(job = qemuBlockJobDiskNewCommit(vm, disk, top_parent, topSource, + baseSource))) goto endjob; disk->mirrorState = VIR_DOMAIN_DISK_MIRROR_STATE_NONE; @@ -18107,15 +18106,34 @@ qemuDomainBlockCommit(virDomainPtr dom, * depending on whether the input was specified as relative or * absolute (that is, our absolute top_canon may do the wrong * thing if the user specified a relative name). */ + + if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV)) { + persistjob = true; + jobname = job->name; + nodetop = topSource->nodeformat; + nodebase = baseSource->nodeformat; + device = disk->src->nodeformat; + if (!backingPath && top_parent && + !(backingPath = qemuBlockGetBackingStoreString(baseSource))) + goto endjob; + } else { + device = job->name; + } + qemuDomainObjEnterMonitor(driver, vm); - basePath = qemuMonitorDiskNameLookup(priv->mon, device, disk->src, - baseSource); - topPath = qemuMonitorDiskNameLookup(priv->mon, device, disk->src, - topSource); - if (basePath && topPath) - ret = qemuMonitorBlockCommit(priv->mon, device, NULL, false, - topPath, NULL, basePath, NULL, backingPath, - speed); + + if (!jobname) { + basePath = qemuMonitorDiskNameLookup(priv->mon, device, disk->src, + baseSource); + topPath = qemuMonitorDiskNameLookup(priv->mon, device, disk->src, + topSource); + } + + if ((basePath && topPath) || jobname) + ret = qemuMonitorBlockCommit(priv->mon, device, jobname, persistjob, + topPath, nodetop, basePath, nodebase, + backingPath, speed); + if (qemuDomainObjExitMonitor(driver, vm) < 0 || ret < 0) { ret = -1; goto endjob; diff --git a/tests/qemustatusxml2xmldata/blockjob-blockdev-in.xml b/tests/qemustatusxml2xmldata/blockjob-blockdev-in.xml index e962b837ac..d06bbb7059 100644 --- a/tests/qemustatusxml2xmldata/blockjob-blockdev-in.xml +++ b/tests/qemustatusxml2xmldata/blockjob-blockdev-in.xml @@ -238,6 +238,17 @@ <disk dst='vdb'/> <base node='libvirt-14-format'/> </blockjob> + <blockjob name='pull-vdd-libvirt-17-format' type='active-commit' state='ready'> + <disk dst='vdd'/> + <base node='libvirt-19-format'/> + <top node='libvirt-17-format'/> + </blockjob> + <blockjob name='pull-vdc-libvirt-9-format' type='commit' state='running'> + <disk dst='vdc'/> + <base node='libvirt-11-format'/> + <top node='libvirt-9-format'/> + <topparent node='libvirt-2-format'/> + </blockjob> <blockjob name='drive-virtio-disk0' type='copy' state='ready'> <disk dst='vda' mirror='yes'/> </blockjob> @@ -581,6 +592,10 @@ </backingStore> </backingStore> </backingStore> + <mirror type='file' job='active-commit' ready='yes'> + <format type='qcow2'/> + <source file='/tmp/activecommit2.qcow2'/> + </mirror> <target dev='vdd' bus='virtio'/> <alias name='virtio-disk3'/> <address type='pci' domain='0x0000' bus='0x00' slot='0x0e' function='0x0'/> -- 2.21.0

On Wed, Jul 24, 2019 at 11:07:36PM +0200, Peter Krempa wrote:
Introduce the handler for finalizing a block commit and active bloc commit job which will allow to use it with blockdev.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_blockjob.c | 212 ++++++++++++++++++ src/qemu/qemu_blockjob.h | 18 ++ src/qemu/qemu_domain.c | 32 +++ src/qemu/qemu_driver.c | 50 +++-- .../blockjob-blockdev-in.xml | 15 ++ 5 files changed, 311 insertions(+), 16 deletions(-)
diff --git a/src/qemu/qemu_blockjob.c b/src/qemu/qemu_blockjob.c index a29af7ec48..4cbdc34b66 100644 --- a/src/qemu/qemu_blockjob.c +++ b/src/qemu/qemu_blockjob.c @@ -237,6 +237,43 @@ qemuBlockJobDiskNewPull(virDomainObjPtr vm, }
+qemuBlockJobDataPtr +qemuBlockJobDiskNewCommit(virDomainObjPtr vm, + virDomainDiskDefPtr disk, + virStorageSourcePtr topparent, + virStorageSourcePtr top, + virStorageSourcePtr base) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + VIR_AUTOUNREF(qemuBlockJobDataPtr) job = NULL; + VIR_AUTOFREE(char *) jobname = NULL; + qemuBlockJobType jobtype = QEMU_BLOCKJOB_TYPE_COMMIT; + + if (topparent == NULL) + jobtype = QEMU_BLOCKJOB_TYPE_ACTIVE_COMMIT; + + if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV)) { + if (virAsprintf(&jobname, "commit-%s-%s", disk->dst, top->nodeformat) < 0) + return NULL; + } else { + if (!(jobname = qemuAliasDiskDriveFromDisk(disk))) + return NULL; + } + + if (!(job = qemuBlockJobDataNew(jobtype, jobname))) + return NULL; + + job->data.commit.topparent = topparent;
/me considers making World's Top Parent T-shirts
+ job->data.commit.top = top; + job->data.commit.base = base; + + if (qemuBlockJobRegister(job, vm, disk, true) < 0) + return NULL; + + VIR_RETURN_PTR(job); +} + + /** * qemuBlockJobDiskRegisterMirror: * @job: block job to register 'mirror' chain on @@ -18107,15 +18106,34 @@ qemuDomainBlockCommit(virDomainPtr dom, * depending on whether the input was specified as relative or * absolute (that is, our absolute top_canon may do the wrong * thing if the user specified a relative name). */ + + if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV)) {
if (blockdev)
+ persistjob = true; + jobname = job->name; + nodetop = topSource->nodeformat; + nodebase = baseSource->nodeformat; + device = disk->src->nodeformat; + if (!backingPath && top_parent && + !(backingPath = qemuBlockGetBackingStoreString(baseSource))) + goto endjob; + } else { + device = job->name; + } + qemuDomainObjEnterMonitor(driver, vm); - basePath = qemuMonitorDiskNameLookup(priv->mon, device, disk->src, - baseSource); - topPath = qemuMonitorDiskNameLookup(priv->mon, device, disk->src, - topSource); - if (basePath && topPath) - ret = qemuMonitorBlockCommit(priv->mon, device, NULL, false, - topPath, NULL, basePath, NULL, backingPath, - speed); + + if (!jobname) { + basePath = qemuMonitorDiskNameLookup(priv->mon, device, disk->src, + baseSource); + topPath = qemuMonitorDiskNameLookup(priv->mon, device, disk->src, + topSource); + } + + if ((basePath && topPath) || jobname)
This is option a) I proposed in the previous patch.
+ ret = qemuMonitorBlockCommit(priv->mon, device, jobname, persistjob, + topPath, nodetop, basePath, nodebase, + backingPath, speed); +
Reviewed-by: Ján Tomko <jtomko@redhat.com> Jano
participants (3)
-
Eric Blake
-
Ján Tomko
-
Peter Krempa