Merge remote-tracking branch 'remotes/nvme/tags/nvme-fixes-20210407-pull-request...
[qemu/ar7.git] / tests / qemu-iotests / 040
blobba7cb34ce8cf95df6fa63f9eb8f0bdc4bbc504af
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             result = self.vm.qmp('block-commit', device='drive0', top_node=top, base_node=base)
65         else:
66             result = self.vm.qmp('block-commit', device='drive0', top=top, base=base)
67         self.assert_qmp(result, 'return', {})
68         self.wait_for_complete(need_ready)
70     def run_default_commit_test(self):
71         self.assert_no_active_block_jobs()
72         result = self.vm.qmp('block-commit', device='drive0')
73         self.assert_qmp(result, 'return', {})
74         self.wait_for_complete()
76 class TestSingleDrive(ImageCommitTestCase):
77     # Need some space after the copied data so that throttling is effective in
78     # tests that use it rather than just completing the job immediately
79     image_len = 2 * 1024 * 1024
80     test_len = 1 * 1024 * 256
82     def setUp(self):
83         iotests.create_image(backing_img, self.image_len)
84         qemu_img('create', '-f', iotests.imgfmt,
85                  '-o', 'backing_file=%s' % backing_img, '-F', 'raw', mid_img)
86         qemu_img('create', '-f', iotests.imgfmt,
87                  '-o', 'backing_file=%s' % mid_img,
88                  '-F', iotests.imgfmt, test_img)
89         qemu_io('-f', 'raw', '-c', 'write -P 0xab 0 524288', backing_img)
90         qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0xef 524288 524288', 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()
95         self.has_quit = False
97     def tearDown(self):
98         self.vm.shutdown(has_quit=self.has_quit)
99         os.remove(test_img)
100         os.remove(mid_img)
101         os.remove(backing_img)
103     def test_commit(self):
104         self.run_commit_test(mid_img, backing_img)
105         self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', backing_img).find("verification failed"))
106         self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', backing_img).find("verification failed"))
108     def test_commit_node(self):
109         self.run_commit_test("mid", "base", node_names=True)
110         self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', backing_img).find("verification failed"))
111         self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', backing_img).find("verification failed"))
113     @iotests.skip_if_unsupported(['throttle'])
114     def test_commit_with_filter_and_quit(self):
115         result = self.vm.qmp('object-add', qom_type='throttle-group', id='tg')
116         self.assert_qmp(result, 'return', {})
118         # Add a filter outside of the backing chain
119         result = self.vm.qmp('blockdev-add', driver='throttle', node_name='filter', throttle_group='tg', file='mid')
120         self.assert_qmp(result, 'return', {})
122         result = self.vm.qmp('block-commit', device='drive0')
123         self.assert_qmp(result, 'return', {})
125         # Quit immediately, thus forcing a simultaneous cancel of the
126         # block job and a bdrv_drain_all()
127         result = self.vm.qmp('quit')
128         self.assert_qmp(result, 'return', {})
130         self.has_quit = True
132     # Same as above, but this time we add the filter after starting the job
133     @iotests.skip_if_unsupported(['throttle'])
134     def test_commit_plus_filter_and_quit(self):
135         result = self.vm.qmp('object-add', qom_type='throttle-group', id='tg')
136         self.assert_qmp(result, 'return', {})
138         result = self.vm.qmp('block-commit', device='drive0')
139         self.assert_qmp(result, 'return', {})
141         # Add a filter outside of the backing chain
142         result = self.vm.qmp('blockdev-add', driver='throttle', node_name='filter', throttle_group='tg', file='mid')
143         self.assert_qmp(result, 'return', {})
145         # Quit immediately, thus forcing a simultaneous cancel of the
146         # block job and a bdrv_drain_all()
147         result = self.vm.qmp('quit')
148         self.assert_qmp(result, 'return', {})
150         self.has_quit = True
152     def test_device_not_found(self):
153         result = self.vm.qmp('block-commit', device='nonexistent', top='%s' % mid_img)
154         self.assert_qmp(result, 'error/class', 'DeviceNotFound')
156     def test_top_same_base(self):
157         self.assert_no_active_block_jobs()
158         result = self.vm.qmp('block-commit', device='drive0', top='%s' % backing_img, base='%s' % backing_img)
159         self.assert_qmp(result, 'error/class', 'GenericError')
160         self.assert_qmp(result, 'error/desc', "Can't find '%s' in the backing chain" % backing_img)
162     def test_top_invalid(self):
163         self.assert_no_active_block_jobs()
164         result = self.vm.qmp('block-commit', device='drive0', top='badfile', base='%s' % backing_img)
165         self.assert_qmp(result, 'error/class', 'GenericError')
166         self.assert_qmp(result, 'error/desc', 'Top image file badfile not found')
168     def test_base_invalid(self):
169         self.assert_no_active_block_jobs()
170         result = self.vm.qmp('block-commit', device='drive0', top='%s' % mid_img, base='badfile')
171         self.assert_qmp(result, 'error/class', 'GenericError')
172         self.assert_qmp(result, 'error/desc', "Can't find 'badfile' in the backing chain")
174     def test_top_node_invalid(self):
175         self.assert_no_active_block_jobs()
176         result = self.vm.qmp('block-commit', device='drive0', top_node='badfile', base_node='base')
177         self.assert_qmp(result, 'error/class', 'GenericError')
178         self.assert_qmp(result, 'error/desc', "Cannot find device='' nor node-name='badfile'")
180     def test_base_node_invalid(self):
181         self.assert_no_active_block_jobs()
182         result = self.vm.qmp('block-commit', device='drive0', top_node='mid', base_node='badfile')
183         self.assert_qmp(result, 'error/class', 'GenericError')
184         self.assert_qmp(result, 'error/desc', "Cannot find device='' nor node-name='badfile'")
186     def test_top_path_and_node(self):
187         self.assert_no_active_block_jobs()
188         result = self.vm.qmp('block-commit', device='drive0', top_node='mid', base_node='base', top='%s' % mid_img)
189         self.assert_qmp(result, 'error/class', 'GenericError')
190         self.assert_qmp(result, 'error/desc', "'top-node' and 'top' are mutually exclusive")
192     def test_base_path_and_node(self):
193         self.assert_no_active_block_jobs()
194         result = self.vm.qmp('block-commit', device='drive0', top_node='mid', base_node='base', base='%s' % backing_img)
195         self.assert_qmp(result, 'error/class', 'GenericError')
196         self.assert_qmp(result, 'error/desc', "'base-node' and 'base' are mutually exclusive")
198     def test_top_is_active(self):
199         self.run_commit_test(test_img, backing_img, need_ready=True)
200         self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', backing_img).find("verification failed"))
201         self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', backing_img).find("verification failed"))
203     def test_top_is_default_active(self):
204         self.run_default_commit_test()
205         self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', backing_img).find("verification failed"))
206         self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', backing_img).find("verification failed"))
208     def test_top_and_base_reversed(self):
209         self.assert_no_active_block_jobs()
210         result = self.vm.qmp('block-commit', device='drive0', top='%s' % backing_img, base='%s' % mid_img)
211         self.assert_qmp(result, 'error/class', 'GenericError')
212         self.assert_qmp(result, 'error/desc', "Can't find '%s' in the backing chain" % mid_img)
214     def test_top_and_base_node_reversed(self):
215         self.assert_no_active_block_jobs()
216         result = self.vm.qmp('block-commit', device='drive0', top_node='base', base_node='top')
217         self.assert_qmp(result, 'error/class', 'GenericError')
218         self.assert_qmp(result, 'error/desc', "'top' is not in this backing file chain")
220     def test_top_node_in_wrong_chain(self):
221         self.assert_no_active_block_jobs()
223         result = self.vm.qmp('blockdev-add', driver='null-co', node_name='null')
224         self.assert_qmp(result, 'return', {})
226         result = self.vm.qmp('block-commit', device='drive0', top_node='null', base_node='base')
227         self.assert_qmp(result, 'error/class', 'GenericError')
228         self.assert_qmp(result, 'error/desc', "'null' is not in this backing file chain")
230     # When the job is running on a BB that is automatically deleted on hot
231     # unplug, the job is cancelled when the device disappears
232     def test_hot_unplug(self):
233         if self.image_len == 0:
234             return
236         self.assert_no_active_block_jobs()
237         result = self.vm.qmp('block-commit', device='drive0', top=mid_img,
238                              base=backing_img, speed=(self.image_len // 4))
239         self.assert_qmp(result, 'return', {})
240         result = self.vm.qmp('device_del', id='scsi0')
241         self.assert_qmp(result, 'return', {})
243         cancelled = False
244         deleted = False
245         while not cancelled or not deleted:
246             for event in self.vm.get_qmp_events(wait=True):
247                 if event['event'] == 'DEVICE_DELETED':
248                     self.assert_qmp(event, 'data/device', 'scsi0')
249                     deleted = True
250                 elif event['event'] == 'BLOCK_JOB_CANCELLED':
251                     self.assert_qmp(event, 'data/device', 'drive0')
252                     cancelled = True
253                 elif event['event'] == 'JOB_STATUS_CHANGE':
254                     self.assert_qmp(event, 'data/id', 'drive0')
255                 else:
256                     self.fail("Unexpected event %s" % (event['event']))
258         self.assert_no_active_block_jobs()
260     # Tests that the insertion of the commit_top filter node doesn't make a
261     # difference to query-blockstat
262     def test_implicit_node(self):
263         if self.image_len == 0:
264             return
266         self.assert_no_active_block_jobs()
267         result = self.vm.qmp('block-commit', device='drive0', top=mid_img,
268                              base=backing_img, speed=(self.image_len // 4))
269         self.assert_qmp(result, 'return', {})
271         result = self.vm.qmp('query-block')
272         self.assert_qmp(result, 'return[0]/inserted/file', test_img)
273         self.assert_qmp(result, 'return[0]/inserted/drv', iotests.imgfmt)
274         self.assert_qmp(result, 'return[0]/inserted/backing_file', mid_img)
275         self.assert_qmp(result, 'return[0]/inserted/backing_file_depth', 2)
276         self.assert_qmp(result, 'return[0]/inserted/image/filename', test_img)
277         self.assert_qmp(result, 'return[0]/inserted/image/backing-image/filename', mid_img)
278         self.assert_qmp(result, 'return[0]/inserted/image/backing-image/backing-image/filename', backing_img)
280         result = self.vm.qmp('query-blockstats')
281         self.assert_qmp(result, 'return[0]/node-name', 'top')
282         self.assert_qmp(result, 'return[0]/backing/node-name', 'mid')
283         self.assert_qmp(result, 'return[0]/backing/backing/node-name', 'base')
285         self.cancel_and_wait()
286         self.assert_no_active_block_jobs()
288 class TestRelativePaths(ImageCommitTestCase):
289     image_len = 1 * 1024 * 1024
290     test_len = 1 * 1024 * 256
292     dir1 = "dir1"
293     dir2 = "dir2/"
294     dir3 = "dir2/dir3/"
296     test_img = os.path.join(iotests.test_dir, dir3, 'test.img')
297     mid_img = "../mid.img"
298     backing_img = "../dir1/backing.img"
300     backing_img_abs = os.path.join(iotests.test_dir, dir1, 'backing.img')
301     mid_img_abs = os.path.join(iotests.test_dir, dir2, 'mid.img')
303     def setUp(self):
304         try:
305             os.mkdir(os.path.join(iotests.test_dir, self.dir1))
306             os.mkdir(os.path.join(iotests.test_dir, self.dir2))
307             os.mkdir(os.path.join(iotests.test_dir, self.dir3))
308         except OSError as exception:
309             if exception.errno != errno.EEXIST:
310                 raise
311         iotests.create_image(self.backing_img_abs, TestRelativePaths.image_len)
312         qemu_img('create', '-f', iotests.imgfmt,
313                  '-o', 'backing_file=%s' % self.backing_img_abs,
314                  '-F', 'raw', self.mid_img_abs)
315         qemu_img('create', '-f', iotests.imgfmt,
316                  '-o', 'backing_file=%s' % self.mid_img_abs,
317                  '-F', iotests.imgfmt, self.test_img)
318         qemu_img('rebase', '-u', '-b', self.backing_img,
319                  '-F', 'raw', self.mid_img_abs)
320         qemu_img('rebase', '-u', '-b', self.mid_img,
321                  '-F', iotests.imgfmt, self.test_img)
322         qemu_io('-f', 'raw', '-c', 'write -P 0xab 0 524288', self.backing_img_abs)
323         qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0xef 524288 524288', self.mid_img_abs)
324         self.vm = iotests.VM().add_drive(self.test_img)
325         self.vm.launch()
327     def tearDown(self):
328         self.vm.shutdown()
329         os.remove(self.test_img)
330         os.remove(self.mid_img_abs)
331         os.remove(self.backing_img_abs)
332         try:
333             os.rmdir(os.path.join(iotests.test_dir, self.dir1))
334             os.rmdir(os.path.join(iotests.test_dir, self.dir3))
335             os.rmdir(os.path.join(iotests.test_dir, self.dir2))
336         except OSError as exception:
337             if exception.errno != errno.EEXIST and exception.errno != errno.ENOTEMPTY:
338                 raise
340     def test_commit(self):
341         self.run_commit_test(self.mid_img, self.backing_img)
342         self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', self.backing_img_abs).find("verification failed"))
343         self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', self.backing_img_abs).find("verification failed"))
345     def test_device_not_found(self):
346         result = self.vm.qmp('block-commit', device='nonexistent', top='%s' % self.mid_img)
347         self.assert_qmp(result, 'error/class', 'DeviceNotFound')
349     def test_top_same_base(self):
350         self.assert_no_active_block_jobs()
351         result = self.vm.qmp('block-commit', device='drive0', top='%s' % self.mid_img, base='%s' % self.mid_img)
352         self.assert_qmp(result, 'error/class', 'GenericError')
353         self.assert_qmp(result, 'error/desc', "Can't find '%s' in the backing chain" % self.mid_img)
355     def test_top_invalid(self):
356         self.assert_no_active_block_jobs()
357         result = self.vm.qmp('block-commit', device='drive0', top='badfile', base='%s' % self.backing_img)
358         self.assert_qmp(result, 'error/class', 'GenericError')
359         self.assert_qmp(result, 'error/desc', 'Top image file badfile not found')
361     def test_base_invalid(self):
362         self.assert_no_active_block_jobs()
363         result = self.vm.qmp('block-commit', device='drive0', top='%s' % self.mid_img, base='badfile')
364         self.assert_qmp(result, 'error/class', 'GenericError')
365         self.assert_qmp(result, 'error/desc', "Can't find 'badfile' in the backing chain")
367     def test_top_is_active(self):
368         self.run_commit_test(self.test_img, self.backing_img)
369         self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', self.backing_img_abs).find("verification failed"))
370         self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', self.backing_img_abs).find("verification failed"))
372     def test_top_and_base_reversed(self):
373         self.assert_no_active_block_jobs()
374         result = self.vm.qmp('block-commit', device='drive0', top='%s' % self.backing_img, base='%s' % self.mid_img)
375         self.assert_qmp(result, 'error/class', 'GenericError')
376         self.assert_qmp(result, 'error/desc', "Can't find '%s' in the backing chain" % self.mid_img)
379 class TestSetSpeed(ImageCommitTestCase):
380     image_len = 80 * 1024 * 1024 # MB
382     def setUp(self):
383         qemu_img('create', backing_img, str(TestSetSpeed.image_len))
384         qemu_img('create', '-f', iotests.imgfmt,
385                  '-o', 'backing_file=%s' % backing_img, '-F', 'raw', mid_img)
386         qemu_img('create', '-f', iotests.imgfmt,
387                  '-o', 'backing_file=%s' % mid_img,
388                  '-F', iotests.imgfmt, test_img)
389         qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x1 0 512', test_img)
390         qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0xef 524288 524288', mid_img)
391         self.vm = iotests.VM().add_drive('blkdebug::' + test_img)
392         self.vm.launch()
394     def tearDown(self):
395         self.vm.shutdown()
396         os.remove(test_img)
397         os.remove(mid_img)
398         os.remove(backing_img)
400     def test_set_speed(self):
401         self.assert_no_active_block_jobs()
403         self.vm.pause_drive('drive0')
404         result = self.vm.qmp('block-commit', device='drive0', top=mid_img, speed=1024 * 1024)
405         self.assert_qmp(result, 'return', {})
407         # Ensure the speed we set was accepted
408         result = self.vm.qmp('query-block-jobs')
409         self.assert_qmp(result, 'return[0]/device', 'drive0')
410         self.assert_qmp(result, 'return[0]/speed', 1024 * 1024)
412         self.cancel_and_wait(resume=True)
414 class TestActiveZeroLengthImage(TestSingleDrive):
415     image_len = 0
417 class TestReopenOverlay(ImageCommitTestCase):
418     image_len = 1024 * 1024
419     img0 = os.path.join(iotests.test_dir, '0.img')
420     img1 = os.path.join(iotests.test_dir, '1.img')
421     img2 = os.path.join(iotests.test_dir, '2.img')
422     img3 = os.path.join(iotests.test_dir, '3.img')
424     def setUp(self):
425         iotests.create_image(self.img0, self.image_len)
426         qemu_img('create', '-f', iotests.imgfmt,
427                  '-o', 'backing_file=%s' % self.img0, '-F', 'raw', self.img1)
428         qemu_img('create', '-f', iotests.imgfmt,
429                  '-o', 'backing_file=%s' % self.img1,
430                  '-F', iotests.imgfmt, self.img2)
431         qemu_img('create', '-f', iotests.imgfmt,
432                  '-o', 'backing_file=%s' % self.img2,
433                  '-F', iotests.imgfmt, self.img3)
434         qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0xab 0 128K', self.img1)
435         self.vm = iotests.VM().add_drive(self.img3)
436         self.vm.launch()
438     def tearDown(self):
439         self.vm.shutdown()
440         os.remove(self.img0)
441         os.remove(self.img1)
442         os.remove(self.img2)
443         os.remove(self.img3)
445     # This tests what happens when the overlay image of the 'top' node
446     # needs to be reopened in read-write mode in order to update the
447     # backing image string.
448     def test_reopen_overlay(self):
449         self.run_commit_test(self.img1, self.img0)
451 class TestErrorHandling(iotests.QMPTestCase):
452     image_len = 2 * 1024 * 1024
454     def setUp(self):
455         iotests.create_image(backing_img, self.image_len)
456         qemu_img('create', '-f', iotests.imgfmt,
457                  '-o', 'backing_file=%s' % backing_img,
458                  '-F', 'raw', mid_img)
459         qemu_img('create', '-f', iotests.imgfmt,
460                  '-o', 'backing_file=%s' % mid_img,
461                  '-F', iotests.imgfmt, test_img)
463         qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x11 0 512k', mid_img)
464         qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x22 0 512k', test_img)
466         self.vm = iotests.VM()
467         self.vm.launch()
469         self.blkdebug_file = iotests.file_path("blkdebug.conf")
471     def tearDown(self):
472         self.vm.shutdown()
473         os.remove(test_img)
474         os.remove(mid_img)
475         os.remove(backing_img)
477     def blockdev_add(self, **kwargs):
478         result = self.vm.qmp('blockdev-add', **kwargs)
479         self.assert_qmp(result, 'return', {})
481     def add_block_nodes(self, base_debug=None, mid_debug=None, top_debug=None):
482         self.blockdev_add(node_name='base-file', driver='file',
483                           filename=backing_img)
484         self.blockdev_add(node_name='mid-file', driver='file',
485                           filename=mid_img)
486         self.blockdev_add(node_name='top-file', driver='file',
487                           filename=test_img)
489         if base_debug:
490             self.blockdev_add(node_name='base-dbg', driver='blkdebug',
491                               image='base-file', inject_error=base_debug)
492         if mid_debug:
493             self.blockdev_add(node_name='mid-dbg', driver='blkdebug',
494                               image='mid-file', inject_error=mid_debug)
495         if top_debug:
496             self.blockdev_add(node_name='top-dbg', driver='blkdebug',
497                               image='top-file', inject_error=top_debug)
499         self.blockdev_add(node_name='base-fmt', driver='raw',
500                           file=('base-dbg' if base_debug else 'base-file'))
501         self.blockdev_add(node_name='mid-fmt', driver=iotests.imgfmt,
502                           file=('mid-dbg' if mid_debug else 'mid-file'),
503                           backing='base-fmt')
504         self.blockdev_add(node_name='top-fmt', driver=iotests.imgfmt,
505                           file=('top-dbg' if top_debug else 'top-file'),
506                           backing='mid-fmt')
508     def run_job(self, expected_events, error_pauses_job=False):
509         match_device = {'data': {'device': 'job0'}}
510         events = [
511             ('BLOCK_JOB_COMPLETED', match_device),
512             ('BLOCK_JOB_CANCELLED', match_device),
513             ('BLOCK_JOB_ERROR', match_device),
514             ('BLOCK_JOB_READY', match_device),
515         ]
517         completed = False
518         log = []
519         while not completed:
520             ev = self.vm.events_wait(events, timeout=5.0)
521             if ev['event'] == 'BLOCK_JOB_COMPLETED':
522                 completed = True
523             elif ev['event'] == 'BLOCK_JOB_ERROR':
524                 if error_pauses_job:
525                     result = self.vm.qmp('block-job-resume', device='job0')
526                     self.assert_qmp(result, 'return', {})
527             elif ev['event'] == 'BLOCK_JOB_READY':
528                 result = self.vm.qmp('block-job-complete', device='job0')
529                 self.assert_qmp(result, 'return', {})
530             else:
531                 self.fail("Unexpected event: %s" % ev)
532             log.append(iotests.filter_qmp_event(ev))
534         self.maxDiff = None
535         self.assertEqual(expected_events, log)
537     def event_error(self, op, action):
538         return {
539             'event': 'BLOCK_JOB_ERROR',
540             'data': {'action': action, 'device': 'job0', 'operation': op},
541             'timestamp': {'microseconds': 'USECS', 'seconds': 'SECS'}
542         }
544     def event_ready(self):
545         return {
546             'event': 'BLOCK_JOB_READY',
547             'data': {'device': 'job0',
548                      'len': 524288,
549                      'offset': 524288,
550                      'speed': 0,
551                      'type': 'commit'},
552             'timestamp': {'microseconds': 'USECS', 'seconds': 'SECS'},
553         }
555     def event_completed(self, errmsg=None, active=True):
556         max_len = 524288 if active else self.image_len
557         data = {
558             'device': 'job0',
559             'len': max_len,
560             'offset': 0 if errmsg else max_len,
561             'speed': 0,
562             'type': 'commit'
563         }
564         if errmsg:
565             data['error'] = errmsg
567         return {
568             'event': 'BLOCK_JOB_COMPLETED',
569             'data': data,
570             'timestamp': {'microseconds': 'USECS', 'seconds': 'SECS'},
571         }
573     def blkdebug_event(self, event, is_raw=False):
574         if event:
575             return [{
576                 'event': event,
577                 'sector': 512 if is_raw else 1024,
578                 'once': True,
579             }]
580         return None
582     def prepare_and_start_job(self, on_error, active=True,
583                               top_event=None, mid_event=None, base_event=None):
585         top_debug = self.blkdebug_event(top_event)
586         mid_debug = self.blkdebug_event(mid_event)
587         base_debug = self.blkdebug_event(base_event, True)
589         self.add_block_nodes(top_debug=top_debug, mid_debug=mid_debug,
590                              base_debug=base_debug)
592         result = self.vm.qmp('block-commit', job_id='job0', device='top-fmt',
593                              top_node='top-fmt' if active else 'mid-fmt',
594                              base_node='mid-fmt' if active else 'base-fmt',
595                              on_error=on_error)
596         self.assert_qmp(result, 'return', {})
598     def testActiveReadErrorReport(self):
599         self.prepare_and_start_job('report', top_event='read_aio')
600         self.run_job([
601             self.event_error('read', 'report'),
602             self.event_completed('Input/output error')
603         ])
605         self.vm.shutdown()
606         self.assertFalse(iotests.compare_images(test_img, mid_img),
607                          'target image matches source after error')
609     def testActiveReadErrorStop(self):
610         self.prepare_and_start_job('stop', top_event='read_aio')
611         self.run_job([
612             self.event_error('read', 'stop'),
613             self.event_ready(),
614             self.event_completed()
615         ], error_pauses_job=True)
617         self.vm.shutdown()
618         self.assertTrue(iotests.compare_images(test_img, mid_img),
619                         'target image does not match source after commit')
621     def testActiveReadErrorIgnore(self):
622         self.prepare_and_start_job('ignore', top_event='read_aio')
623         self.run_job([
624             self.event_error('read', 'ignore'),
625             self.event_ready(),
626             self.event_completed()
627         ])
629         # For commit, 'ignore' actually means retry, so this will succeed
630         self.vm.shutdown()
631         self.assertTrue(iotests.compare_images(test_img, mid_img),
632                         'target image does not match source after commit')
634     def testActiveWriteErrorReport(self):
635         self.prepare_and_start_job('report', mid_event='write_aio')
636         self.run_job([
637             self.event_error('write', 'report'),
638             self.event_completed('Input/output error')
639         ])
641         self.vm.shutdown()
642         self.assertFalse(iotests.compare_images(test_img, mid_img),
643                          'target image matches source after error')
645     def testActiveWriteErrorStop(self):
646         self.prepare_and_start_job('stop', mid_event='write_aio')
647         self.run_job([
648             self.event_error('write', 'stop'),
649             self.event_ready(),
650             self.event_completed()
651         ], error_pauses_job=True)
653         self.vm.shutdown()
654         self.assertTrue(iotests.compare_images(test_img, mid_img),
655                         'target image does not match source after commit')
657     def testActiveWriteErrorIgnore(self):
658         self.prepare_and_start_job('ignore', mid_event='write_aio')
659         self.run_job([
660             self.event_error('write', 'ignore'),
661             self.event_ready(),
662             self.event_completed()
663         ])
665         # For commit, 'ignore' actually means retry, so this will succeed
666         self.vm.shutdown()
667         self.assertTrue(iotests.compare_images(test_img, mid_img),
668                         'target image does not match source after commit')
670     def testIntermediateReadErrorReport(self):
671         self.prepare_and_start_job('report', active=False, mid_event='read_aio')
672         self.run_job([
673             self.event_error('read', 'report'),
674             self.event_completed('Input/output error', active=False)
675         ])
677         self.vm.shutdown()
678         self.assertFalse(iotests.compare_images(mid_img, backing_img, fmt2='raw'),
679                          'target image matches source after error')
681     def testIntermediateReadErrorStop(self):
682         self.prepare_and_start_job('stop', active=False, mid_event='read_aio')
683         self.run_job([
684             self.event_error('read', 'stop'),
685             self.event_completed(active=False)
686         ], error_pauses_job=True)
688         self.vm.shutdown()
689         self.assertTrue(iotests.compare_images(mid_img, backing_img, fmt2='raw'),
690                         'target image does not match source after commit')
692     def testIntermediateReadErrorIgnore(self):
693         self.prepare_and_start_job('ignore', active=False, mid_event='read_aio')
694         self.run_job([
695             self.event_error('read', 'ignore'),
696             self.event_completed(active=False)
697         ])
699         # For commit, 'ignore' actually means retry, so this will succeed
700         self.vm.shutdown()
701         self.assertTrue(iotests.compare_images(mid_img, backing_img, fmt2='raw'),
702                         'target image does not match source after commit')
704     def testIntermediateWriteErrorReport(self):
705         self.prepare_and_start_job('report', active=False, base_event='write_aio')
706         self.run_job([
707             self.event_error('write', 'report'),
708             self.event_completed('Input/output error', active=False)
709         ])
711         self.vm.shutdown()
712         self.assertFalse(iotests.compare_images(mid_img, backing_img, fmt2='raw'),
713                          'target image matches source after error')
715     def testIntermediateWriteErrorStop(self):
716         self.prepare_and_start_job('stop', active=False, base_event='write_aio')
717         self.run_job([
718             self.event_error('write', 'stop'),
719             self.event_completed(active=False)
720         ], error_pauses_job=True)
722         self.vm.shutdown()
723         self.assertTrue(iotests.compare_images(mid_img, backing_img, fmt2='raw'),
724                         'target image does not match source after commit')
726     def testIntermediateWriteErrorIgnore(self):
727         self.prepare_and_start_job('ignore', active=False, base_event='write_aio')
728         self.run_job([
729             self.event_error('write', 'ignore'),
730             self.event_completed(active=False)
731         ])
733         # For commit, 'ignore' actually means retry, so this will succeed
734         self.vm.shutdown()
735         self.assertTrue(iotests.compare_images(mid_img, backing_img, fmt2='raw'),
736                         'target image does not match source after commit')
738 class TestCommitWithFilters(iotests.QMPTestCase):
739     img0 = os.path.join(iotests.test_dir, '0.img')
740     img1 = os.path.join(iotests.test_dir, '1.img')
741     img2 = os.path.join(iotests.test_dir, '2.img')
742     img3 = os.path.join(iotests.test_dir, '3.img')
744     def do_test_io(self, read_or_write):
745         for index, pattern_file in enumerate(self.pattern_files):
746             result = qemu_io('-f', iotests.imgfmt,
747                              '-c',
748                              f'{read_or_write} -P {index + 1} {index}M 1M',
749                              pattern_file)
750             self.assertFalse('Pattern verification failed' in result)
752     def setUp(self):
753         qemu_img('create', '-f', iotests.imgfmt, self.img0, '64M')
754         qemu_img('create', '-f', iotests.imgfmt, self.img1, '64M')
755         qemu_img('create', '-f', iotests.imgfmt, self.img2, '64M')
756         qemu_img('create', '-f', iotests.imgfmt, self.img3, '64M')
758         # Distributions of the patterns in the files; this is checked
759         # by tearDown() and should be changed by the test cases as is
760         # necessary
761         self.pattern_files = [self.img0, self.img1, self.img2, self.img3]
763         self.do_test_io('write')
765         self.vm = iotests.VM().add_device('virtio-scsi,id=vio-scsi')
766         self.vm.launch()
768         result = self.vm.qmp('object-add', qom_type='throttle-group', id='tg')
769         self.assert_qmp(result, 'return', {})
771         result = self.vm.qmp('blockdev-add', **{
772                 'node-name': 'top-filter',
773                 'driver': 'throttle',
774                 'throttle-group': 'tg',
775                 'file': {
776                     'node-name': 'cow-3',
777                     'driver': iotests.imgfmt,
778                     'file': {
779                         'driver': 'file',
780                         'filename': self.img3
781                     },
782                     'backing': {
783                         'node-name': 'cow-2',
784                         'driver': iotests.imgfmt,
785                         'file': {
786                             'driver': 'file',
787                             'filename': self.img2
788                         },
789                         'backing': {
790                             'node-name': 'cow-1',
791                             'driver': iotests.imgfmt,
792                             'file': {
793                                 'driver': 'file',
794                                 'filename': self.img1
795                             },
796                             'backing': {
797                                 'node-name': 'bottom-filter',
798                                 'driver': 'throttle',
799                                 'throttle-group': 'tg',
800                                 'file': {
801                                     'node-name': 'cow-0',
802                                     'driver': iotests.imgfmt,
803                                     'file': {
804                                         'driver': 'file',
805                                         'filename': self.img0
806                                     }
807                                 }
808                             }
809                         }
810                     }
811                 }
812             })
813         self.assert_qmp(result, 'return', {})
815     def tearDown(self):
816         self.vm.shutdown()
817         self.do_test_io('read')
819         os.remove(self.img3)
820         os.remove(self.img2)
821         os.remove(self.img1)
822         os.remove(self.img0)
824     # Filters make for funny filenames, so we cannot just use
825     # self.imgX to get them
826     def get_filename(self, node):
827         return self.vm.node_info(node)['image']['filename']
829     def test_filterless_commit(self):
830         result = self.vm.qmp('block-commit',
831                              job_id='commit',
832                              device='top-filter',
833                              top_node='cow-2',
834                              base_node='cow-1')
835         self.assert_qmp(result, 'return', {})
836         self.wait_until_completed(drive='commit')
838         self.assertIsNotNone(self.vm.node_info('cow-3'))
839         self.assertIsNone(self.vm.node_info('cow-2'))
840         self.assertIsNotNone(self.vm.node_info('cow-1'))
842         # 2 has been comitted into 1
843         self.pattern_files[2] = self.img1
845     def test_commit_through_filter(self):
846         result = self.vm.qmp('block-commit',
847                              job_id='commit',
848                              device='top-filter',
849                              top_node='cow-1',
850                              base_node='cow-0')
851         self.assert_qmp(result, 'return', {})
852         self.wait_until_completed(drive='commit')
854         self.assertIsNotNone(self.vm.node_info('cow-2'))
855         self.assertIsNone(self.vm.node_info('cow-1'))
856         self.assertIsNone(self.vm.node_info('bottom-filter'))
857         self.assertIsNotNone(self.vm.node_info('cow-0'))
859         # 1 has been comitted into 0
860         self.pattern_files[1] = self.img0
862     def test_filtered_active_commit_with_filter(self):
863         # Add a device, so the commit job finds a parent it can change
864         # to point to the base node (so we can test that top-filter is
865         # dropped from the graph)
866         result = self.vm.qmp('device_add', id='drv0', driver='scsi-hd',
867                              bus='vio-scsi.0', drive='top-filter')
868         self.assert_qmp(result, 'return', {})
870         # Try to release our reference to top-filter; that should not
871         # work because drv0 uses it
872         result = self.vm.qmp('blockdev-del', node_name='top-filter')
873         self.assert_qmp(result, 'error/class', 'GenericError')
874         self.assert_qmp(result, 'error/desc', 'Node top-filter is in use')
876         result = self.vm.qmp('block-commit',
877                              job_id='commit',
878                              device='top-filter',
879                              base_node='cow-2')
880         self.assert_qmp(result, 'return', {})
881         self.complete_and_wait(drive='commit')
883         # Try to release our reference to top-filter again
884         result = self.vm.qmp('blockdev-del', node_name='top-filter')
885         self.assert_qmp(result, 'return', {})
887         self.assertIsNone(self.vm.node_info('top-filter'))
888         self.assertIsNone(self.vm.node_info('cow-3'))
889         self.assertIsNotNone(self.vm.node_info('cow-2'))
891         # Check that drv0 is now connected to cow-2
892         blockdevs = self.vm.qmp('query-block')['return']
893         drv0 = next(dev for dev in blockdevs if dev['qdev'] == 'drv0')
894         self.assertEqual(drv0['inserted']['node-name'], 'cow-2')
896         # 3 has been comitted into 2
897         self.pattern_files[3] = self.img2
899     def test_filtered_active_commit_without_filter(self):
900         result = self.vm.qmp('block-commit',
901                              job_id='commit',
902                              device='top-filter',
903                              top_node='cow-3',
904                              base_node='cow-2')
905         self.assert_qmp(result, 'return', {})
906         self.complete_and_wait(drive='commit')
908         self.assertIsNotNone(self.vm.node_info('top-filter'))
909         self.assertIsNone(self.vm.node_info('cow-3'))
910         self.assertIsNotNone(self.vm.node_info('cow-2'))
912         # 3 has been comitted into 2
913         self.pattern_files[3] = self.img2
915 class TestCommitWithOverriddenBacking(iotests.QMPTestCase):
916     img_base_a = os.path.join(iotests.test_dir, 'base_a.img')
917     img_base_b = os.path.join(iotests.test_dir, 'base_b.img')
918     img_top = os.path.join(iotests.test_dir, 'top.img')
920     def setUp(self):
921         qemu_img('create', '-f', iotests.imgfmt, self.img_base_a, '1M')
922         qemu_img('create', '-f', iotests.imgfmt, self.img_base_b, '1M')
923         qemu_img('create', '-f', iotests.imgfmt, '-b', self.img_base_a, \
924                  self.img_top)
926         self.vm = iotests.VM()
927         self.vm.launch()
929         # Use base_b instead of base_a as the backing of top
930         result = self.vm.qmp('blockdev-add', **{
931                                 'node-name': 'top',
932                                 'driver': iotests.imgfmt,
933                                 'file': {
934                                     'driver': 'file',
935                                     'filename': self.img_top
936                                 },
937                                 'backing': {
938                                     'node-name': 'base',
939                                     'driver': iotests.imgfmt,
940                                     'file': {
941                                         'driver': 'file',
942                                         'filename': self.img_base_b
943                                     }
944                                 }
945                             })
946         self.assert_qmp(result, 'return', {})
948     def tearDown(self):
949         self.vm.shutdown()
950         os.remove(self.img_top)
951         os.remove(self.img_base_a)
952         os.remove(self.img_base_b)
954     def test_commit_to_a(self):
955         # Try committing to base_a (which should fail, as top's
956         # backing image is base_b instead)
957         result = self.vm.qmp('block-commit',
958                              job_id='commit',
959                              device='top',
960                              base=self.img_base_a)
961         self.assert_qmp(result, 'error/class', 'GenericError')
963     def test_commit_to_b(self):
964         # Try committing to base_b (which should work, since that is
965         # actually top's backing image)
966         result = self.vm.qmp('block-commit',
967                              job_id='commit',
968                              device='top',
969                              base=self.img_base_b)
970         self.assert_qmp(result, 'return', {})
972         self.vm.event_wait('BLOCK_JOB_READY')
973         self.vm.qmp('block-job-complete', device='commit')
974         self.vm.event_wait('BLOCK_JOB_COMPLETED')
976 if __name__ == '__main__':
977     iotests.main(supported_fmts=['qcow2', 'qed'],
978                  supported_protocols=['file'])