Skip to contentSkip to conversation

block: Fix crash on migration with explicit child nodes

PATCH Superseded Kevin Wolf 2 participants 5 messages Nov 26, 2018

v2 of 3 Superseded Latest is v3 →

lore
Share & tools
lore

Kevin Wolf

author
#0 lore raw

v2 cover block: Fix crash on migration with explicit child nodes

v2:
- Cover children that were created after their parents [Max]

Kevin Wolf (2):
block: Don't inactivate children before parents
iotests: Test migration with -blockdev

 block.c                    |  32 ++++++++++-
 tests/qemu-iotests/234     | 115 +++++++++++++++++++++++++++++++++++++
 tests/qemu-iotests/234.out |  27 +++++++++
 tests/qemu-iotests/group   |   1 +
 4 files changed, 174 insertions(+), 1 deletion(-)
 create mode 100755 tests/qemu-iotests/234
 create mode 100644 tests/qemu-iotests/234.out
Signature · 1 line
2.19.1
author
#1 lore raw

PATCH v2 1/2 block: Don't inactivate children before parents

bdrv_child_cb_inactivate() asserts that parents are already inactive when children get inactivated. This precondition is necessary because parents could still issue requests in their inactivation code.

When block nodes are created individually with -blockdev, all of them are monitor owned and will be returned by bdrv_next() in an undefined order (in practice, in the order of their creation, which is usually children before parents), which obviously fails the assertion:

qemu: block.c:899: bdrv_child_cb_inactivate: Assertion `bs->open_flags & BDRV_O_INACTIVE' failed.

This patch fixes the ordering by skipping nodes with still active parents in bdrv_inactivate_recurse() because we know that they will be covered by recursion when the last active parent becomes inactive.

---
 block.c | 32 +++++++++++++++++++++++++++++++-
 1 file changed, 31 insertions(+), 1 deletion(-)
block.c +31 -1
index 5ba3435f8f..e9181c3be7 100644
@@ -4612,6 +4612,22 @@ void bdrv_invalidate_cache_all(Error **errp)
     }
 }
 
+static bool bdrv_has_bds_parent(BlockDriverState *bs, bool only_active)
+{
+    BdrvChild *parent;
+
+    QLIST_FOREACH(parent, &bs->parents, next_parent) {
+        if (parent->role->parent_is_bds) {
+            BlockDriverState *parent_bs = parent->opaque;
+            if (!only_active || !(parent_bs->open_flags & BDRV_O_INACTIVE)) {
+                return true;
+            }
+        }
+    }
+
+    return false;
+}
+
 static int bdrv_inactivate_recurse(BlockDriverState *bs,
                                    bool setting_flag)
 {
@@ -4622,6 +4638,14 @@ static int bdrv_inactivate_recurse(BlockDriverState *bs,
         return -ENOMEDIUM;
     }
 
+    /* Make sure that we don't inactivate a child before its parent.
+     * It will be covered by recursion from the yet active parent. */
+    if (bdrv_has_bds_parent(bs, true)) {
+        return 0;
+    }
+
+    assert(!(bs->open_flags & BDRV_O_INACTIVE));
+
     if (!setting_flag && bs->drv->bdrv_inactivate) {
         ret = bs->drv->bdrv_inactivate(bs);
         if (ret < 0) {
@@ -4629,7 +4653,7 @@ static int bdrv_inactivate_recurse(BlockDriverState *bs,
         }
     }
 
-    if (setting_flag && !(bs->open_flags & BDRV_O_INACTIVE)) {
+    if (setting_flag) {
         uint64_t perm, shared_perm;
 
         QLIST_FOREACH(parent, &bs->parents, next_parent) {
@@ -4682,6 +4706,12 @@ int bdrv_inactivate_all(void)
      * is allowed. */
     for (pass = 0; pass < 2; pass++) {
         for (bs = bdrv_first(&it); bs; bs = bdrv_next(&it)) {
+            /* Nodes with BDS parents are covered by recursion from the last
+             * parent that gets inactivated. Don't inactivate them a second
+             * time if that has already happened. */
+            if (bdrv_has_bds_parent(bs, false)) {
+                continue;
+            }
             ret = bdrv_inactivate_recurse(bs, pass);
             if (ret < 0) {
                 bdrv_next_cleanup(&it);
Signature · 1 line
2.19.1
#2 lore raw

Re: [Qemu-devel] [PATCH for-3.1 v2 1/2] block: Don't inactivate children before parents

Quoted 24 lines · Kevin Wolf

bdrv_child_cb_inactivate() asserts that parents are already inactive when children get inactivated. This precondition is necessary because parents could still issue requests in their inactivation code.

When block nodes are created individually with -blockdev, all of them are monitor owned and will be returned by bdrv_next() in an undefined order (in practice, in the order of their creation, which is usually children before parents), which obviously fails the assertion:

qemu: block.c:899: bdrv_child_cb_inactivate: Assertion `bs->open_flags & BDRV_O_INACTIVE' failed.

