Bug 1594747 - copy-on-read filter breaks backing chain
Summary: copy-on-read filter breaks backing chain
Keywords:
Status: CLOSED ERRATA
Alias: None
Product: Red Hat Enterprise Linux Advanced Virtualization
Classification: Red Hat
Component: qemu-kvm
Version: ---
Hardware: Unspecified
OS: Unspecified
low
medium
Target Milestone: rc
: ---
Assignee: Hanna Czenczek
QA Contact: aihua liang
URL:
Whiteboard:
Depends On:
Blocks: 1599277 1599585 1792195
TreeView+ depends on / blocked
 
Reported: 2018-06-25 10:23 UTC by Kevin Wolf
Modified: 2021-05-25 06:41 UTC (History)
11 users (show)

Fixed In Version: qemu-kvm-5.2.0-1.module+el8.4.0+9091+650b220a
Doc Type: If docs needed, set a value
Doc Text:
Clone Of:
Environment:
Last Closed: 2021-05-25 06:41:16 UTC
Type: Bug
Target Upstream Version:
Embargoed:


Attachments (Terms of Use)
Shell script generating QMP output that breaks block-commit (1.38 KB, application/x-shellscript)
2018-07-18 20:48 UTC, Hanna Czenczek
no flags Details

Description Kevin Wolf 2018-06-25 10:23:38 UTC
There are functions in QEMU that iterate through all images in the backing chain by following the bs->backing link. If a copy-on-read filter node is inserted at any point in the chain, there is no bs->backing link and the iteration will erroneously think that it has reached the base image and exit prematurely, ignoring any backing files below the filter driver.

This is currently true in upstream, but also with the backport for bug 1518738.

Comment 1 Hanna Czenczek 2018-07-18 20:44:47 UTC
A couple of notes on this:

Backing files are opened as read-only nodes by default, so you cannot insert copy-on-read nodes into an implicitly opened chain anyway.  If you add every node with blockdev-add, however, it becomes possible.  Also, you can add COR on top of everything, which makes it part of hte backing chain.

The next question would be which functions those are.  The main one that comes to my mind is bdrv_find_backing_image() for block-commit and block-stream.  block-commit disallows committing through explicitly added filter nodes, and maybe block-stream should, too, but it doesn’t.  So the main case here is a chain which has a COR filter on top and you want to commit something down the chain.

Non-active commit is something that can be made to work.  Active commit, however, is a bit trickier.  Something to note here: Our current distinction between active and non-active commit is broken, I believe.  It should not be based on whether the top node is a root node, but whether the top node is R/W, regardless of whether it is a root node.

Anyway, when you have a COR node as your root node and want to commit its filtered node somewhere down the backing chain, that will not work.  You cannot commit the COR node itself (for one thing, because we disallow committing through explicitly added filter nodes), and you cannot specify the filtered node, actually.  bdrv_find_backing_image() looks for *backing images* and this is based on bs->backing_file (which is completely wrong, but that’s a different matter[1]).  Now the COR node obviously does not have a bs->backing_file, so you cannot specify the node it filters.

But let’s assume you could specify it.  Oops, then block-commit would believe it’s a non-active commit because the node you specified is not a root node.  So that completely breaks then (maybe even silently, just corrupting your data).

So this is one case where I am actually inclined to believe it is best that COR breaks backing chains for now, because filters and backing chains just may not go too well together for now and there may be many things to fix.

(For fun I’ve attached a shell script that creates three qcow2 images in /tmp and generates some QMP output that breaks commit because it should be an active commit, but it is a non-active commit.  Use it like so:
$ sh broken-commit.sh | x86_64-softmmu/qemu-system-x86_64 -qmp stdio
[...]
=== Read works (before commit has completed) ===

read 16777216/16777216 bytes at offset 0
[...]
=== Read fails (after commit has completed) ===

Pattern verification failed at offset 0, 16777216 bytes
[...])


So what I am trying to say is that this is probably not as simple as replacing some bs->backing instances by a function bdrv_filtered_cow_bs().  Allowing filters in backing chains may lead to all kinds of funny stuff.


