Add 'x-blockdev-del' command
v2 cover Add 'x-blockdev-del' command
Here's the new version of the blockdev-del command.
The first change is that I renamed it to 'x-blockdev-del' as Markus suggested.
I also extended the test cases, which should be way more robust now. This actually made me realize that there's tricky scenarios that require changes in other parts of the code.
The main example is the 'drive-mirror' block job. During the operation the target image has a node name and is only referenced by the monitor, so there's nothing preventing its deletion using 'x-blockdev-del'. I added an extra reference to solve that.
'drive-backup' has the same problem. Although the user cannot set the node name here, we are auto-generating them now so the command is also vulnerable to this.
The semantics of 'x-blockdev-del' also changed a bit, trying to mirror the semantics of 'blockdev-add' as I discussed with Kevin on the previous thread. There's two parameters: 'id' and 'node-name' and only one can be specified.
1) 'x-blockdev-del id=foo' deletes the backend foo with its BDS, if and only if neither have more than 1 reference and the BDS has no parents.
2) 'x-blockdev-del node-name=foo' deletes the BDS foo, if and only if it only has one reference, no parents AND it is not attached to any block backend.
Thank you all for your feedback.
Regards,
Berto
v2:
- Rename it as 'x-blockdev-del' and label it as experimental.
- Use two parameters instead of just one. If you try to delete a BDS, it must not be attached to any backend.
- New test cases.
- Hold extra references during the mirror and backup block jobs.
v1: https://lists.gnu.org/archive/html/qemu-devel/2015-10/msg02999.html
- Initial implementation
Alberto Garcia (5):
mirror: keep an extra reference to the target image during the job
backup: keep an extra reference to the target image during the job
block: Add blk_get_refcnt()
block: Add 'x-blockdev-del' QMP command
iotests: Add tests for the x-blockdev-del command
block/backup.c | 5 + block/block-backend.c | 5 + block/mirror.c | 6 + blockdev.c | 66 +++++++ include/sysemu/block-backend.h | 1 + qapi/block-core.json | 32 +++- qmp-commands.hx | 46 ++++- tests/qemu-iotests/139 | 408 +++++++++++++++++++++++++++++++++++++++++ tests/qemu-iotests/139.out | 5 + tests/qemu-iotests/group | 1 + 10 files changed, 571 insertions(+), 4 deletions(-) create mode 100644 tests/qemu-iotests/139 create mode 100644 tests/qemu-iotests/139.out
Signature · 1 line
2.6.1
PATCH v2 1/5 mirror: keep an extra reference to the target image during the job
During the 'drive-mirror' operation the target image only has the monitor reference, therefore there's nothing that prevents its deletion using the 'x-blockdev-del' command before the block job has finished.
--- block/mirror.c | 6 ++++++ 1 file changed, 6 insertions(+)
block/mirror.c +6 -0
@@ -385,6 +385,7 @@ static void mirror_exit(BlockJob *job, void *opaque) }g_free (s->replaces);bdrv_unref (s->target); +bdrv_unref (s->target);/* extra reference added in mirror_start_job() */ block_job_completed (&s->common, data->ret);g_free (data);bdrv_unref (src);
@@ -744,6 +745,11 @@ static void mirror_start_job(BlockDriverState *bs, BlockDriverState *target,block_job_release (bs);return ; } + +/* We keep an extra reference during the block job so the target +* BDS cannot be deleted using x-blockdev-del */ +bdrv_ref (target); +bdrv_set_enable_write_cache (s->target, true);if (s->target->blk) {blk_set_on_error (s->target->blk, on_target_error, on_target_error);
Signature · 1 line
2.6.1
Re: [Qemu-devel] [PATCH v2 1/5] mirror: keep an extra reference to the target image during the job
Quoted 4 lines · Alberto Garcia
During the 'drive-mirror' operation the target image only has the monitor reference, therefore there's nothing that prevents its deletion using the 'x-blockdev-del' command before the block job has finished.
Answering myself: it's probably better to use op blockers for this.
Berto
PATCH v2 2/5 backup: keep an extra reference to the target image during the job
In reply to Alberto GarciaDuring the 'drive-backup' operation the target image only has the monitor reference, therefore there's nothing that prevents its deletion using the 'x-blockdev-del' command before the block job has finished.
Although the operation itself does not allow the user to set a node name that can be used to delete the new image, we are auto-generating them since 15489c769b9a4b3bec5b5848af2960689d7b4bd8.
--- block/backup.c | 5 +++++ 1 file changed, 5 insertions(+)
block/backup.c +5 -0
@@ -250,6 +250,7 @@ static void backup_complete(BlockJob *job, void *opaque)BackupCompleteData *data = opaque;bdrv_unref (s->target); +bdrv_unref (s->target);/* extra reference added in backup_start() */ block_job_completed (job, data->ret);g_free (data);
@@ -546,6 +547,10 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target,bdrv_op_block_all (target, job->common.blocker); +/* We keep an extra reference during the block job so the target +* BDS cannot be deleted using x-blockdev-del */ +bdrv_ref (target); + job->on_source_error = on_source_error; job->on_target_error = on_target_error; job->target = target;
Signature · 1 line
2.6.1
PATCH v2 3/5 block: Add blk_get_refcnt()
In reply to Alberto GarciaThis function returns the reference count of a given BlockBackend. For convenience, it returns 0 if the BlockBackend pointer is NULL.
--- block/block-backend.c | 5 +++++ include/sysemu/block-backend.h | 1 + 2 files changed, 6 insertions(+)
block/block-backend.c +5 -0
@@ -189,6 +189,11 @@ static void drive_info_del(DriveInfo *dinfo)g_free (dinfo); } +int blk_get_refcnt (BlockBackend *blk) +{ +return blk ? blk->refcnt :0 ; +} +/* * Increment @blk's reference count. * @blk must not be null.
include/sysemu/block-backend.h +1 -0
@@ -65,6 +65,7 @@ BlockBackend *blk_new_with_bs(const char *name, Error **errp);BlockBackend *blk_new_open (const char *name,const char *filename,const char *reference,QDict *options,int flags,Error **errp); +int blk_get_refcnt (BlockBackend *blk);void blk_ref (BlockBackend *blk);void blk_unref (BlockBackend *blk);const char *blk_name (BlockBackend *blk);
Signature · 1 line
2.6.1
PATCH v2 4/5 block: Add 'x-blockdev-del' QMP command
In reply to Alberto GarciaThis command is still experimental, hence the name.
This is the companion to 'blockdev-add'. It allows deleting a BlockBackend with its associated BlockDriverState tree, or a BlockDriverState that is not attached to any backend.
In either case, the command fails if the reference count is greater than 1 or the BlockDriverState has any parents.
--- blockdev.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++ qapi/block-core.json | 32 +++++++++++++++++++++++-- qmp-commands.hx | 46 ++++++++++++++++++++++++++++++++++-- 3 files changed, 140 insertions(+), 4 deletions(-)
blockdev.c +66 -0
@@ -3432,6 +3432,72 @@ fail:qmp_output_visitor_cleanup (ov); } +void qmp_x_blockdev_del (bool has_id,const char *id, +bool has_node_name,const char *node_name,Error **errp) +{ +AioContext *aio_context; +BlockBackend *blk; +BlockDriverState *bs; + +if (has_id && has_node_name) { +error_setg (errp,"Only one of id and node-name must be specified" ); +return ; + }else if (!has_id && !has_node_name) { +error_setg (errp,"No block device specified" ); +return ; + } + +if (has_id) { + blk =blk_by_name (id); +if (!blk) { +error_setg (errp,"Cannot find block backend %s" , id); +return ; + } +if (blk_get_refcnt (blk) >1 ) { +error_setg (errp,"Block backend %s is being used" , id); +return ; + } + bs =blk_bs (blk); + aio_context =blk_get_aio_context (blk); + }else { + bs =bdrv_find_node (node_name); +if (!bs) { +error_setg (errp,"Cannot find node %s" , node_name); +return ; + } + blk = bs->blk; +if (blk) { +error_setg (errp,"Node %s is being used by %s" , + node_name,blk_name (blk)); +return ; + } + aio_context =bdrv_get_aio_context (bs); + } + +aio_context_acquire (aio_context); + +if (bs) { +if (bdrv_op_is_blocked (bs,BLOCK_OP_TYPE_DRIVE_DEL , errp)) { + goto out; + } + +if (bs->refcnt >1 || !QLIST_EMPTY (&bs->parents)) { +error_setg (errp,"Block device %s is being used" , +bdrv_get_device_or_node_name (bs)); + goto out; + } + } + +if (blk) { +blk_unref (blk); + }else { +bdrv_unref (bs); + } + +out: +aio_context_release (aio_context); +} +BlockJobInfoList *qmp_query_block_jobs (Error **errp) {BlockJobInfoList *head =NULL , **p_next = &head;
qapi/block-core.json +30 -2
@@ -1895,8 +1895,8 @@ # level and no BlockBackend will be created. # # This command is still a work in progress. It doesn't support all -# block drivers, it lacks a matching blockdev-del, and more. Stay -# away from it unless you want to help with its development. +# block drivers among other things. Stay away from it unless you want +# to help with its development. # # @options: block device options for the new device #
@@ -1905,6 +1905,34 @@ { 'command': 'blockdev-add', 'data': { 'options': 'BlockdevOptions' } } ## +# @x-blockdev-del: +# +# Deletes a block device that has been added using blockdev-add. +# The selected device can be either a block backend or a graph node. +# +# In the former case the backend will be destroyed, along with its +# inserted medium if there's any. The command will fail if the backend +# or its medium are being used. +# +# In the latter case the node will be destroyed. The command will fail +# if the node is attached to a block backend or is otherwise being +# used. +# +# One of @id or @node-name must be specified, but not both. +# +# This command is still a work in progress and is considered +# experimental. Stay away from it unless you want to help with its +# development. +# +# @id: #optional Name of the block backend device to delete. +# +# @node-name: #optional Name of the graph node to delete. +# +# Since: 2.5 +## +{ 'command': 'x-blockdev-del', 'data': { '*id': 'str', '*node-name': 'str' } } + +## # @blockdev-open-tray: # # Opens a block device's tray. If there is a block driver state tree inserted as
qmp-commands.hx +44 -2
58 lines not on this page · raw message
Signature · 1 line
2.6.1
PATCH v2 5/5 iotests: Add tests for the x-blockdev-del command
In reply to Alberto Garcia--- tests/qemu-iotests/139 | 408 +++++++++++++++++++++++++++++++++++++++++++++ tests/qemu-iotests/139.out | 5 + tests/qemu-iotests/group | 1 + 3 files changed, 414 insertions(+) create mode 100644 tests/qemu-iotests/139 create mode 100644 tests/qemu-iotests/139.out
tests/qemu-iotests/139 new file +408 -0
@@ -0,0 +1,408 @@ +#!/usr/bin/env python +# +# Test cases for the QMP 'x-blockdev-del' command +# +# Copyright (C) 2015 Igalia, S.L. +# Author: Alberto Garcia <[email protected]> +# +# 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/>. +# + +import os +import iotests +import time + +base_img = os.path.join(iotests.test_dir, 'base.img') +new_img = os.path.join(iotests.test_dir, 'new.img') + +class TestBlockdevDel(iotests.QMPTestCase): + + def setUp(self): + iotests.qemu_img('create', '-f', iotests.imgfmt, base_img, '1M') + self.vm = iotests.VM() + self.vm.launch() + + def tearDown(self): + self.vm.shutdown() + os.remove(base_img) + if os.path.isfile(new_img): + os.remove(new_img) + + # Check whether a BlockBackend exists + def checkBlockBackend(self, backend, node, must_exist = True): + result = self.vm.qmp('query-block') + backends = filter(lambda x: x['device'] == backend, result['return']) + self.assertLessEqual(len(backends), 1) + self.assertEqual(must_exist, len(backends) == 1) + if must_exist: + if node: + self.assertEqual(backends[0]['inserted']['node-name'], node) + else: + self.assertFalse(backends[0].has_key('inserted')) + + # Check whether a BlockDriverState exists + def checkBlockDriverState(self, node, must_exist = True): + result = self.vm.qmp('query-named-block-nodes') + nodes = filter(lambda x: x['node-name'] == node, result['return']) + self.assertLessEqual(len(nodes), 1) + self.assertEqual(must_exist, len(nodes) == 1) + + # Add a new BlockBackend (with its attached BlockDriverState) + def addBlockBackend(self, backend, node): + file_node = '%s_file' % node + self.checkBlockBackend(backend, node, False) + self.checkBlockDriverState(node, False) + self.checkBlockDriverState(file_node, False) + opts = {'driver': iotests.imgfmt, + 'id': backend, + 'node-name': node, + 'file': {'driver': 'file', + 'node-name': file_node, + 'filename': base_img}} + result = self.vm.qmp('blockdev-add', conv_keys = False, options = opts) + self.assert_qmp(result, 'return', {}) + self.checkBlockBackend(backend, node) + self.checkBlockDriverState(node) + self.checkBlockDriverState(file_node) + + # Add a BlockDriverState without a BlockBackend + def addBlockDriverState(self, node): + file_node = '%s_file' % node + self.checkBlockDriverState(node, False) + self.checkBlockDriverState(file_node, False) + opts = {'driver': iotests.imgfmt, + 'node-name': node, + 'file': {'driver': 'file', + 'node-name': file_node, + 'filename': base_img}} + result = self.vm.qmp('blockdev-add', conv_keys = False, options = opts) + self.assert_qmp(result, 'return', {}) + self.checkBlockDriverState(node) + self.checkBlockDriverState(file_node) + + # Add a BlockDriverState that has base_img as its backing file + def addBlockDriverStateOverlay(self, node): + self.checkBlockDriverState(node, False) + iotests.qemu_img('create', '-f', iotests.imgfmt, + '-b', base_img, new_img, '1M') + opts = {'driver': iotests.imgfmt, + 'node-name': node, + 'backing': '', + 'file': {'driver': 'file', + 'filename': new_img}} + result = self.vm.qmp('blockdev-add', conv_keys = False, options = opts) + self.assert_qmp(result, 'return', {}) + self.checkBlockDriverState(node) + + # Delete a BlockBackend + def delBlockBackend(self, backend, node, expect_error = False, + destroys_media = True): + self.checkBlockBackend(backend, node) + if node: + self.checkBlockDriverState(node) + result = self.vm.qmp('x-blockdev-del', id = backend) + if expect_error: + self.assert_qmp(result, 'error/class', 'GenericError') + if node: + self.checkBlockDriverState(node) + else: + self.assert_qmp(result, 'return', {}) + if node: + self.checkBlockDriverState(node, not destroys_media) + self.checkBlockBackend(backend, node, must_exist = expect_error) + + # Delete a BlockDriverState + def delBlockDriverState(self, node, expect_error = False): + self.checkBlockDriverState(node) + result = self.vm.qmp('x-blockdev-del', node_name = node) + if expect_error: + self.assert_qmp(result, 'error/class', 'GenericError') + else: + self.assert_qmp(result, 'return', {}) + self.checkBlockDriverState(node, expect_error) + + # Add a device model + def addDeviceModel(self, device, backend): + result = self.vm.qmp('device_add', id = device, + driver = 'virtio-blk-pci', drive = backend) + self.assert_qmp(result, 'return', {}) + + # Delete a device model + def delDeviceModel(self, device): + result = self.vm.qmp('device_del', id = device) + self.assert_qmp(result, 'return', {}) + + result = self.vm.qmp('system_reset') + self.assert_qmp(result, 'return', {}) + + device_path = '/machine/peripheral/%s/virtio-backend' % device + event = self.vm.event_wait(name="DEVICE_DELETED", + match={'data': {'path': device_path}}) + self.assertNotEqual(event, None) + + event = self.vm.event_wait(name="DEVICE_DELETED", + match={'data': {'device': device}}) + self.assertNotEqual(event, None) + + # Remove a BlockDriverState + def ejectDrive(self, backend, node, expect_error = False, + destroys_media = True): + self.checkBlockBackend(backend, node) + self.checkBlockDriverState(node) + result = self.vm.qmp('eject', device = backend) + if expect_error: + self.assert_qmp(result, 'error/class', 'GenericError') + self.checkBlockDriverState(node) + self.checkBlockBackend(backend, node) + else: + self.assert_qmp(result, 'return', {}) + self.checkBlockDriverState(node, not destroys_media) + self.checkBlockBackend(backend, None) + + # Insert a BlockDriverState + def insertDrive(self, backend, node): + self.checkBlockBackend(backend, None) + self.checkBlockDriverState(node) + result = self.vm.qmp('blockdev-insert-medium', + device = backend, node_name = node) + self.assert_qmp(result, 'return', {}) + self.checkBlockBackend(backend, node) + self.checkBlockDriverState(node) + + # Create a snapshot using 'blockdev-snapshot-sync' + def createSnapshotSync(self, node, overlay): + self.checkBlockDriverState(node) + self.checkBlockDriverState(overlay, False) + opts = {'node-name': node, + 'snapshot-file': new_img, + 'snapshot-node-name': overlay, + 'format': iotests.imgfmt} + result = self.vm.qmp('blockdev-snapshot-sync', conv_keys=False, **opts) + self.assert_qmp(result, 'return', {}) + self.checkBlockDriverState(node) + self.checkBlockDriverState(overlay) + + # Create a snapshot using 'blockdev-snapshot' + def createSnapshot(self, node, overlay): + self.checkBlockDriverState(node) + self.checkBlockDriverState(overlay) + result = self.vm.qmp('blockdev-snapshot', + node = node, overlay = overlay) + self.assert_qmp(result, 'return', {}) + self.checkBlockDriverState(node) + self.checkBlockDriverState(overlay) + + # Create a mirror + def createMirror(self, backend, node, new_node): + self.checkBlockBackend(backend, node) + self.checkBlockDriverState(new_node, False) + opts = {'device': backend, + 'target': new_img, + 'node-name': new_node, + 'sync': 'top', + 'format': iotests.imgfmt} + result = self.vm.qmp('drive-mirror', conv_keys=False, **opts) + self.assert_qmp(result, 'return', {}) + self.checkBlockBackend(backend, node) + self.checkBlockDriverState(new_node) + + # Complete an existing block job + def completeBlockJob(self, backend, node_before, node_after): + self.checkBlockBackend(backend, node_before) + result = self.vm.qmp('block-job-complete', device=backend) + self.assert_qmp(result, 'return', {}) + self.checkBlockBackend(backend, node_after) + + # Add a BlkDebug node + def addBlkDebug(self, debug, node): + self.checkBlockDriverState(node, False) + self.checkBlockDriverState(debug, False) + image = {'driver': iotests.imgfmt, + 'node-name': node, + 'file': {'driver': 'file', + 'filename': base_img}} + opts = {'driver': 'blkdebug', + 'node-name': debug, + 'image': image} + result = self.vm.qmp('blockdev-add', conv_keys = False, options = opts) + self.assert_qmp(result, 'return', {}) + self.checkBlockDriverState(node) + self.checkBlockDriverState(debug) + + # Add a BlkVerify node + def addBlkVerify(self, blkverify, test, raw): + self.checkBlockDriverState(test, False) + self.checkBlockDriverState(raw, False) + self.checkBlockDriverState(blkverify, False) + iotests.qemu_img('create', '-f', iotests.imgfmt, new_img, '1M') + node_0 = {'driver': iotests.imgfmt, + 'node-name': test, + 'file': {'driver': 'file', + 'filename': base_img}} + node_1 = {'driver': iotests.imgfmt, + 'node-name': raw, + 'file': {'driver': 'file', + 'filename': new_img}} + opts = {'driver': 'blkverify', + 'node-name': blkverify, + 'test': node_0, + 'raw': node_1} + result = self.vm.qmp('blockdev-add', conv_keys = False, options = opts) + self.assert_qmp(result, 'return', {}) + self.checkBlockDriverState(test) + self.checkBlockDriverState(raw) + self.checkBlockDriverState(blkverify) + + # Add a Quorum node + def addQuorum(self, quorum, child0, child1): + self.checkBlockDriverState(child0, False) + self.checkBlockDriverState(child1, False) + self.checkBlockDriverState(quorum, False) + iotests.qemu_img('create', '-f', iotests.imgfmt, new_img, '1M') + child_0 = {'driver': iotests.imgfmt, + 'node-name': child0, + 'file': {'driver': 'file', + 'filename': base_img}} + child_1 = {'driver': iotests.imgfmt, + 'node-name': child1, + 'file': {'driver': 'file', + 'filename': new_img}} + opts = {'driver': 'quorum', + 'node-name': quorum, + 'vote-threshold': 1, + 'children': [ child_0, child_1 ]} + result = self.vm.qmp('blockdev-add', conv_keys = False, options = opts) + self.assert_qmp(result, 'return', {}) + self.checkBlockDriverState(child0) + self.checkBlockDriverState(child1) + self.checkBlockDriverState(quorum) + + ######################## + # The tests start here # + ######################## + + def testWrongParameters(self): + self.addBlockBackend('drive0', 'node0') + result = self.vm.qmp('x-blockdev-del') + self.assert_qmp(result, 'error/class', 'GenericError') + result = self.vm.qmp('x-blockdev-del', id='drive0', node_name='node0') + self.assert_qmp(result, 'error/class', 'GenericError') + self.delBlockBackend('drive0', 'node0') + + def testBlockBackend(self): + self.addBlockBackend('drive0', 'node0') + # You cannot delete a BDS that is attached to a backend + self.delBlockDriverState('node0', expect_error = True) + self.delBlockBackend('drive0', 'node0') + + def testBlockDriverState(self): + self.addBlockDriverState('node0') + # You cannot delete a file BDS directly + self.delBlockDriverState('node0_file', expect_error = True) + self.delBlockDriverState('node0') + + def testEject(self): + self.addBlockBackend('drive0', 'node0') + self.ejectDrive('drive0', 'node0') + self.delBlockBackend('drive0', None) + + def testDeviceModel(self): + self.addBlockBackend('drive0', 'node0') + self.addDeviceModel('device0', 'drive0') + self.ejectDrive('drive0', 'node0', expect_error = True) + self.delBlockBackend('drive0', 'node0', expect_error = True) + self.delDeviceModel('device0') + self.delBlockBackend('drive0', 'node0') + + def testAttachMedia(self): + # This creates a BlockBackend and removes its media + self.addBlockBackend('drive0', 'node0') + self.ejectDrive('drive0', 'node0') + # This creates a new BlockDriverState and inserts it into the backend + self.addBlockDriverState('node1') + self.insertDrive('drive0', 'node1') + # The backend can't be removed: the new BDS has an extra reference + self.delBlockBackend('drive0', 'node1', expect_error = True) + self.delBlockDriverState('node1', expect_error = True) + # The BDS still exists after being ejected, but now it can be removed + self.ejectDrive('drive0', 'node1', destroys_media = False) + self.delBlockDriverState('node1') + self.delBlockBackend('drive0', None) + + def testSnapshotSync(self): + self.addBlockBackend('drive0', 'node0') + self.createSnapshotSync('node0', 'overlay0') + # This fails because node0 is now being used as a backing image + self.delBlockDriverState('node0', expect_error = True) + # This succeeds because overlay0 only has the backend reference + self.delBlockBackend('drive0', 'overlay0') + self.checkBlockDriverState('node0', False) + + def testSnapshot(self): + self.addBlockBackend('drive0', 'node0') + self.addBlockDriverStateOverlay('overlay0') + self.createSnapshot('node0', 'overlay0') + self.delBlockBackend('drive0', 'overlay0', expect_error = True) + self.delBlockDriverState('node0', expect_error = True) + self.delBlockDriverState('overlay0', expect_error = True) + self.ejectDrive('drive0', 'overlay0', destroys_media = False) + self.delBlockBackend('drive0', None) + self.delBlockDriverState('node0', expect_error = True) + self.delBlockDriverState('overlay0') + self.checkBlockDriverState('node0', False) + + def testMirror(self): + self.addBlockBackend('drive0', 'node0') + self.createMirror('drive0', 'node0', 'mirror0') + # The block job prevents removing the device + self.delBlockBackend('drive0', 'node0', expect_error = True) + self.delBlockDriverState('node0', expect_error = True) + self.delBlockDriverState('mirror0', expect_error = True) + self.completeBlockJob('drive0', 'node0', 'mirror0') + self.assert_no_active_block_jobs() + self.checkBlockDriverState('node0', False) + # This succeeds because the backend now points to mirror0 + self.delBlockBackend('drive0', 'mirror0') + + def testBlkDebug(self): + self.addBlkDebug('debug0', 'node0') + # 'node0' is used by the blkdebug node + self.delBlockDriverState('node0', expect_error = True) + # But we can remove the blkdebug node directly + self.delBlockDriverState('debug0') + self.checkBlockDriverState('node0', False) + + def testBlkVerify(self): + self.addBlkVerify('verify0', 'node0', 'node1') + # We cannot remove the children of a blkverify device + self.delBlockDriverState('node0', expect_error = True) + self.delBlockDriverState('node1', expect_error = True) + # But we can remove the blkdebug node directly + self.delBlockDriverState('verify0') + self.checkBlockDriverState('node0', False) + self.checkBlockDriverState('node1', False) + + def testQuorum(self): + self.addQuorum('quorum0', 'node0', 'node1') + # We cannot remove the children of a Quorum device + self.delBlockDriverState('node0', expect_error = True) + self.delBlockDriverState('node1', expect_error = True) + # But we can remove the Quorum node directly + self.delBlockDriverState('quorum0') + self.checkBlockDriverState('node0', False) + self.checkBlockDriverState('node1', False) + + +if __name__ == '__main__': + iotests.main(supported_fmts=["qcow2"])
tests/qemu-iotests/139.out new file +5 -0
@@ -0,0 +1,5 @@ +............ +---------------------------------------------------------------------- +Ran 12 tests + +OK
tests/qemu-iotests/group +1 -0
@@ -138,3 +138,4 @@ 135 rw auto 137 rw auto 138 rw auto quick +139 rw auto quick
Signature · 1 line
2.6.1