This patch fixes the ordering by skipping nodes with still active parents in bdrv_inactivate_recurse() because we know that they will be covered by recursion when the last active parent becomes inactive.

---
 block.c | 32 +++++++++++++++++++++++++++++++-
 1 file changed, 31 insertions(+), 1 deletion(-)
block.c +0 -0
index 5ba3435f8f..e9181c3be7 100644

[...]

block.c static int bdrv_inactivate_recurse(BlockDriverState *bs, · line 4643

Quoted 7 lines
block.c +3 -0
@@ -4622,6 +4638,14 @@ static int bdrv_inactivate_recurse(BlockDriverState *bs,
         return -ENOMEDIUM;
     }
 
+    /* Make sure that we don't inactivate a child before its parent.
+     * It will be covered by recursion from the yet active parent. */
+    if (bdrv_has_bds_parent(bs, true)) {

Overall the patch looks good to me, apart from the fact that I still think this should only be checked if setting_flag is true.

(Because BDRV_O_INACTIVE is only set during the second pass, so this check will lead to the first pass not doing anything (and thus not calling bs->drv->bdrv_inactivate()) on any non-root bs.)

Max

Quoted 8 lines
block.c +5 -0
+        return 0;
+    }
+
+    assert(!(bs->open_flags & BDRV_O_INACTIVE));
+
     if (!setting_flag && bs->drv->bdrv_inactivate) {
         ret = bs->drv->bdrv_inactivate(bs);
         if (ret < 0) {
author
#3 lore raw

PATCH v2 2/2 iotests: Test migration with -blockdev

In reply to Kevin Wolf

Check that block node activation and inactivation works with a block graph that is built with individually created nodes.

---
 tests/qemu-iotests/234     | 115 +++++++++++++++++++++++++++++++++++++
 tests/qemu-iotests/234.out |  27 +++++++++
 tests/qemu-iotests/group   |   1 +
 3 files changed, 143 insertions(+)
 create mode 100755 tests/qemu-iotests/234
 create mode 100644 tests/qemu-iotests/234.out
tests/qemu-iotests/234 new file +115 -0
new file mode 100755
index 0000000000..58886b8dea
@@ -0,0 +1,115 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2018 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Creator/Owner: Kevin Wolf <[email protected]>
+#
+# Check that block node activation and inactivation works with a block graph
+# that is built with individually created nodes
+
+import iotests
+import os
+
+iotests.verify_image_format(supported_fmts=['raw', 'qcow2'])
+iotests.verify_platform(['linux'])
+
+with iotests.FilePath('img') as img_path, \
+     iotests.FilePath('backing') as backing_path, \
+     iotests.FilePath('mig_fifo_a') as fifo_a, \
+     iotests.FilePath('mig_fifo_b') as fifo_b, \
+     iotests.VM(path_suffix='a') as vm_a, \
+     iotests.VM(path_suffix='b') as vm_b:
+
+    iotests.qemu_img_pipe('create', '-f', iotests.imgfmt, backing_path, '64M')
+    iotests.qemu_img_pipe('create', '-f', iotests.imgfmt, img_path, '64M')
+
+    os.mkfifo(fifo_a)
+    os.mkfifo(fifo_b)
+
+    iotests.log('Launching source VM...')
+    (vm_a.add_blockdev('file,filename=%s,node-name=drive0-file' % (img_path))
+         .add_blockdev('%s,file=drive0-file,node-name=drive0' % (iotests.imgfmt))
+         .add_blockdev('file,filename=%s,node-name=drive0-backing-file' % (backing_path))
+         .add_blockdev('%s,file=drive0-backing-file,node-name=drive0-backing' % (iotests.imgfmt))
+         .launch())
+
+    iotests.log('Launching destination VM...')
+    (vm_b.add_blockdev('file,filename=%s,node-name=drive0-file' % (img_path))
+         .add_blockdev('%s,file=drive0-file,node-name=drive0' % (iotests.imgfmt))
+         .add_blockdev('file,filename=%s,node-name=drive0-backing-file' % (backing_path))
+         .add_blockdev('%s,file=drive0-backing-file,node-name=drive0-backing' % (iotests.imgfmt))
+         .add_incoming("exec: cat '%s'" % (fifo_a))
+         .launch())
+
+    # Add a child node that was created after the parent node. The reverse case
+    # is covered by the -blockdev options above.
+    vm_a.qmp('blockdev-snapshot', node='drive0-backing', overlay='drive0')
+
+    iotests.log('Enabling migration QMP events on A...')
+    iotests.log(vm_a.qmp('migrate-set-capabilities', capabilities=[
+        {
+            'capability': 'events',
+            'state': True
+        }
+    ]))
+
+    iotests.log('Starting migration to B...')
+    iotests.log(vm_a.qmp('migrate', uri='exec:cat >%s' % (fifo_a)))
+    with iotests.Timeout(3, 'Migration does not complete'):
+        while True:
+            event = vm_a.event_wait('MIGRATION')
+            iotests.log(event, filters=[iotests.filter_qmp_event])
+            if event['data']['status'] == 'completed':
+                break
+
+    iotests.log(vm_a.qmp('query-migrate')['return']['status'])
+    iotests.log(vm_b.qmp('query-migrate')['return']['status'])
+
+    iotests.log(vm_a.qmp('query-status'))
+    iotests.log(vm_b.qmp('query-status'))
+
+    iotests.log('Add a second parent to drive0-file...')
+    iotests.log(vm_b.qmp('blockdev-add', driver='raw', file='drive0-file',
+                         node_name='drive0-raw'))
+
+    iotests.log('Restart A with -incoming and second parent...')
+    vm_a.shutdown()
+    (vm_a.add_blockdev('raw,file=drive0-file,node-name=drive0-raw')
+         .add_incoming("exec: cat '%s'" % (fifo_b))
+         .launch())
+
+    iotests.log('Enabling migration QMP events on B...')
+    iotests.log(vm_b.qmp('migrate-set-capabilities', capabilities=[
+        {
+            'capability': 'events',
+            'state': True
+        }
+    ]))
+
+    iotests.log('Starting migration back to A...')
+    iotests.log(vm_b.qmp('migrate', uri='exec:cat >%s' % (fifo_b)))
+    with iotests.Timeout(3, 'Migration does not complete'):
+        while True:
+            event = vm_b.event_wait('MIGRATION')
+            iotests.log(event, filters=[iotests.filter_qmp_event])
+            if event['data']['status'] == 'completed':
+                break
+
+    iotests.log(vm_a.qmp('query-migrate')['return']['status'])
+    iotests.log(vm_b.qmp('query-migrate')['return']['status'])
+
+    iotests.log(vm_a.qmp('query-status'))
+    iotests.log(vm_b.qmp('query-status'))
tests/qemu-iotests/234.out new file +27 -0
new file mode 100644
index 0000000000..115537cd5c
@@ -0,0 +1,27 @@
+Launching source VM...
+Launching destination VM...
+Enabling migration QMP events on A...
+{"return": {}}
+Starting migration to B...
+{"return": {}}
+{"data": {"status": "setup"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
+{"data": {"status": "active"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
+{"data": {"status": "completed"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
+completed
+completed
+{"return": {"running": false, "singlestep": false, "status": "postmigrate"}}
+{"return": {"running": true, "singlestep": false, "status": "running"}}
+Add a second parent to drive0-file...
+{"return": {}}
+Restart A with -incoming and second parent...
+Enabling migration QMP events on B...
+{"return": {}}
+Starting migration back to A...
+{"return": {}}
+{"data": {"status": "setup"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
+{"data": {"status": "active"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
+{"data": {"status": "completed"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
+completed
+completed
+{"return": {"running": true, "singlestep": false, "status": "running"}}
+{"return": {"running": false, "singlestep": false, "status": "postmigrate"}}
tests/qemu-iotests/group +1 -0
index ddf1a5b549..8c56a0ad11 100644
@@ -231,3 +231,4 @@
 231 auto quick
 232 auto quick
 233 auto quick
+234 auto quick migration
Signature · 1 line
2.19.1
#4 lore raw

Re: [Qemu-devel] [PATCH for-3.1 v2 2/2] iotests: Test migration with -blockdev

Quoted 67 lines · Kevin Wolf

Check that block node activation and inactivation works with a block graph that is built with individually created nodes.

---
 tests/qemu-iotests/234     | 115 +++++++++++++++++++++++++++++++++++++
 tests/qemu-iotests/234.out |  27 +++++++++
 tests/qemu-iotests/group   |   1 +
 3 files changed, 143 insertions(+)
 create mode 100755 tests/qemu-iotests/234
 create mode 100644 tests/qemu-iotests/234.out
tests/qemu-iotests/234 new file +49 -0
new file mode 100755
index 0000000000..58886b8dea
@@ -0,0 +1,115 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2018 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Creator/Owner: Kevin Wolf <[email protected]>
+#
+# Check that block node activation and inactivation works with a block graph
+# that is built with individually created nodes
+
+import iotests
+import os
+
+iotests.verify_image_format(supported_fmts=['raw', 'qcow2'])
+iotests.verify_platform(['linux'])
+
+with iotests.FilePath('img') as img_path, \
+     iotests.FilePath('backing') as backing_path, \
+     iotests.FilePath('mig_fifo_a') as fifo_a, \
+     iotests.FilePath('mig_fifo_b') as fifo_b, \
+     iotests.VM(path_suffix='a') as vm_a, \
+     iotests.VM(path_suffix='b') as vm_b:
+
+    iotests.qemu_img_pipe('create', '-f', iotests.imgfmt, backing_path, '64M')
+    iotests.qemu_img_pipe('create', '-f', iotests.imgfmt, img_path, '64M')
+
+    os.mkfifo(fifo_a)
+    os.mkfifo(fifo_b)
+
+    iotests.log('Launching source VM...')
+    (vm_a.add_blockdev('file,filename=%s,node-name=drive0-file' % (img_path))
+         .add_blockdev('%s,file=drive0-file,node-name=drive0' % (iotests.imgfmt))
+         .add_blockdev('file,filename=%s,node-name=drive0-backing-file' % (backing_path))
+         .add_blockdev('%s,file=drive0-backing-file,node-name=drive0-backing' % (iotests.imgfmt))
+         .launch())
+
+    iotests.log('Launching destination VM...')

tests/qemu-iotests/234 line 59

Quoted 10 lines
tests/qemu-iotests/234 new file +10 -0
+    (vm_b.add_blockdev('file,filename=%s,node-name=drive0-file' % (img_path))
+         .add_blockdev('%s,file=drive0-file,node-name=drive0' % (iotests.imgfmt))
+         .add_blockdev('file,filename=%s,node-name=drive0-backing-file' % (backing_path))
+         .add_blockdev('%s,file=drive0-backing-file,node-name=drive0-backing' % (iotests.imgfmt))
+         .add_incoming("exec: cat '%s'" % (fifo_a))
+         .launch())
+
+    # Add a child node that was created after the parent node. The reverse case
+    # is covered by the -blockdev options above.
+    vm_a.qmp('blockdev-snapshot', node='drive0-backing', overlay='drive0')

Sorry if I made it sound like my diff was ready for inclusion as-is. O:-)

I think this should be vm_a.qmp_log() at least (and the reference output needs to be adjusted accordingly).

Also, we probably have to execute the same command on vm_b...

Quoted 22 lines
tests/qemu-iotests/234 new file +22 -0
+    iotests.log('Enabling migration QMP events on A...')
+    iotests.log(vm_a.qmp('migrate-set-capabilities', capabilities=[
+        {
+            'capability': 'events',
+            'state': True
+        }
+    ]))
+
+    iotests.log('Starting migration to B...')
+    iotests.log(vm_a.qmp('migrate', uri='exec:cat >%s' % (fifo_a)))
+    with iotests.Timeout(3, 'Migration does not complete'):
+        while True:
+            event = vm_a.event_wait('MIGRATION')
+            iotests.log(event, filters=[iotests.filter_qmp_event])
+            if event['data']['status'] == 'completed':
+                break
+
+    iotests.log(vm_a.qmp('query-migrate')['return']['status'])
+    iotests.log(vm_b.qmp('query-migrate')['return']['status'])
+
+    iotests.log(vm_a.qmp('query-status'))
+    iotests.log(vm_b.qmp('query-status'))

tests/qemu-iotests/234 line 92

Quoted 10 lines
tests/qemu-iotests/234 new file +10 -0
+
+    iotests.log('Add a second parent to drive0-file...')
+    iotests.log(vm_b.qmp('blockdev-add', driver='raw', file='drive0-file',
+                         node_name='drive0-raw'))
+
+    iotests.log('Restart A with -incoming and second parent...')
+    vm_a.shutdown()
+    (vm_a.add_blockdev('raw,file=drive0-file,node-name=drive0-raw')
+         .add_incoming("exec: cat '%s'" % (fifo_b))
+         .launch())

...and here on vm_a again?

Max

Quoted 65 lines
tests/qemu-iotests/234 new file +22 -0
+    iotests.log('Enabling migration QMP events on B...')
+    iotests.log(vm_b.qmp('migrate-set-capabilities', capabilities=[
+        {
+            'capability': 'events',
+            'state': True
+        }
+    ]))
+
+    iotests.log('Starting migration back to A...')
+    iotests.log(vm_b.qmp('migrate', uri='exec:cat >%s' % (fifo_b)))
+    with iotests.Timeout(3, 'Migration does not complete'):
+        while True:
+            event = vm_b.event_wait('MIGRATION')
+            iotests.log(event, filters=[iotests.filter_qmp_event])
+            if event['data']['status'] == 'completed':
+                break
+
+    iotests.log(vm_a.qmp('query-migrate')['return']['status'])
+    iotests.log(vm_b.qmp('query-migrate')['return']['status'])
+
+    iotests.log(vm_a.qmp('query-status'))
+    iotests.log(vm_b.qmp('query-status'))
tests/qemu-iotests/234.out new file +27 -0
new file mode 100644
index 0000000000..115537cd5c
@@ -0,0 +1,27 @@
+Launching source VM...
+Launching destination VM...
+Enabling migration QMP events on A...
+{"return": {}}
+Starting migration to B...
+{"return": {}}
+{"data": {"status": "setup"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
+{"data": {"status": "active"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
+{"data": {"status": "completed"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
+completed
+completed
+{"return": {"running": false, "singlestep": false, "status": "postmigrate"}}
+{"return": {"running": true, "singlestep": false, "status": "running"}}
+Add a second parent to drive0-file...
+{"return": {}}
+Restart A with -incoming and second parent...
+Enabling migration QMP events on B...
+{"return": {}}
+Starting migration back to A...
+{"return": {}}
+{"data": {"status": "setup"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
+{"data": {"status": "active"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
+{"data": {"status": "completed"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
+completed
+completed
+{"return": {"running": true, "singlestep": false, "status": "running"}}
+{"return": {"running": false, "singlestep": false, "status": "postmigrate"}}
tests/qemu-iotests/group +1 -0
index ddf1a5b549..8c56a0ad11 100644
@@ -231,3 +231,4 @@
 231 auto quick
 232 auto quick
 233 auto quick
+234 auto quick migration