[1] It’s wrong because it assumes that bs->backing_file contains the image header’s backing file string.  Which it does not.  But it probably should.

Comment 2 Hanna Czenczek 2018-07-18 20:48:09 UTC
Created attachment 1459802 [details]
Shell script generating QMP output that breaks block-commit

This script invokes block-commit, which is performed as a non-active commit.  However, the node being committed may be written to.  Data written during committing may or may not be written to the base, and thus may or may not disappear when the block job completes.

Comment 3 Hanna Czenczek 2018-07-18 20:59:09 UTC
More to note, before I forget: In fact, we’d even need an active commit when any images in the backing chain of top may be written to (so top may change its data even without being written to).

However, this eventually boils down to how broken writable backing files are, because even active commit would choke on such a case.  Dirty bitmaps only track writes to the BDS, but not writes to children (we’ve had this discussion before).  When creating a dirty bitmap, we probably need to ensure that the current node blocks the WRITE permission on all of its children so it can only change by immediate writes to it.
(And we need to ensure the same when doing a non-active commit.)

Now you could say that all of this is a different kettle of fish, but it really isn’t.  The issue it all comes down to is that our filtering system is broken and we don’t quite know, how.

There are nodes that filter other nodes R/W (actual filters).  There are nodes that filter other nodes with COW (backing chains).  There are nodes that filter other nodes both R/W and COW (active mirror).  Now we mix it all and suddenly everything breaks down, which honestly probably should not be surprising.

So in fact I believe that it may be for the best that the COR driver just does not work with backing chains for now.  Because I don’t think we quite know what breaks when we do allow COR in backing chains.

(And as you can see from the commit example, things may very well break silently and just corrupt your data.)

Max

