xref: /qemu/tests/qemu-iotests/139 (revision 7271a819)
1#!/usr/bin/env python
2#
3# Test cases for the QMP 'blockdev-del' command
4#
5# Copyright (C) 2015 Igalia, S.L.
6# Author: Alberto Garcia <berto@igalia.com>
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation; either version 2 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with this program.  If not, see <http://www.gnu.org/licenses/>.
20#
21
22import os
23import iotests
24import time
25
26base_img = os.path.join(iotests.test_dir, 'base.img')
27new_img = os.path.join(iotests.test_dir, 'new.img')
28if iotests.qemu_default_machine == 's390-ccw-virtio':
29    default_virtio_blk = 'virtio-blk-ccw'
30else:
31    default_virtio_blk = 'virtio-blk-pci'
32
33class TestBlockdevDel(iotests.QMPTestCase):
34
35    def setUp(self):
36        iotests.qemu_img('create', '-f', iotests.imgfmt, base_img, '1M')
37        self.vm = iotests.VM()
38        if iotests.qemu_default_machine == 's390-ccw-virtio':
39            self.vm.add_device("virtio-scsi-ccw,id=virtio-scsi")
40        else:
41            self.vm.add_device("virtio-scsi-pci,id=virtio-scsi")
42
43        self.vm.launch()
44
45    def tearDown(self):
46        self.vm.shutdown()
47        os.remove(base_img)
48        if os.path.isfile(new_img):
49            os.remove(new_img)
50
51    # Check whether a BlockDriverState exists
52    def checkBlockDriverState(self, node, must_exist = True):
53        result = self.vm.qmp('query-named-block-nodes')
54        nodes = filter(lambda x: x['node-name'] == node, result['return'])
55        self.assertLessEqual(len(nodes), 1)
56        self.assertEqual(must_exist, len(nodes) == 1)
57
58    # Add a BlockDriverState without a BlockBackend
59    def addBlockDriverState(self, node):
60        file_node = '%s_file' % node
61        self.checkBlockDriverState(node, False)
62        self.checkBlockDriverState(file_node, False)
63        opts = {'driver': iotests.imgfmt,
64                'node-name': node,
65                'file': {'driver': 'file',
66                         'node-name': file_node,
67                         'filename': base_img}}
68        result = self.vm.qmp('blockdev-add', conv_keys = False, **opts)
69        self.assert_qmp(result, 'return', {})
70        self.checkBlockDriverState(node)
71        self.checkBlockDriverState(file_node)
72
73    # Add a BlockDriverState that will be used as overlay for the base_img BDS
74    def addBlockDriverStateOverlay(self, node):
75        self.checkBlockDriverState(node, False)
76        iotests.qemu_img('create', '-u', '-f', iotests.imgfmt,
77                         '-b', base_img, new_img, '1M')
78        opts = {'driver': iotests.imgfmt,
79                'node-name': node,
80                'backing': None,
81                'file': {'driver': 'file',
82                         'filename': new_img}}
83        result = self.vm.qmp('blockdev-add', conv_keys = False, **opts)
84        self.assert_qmp(result, 'return', {})
85        self.checkBlockDriverState(node)
86
87    # Delete a BlockDriverState
88    def delBlockDriverState(self, node, expect_error = False):
89        self.checkBlockDriverState(node)
90        result = self.vm.qmp('blockdev-del', node_name = node)
91        if expect_error:
92            self.assert_qmp(result, 'error/class', 'GenericError')
93        else:
94            self.assert_qmp(result, 'return', {})
95        self.checkBlockDriverState(node, expect_error)
96
97    # Add a device model
98    def addDeviceModel(self, device, backend, driver = default_virtio_blk):
99        result = self.vm.qmp('device_add', id = device,
100                             driver = driver, drive = backend)
101        self.assert_qmp(result, 'return', {})
102
103    # Delete a device model
104    def delDeviceModel(self, device, is_virtio_blk = True):
105        result = self.vm.qmp('device_del', id = device)
106        self.assert_qmp(result, 'return', {})
107
108        result = self.vm.qmp('system_reset')
109        self.assert_qmp(result, 'return', {})
110
111        if is_virtio_blk:
112            device_path = '/machine/peripheral/%s/virtio-backend' % device
113            event = self.vm.event_wait(name="DEVICE_DELETED",
114                                       match={'data': {'path': device_path}})
115            self.assertNotEqual(event, None)
116
117        event = self.vm.event_wait(name="DEVICE_DELETED",
118                                   match={'data': {'device': device}})
119        self.assertNotEqual(event, None)
120
121    # Remove a BlockDriverState
122    def ejectDrive(self, device, node, expect_error = False,
123                   destroys_media = True):
124        self.checkBlockDriverState(node)
125        result = self.vm.qmp('eject', id = device)
126        if expect_error:
127            self.assert_qmp(result, 'error/class', 'GenericError')
128            self.checkBlockDriverState(node)
129        else:
130            self.assert_qmp(result, 'return', {})
131            self.checkBlockDriverState(node, not destroys_media)
132
133    # Insert a BlockDriverState
134    def insertDrive(self, device, node):
135        self.checkBlockDriverState(node)
136        result = self.vm.qmp('x-blockdev-insert-medium',
137                             id = device, node_name = node)
138        self.assert_qmp(result, 'return', {})
139        self.checkBlockDriverState(node)
140
141    # Create a snapshot using 'blockdev-snapshot-sync'
142    def createSnapshotSync(self, node, overlay):
143        self.checkBlockDriverState(node)
144        self.checkBlockDriverState(overlay, False)
145        opts = {'node-name': node,
146                'snapshot-file': new_img,
147                'snapshot-node-name': overlay,
148                'format': iotests.imgfmt}
149        result = self.vm.qmp('blockdev-snapshot-sync', conv_keys=False, **opts)
150        self.assert_qmp(result, 'return', {})
151        self.checkBlockDriverState(node)
152        self.checkBlockDriverState(overlay)
153
154    # Create a snapshot using 'blockdev-snapshot'
155    def createSnapshot(self, node, overlay):
156        self.checkBlockDriverState(node)
157        self.checkBlockDriverState(overlay)
158        result = self.vm.qmp('blockdev-snapshot',
159                             node = node, overlay = overlay)
160        self.assert_qmp(result, 'return', {})
161        self.checkBlockDriverState(node)
162        self.checkBlockDriverState(overlay)
163
164    # Create a mirror
165    def createMirror(self, node, new_node):
166        self.checkBlockDriverState(new_node, False)
167        opts = {'device': node,
168                'job-id': node,
169                'target': new_img,
170                'node-name': new_node,
171                'sync': 'top',
172                'format': iotests.imgfmt}
173        result = self.vm.qmp('drive-mirror', conv_keys=False, **opts)
174        self.assert_qmp(result, 'return', {})
175        self.checkBlockDriverState(new_node)
176
177    # Complete an existing block job
178    def completeBlockJob(self, id, node_before, node_after):
179        result = self.vm.qmp('block-job-complete', device=id)
180        self.assert_qmp(result, 'return', {})
181        self.wait_until_completed(id)
182
183    # Add a BlkDebug node
184    # Note that the purpose of this is to test the blockdev-del
185    # sanity checks, not to create a usable blkdebug drive
186    def addBlkDebug(self, debug, node):
187        self.checkBlockDriverState(node, False)
188        self.checkBlockDriverState(debug, False)
189        image = {'driver': iotests.imgfmt,
190                 'node-name': node,
191                 'file': {'driver': 'file',
192                          'filename': base_img}}
193        opts = {'driver': 'blkdebug',
194                'node-name': debug,
195                'image': image}
196        result = self.vm.qmp('blockdev-add', conv_keys = False, **opts)
197        self.assert_qmp(result, 'return', {})
198        self.checkBlockDriverState(node)
199        self.checkBlockDriverState(debug)
200
201    # Add a BlkVerify node
202    # Note that the purpose of this is to test the blockdev-del
203    # sanity checks, not to create a usable blkverify drive
204    def addBlkVerify(self, blkverify, test, raw):
205        self.checkBlockDriverState(test, False)
206        self.checkBlockDriverState(raw, False)
207        self.checkBlockDriverState(blkverify, False)
208        iotests.qemu_img('create', '-f', iotests.imgfmt, new_img, '1M')
209        node_0 = {'driver': iotests.imgfmt,
210                  'node-name': test,
211                  'file': {'driver': 'file',
212                           'filename': base_img}}
213        node_1 = {'driver': iotests.imgfmt,
214                  'node-name': raw,
215                  'file': {'driver': 'file',
216                           'filename': new_img}}
217        opts = {'driver': 'blkverify',
218                'node-name': blkverify,
219                'test': node_0,
220                'raw': node_1}
221        result = self.vm.qmp('blockdev-add', conv_keys = False, **opts)
222        self.assert_qmp(result, 'return', {})
223        self.checkBlockDriverState(test)
224        self.checkBlockDriverState(raw)
225        self.checkBlockDriverState(blkverify)
226
227    # Add a Quorum node
228    def addQuorum(self, quorum, child0, child1):
229        self.checkBlockDriverState(child0, False)
230        self.checkBlockDriverState(child1, False)
231        self.checkBlockDriverState(quorum, False)
232        iotests.qemu_img('create', '-f', iotests.imgfmt, new_img, '1M')
233        child_0 = {'driver': iotests.imgfmt,
234                   'node-name': child0,
235                   'file': {'driver': 'file',
236                            'filename': base_img}}
237        child_1 = {'driver': iotests.imgfmt,
238                   'node-name': child1,
239                   'file': {'driver': 'file',
240                            'filename': new_img}}
241        opts = {'driver': 'quorum',
242                'node-name': quorum,
243                'vote-threshold': 1,
244                'children': [ child_0, child_1 ]}
245        result = self.vm.qmp('blockdev-add', conv_keys = False, **opts)
246        self.assert_qmp(result, 'return', {})
247        self.checkBlockDriverState(child0)
248        self.checkBlockDriverState(child1)
249        self.checkBlockDriverState(quorum)
250
251    ########################
252    # The tests start here #
253    ########################
254
255    def testBlockDriverState(self):
256        self.addBlockDriverState('node0')
257        # You cannot delete a file BDS directly
258        self.delBlockDriverState('node0_file', expect_error = True)
259        self.delBlockDriverState('node0')
260
261    def testDeviceModel(self):
262        self.addBlockDriverState('node0')
263        self.addDeviceModel('device0', 'node0')
264        self.ejectDrive('device0', 'node0', expect_error = True)
265        self.delBlockDriverState('node0', expect_error = True)
266        self.delDeviceModel('device0')
267        self.delBlockDriverState('node0')
268
269    def testAttachMedia(self):
270        # This creates a BlockBackend and removes its media
271        self.addBlockDriverState('node0')
272        self.addDeviceModel('device0', 'node0', 'scsi-cd')
273        self.ejectDrive('device0', 'node0', destroys_media = False)
274        self.delBlockDriverState('node0')
275
276        # This creates a new BlockDriverState and inserts it into the device
277        self.addBlockDriverState('node1')
278        self.insertDrive('device0', 'node1')
279        # The node can't be removed: the new device has an extra reference
280        self.delBlockDriverState('node1', expect_error = True)
281        # The BDS still exists after being ejected, but now it can be removed
282        self.ejectDrive('device0', 'node1', destroys_media = False)
283        self.delBlockDriverState('node1')
284        self.delDeviceModel('device0', False)
285
286    def testSnapshotSync(self):
287        self.addBlockDriverState('node0')
288        self.addDeviceModel('device0', 'node0')
289        self.createSnapshotSync('node0', 'overlay0')
290        # This fails because node0 is now being used as a backing image
291        self.delBlockDriverState('node0', expect_error = True)
292        self.delBlockDriverState('overlay0', expect_error = True)
293        # This succeeds because device0 only has the backend reference
294        self.delDeviceModel('device0')
295        # FIXME Would still be there if blockdev-snapshot-sync took a ref
296        self.checkBlockDriverState('overlay0', False)
297        self.delBlockDriverState('node0')
298
299    def testSnapshot(self):
300        self.addBlockDriverState('node0')
301        self.addDeviceModel('device0', 'node0', 'scsi-cd')
302        self.addBlockDriverStateOverlay('overlay0')
303        self.createSnapshot('node0', 'overlay0')
304        self.delBlockDriverState('node0', expect_error = True)
305        self.delBlockDriverState('overlay0', expect_error = True)
306        self.ejectDrive('device0', 'overlay0', destroys_media = False)
307        self.delBlockDriverState('node0', expect_error = True)
308        self.delBlockDriverState('overlay0')
309        self.delBlockDriverState('node0')
310
311    def testMirror(self):
312        self.addBlockDriverState('node0')
313        self.addDeviceModel('device0', 'node0', 'scsi-cd')
314        self.createMirror('node0', 'mirror0')
315        # The block job prevents removing the device
316        self.delBlockDriverState('node0', expect_error = True)
317        self.delBlockDriverState('mirror0', expect_error = True)
318        self.wait_ready('node0')
319        self.completeBlockJob('node0', 'node0', 'mirror0')
320        self.assert_no_active_block_jobs()
321        # This succeeds because the device now points to mirror0
322        self.delBlockDriverState('node0')
323        self.delBlockDriverState('mirror0', expect_error = True)
324        self.delDeviceModel('device0', False)
325        # FIXME mirror0 disappears, drive-mirror doesn't take a reference
326        #self.delBlockDriverState('mirror0')
327
328    def testBlkDebug(self):
329        self.addBlkDebug('debug0', 'node0')
330        # 'node0' is used by the blkdebug node
331        self.delBlockDriverState('node0', expect_error = True)
332        # But we can remove the blkdebug node directly
333        self.delBlockDriverState('debug0')
334        self.checkBlockDriverState('node0', False)
335
336    def testBlkVerify(self):
337        self.addBlkVerify('verify0', 'node0', 'node1')
338        # We cannot remove the children of a blkverify device
339        self.delBlockDriverState('node0', expect_error = True)
340        self.delBlockDriverState('node1', expect_error = True)
341        # But we can remove the blkverify node directly
342        self.delBlockDriverState('verify0')
343        self.checkBlockDriverState('node0', False)
344        self.checkBlockDriverState('node1', False)
345
346    def testQuorum(self):
347        if not iotests.supports_quorum():
348            return
349
350        self.addQuorum('quorum0', 'node0', 'node1')
351        # We cannot remove the children of a Quorum device
352        self.delBlockDriverState('node0', expect_error = True)
353        self.delBlockDriverState('node1', expect_error = True)
354        # But we can remove the Quorum node directly
355        self.delBlockDriverState('quorum0')
356        self.checkBlockDriverState('node0', False)
357        self.checkBlockDriverState('node1', False)
358
359
360if __name__ == '__main__':
361    iotests.main(supported_fmts=["qcow2"])
362