| #!/usr/bin/env python |
| # |
| # Test cases for the QMP 'blockdev-del' command |
| # |
| # Copyright (C) 2015 Igalia, S.L. |
| # Author: Alberto Garcia <berto@igalia.com> |
| # |
| # 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') |
| if iotests.qemu_default_machine == 's390-ccw-virtio': |
| default_virtio_blk = 'virtio-blk-ccw' |
| else: |
| default_virtio_blk = 'virtio-blk-pci' |
| |
| class TestBlockdevDel(iotests.QMPTestCase): |
| |
| def setUp(self): |
| iotests.qemu_img('create', '-f', iotests.imgfmt, base_img, '1M') |
| self.vm = iotests.VM() |
| self.vm.add_device("{},id=virtio-scsi".format( |
| iotests.get_virtio_scsi_device())) |
| 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 BlockDriverState exists |
| def checkBlockDriverState(self, node, must_exist = True): |
| result = self.vm.qmp('query-named-block-nodes') |
| nodes = [x for x in result['return'] if x['node-name'] == node] |
| self.assertLessEqual(len(nodes), 1) |
| self.assertEqual(must_exist, len(nodes) == 1) |
| |
| # 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, **opts) |
| self.assert_qmp(result, 'return', {}) |
| self.checkBlockDriverState(node) |
| self.checkBlockDriverState(file_node) |
| |
| # Add a BlockDriverState that will be used as overlay for the base_img BDS |
| def addBlockDriverStateOverlay(self, node): |
| self.checkBlockDriverState(node, False) |
| iotests.qemu_img('create', '-u', '-f', iotests.imgfmt, |
| '-b', base_img, new_img, '1M') |
| opts = {'driver': iotests.imgfmt, |
| 'node-name': node, |
| 'backing': None, |
| 'file': {'driver': 'file', |
| 'filename': new_img}} |
| result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) |
| self.assert_qmp(result, 'return', {}) |
| self.checkBlockDriverState(node) |
| |
| # Delete a BlockDriverState |
| def delBlockDriverState(self, node, expect_error = False): |
| self.checkBlockDriverState(node) |
| result = self.vm.qmp('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, driver = default_virtio_blk): |
| result = self.vm.qmp('device_add', id = device, |
| driver = driver, drive = backend) |
| self.assert_qmp(result, 'return', {}) |
| |
| # Delete a device model |
| def delDeviceModel(self, device, is_virtio_blk = True): |
| result = self.vm.qmp('device_del', id = device) |
| self.assert_qmp(result, 'return', {}) |
| |
| result = self.vm.qmp('system_reset') |
| self.assert_qmp(result, 'return', {}) |
| |
| if is_virtio_blk: |
| 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, device, node, expect_error = False, |
| destroys_media = True): |
| self.checkBlockDriverState(node) |
| result = self.vm.qmp('eject', id = device) |
| if expect_error: |
| self.assert_qmp(result, 'error/class', 'GenericError') |
| self.checkBlockDriverState(node) |
| else: |
| self.assert_qmp(result, 'return', {}) |
| self.checkBlockDriverState(node, not destroys_media) |
| |
| # Insert a BlockDriverState |
| def insertDrive(self, device, node): |
| self.checkBlockDriverState(node) |
| result = self.vm.qmp('blockdev-insert-medium', |
| id = device, node_name = node) |
| self.assert_qmp(result, 'return', {}) |
| 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, node, new_node): |
| self.checkBlockDriverState(new_node, False) |
| opts = {'device': node, |
| 'job-id': node, |
| '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.checkBlockDriverState(new_node) |
| |
| # Complete an existing block job |
| def completeBlockJob(self, id, node_before, node_after): |
| result = self.vm.qmp('block-job-complete', device=id) |
| self.assert_qmp(result, 'return', {}) |
| self.wait_until_completed(id) |
| |
| # Add a BlkDebug node |
| # Note that the purpose of this is to test the blockdev-del |
| # sanity checks, not to create a usable blkdebug drive |
| 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, **opts) |
| self.assert_qmp(result, 'return', {}) |
| self.checkBlockDriverState(node) |
| self.checkBlockDriverState(debug) |
| |
| # Add a BlkVerify node |
| # Note that the purpose of this is to test the blockdev-del |
| # sanity checks, not to create a usable blkverify drive |
| 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, **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, **opts) |
| self.assert_qmp(result, 'return', {}) |
| self.checkBlockDriverState(child0) |
| self.checkBlockDriverState(child1) |
| self.checkBlockDriverState(quorum) |
| |
| ######################## |
| # The tests start here # |
| ######################## |
| |
| def testBlockDriverState(self): |
| self.addBlockDriverState('node0') |
| # You cannot delete a file BDS directly |
| self.delBlockDriverState('node0_file', expect_error = True) |
| self.delBlockDriverState('node0') |
| |
| def testDeviceModel(self): |
| self.addBlockDriverState('node0') |
| self.addDeviceModel('device0', 'node0') |
| self.ejectDrive('device0', 'node0', expect_error = True) |
| self.delBlockDriverState('node0', expect_error = True) |
| self.delDeviceModel('device0') |
| self.delBlockDriverState('node0') |
| |
| def testAttachMedia(self): |
| # This creates a BlockBackend and removes its media |
| self.addBlockDriverState('node0') |
| self.addDeviceModel('device0', 'node0', 'scsi-cd') |
| self.ejectDrive('device0', 'node0', destroys_media = False) |
| self.delBlockDriverState('node0') |
| |
| # This creates a new BlockDriverState and inserts it into the device |
| self.addBlockDriverState('node1') |
| self.insertDrive('device0', 'node1') |
| # The node can't be removed: the new device has an extra reference |
| self.delBlockDriverState('node1', expect_error = True) |
| # The BDS still exists after being ejected, but now it can be removed |
| self.ejectDrive('device0', 'node1', destroys_media = False) |
| self.delBlockDriverState('node1') |
| self.delDeviceModel('device0', False) |
| |
| def testSnapshotSync(self): |
| self.addBlockDriverState('node0') |
| self.addDeviceModel('device0', 'node0') |
| self.createSnapshotSync('node0', 'overlay0') |
| # This fails because node0 is now being used as a backing image |
| self.delBlockDriverState('node0', expect_error = True) |
| self.delBlockDriverState('overlay0', expect_error = True) |
| # This succeeds because device0 only has the backend reference |
| self.delDeviceModel('device0') |
| # FIXME Would still be there if blockdev-snapshot-sync took a ref |
| self.checkBlockDriverState('overlay0', False) |
| self.delBlockDriverState('node0') |
| |
| def testSnapshot(self): |
| self.addBlockDriverState('node0') |
| self.addDeviceModel('device0', 'node0', 'scsi-cd') |
| self.addBlockDriverStateOverlay('overlay0') |
| self.createSnapshot('node0', 'overlay0') |
| self.delBlockDriverState('node0', expect_error = True) |
| self.delBlockDriverState('overlay0', expect_error = True) |
| self.ejectDrive('device0', 'overlay0', destroys_media = False) |
| self.delBlockDriverState('node0', expect_error = True) |
| self.delBlockDriverState('overlay0') |
| self.delBlockDriverState('node0') |
| |
| def testMirror(self): |
| self.addBlockDriverState('node0') |
| self.addDeviceModel('device0', 'node0', 'scsi-cd') |
| self.createMirror('node0', 'mirror0') |
| # The block job prevents removing the device |
| self.delBlockDriverState('node0', expect_error = True) |
| self.delBlockDriverState('mirror0', expect_error = True) |
| self.wait_ready('node0') |
| self.completeBlockJob('node0', 'node0', 'mirror0') |
| self.assert_no_active_block_jobs() |
| # This succeeds because the device now points to mirror0 |
| self.delBlockDriverState('node0') |
| self.delBlockDriverState('mirror0', expect_error = True) |
| self.delDeviceModel('device0', False) |
| # FIXME mirror0 disappears, drive-mirror doesn't take a reference |
| #self.delBlockDriverState('mirror0') |
| |
| @iotests.skip_if_unsupported(['blkdebug']) |
| 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) |
| |
| @iotests.skip_if_unsupported(['blkverify']) |
| 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 blkverify node directly |
| self.delBlockDriverState('verify0') |
| self.checkBlockDriverState('node0', False) |
| self.checkBlockDriverState('node1', False) |
| |
| @iotests.skip_if_unsupported(['quorum']) |
| def testQuorum(self): |
| if not iotests.supports_quorum(): |
| return |
| |
| 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"], |
| supported_protocols=["file"]) |