Comment 4 Hanna Czenczek 2018-08-01 16:20:57 UTC
(In reply to Max Reitz from comment #1)
> [1] It’s wrong because it assumes that bs->backing_file contains the image
> header’s backing file string.  Which it does not.  But it probably should.

Turns out this is extremely important because when you actually write a test to test this, you’ll see that you actually cannot select a backing image behind a filter node simply because the filter node distorts the filename.

Max

Comment 5 Hanna Czenczek 2018-08-01 18:08:24 UTC
So what we need first is BDS.backing_file to reflect the image header and nothing else, because if it deals with anything else it becomes a mess.
But that is not enough, because you can override backing files and then bdrv_find_backing_image() needs to ignore BDS.backing_file and needs to compare the given filename to the actual backing filename.
(But we do want it to use the image header value if the user has not overridden it, not least because otherwise we basically force the user to check out what qemu has come up due to bdrv_refresh_filename(), which is not quite optimal.)

So that basically means I need a way of telling whether a backing file has been overridden to use the correct reference in bdrv_find_backing_image(), so yeah, I need the filename series.

Max

Comment 6 Hanna Czenczek 2018-08-01 18:10:29 UTC
Also, it should be noted that throttle of course breaks backing chains exactly like COR does, probably even worse, because you can use throttle on read-only nodes and therefore actually inside of backing chains.

Max

Comment 7 Hanna Czenczek 2018-08-01 22:27:43 UTC
After tinkering more, it turns out that bdrv_find_backing_image() just doesn’t work with filters, basically whatever you do.

It doesn’t work with a filter on the top because the image below is not a backing image, so you cannot specify it.

It doesn’t work with filters below because they make using filenames effectively useless (you can query them with some command and specify them, but that’s just not OK).

So I’m back to my original question.  What is broken that isn’t broken by something else just by virtue of using filters?  query-block’s auto-descend through the backing chain maybe?

In my honest opinion, after having worked about two weeks on this (with absolutely no end in sight), the real fix is to add node name support to block-commit and to fix the query infrastructure.

Max

Comment 8 Kevin Wolf 2018-09-07 08:23:44 UTC
(In reply to Max Reitz from comment #7)
> In my honest opinion, after having worked about two weeks on this (with
> absolutely no end in sight), the real fix is to add node name support to
> block-commit and to fix the query infrastructure.

Node name support in block-commit doesn't contribute anything here because the commit job still needs a backing subchain, not just two random nodes.

$ ./qemu-img create -f qcow2 /tmp/backing.raw 64M
Formatting '/tmp/backing.raw', fmt=qcow2 size=67108864 cluster_size=65536 lazy_refcounts=off refcount_bits=16

$ ./qemu-img create -f qcow2 -b /tmp/backing.qcow2 /tmp/overlay.qcow2
Formatting '/tmp/overlay.qcow2', fmt=qcow2 size=67108864 backing_file=/tmp/backing.qcow2 cluster_size=65536 lazy_refcounts=off refcount_bits=16

$ x86_64-softmmu/qemu-system-x86_64 -blockdev file,filename=/tmp/backing.raw,node-name=backing -blockdev copy-on-read,file=backing,node-name=cor -blockdev file,filename=/tmp/overlay.qcow2,node-name=overlay-file -blockdev qcow2,file=overlay-file,backing=cor,node-name=overlay -qmp stdio

{"QMP": {"version": {"qemu": {"micro": 50, "minor": 0, "major": 3}, "package": "v3.0.0-628-ga06176f961"}, "capabilities": []}}
{"execute":"qmp_capabilities"}
{"return": {}}
{"execute":"block-commit","arguments":{"device":"overlay","top-node":"overlay","base-node":"backing","job-id":"job0"}}
{"error": {"class": "GenericError", "desc": "'backing' is not in this backing file chain"}}

Comment 9 Hanna Czenczek 2018-09-07 12:20:23 UTC
(In reply to Kevin Wolf from comment #8)
> (In reply to Max Reitz from comment #7)
> > In my honest opinion, after having worked about two weeks on this (with
> > absolutely no end in sight), the real fix is to add node name support to
> > block-commit and to fix the query infrastructure.
> 
> Node name support in block-commit doesn't contribute anything here because
> the commit job still needs a backing subchain, not just two random nodes.

Of course it contributes something.  As I asked:

(In reply to Max Reitz from comment #7)
> So I’m back to my original question.  What is broken that isn’t broken by
> something else just by virtue of using filters?

Now we have node-name support for block-commit, that’s good.  (And your series wasn‘t even on the list when I wrote that comment.)  But before, you just couldn’t issue a block-commit even with my now-upstream series on top, because filenames weren’t enough.

So, yes, now there is a good example.  But I didn’t have that a month ago.


Anyway, the result with my series is this:

$ x86_64-softmmu/qemu-system-x86_64 -blockdev file,filename=/tmp/backing.qcow2,node-name=backing -blockdev copy-on-read,file=backing,node-name=cor -blockdev file,filename=/tmp/overlay.qcow2,node-name=overlay-file -blockdev qcow2,file=overlay-file,backing=cor,node-name=overlay -qmp stdio
{"QMP": {"version": {"qemu": {"micro": 50, "minor": 0, "major": 3}, "package": "v3.0.0-668-ge4d18b7759"}, "capabilities": []}}
{"execute":"qmp_capabilities"}
{"return": {}}
{"execute":"block-commit","arguments":{"device":"overlay","top-node":"overlay","base-node":"backing","job-id":"job0"}}
{"error": {"class": "GenericError", "desc": "Cannot commit through explicit filter nodes"}}

Because I didn't want to be rash.  What do you think should happen with the COR node in the case of commit?  Or rather, what should happen with a generic filter node?

commit and mirror just read from the overlay, so the filter gets applied, which seems correct, I guess?  And since filters cannot change data, it isn't like writing the data to the base would then change its appearance in the overlay.

But then commit would need to remove the filter.  Is that what we want?  Imagine you have a throttle filter in there, is removing what the user wants?

Or with COR, you could have this configuration:

http -> file A -> COR -> file B

And then you commit file B into file A, so you get:

http -> file A

That's weird.


Now the question is what we want to do.  Either we allow this and just remove the filter drivers, explicitly documenting it.  Users have to make sure to move filter drivers before they finalize the job.  (Or along with that finalization, in a transaction.)

Or we don't allow it.  Then users would need to move the filters before invoking the job.  May be functionally the same, but I suppose it's a bit stupid.


So I suppose we do want to remove the filter nodes, we just need to document it.  (My measure is: It doesn't seem to hard to explain ("Everything in (base, top] gets committed into base, so naturally everything disappears from there"), so it sounds like a good idea.)

Max

Comment 10 Kevin Wolf 2018-09-07 13:05:59 UTC
Yes, I think removing is the right thing to do. We require the management tool to know the graph structure when it wants to use any operation on a node level. If it wanted to keep the filter, it shouldn't commit into "file A", but into "COR".

Of course, if we want to translate the existing copy-on-read=on option into a filter at some point (or throttling, or anything else), we'd have to care about compatibility with management tools that don't know about nodes. Then a filename would refer to the base file, but we would actually have to select the topmost filter above that base node as the target for the commit job.

Maybe deprecating the old options would be the easier solution. ;-)

Comment 15 Hanna Czenczek 2019-11-19 10:48:50 UTC
I’m setting the ITR to 8.3.0 because the current upstream series will not go into qemu 4.2.0, and it would be very difficult to backport it.

Max

Comment 16 Peter Krempa 2020-01-23 15:34:30 UTC
This bug breaks block-commit in the following libvirt configuration:

    <disk type='file' device='disk'>
      <driver name='qemu' type='qcow2' copy_on_read='on'/>
      <source file='/tmp/copy4.qcow2'/>
      <target dev='vda' bus='virtio'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x0a' function='0x0'/>
    </disk>

When a 'copy-on-read' driver is on top of a backing chain block-commit is not possible as QEMU reports:

{"class": "GenericError", "desc": "'libvirt-4-format' is not in this backing file chain"}}

libvirt would start qemu with the following (snipped) commandline:

/home/pipo/git/qemu.git/x86_64-softmmu/qemu-system-x86_64 \

[...]

-blockdev '{"driver":"file","filename":"/var/lib/libvirt/images/systemrescuecd-x86-4.9.5.iso","node-name":"libvirt-2-storage","auto-read-only":true,"discard":"unmap"}' \
-blockdev '{"node-name":"libvirt-2-format","read-only":true,"driver":"raw","file":"libvirt-2-storage"}' \

-device ide-cd,bus=ide.0,unit=0,drive=libvirt-2-format,id=ide0-0-0,bootindex=1 \

-blockdev '{"driver":"file","filename":"/tmp/copy0.qcow2","node-name":"libvirt-6-storage","auto-read-only":true,"discard":"unmap"}' \
-blockdev '{"node-name":"libvirt-6-format","read-only":true,"driver":"qcow2","file":"libvirt-6-storage","backing":null}' \
-blockdev '{"driver":"file","filename":"/tmp/copy1.qcow2","node-name":"libvirt-5-storage","auto-read-only":true,"discard":"unmap"}' \
-blockdev '{"node-name":"libvirt-5-format","read-only":true,"driver":"qcow2","file":"libvirt-5-storage","backing":"libvirt-6-format"}' \
-blockdev '{"driver":"file","filename":"/tmp/copy2.qcow2","node-name":"libvirt-4-storage","auto-read-only":true,"discard":"unmap"}' \
-blockdev '{"node-name":"libvirt-4-format","read-only":true,"driver":"qcow2","file":"libvirt-4-storage","backing":"libvirt-5-format"}' \
-blockdev '{"driver":"file","filename":"/tmp/copy3.qcow2","node-name":"libvirt-3-storage","auto-read-only":true,"discard":"unmap"}' \
-blockdev '{"node-name":"libvirt-3-format","read-only":true,"driver":"qcow2","file":"libvirt-3-storage","backing":"libvirt-4-format"}' \
-blockdev '{"driver":"file","filename":"/tmp/copy4.qcow2","node-name":"libvirt-1-storage","auto-read-only":true,"discard":"unmap"}' \
-blockdev '{"node-name":"libvirt-1-format","read-only":false,"driver":"qcow2","file":"libvirt-1-storage","backing":"libvirt-3-format"}' \
-blockdev '{"driver":"copy-on-read","node-name":"libvirt-CoR-vda","file":"libvirt-1-format"}' \

-device virtio-blk-pci,scsi=off,bus=pci.0,addr=0xa,drive=libvirt-CoR-vda,id=virtio-disk0 \

[...]

$ virsh qemu-monitor-command --pretty upstream-bj '{"execute":"x-debug-query-block-graph"}'
{
  "return": {
    "edges": [
      {
        "name": "file",
        "parent": 4,
        "shared-perm": [
          "graph-mod",
          "resize",
          "write-unchanged",
          "consistent-read"
        ],
        "perm": [
          "write-unchanged",
          "write",
          "consistent-read"
        ],
        "child": 15
      },
      {
        "name": "file",
        "parent": 15,
        "shared-perm": [
          "graph-mod",
          "write-unchanged",
          "consistent-read"
        ],
        "perm": [
          "resize",
          "write-unchanged",
          "write",
          "consistent-read"
        ],
        "child": 14
      },
      {
        "name": "backing",
        "parent": 15,
        "shared-perm": [
          "graph-mod",
          "write-unchanged",
          "consistent-read"
        ],
        "perm": [
          "consistent-read"
        ],
        "child": 13
      },
      {
        "name": "file",
        "parent": 13,
        "shared-perm": [
          "graph-mod",
          "write-unchanged",
          "consistent-read"
        ],
        "perm": [
          "consistent-read"
        ],
        "child": 12
      },
      {
        "name": "backing",
        "parent": 13,
        "shared-perm": [
          "graph-mod",
          "write-unchanged",
          "consistent-read"
        ],
        "perm": [
          "consistent-read"
        ],
        "child": 11
      },
      {
        "name": "file",
        "parent": 11,
        "shared-perm": [
          "graph-mod",
          "write-unchanged",
          "consistent-read"
        ],
        "perm": [
          "consistent-read"
        ],
        "child": 10
      },
      {
        "name": "backing",
        "parent": 11,
        "shared-perm": [
          "graph-mod",
          "write-unchanged",
          "consistent-read"
        ],
        "perm": [
          "consistent-read"
        ],
        "child": 9
      },
      {
        "name": "file",
        "parent": 9,
        "shared-perm": [
          "graph-mod",
          "write-unchanged",
          "consistent-read"
        ],
        "perm": [
          "consistent-read"
        ],
        "child": 8
      },
      {
        "name": "backing",
        "parent": 9,
        "shared-perm": [
          "graph-mod",
          "write-unchanged",
          "consistent-read"
        ],
        "perm": [
          "consistent-read"
        ],
        "child": 7
      },
      {
        "name": "file",
        "parent": 7,
        "shared-perm": [
          "graph-mod",
          "write-unchanged",
          "consistent-read"
        ],
        "perm": [
          "consistent-read"
        ],
        "child": 6
      },
      {
        "name": "file",
        "parent": 2,
        "shared-perm": [
          "graph-mod",
          "write-unchanged",
          "consistent-read"
        ],
        "perm": [
          "consistent-read"
        ],
        "child": 5
      },
      {
        "name": "root",
        "parent": 3,
        "shared-perm": [
          "graph-mod",
          "resize",
          "write-unchanged",
          "consistent-read"
        ],
        "perm": [
          "write",
          "consistent-read"
        ],
        "child": 4
      },
      {
        "name": "root",
        "parent": 1,
        "shared-perm": [
          "graph-mod",
          "write-unchanged",
          "consistent-read"
        ],
        "perm": [
          "consistent-read"
        ],
        "child": 2
      }
    ],
    "nodes": [
      {
        "name": "libvirt-CoR-vda",
        "type": "block-driver",
        "id": 4
      },
      {
        "name": "libvirt-1-format",
        "type": "block-driver",
        "id": 15
      },
      {
        "name": "libvirt-1-storage",
        "type": "block-driver",
        "id": 14
      },
      {
        "name": "libvirt-3-format",
        "type": "block-driver",
        "id": 13
      },
      {
        "name": "libvirt-3-storage",
        "type": "block-driver",
        "id": 12
      },
      {
        "name": "libvirt-4-format",
        "type": "block-driver",
        "id": 11
      },
      {
        "name": "libvirt-4-storage",
        "type": "block-driver",
        "id": 10
      },
      {
        "name": "libvirt-5-format",
        "type": "block-driver",
        "id": 9
      },
      {
        "name": "libvirt-5-storage",
        "type": "block-driver",
        "id": 8
      },
      {
        "name": "libvirt-6-format",
        "type": "block-driver",
        "id": 7
      },
      {
        "name": "libvirt-6-storage",
        "type": "block-driver",
        "id": 6
      },
      {
        "name": "libvirt-2-format",
        "type": "block-driver",
        "id": 2
      },
      {
        "name": "libvirt-2-storage",
        "type": "block-driver",
        "id": 5
      },
      {
        "name": "/machine/peripheral/virtio-disk0/virtio-backend",
        "type": "block-backend",
        "id": 3
      },
      {
        "name": "ide0-0-0",
        "type": "block-backend",
        "id": 1
      }
    ]
  },
  "id": "libvirt-367"
}

Upstream libvirt currently does:
2020-01-23 15:09:45.621+0000: 2898636: info : qemuMonitorIOWrite:450 : QEMU_MONITOR_IO_WRITE: mon=0x7fffe00492f0 buf={"execute":"block-commit","arguments":{"device":"libvirt-1-format","job-id":"commit-vda-libvirt-4-format","top-node":"libvirt-4-format","base-node":"libvirt-5-format","backing-file":"/tmp/copy1.qcow2","auto-finalize":true,"auto-dismiss":false},"id":"libvirt-368"}
 len=265 ret=265 errno=0
2020-01-23 15:09:45.623+0000: 2898636: debug : qemuMonitorJSONIOProcessLine:220 : Line [{"id": "libvirt-368", "error": {"class": "GenericError", "desc": "Need a root block node"}}]
2020-01-23 15:09:45.623+0000: 2898636: info : qemuMonitorJSONIOProcessLine:239 : QEMU_MONITOR_RECV_REPLY: mon=0x7fffe00492f0 reply={"id": "libvirt-368", "error": {"class": "GenericError", "desc": "Need a root block node"}}

The above is a libvirt bug which I'm trying to fix. With the fix the following command is attempted:

2020-01-23 15:11:44.464+0000: 2912632: info : qemuMonitorIOWrite:450 : QEMU_MONITOR_IO_WRITE: mon=0x7fff941687c0 buf={"execute":"block-commit","arguments":{"device":"libvirt-CoR-vda","job-id":"commit-vda-libvirt-4-format","top-node":"libvirt-4-format","base-node":"libvirt-5-format","backing-file":"/tmp/copy1.qcow2","auto-finalize":true,"auto-dismiss":false},"id":"libvirt-12"}
 len=263 ret=263 errno=0
2020-01-23 15:11:44.464+0000: 2912632: debug : qemuMonitorJSONIOProcessLine:220 : Line [{"id": "libvirt-12", "error": {"class": "GenericError", "desc": "'libvirt-4-format' is not in this backing file chain"}}]
2020-01-23 15:11:44.464+0000: 2912632: info : qemuMonitorJSONIOProcessLine:239 : QEMU_MONITOR_RECV_REPLY: mon=0x7fff941687c0 reply={"id": "libvirt-12", "error": {"class": "GenericError", "desc": "'libvirt-4-format' is not in this backing file chain"}}


As a side note. block-stream does not report the "Need a root block node" error and only works correctly if the 'device' parameter doesn't point to the copy-on-read driver. Is this expected?

Comment 17 Hanna Czenczek 2020-01-24 10:09:13 UTC
(In reply to Peter Krempa from comment #16)
> As a side note. block-stream does not report the "Need a root block node"
> error and only works correctly if the 'device' parameter doesn't point to
> the copy-on-read driver. Is this expected?

block-stream’s documentation doesn’t say that @device needs to be a root node.  This was changed in qemu commit 554b6147650.  So I’d say it’s entirely expected that there’s no error.

As for “it only works correctly if @device isn’t a COR filter”, that’s a bug.  Namely this one here.  (I’d like to put a smiley here, but there’s really not too much to smile about when it comes to this BZ.)

Max

Comment 18 Peter Krempa 2020-01-24 16:32:58 UTC
Thank you for the explanation! As of the block-stream operation and the clarification on what the fields mean, the current behaviour of libvirt makes the most sense. I've already sent patches which fix the arguments for block-commit and blockdev-mirror and set the bug tracking the issue to depend on this.

Comment 19 Ademar Reis 2020-02-05 22:48:00 UTC
QEMU has been recently split into sub-components and as a one-time operation to avoid breakage of tools, we are setting the QEMU sub-component of this BZ to "General". Please review and change the sub-component if necessary the next time you review this BZ. Thanks

Comment 22 Hanna Czenczek 2020-11-10 10:57:48 UTC
The following is a reproducer (creates a simple backing chain with a copy-on-read filter on top, then tries an active commit):

$ qemu-img create -f qcow2 base.qcow2 64M
Formatting 'base.qcow2', fmt=qcow2 cluster_size=65536 compression_type=zlib size=67108864 lazy_refcounts=off refcount_bits=16

$ qemu-img create -f qcow2 -b base.qcow2 -F qcow2 top.qcow2 64M
Formatting 'top.qcow2', fmt=qcow2 cluster_size=65536 compression_type=zlib size=67108864 backing_file=base.qcow2 backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16

$ qemu-system-x86_64 \
    -nodefaults \
    -qmp stdio \
    -blockdev qcow2,node-name=base,file.driver=file,file.filename=base.qcow2 \
    -blockdev qcow2,node-name=top,file.driver=file,file.filename=top.qcow2,backing=base \
    -blockdev copy-on-read,node-name=cor,file=top \
    -device virtio-blk,drive=cor

QMP traffic on 5.2.0 (desired behavior):
(We commit everything down to the "base" node, and then we can delete the "cor" and "top" nodes because the virtio-blk device will be attached to "base" after the commit.)

{"QMP": {"version": {"qemu": {"micro": 90, "minor": 1, "major": 5}, "package": "v5.2.0-rc0-130-g43afbbd9fe"}, "capabilities": ["oob"]}}

{'execute':'qmp_capabilities'}
{"return": {}}

{'execute':'block-commit','arguments':{'job-id':'commit','device':'cor','base-node':'base'}}
{"timestamp": {"seconds": 1605005341, "microseconds": 114782}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "commit"}}
{"timestamp": {"seconds": 1605005341, "microseconds": 114888}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "commit"}}
{"return": {}}

{"timestamp": {"seconds": 1605005341, "microseconds": 115561}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "commit"}}
{"timestamp": {"seconds": 1605005341, "microseconds": 115589}, "event": "BLOCK_JOB_READY", "data": {"device": "commit", "len": 65536, "offset": 65536, "speed": 0, "type": "commit"}}
{"return": {}}

{'execute':'block-job-complete','arguments':{'device':'commit'}}
{"timestamp": {"seconds": 1605005341, "microseconds": 653379}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "commit"}}
{"timestamp": {"seconds": 1605005341, "microseconds": 653481}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "commit"}}
{"timestamp": {"seconds": 1605005341, "microseconds": 653731}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "commit", "len": 65536, "offset": 65536, "speed": 0, "type": "commit"}}
{"timestamp": {"seconds": 1605005341, "microseconds": 653815}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "commit"}}
{"timestamp": {"seconds": 1605005341, "microseconds": 653911}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "commit"}}

