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