tests/qapi-schema: Fix test 'QAPI rST doc'
[qemu/kevin.git] / tests / qemu-iotests / 040
blob5c18e413ec97b1982bc227199d4b9c9f3a370fb6
1 #!/usr/bin/env python3
2 # group: rw auto
4 # Tests for image block commit.
6 # Copyright (C) 2012 IBM, Corp.
7 # Copyright (C) 2012 Red Hat, Inc.
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.
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.
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/>.
22 # Test for live block commit
23 # Derived from Image Streaming Test 030
25 import time
26 import os
27 import iotests
28 from iotests import qemu_img, qemu_io
29 import struct
30 import errno
32 backing_img = os.path.join(iotests.test_dir, 'backing.img')
33 mid_img = os.path.join(iotests.test_dir, 'mid.img')
34 test_img = os.path.join(iotests.test_dir, 'test.img')
36 class ImageCommitTestCase(iotests.QMPTestCase):
37     '''Abstract base class for image commit test cases'''
39     def wait_for_complete(self, need_ready=False):
40         completed = False
41         ready = False
42         while not completed:
43             for event in self.vm.get_qmp_events(wait=True):
44                 if event['event'] == 'BLOCK_JOB_COMPLETED':
45                     self.assert_qmp_absent(event, 'data/error')
46                     self.assert_qmp(event, 'data/type', 'commit')
47                     self.assert_qmp(event, 'data/device', 'drive0')
48                     self.assert_qmp(event, 'data/offset', event['data']['len'])
49                     if need_ready:
50                         self.assertTrue(ready, "Expecting BLOCK_JOB_COMPLETED event")
51                     completed = True
52                 elif event['event'] == 'BLOCK_JOB_READY':
53                     ready = True
54                     self.assert_qmp(event, 'data/type', 'commit')
55                     self.assert_qmp(event, 'data/device', 'drive0')
56                     self.vm.qmp('block-job-complete', device='drive0')
58         self.assert_no_active_block_jobs()
59         self.vm.shutdown()
61     def run_commit_test(self, top, base, need_ready=False, node_names=False):
62         self.assert_no_active_block_jobs()
63         if node_names:
64             self.vm.cmd('block-commit', device='drive0', top_node=top, base_node=base)
65         else:
66             self.vm.cmd('block-commit', device='drive0', top=top, base=base)
67         self.wait_for_complete(need_ready)
69     def run_default_commit_test(self):
70         self.assert_no_active_block_jobs()
71         self.vm.cmd('block-commit', device='drive0')
72         self.wait_for_complete()
74 class TestSingleDrive(ImageCommitTestCase):
75     # Need some space after the copied data so that throttling is effective in
76     # tests that use it rather than just completing the job immediately
77     image_len = 2 * 1024 * 1024
78     test_len = 1 * 1024 * 256
80     def setUp(self):
81         iotests.create_image(backing_img, self.image_len)
82         qemu_img('create', '-f', iotests.imgfmt,
83                  '-o', 'backing_file=%s' % backing_img, '-F', 'raw', mid_img)
84         qemu_img('create', '-f', iotests.imgfmt,
85                  '-o', 'backing_file=%s' % mid_img,
86                  '-F', iotests.imgfmt, test_img)
87         if self.image_len:
88             qemu_io('-f', 'raw', '-c', 'write -P 0xab 0 524288', backing_img)
89             qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0xef 524288 524288',
90                     mid_img)
91         self.vm = iotests.VM().add_drive(test_img, "node-name=top,backing.node-name=mid,backing.backing.node-name=base", interface="none")
92         self.vm.add_device('virtio-scsi')
93         self.vm.add_device("scsi-hd,id=scsi0,drive=drive0")
94         self.vm.launch()
96     def tearDown(self):
97         self.vm.shutdown()
98         os.remove(test_img)
99         os.remove(mid_img)
100         os.remove(backing_img)
102     def test_commit(self):
103         self.run_commit_test(mid_img, backing_img)
104         if not self.image_len:
105             return
106         qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', backing_img)
107         qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', backing_img)
109     def test_commit_node(self):
110         self.run_commit_test("mid", "base", node_names=True)
111         if not self.image_len:
112             return
113         qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', backing_img)
114         qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', backing_img)
116     @iotests.skip_if_unsupported(['throttle'])
117     def test_commit_with_filter_and_quit(self):
118         self.vm.cmd('object-add', qom_type='throttle-group', id='tg')
120         # Add a filter outside of the backing chain
121         self.vm.cmd('blockdev-add', driver='throttle', node_name='filter', throttle_group='tg', file='mid')
123         self.vm.cmd('block-commit', device='drive0')
125         # Quit immediately, thus forcing a simultaneous cancel of the
126         # block job and a bdrv_drain_all()
127         self.vm.cmd('quit')
129     # Same as above, but this time we add the filter after starting the job
130     @iotests.skip_if_unsupported(['throttle'])
131     def test_commit_plus_filter_and_quit(self):
132         self.vm.cmd('object-add', qom_type='throttle-group', id='tg')
134         self.vm.cmd('block-commit', device='drive0')
136         # Add a filter outside of the backing chain
137         self.vm.cmd('blockdev-add', driver='throttle', node_name='filter', throttle_group='tg', file='mid')
139         # Quit immediately, thus forcing a simultaneous cancel of the
140         # block job and a bdrv_drain_all()
141         self.vm.cmd('quit')
143     def test_device_not_found(self):
144         result = self.vm.qmp('block-commit', device='nonexistent', top='%s' % mid_img)
145         self.assert_qmp(result, 'error/class', 'DeviceNotFound')
147     def test_top_same_base(self):
148         self.assert_no_active_block_jobs()
149         result = self.vm.qmp('block-commit', device='drive0', top='%s' % backing_img, base='%s' % backing_img)
150         self.assert_qmp(result, 'error/class', 'GenericError')
151         self.assert_qmp(result, 'error/desc', "Can't find '%s' in the backing chain" % backing_img)
153     def test_top_invalid(self):
154         self.assert_no_active_block_jobs()
155         result = self.vm.qmp('block-commit', device='drive0', top='badfile', base='%s' % backing_img)
156         self.assert_qmp(result, 'error/class', 'GenericError')
157         self.assert_qmp(result, 'error/desc', 'Top image file badfile not found')
159     def test_base_invalid(self):
160         self.assert_no_active_block_jobs()
161         result = self.vm.qmp('block-commit', device='drive0', top='%s' % mid_img, base='badfile')
162         self.assert_qmp(result, 'error/class', 'GenericError')
163         self.assert_qmp(result, 'error/desc', "Can't find 'badfile' in the backing chain")
165     def test_top_node_invalid(self):
166         self.assert_no_active_block_jobs()
167         result = self.vm.qmp('block-commit', device='drive0', top_node='badfile', base_node='base')
168         self.assert_qmp(result, 'error/class', 'GenericError')
169         self.assert_qmp(result, 'error/desc', "Cannot find device='' nor node-name='badfile'")
171     def test_base_node_invalid(self):
172         self.assert_no_active_block_jobs()
173         result = self.vm.qmp('block-commit', device='drive0', top_node='mid', base_node='badfile')
174         self.assert_qmp(result, 'error/class', 'GenericError')
175         self.assert_qmp(result, 'error/desc', "Cannot find device='' nor node-name='badfile'")
177     def test_top_path_and_node(self):
178         self.assert_no_active_block_jobs()
179         result = self.vm.qmp('block-commit', device='drive0', top_node='mid', base_node='base', top='%s' % mid_img)
180         self.assert_qmp(result, 'error/class', 'GenericError')
181         self.assert_qmp(result, 'error/desc', "'top-node' and 'top' are mutually exclusive")
183     def test_base_path_and_node(self):
184         self.assert_no_active_block_jobs()
185         result = self.vm.qmp('block-commit', device='drive0', top_node='mid', base_node='base', base='%s' % backing_img)
186         self.assert_qmp(result, 'error/class', 'GenericError')
187         self.assert_qmp(result, 'error/desc', "'base-node' and 'base' are mutually exclusive")
189     def test_top_is_active(self):
190         self.run_commit_test(test_img, backing_img, need_ready=True)
191         if not self.image_len:
192             return
193         qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', backing_img)
194         qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', backing_img)
196     def test_top_is_default_active(self):
197         self.run_default_commit_test()
198         if not self.image_len:
199             return
200         qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', backing_img)
201         qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', backing_img)
203     def test_top_and_base_reversed(self):
204         self.assert_no_active_block_jobs()
205         result = self.vm.qmp('block-commit', device='drive0', top='%s' % backing_img, base='%s' % mid_img)
206         self.assert_qmp(result, 'error/class', 'GenericError')
207         self.assert_qmp(result, 'error/desc', "Can't find '%s' in the backing chain" % mid_img)
209     def test_top_and_base_node_reversed(self):
210         self.assert_no_active_block_jobs()
211         result = self.vm.qmp('block-commit', device='drive0', top_node='base', base_node='top')
212         self.assert_qmp(result, 'error/class', 'GenericError')
213         self.assert_qmp(result, 'error/desc', "'top' is not in this backing file chain")
215     def test_top_node_in_wrong_chain(self):
216         self.assert_no_active_block_jobs()
218         self.vm.cmd('blockdev-add', driver='null-co', node_name='null')
220         result = self.vm.qmp('block-commit', device='drive0', top_node='null', base_node='base')
221         self.assert_qmp(result, 'error/class', 'GenericError')
222         self.assert_qmp(result, 'error/desc', "'null' is not in this backing file chain")
224     # When the job is running on a BB that is automatically deleted on hot
225     # unplug, the job is cancelled when the device disappears
226     def test_hot_unplug(self):
227         if self.image_len == 0:
228             return
230         self.assert_no_active_block_jobs()
231         self.vm.cmd('block-commit', device='drive0', top=mid_img,
232                     base=backing_img, speed=(self.image_len // 4))
233         self.vm.cmd('device_del', id='scsi0')
235         cancelled = False
236         deleted = False
237         while not cancelled or not deleted:
238             for event in self.vm.get_qmp_events(wait=True):
239                 if event['event'] == 'DEVICE_DELETED':
240                     self.assert_qmp(event, 'data/device', 'scsi0')
241                     deleted = True
242                 elif event['event'] == 'BLOCK_JOB_CANCELLED':
243                     self.assert_qmp(event, 'data/device', 'drive0')
244                     cancelled = True
245                 elif event['event'] == 'JOB_STATUS_CHANGE':
246                     self.assert_qmp(event, 'data/id', 'drive0')
247                 else:
248                     self.fail("Unexpected event %s" % (event['event']))
250         self.assert_no_active_block_jobs()
252     # Tests that the insertion of the commit_top filter node doesn't make a
253     # difference to query-blockstat
254     def test_implicit_node(self):
255         if self.image_len == 0:
256             return
258         self.assert_no_active_block_jobs()
259         self.vm.cmd('block-commit', device='drive0', top=mid_img,
260                     base=backing_img, speed=(self.image_len // 4))
262         result = self.vm.qmp('query-block')
263         self.assert_qmp(result, 'return[0]/inserted/file', test_img)
264         self.assert_qmp(result, 'return[0]/inserted/drv', iotests.imgfmt)
265         self.assert_qmp(result, 'return[0]/inserted/backing_file', mid_img)
266         self.assert_qmp(result, 'return[0]/inserted/backing_file_depth', 2)
267         self.assert_qmp(result, 'return[0]/inserted/image/filename', test_img)
268         self.assert_qmp(result, 'return[0]/inserted/image/backing-image/filename', mid_img)
269         self.assert_qmp(result, 'return[0]/inserted/image/backing-image/backing-image/filename', backing_img)
271         result = self.vm.qmp('query-blockstats')
272         self.assert_qmp(result, 'return[0]/node-name', 'top')
273         self.assert_qmp(result, 'return[0]/backing/node-name', 'mid')
274         self.assert_qmp(result, 'return[0]/backing/backing/node-name', 'base')
276         self.cancel_and_wait()
277         self.assert_no_active_block_jobs()
279 class TestRelativePaths(ImageCommitTestCase):
280     image_len = 1 * 1024 * 1024
281     test_len = 1 * 1024 * 256
283     dir1 = "dir1"
284     dir2 = "dir2/"
285     dir3 = "dir2/dir3/"
287     test_img = os.path.join(iotests.test_dir, dir3, 'test.img')
288     mid_img = "../mid.img"
289     backing_img = "../dir1/backing.img"
291     backing_img_abs = os.path.join(iotests.test_dir, dir1, 'backing.img')
292     mid_img_abs = os.path.join(iotests.test_dir, dir2, 'mid.img')
294     def setUp(self):
295         try:
296             os.mkdir(os.path.join(iotests.test_dir, self.dir1))
297             os.mkdir(os.path.join(iotests.test_dir, self.dir2))
298             os.mkdir(os.path.join(iotests.test_dir, self.dir3))
299         except OSError as exception:
300             if exception.errno != errno.EEXIST:
301                 raise
302         iotests.create_image(self.backing_img_abs, TestRelativePaths.image_len)
303         qemu_img('create', '-f', iotests.imgfmt,
304                  '-o', 'backing_file=%s' % self.backing_img_abs,
305                  '-F', 'raw', self.mid_img_abs)
306         qemu_img('create', '-f', iotests.imgfmt,
307                  '-o', 'backing_file=%s' % self.mid_img_abs,
308                  '-F', iotests.imgfmt, self.test_img)
309         qemu_img('rebase', '-u', '-b', self.backing_img,
310                  '-F', 'raw', self.mid_img_abs)
311         qemu_img('rebase', '-u', '-b', self.mid_img,
312                  '-F', iotests.imgfmt, self.test_img)
313         qemu_io('-f', 'raw', '-c', 'write -P 0xab 0 524288', self.backing_img_abs)
314         qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0xef 524288 524288', self.mid_img_abs)
315         self.vm = iotests.VM().add_drive(self.test_img)
316         self.vm.launch()
318     def tearDown(self):
319         self.vm.shutdown()
320         os.remove(self.test_img)
321         os.remove(self.mid_img_abs)
322         os.remove(self.backing_img_abs)
323         try:
324             os.rmdir(os.path.join(iotests.test_dir, self.dir1))
325             os.rmdir(os.path.join(iotests.test_dir, self.dir3))
326             os.rmdir(os.path.join(iotests.test_dir, self.dir2))
327         except OSError as exception:
328             if exception.errno != errno.EEXIST and exception.errno != errno.ENOTEMPTY:
329                 raise
331     def test_commit(self):
332         self.run_commit_test(self.mid_img, self.backing_img)
333         qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', self.backing_img_abs)
334         qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', self.backing_img_abs)
336     def test_device_not_found(self):
337         result = self.vm.qmp('block-commit', device='nonexistent', top='%s' % self.mid_img)
338         self.assert_qmp(result, 'error/class', 'DeviceNotFound')
340     def test_top_same_base(self):
341         self.assert_no_active_block_jobs()
342         result = self.vm.qmp('block-commit', device='drive0', top='%s' % self.mid_img, base='%s' % self.mid_img)
343         self.assert_qmp(result, 'error/class', 'GenericError')
344         self.assert_qmp(result, 'error/desc', "Can't find '%s' in the backing chain" % self.mid_img)
346     def test_top_invalid(self):
347         self.assert_no_active_block_jobs()
348         result = self.vm.qmp('block-commit', device='drive0', top='badfile', base='%s' % self.backing_img)
349         self.assert_qmp(result, 'error/class', 'GenericError')
350         self.assert_qmp(result, 'error/desc', 'Top image file badfile not found')
352     def test_base_invalid(self):
353         self.assert_no_active_block_jobs()
354         result = self.vm.qmp('block-commit', device='drive0', top='%s' % self.mid_img, base='badfile')
355         self.assert_qmp(result, 'error/class', 'GenericError')
356         self.assert_qmp(result, 'error/desc', "Can't find 'badfile' in the backing chain")
358     def test_top_is_active(self):
359         self.run_commit_test(self.test_img, self.backing_img)
360         qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', self.backing_img_abs)
361         qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', self.backing_img_abs)
363     def test_top_and_base_reversed(self):
364         self.assert_no_active_block_jobs()
365         result = self.vm.qmp('block-commit', device='drive0', top='%s' % self.backing_img, base='%s' % self.mid_img)
366         self.assert_qmp(result, 'error/class', 'GenericError')
367         self.assert_qmp(result, 'error/desc', "Can't find '%s' in the backing chain" % self.mid_img)
370 class TestSetSpeed(ImageCommitTestCase):
371     image_len = 80 * 1024 * 1024 # MB
373     def setUp(self):
374         qemu_img('create', backing_img, str(TestSetSpeed.image_len))
375         qemu_img('create', '-f', iotests.imgfmt,
376                  '-o', 'backing_file=%s' % backing_img, '-F', 'raw', mid_img)
377         qemu_img('create', '-f', iotests.imgfmt,
378                  '-o', 'backing_file=%s' % mid_img,
379                  '-F', iotests.imgfmt, test_img)
380         qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x1 0 512', test_img)
381         qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0xef 524288 524288', mid_img)
382         self.vm = iotests.VM().add_drive('blkdebug::' + test_img)
383         self.vm.launch()
385     def tearDown(self):
386         self.vm.shutdown()
387         os.remove(test_img)
388         os.remove(mid_img)
389         os.remove(backing_img)
391     def test_set_speed(self):
392         self.assert_no_active_block_jobs()
394         self.vm.pause_drive('drive0')
395         self.vm.cmd('block-commit', device='drive0', top=mid_img, speed=1024 * 1024)
397         # Ensure the speed we set was accepted
398         result = self.vm.qmp('query-block-jobs')
399         self.assert_qmp(result, 'return[0]/device', 'drive0')
400         self.assert_qmp(result, 'return[0]/speed', 1024 * 1024)
402         self.cancel_and_wait(resume=True)
404 class TestActiveZeroLengthImage(TestSingleDrive):
405     image_len = 0
407 class TestReopenOverlay(ImageCommitTestCase):
408     image_len = 1024 * 1024
409     img0 = os.path.join(iotests.test_dir, '0.img')
410     img1 = os.path.join(iotests.test_dir, '1.img')
411     img2 = os.path.join(iotests.test_dir, '2.img')
412     img3 = os.path.join(iotests.test_dir, '3.img')
414     def setUp(self):
415         iotests.create_image(self.img0, self.image_len)
416         qemu_img('create', '-f', iotests.imgfmt,
417                  '-o', 'backing_file=%s' % self.img0, '-F', 'raw', self.img1)
418         qemu_img('create', '-f', iotests.imgfmt,
419                  '-o', 'backing_file=%s' % self.img1,
420                  '-F', iotests.imgfmt, self.img2)
421         qemu_img('create', '-f', iotests.imgfmt,
422                  '-o', 'backing_file=%s' % self.img2,
423                  '-F', iotests.imgfmt, self.img3)
424         qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0xab 0 128K', self.img1)
425         self.vm = iotests.VM().add_drive(self.img3)
426         self.vm.launch()
428     def tearDown(self):
429         self.vm.shutdown()
430         os.remove(self.img0)
431         os.remove(self.img1)
432         os.remove(self.img2)
433         os.remove(self.img3)
435     # This tests what happens when the overlay image of the 'top' node
436     # needs to be reopened in read-write mode in order to update the
437     # backing image string.
438     def test_reopen_overlay(self):
439         self.run_commit_test(self.img1, self.img0)
441 class TestErrorHandling(iotests.QMPTestCase):
442     image_len = 2 * 1024 * 1024
444     def setUp(self):
445         iotests.create_image(backing_img, self.image_len)
446         qemu_img('create', '-f', iotests.imgfmt,
447                  '-o', 'backing_file=%s' % backing_img,
448                  '-F', 'raw', mid_img)
449         qemu_img('create', '-f', iotests.imgfmt,
450                  '-o', 'backing_file=%s' % mid_img,
451                  '-F', iotests.imgfmt, test_img)
453         qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x11 0 512k', mid_img)
454         qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x22 0 512k', test_img)
456         self.vm = iotests.VM()
457         self.vm.launch()
459         self.blkdebug_file = iotests.file_path("blkdebug.conf")
461     def tearDown(self):
462         self.vm.shutdown()
463         os.remove(test_img)
464         os.remove(mid_img)
465         os.remove(backing_img)
467     def blockdev_add(self, **kwargs):
468         self.vm.cmd('blockdev-add', **kwargs)
470     def add_block_nodes(self, base_debug=None, mid_debug=None, top_debug=None):
471         self.blockdev_add(node_name='base-file', driver='file',
472                           filename=backing_img)
473         self.blockdev_add(node_name='mid-file', driver='file',
474                           filename=mid_img)
475         self.blockdev_add(node_name='top-file', driver='file',
476                           filename=test_img)
478         if base_debug:
479             self.blockdev_add(node_name='base-dbg', driver='blkdebug',
480                               image='base-file', inject_error=base_debug)
481         if mid_debug:
482             self.blockdev_add(node_name='mid-dbg', driver='blkdebug',
483                               image='mid-file', inject_error=mid_debug)
484         if top_debug:
485             self.blockdev_add(node_name='top-dbg', driver='blkdebug',
486                               image='top-file', inject_error=top_debug)
488         self.blockdev_add(node_name='base-fmt', driver='raw',
489                           file=('base-dbg' if base_debug else 'base-file'))
490         self.blockdev_add(node_name='mid-fmt', driver=iotests.imgfmt,
491                           file=('mid-dbg' if mid_debug else 'mid-file'),
492                           backing='base-fmt')
493         self.blockdev_add(node_name='top-fmt', driver=iotests.imgfmt,
494                           file=('top-dbg' if top_debug else 'top-file'),
495                           backing='mid-fmt')
497     def run_job(self, expected_events, error_pauses_job=False):
498         match_device = {'data': {'device': 'job0'}}
499         events = [
500             ('BLOCK_JOB_COMPLETED', match_device),
501             ('BLOCK_JOB_CANCELLED', match_device),
502             ('BLOCK_JOB_ERROR', match_device),
503             ('BLOCK_JOB_READY', match_device),
504         ]
506         completed = False
507         log = []
508         while not completed:
509             ev = self.vm.events_wait(events, timeout=5.0)
510             if ev['event'] == 'BLOCK_JOB_COMPLETED':
511                 completed = True
512             elif ev['event'] == 'BLOCK_JOB_ERROR':
513                 if error_pauses_job:
514                     self.vm.cmd('block-job-resume', device='job0')
515             elif ev['event'] == 'BLOCK_JOB_READY':
516                 self.vm.cmd('block-job-complete', device='job0')
517             else:
518                 self.fail("Unexpected event: %s" % ev)
519             log.append(iotests.filter_qmp_event(ev))
521         self.maxDiff = None
522         self.assertEqual(expected_events, log)
524     def event_error(self, op, action):
525         return {
526             'event': 'BLOCK_JOB_ERROR',
527             'data': {'action': action, 'device': 'job0', 'operation': op},
528             'timestamp': {'microseconds': 'USECS', 'seconds': 'SECS'}
529         }
531     def event_ready(self):
532         return {
533             'event': 'BLOCK_JOB_READY',
534             'data': {'device': 'job0',
535                      'len': 524288,
536                      'offset': 524288,
537                      'speed': 0,
538                      'type': 'commit'},
539             'timestamp': {'microseconds': 'USECS', 'seconds': 'SECS'},
540         }
542     def event_completed(self, errmsg=None, active=True):
543         max_len = 524288 if active else self.image_len
544         data = {
545             'device': 'job0',
546             'len': max_len,
547             'offset': 0 if errmsg else max_len,
548             'speed': 0,
549             'type': 'commit'
550         }
551         if errmsg:
552             data['error'] = errmsg
554         return {
555             'event': 'BLOCK_JOB_COMPLETED',
556             'data': data,
557             'timestamp': {'microseconds': 'USECS', 'seconds': 'SECS'},
558         }
560     def blkdebug_event(self, event, is_raw=False):
561         if event:
562             return [{
563                 'event': event,
564                 'sector': 512 if is_raw else 1024,
565                 'once': True,
566             }]
567         return None
569     def prepare_and_start_job(self, on_error, active=True,
570                               top_event=None, mid_event=None, base_event=None):
572         top_debug = self.blkdebug_event(top_event)
573         mid_debug = self.blkdebug_event(mid_event)
574         base_debug = self.blkdebug_event(base_event, True)
576         self.add_block_nodes(top_debug=top_debug, mid_debug=mid_debug,
577                              base_debug=base_debug)
579         self.vm.cmd('block-commit', job_id='job0', device='top-fmt',
580                     top_node='top-fmt' if active else 'mid-fmt',
581                     base_node='mid-fmt' if active else 'base-fmt',
582                     on_error=on_error)
584     def testActiveReadErrorReport(self):
585         self.prepare_and_start_job('report', top_event='read_aio')
586         self.run_job([
587             self.event_error('read', 'report'),
588             self.event_completed('Input/output error')
589         ])
591         self.vm.shutdown()
592         self.assertFalse(iotests.compare_images(test_img, mid_img),
593                          'target image matches source after error')
595     def testActiveReadErrorStop(self):
596         self.prepare_and_start_job('stop', top_event='read_aio')
597         self.run_job([
598             self.event_error('read', 'stop'),
599             self.event_ready(),
600             self.event_completed()
601         ], error_pauses_job=True)
603         self.vm.shutdown()
604         self.assertTrue(iotests.compare_images(test_img, mid_img),
605                         'target image does not match source after commit')
607     def testActiveReadErrorIgnore(self):
608         self.prepare_and_start_job('ignore', top_event='read_aio')
609         self.run_job([
610             self.event_error('read', 'ignore'),
611             self.event_ready(),
612             self.event_completed()
613         ])
615         # For commit, 'ignore' actually means retry, so this will succeed
616         self.vm.shutdown()
617         self.assertTrue(iotests.compare_images(test_img, mid_img),
618                         'target image does not match source after commit')
620     def testActiveWriteErrorReport(self):
621         self.prepare_and_start_job('report', mid_event='write_aio')
622         self.run_job([
623             self.event_error('write', 'report'),
624             self.event_completed('Input/output error')
625         ])
627         self.vm.shutdown()
628         self.assertFalse(iotests.compare_images(test_img, mid_img),
629                          'target image matches source after error')
631     def testActiveWriteErrorStop(self):
632         self.prepare_and_start_job('stop', mid_event='write_aio')
633         self.run_job([
634             self.event_error('write', 'stop'),
635             self.event_ready(),
636             self.event_completed()
637         ], error_pauses_job=True)
639         self.vm.shutdown()
640         self.assertTrue(iotests.compare_images(test_img, mid_img),
641                         'target image does not match source after commit')
643     def testActiveWriteErrorIgnore(self):
644         self.prepare_and_start_job('ignore', mid_event='write_aio')
645         self.run_job([
646             self.event_error('write', 'ignore'),
647             self.event_ready(),
648             self.event_completed()
649         ])
651         # For commit, 'ignore' actually means retry, so this will succeed
652         self.vm.shutdown()
653         self.assertTrue(iotests.compare_images(test_img, mid_img),
654                         'target image does not match source after commit')
656     def testIntermediateReadErrorReport(self):
657         self.prepare_and_start_job('report', active=False, mid_event='read_aio')
658         self.run_job([
659             self.event_error('read', 'report'),
660             self.event_completed('Input/output error', active=False)
661         ])
663         self.vm.shutdown()
664         self.assertFalse(iotests.compare_images(mid_img, backing_img, fmt2='raw'),
665                          'target image matches source after error')
667     def testIntermediateReadErrorStop(self):
668         self.prepare_and_start_job('stop', active=False, mid_event='read_aio')
669         self.run_job([
670             self.event_error('read', 'stop'),
671             self.event_completed(active=False)
672         ], error_pauses_job=True)
674         self.vm.shutdown()
675         self.assertTrue(iotests.compare_images(mid_img, backing_img, fmt2='raw'),
676                         'target image does not match source after commit')
678     def testIntermediateReadErrorIgnore(self):
679         self.prepare_and_start_job('ignore', active=False, mid_event='read_aio')
680         self.run_job([
681             self.event_error('read', 'ignore'),
682             self.event_completed(active=False)
683         ])
685         # For commit, 'ignore' actually means retry, so this will succeed
686         self.vm.shutdown()
687         self.assertTrue(iotests.compare_images(mid_img, backing_img, fmt2='raw'),
688                         'target image does not match source after commit')
690     def testIntermediateWriteErrorReport(self):
691         self.prepare_and_start_job('report', active=False, base_event='write_aio')
692         self.run_job([
693             self.event_error('write', 'report'),
694             self.event_completed('Input/output error', active=False)
695         ])
697         self.vm.shutdown()
698         self.assertFalse(iotests.compare_images(mid_img, backing_img, fmt2='raw'),
699                          'target image matches source after error')
701     def testIntermediateWriteErrorStop(self):
702         self.prepare_and_start_job('stop', active=False, base_event='write_aio')
703         self.run_job([
704             self.event_error('write', 'stop'),
705             self.event_completed(active=False)
706         ], error_pauses_job=True)
708         self.vm.shutdown()
709         self.assertTrue(iotests.compare_images(mid_img, backing_img, fmt2='raw'),
710                         'target image does not match source after commit')
712     def testIntermediateWriteErrorIgnore(self):
713         self.prepare_and_start_job('ignore', active=False, base_event='write_aio')
714         self.run_job([
715             self.event_error('write', 'ignore'),
716             self.event_completed(active=False)
717         ])
719         # For commit, 'ignore' actually means retry, so this will succeed
720         self.vm.shutdown()
721         self.assertTrue(iotests.compare_images(mid_img, backing_img, fmt2='raw'),
722                         'target image does not match source after commit')
724 class TestCommitWithFilters(iotests.QMPTestCase):
725     img0 = os.path.join(iotests.test_dir, '0.img')
726     img1 = os.path.join(iotests.test_dir, '1.img')
727     img2 = os.path.join(iotests.test_dir, '2.img')
728     img3 = os.path.join(iotests.test_dir, '3.img')
730     def do_test_io(self, read_or_write):
731         for index, pattern_file in enumerate(self.pattern_files):
732             qemu_io('-f', iotests.imgfmt,
733                     '-c',
734                     f'{read_or_write} -P {index + 1} {index}M 1M',
735                     pattern_file)
737     @iotests.skip_if_unsupported(['throttle'])
738     def setUp(self):
739         qemu_img('create', '-f', iotests.imgfmt, self.img0, '64M')
740         qemu_img('create', '-f', iotests.imgfmt, self.img1, '64M')
741         qemu_img('create', '-f', iotests.imgfmt, self.img2, '64M')
742         qemu_img('create', '-f', iotests.imgfmt, self.img3, '64M')
744         # Distributions of the patterns in the files; this is checked
745         # by tearDown() and should be changed by the test cases as is
746         # necessary
747         self.pattern_files = [self.img0, self.img1, self.img2, self.img3]
749         self.do_test_io('write')
751         self.vm = iotests.VM().add_device('virtio-scsi,id=vio-scsi')
752         self.vm.launch()
754         self.vm.cmd('object-add', qom_type='throttle-group', id='tg')
756         self.vm.cmd('blockdev-add', {
757                 'node-name': 'top-filter',
758                 'driver': 'throttle',
759                 'throttle-group': 'tg',
760                 'file': {
761                     'node-name': 'cow-3',
762                     'driver': iotests.imgfmt,
763                     'file': {
764                         'driver': 'file',
765                         'filename': self.img3
766                     },
767                     'backing': {
768                         'node-name': 'cow-2',
769                         'driver': iotests.imgfmt,
770                         'file': {
771                             'driver': 'file',
772                             'filename': self.img2
773                         },
774                         'backing': {
775                             'node-name': 'cow-1',
776                             'driver': iotests.imgfmt,
777                             'file': {
778                                 'driver': 'file',
779                                 'filename': self.img1
780                             },
781                             'backing': {
782                                 'node-name': 'bottom-filter',
783                                 'driver': 'throttle',
784                                 'throttle-group': 'tg',
785                                 'file': {
786                                     'node-name': 'cow-0',
787                                     'driver': iotests.imgfmt,
788                                     'file': {
789                                         'driver': 'file',
790                                         'filename': self.img0
791                                     }
792                                 }
793                             }
794                         }
795                     }
796                 }
797             })
799     def tearDown(self):
800         self.vm.shutdown()
801         self.do_test_io('read')
803         os.remove(self.img3)
804         os.remove(self.img2)
805         os.remove(self.img1)
806         os.remove(self.img0)
808     # Filters make for funny filenames, so we cannot just use
809     # self.imgX to get them
810     def get_filename(self, node):
811         return self.vm.node_info(node)['image']['filename']
813     def test_filterless_commit(self):
814         self.vm.cmd('block-commit',
815                     job_id='commit',
816                     device='top-filter',
817                     top_node='cow-2',
818                     base_node='cow-1',
819                     backing_file=self.img1)
820         self.wait_until_completed(drive='commit')
822         self.assertIsNotNone(self.vm.node_info('cow-3'))
823         self.assertIsNone(self.vm.node_info('cow-2'))
824         self.assertIsNotNone(self.vm.node_info('cow-1'))
826         # 2 has been committed into 1
827         self.pattern_files[2] = self.img1
829     def test_commit_through_filter(self):
830         self.vm.cmd('block-commit',
831                     job_id='commit',
832                     device='top-filter',
833                     top_node='cow-1',
834                     base_node='cow-0',
835                     backing_file=self.img0)
836         self.wait_until_completed(drive='commit')
838         self.assertIsNotNone(self.vm.node_info('cow-2'))
839         self.assertIsNone(self.vm.node_info('cow-1'))
840         self.assertIsNone(self.vm.node_info('bottom-filter'))
841         self.assertIsNotNone(self.vm.node_info('cow-0'))
843         # 1 has been committed into 0
844         self.pattern_files[1] = self.img0
846     def test_filtered_active_commit_with_filter(self):
847         # Add a device, so the commit job finds a parent it can change
848         # to point to the base node (so we can test that top-filter is
849         # dropped from the graph)
850         self.vm.cmd('device_add', id='drv0', driver='scsi-hd',
851                     bus='vio-scsi.0', drive='top-filter')
853         # Try to release our reference to top-filter; that should not
854         # work because drv0 uses it
855         result = self.vm.qmp('blockdev-del', node_name='top-filter')
856         self.assert_qmp(result, 'error/class', 'GenericError')
857         self.assert_qmp(result, 'error/desc', 'Node top-filter is in use')
859         self.vm.cmd('block-commit',
860                     job_id='commit',
861                     device='top-filter',
862                     base_node='cow-2')
863         self.complete_and_wait(drive='commit')
865         # Try to release our reference to top-filter again
866         self.vm.cmd('blockdev-del', node_name='top-filter')
868         self.assertIsNone(self.vm.node_info('top-filter'))
869         self.assertIsNone(self.vm.node_info('cow-3'))
870         self.assertIsNotNone(self.vm.node_info('cow-2'))
872         # Check that drv0 is now connected to cow-2
873         blockdevs = self.vm.qmp('query-block')['return']
874         drv0 = next(dev for dev in blockdevs if dev['qdev'] == 'drv0')
875         self.assertEqual(drv0['inserted']['node-name'], 'cow-2')
877         # 3 has been committed into 2
878         self.pattern_files[3] = self.img2
880     def test_filtered_active_commit_without_filter(self):
881         self.vm.cmd('block-commit',
882                     job_id='commit',
883                     device='top-filter',
884                     top_node='cow-3',
885                     base_node='cow-2')
886         self.complete_and_wait(drive='commit')
888         self.assertIsNotNone(self.vm.node_info('top-filter'))
889         self.assertIsNone(self.vm.node_info('cow-3'))
890         self.assertIsNotNone(self.vm.node_info('cow-2'))
892         # 3 has been committed into 2
893         self.pattern_files[3] = self.img2
895 class TestCommitWithOverriddenBacking(iotests.QMPTestCase):
896     img_base_a = os.path.join(iotests.test_dir, 'base_a.img')
897     img_base_b = os.path.join(iotests.test_dir, 'base_b.img')
898     img_top = os.path.join(iotests.test_dir, 'top.img')
900     def setUp(self):
901         qemu_img('create', '-f', iotests.imgfmt, self.img_base_a, '1M')
902         qemu_img('create', '-f', iotests.imgfmt, self.img_base_b, '1M')
903         qemu_img('create', '-f', iotests.imgfmt, '-b', self.img_base_a,
904                  '-F', iotests.imgfmt, self.img_top)
906         self.vm = iotests.VM()
907         self.vm.launch()
909         # Use base_b instead of base_a as the backing of top
910         self.vm.cmd('blockdev-add', {
911                         'node-name': 'top',
912                         'driver': iotests.imgfmt,
913                         'file': {
914                             'driver': 'file',
915                             'filename': self.img_top
916                         },
917                         'backing': {
918                             'node-name': 'base',
919                             'driver': iotests.imgfmt,
920                             'file': {
921                                 'driver': 'file',
922                                 'filename': self.img_base_b
923                             }
924                         }
925                     })
927     def tearDown(self):
928         self.vm.shutdown()
929         os.remove(self.img_top)
930         os.remove(self.img_base_a)
931         os.remove(self.img_base_b)
933     def test_commit_to_a(self):
934         # Try committing to base_a (which should fail, as top's
935         # backing image is base_b instead)
936         result = self.vm.qmp('block-commit',
937                              job_id='commit',
938                              device='top',
939                              base=self.img_base_a)
940         self.assert_qmp(result, 'error/class', 'GenericError')
942     def test_commit_to_b(self):
943         # Try committing to base_b (which should work, since that is
944         # actually top's backing image)
945         self.vm.cmd('block-commit',
946                     job_id='commit',
947                     device='top',
948                     base=self.img_base_b)
950         self.vm.event_wait('BLOCK_JOB_READY')
951         self.vm.qmp('block-job-complete', device='commit')
952         self.vm.event_wait('BLOCK_JOB_COMPLETED')
954 if __name__ == '__main__':
955     iotests.main(supported_fmts=['qcow2', 'qed'],
956                  supported_protocols=['file'])