{'execute':'blockdev-del','arguments':{'node-name':'cor'}}
{"return": {}}
{'execute':'blockdev-del','arguments':{'node-name':'top'}}
{"return": {}}


QMP traffic on 5.1.0 (broken):
(Can't even start the commit, because the backing chain is broken by the COR filter, as described by comment 0.)

{"QMP": {"version": {"qemu": {"micro": 0, "minor": 1, "major": 5}, "package": "qemu-5.1.0-5.fc33"}, "capabilities": ["oob"]}}

{'execute':'qmp_capabilities'}
{"return": {}}

{'execute':'block-commit','arguments':{'job-id':'commit','device':'cor','base-node':'base'}}
{"error": {"class": "GenericError", "desc": "'base' is not in this backing file chain"}}

Comment 23 leidwang@redhat.com 2020-12-14 01:38:10 UTC
Hi Max,

Thanks for your guidance! Tested with qemu-kvm-5.2.0-1.module+el8.4.0+9091+650b220a.x86_64,
it works normally.

# qemu-img create -f qcow2 base.qcow2 64M
Formatting 'base.qcow2', fmt=qcow2 cluster_size=65536 extended_l2=off compression_type=zlib size=67108864 lazy_refcounts=off refcount_bits=16

# qemu-img create -f qcow2 -b base.qcow2 -F qcow2 top.qcow2 64M
Formatting 'top.qcow2', fmt=qcow2 cluster_size=65536 extended_l2=off compression_type=zlib size=67108864 backing_file=base.qcow2 backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16

qemu command line:
/usr/libexec/qemu-kvm \
-nodefaults \
-qmp stdio \
-blockdev qcow2,node-name=base,file.driver=file,file.filename=base.qcow2 \
-blockdev qcow2,node-name=top,file.driver=file,file.filename=top.qcow2,backing=base \
-blockdev copy-on-read,node-name=cor,file=top \
-device virtio-blk,drive=cor \
-qmp tcp:127.0.0.1:4444,server,nowait \

# telnet localhost 4444
Escape character is '^]'.
{"QMP": {"version": {"qemu": {"micro": 0, "minor": 2, "major": 5}, "package": "qemu-kvm-5.2.0-1.module+el8.4.0+9091+650b220a"}, "capabilities": ["oob"]}}

{'execute':'qmp_capabilities'}
{"return": {}}

{'execute':'block-commit','arguments':{'job-id':'commit','device':'cor','base-node':'base'}}
{"timestamp": {"seconds": 1607908835, "microseconds": 839549}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "commit"}}
{"timestamp": {"seconds": 1607908835, "microseconds": 839839}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "commit"}}
{"return": {}}
{"timestamp": {"seconds": 1607908835, "microseconds": 847915}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "commit"}}
{"timestamp": {"seconds": 1607908835, "microseconds": 848052}, "event": "BLOCK_JOB_READY", "data": {"device": "commit", "len": 65536, "offset": 65536, "speed": 0, "type": "commit"}}
{"return": {}}

{'execute':'block-job-complete','arguments':{'device':'commit'}}
{"timestamp": {"seconds": 1607908863, "microseconds": 923489}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "commit"}}
{"timestamp": {"seconds": 1607908863, "microseconds": 923617}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "commit"}}
{"timestamp": {"seconds": 1607908863, "microseconds": 923837}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "commit", "len": 65536, "offset": 65536, "speed": 0, "type": "commit"}}
{"timestamp": {"seconds": 1607908863, "microseconds": 923938}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "commit"}}
{"timestamp": {"seconds": 1607908863, "microseconds": 924036}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "commit"}}

{'execute':'blockdev-del','arguments':{'node-name':'cor'}}
{"return": {}}

{'execute':'blockdev-del','arguments':{'node-name':'top'}}
{"return": {}}

Comment 27 leidwang@redhat.com 2020-12-16 05:25:13 UTC
According to Comment 23, set status to VERIFIED.

Comment 32 errata-xmlrpc 2021-05-25 06:41:16 UTC
Since the problem described in this bug report should be
resolved in a recent advisory, it has been closed with a
resolution of ERRATA.

For information on the advisory (virt:av bug fix and enhancement update), and where to find the updated
files, follow the link below.

If the solution does not work for you, open a new bug report.

https://access.redhat.com/errata/RHBA-2021:2098


Note You need to log in before you can comment on or make changes to this bug.