xref: /openbmc/qemu/tests/qemu-iotests/030 (revision c2387413)
1#!/usr/bin/env python3
2# group: rw backing
3#
4# Tests for image streaming.
5#
6# Copyright (C) 2012 IBM Corp.
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation; either version 2 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with this program.  If not, see <http://www.gnu.org/licenses/>.
20#
21
22import time
23import os
24import iotests
25import unittest
26from iotests import qemu_img, qemu_io
27
28backing_img = os.path.join(iotests.test_dir, 'backing.img')
29mid_img = os.path.join(iotests.test_dir, 'mid.img')
30test_img = os.path.join(iotests.test_dir, 'test.img')
31
32class TestSingleDrive(iotests.QMPTestCase):
33    image_len = 1 * 1024 * 1024 # MB
34
35    def setUp(self):
36        iotests.create_image(backing_img, TestSingleDrive.image_len)
37        qemu_img('create', '-f', iotests.imgfmt,
38                 '-o', 'backing_file=%s' % backing_img,
39                 '-F', 'raw', mid_img)
40        qemu_img('create', '-f', iotests.imgfmt,
41                 '-o', 'backing_file=%s' % mid_img,
42                 '-F', iotests.imgfmt, test_img)
43        qemu_io('-f', 'raw', '-c', 'write -P 0x1 0 512', backing_img)
44        qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x1 524288 512', mid_img)
45        self.vm = iotests.VM().add_drive("blkdebug::" + test_img,
46                                         "backing.node-name=mid," +
47                                         "backing.backing.node-name=base")
48        self.vm.launch()
49
50    def tearDown(self):
51        self.vm.shutdown()
52        os.remove(test_img)
53        os.remove(mid_img)
54        os.remove(backing_img)
55
56    def test_stream(self):
57        self.assert_no_active_block_jobs()
58
59        result = self.vm.qmp('block-stream', device='drive0')
60        self.assert_qmp(result, 'return', {})
61
62        self.wait_until_completed()
63
64        self.assert_no_active_block_jobs()
65        self.vm.shutdown()
66
67        self.assertEqual(qemu_io('-f', 'raw', '-c', 'map', backing_img),
68                         qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img),
69                         'image file map does not match backing file after streaming')
70
71    def test_stream_intermediate(self):
72        self.assert_no_active_block_jobs()
73
74        self.assertNotEqual(qemu_io('-f', 'raw', '-rU', '-c', 'map', backing_img),
75                            qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', mid_img),
76                            'image file map matches backing file before streaming')
77
78        result = self.vm.qmp('block-stream', device='mid', job_id='stream-mid')
79        self.assert_qmp(result, 'return', {})
80
81        self.wait_until_completed(drive='stream-mid')
82
83        self.assert_no_active_block_jobs()
84        self.vm.shutdown()
85
86        self.assertEqual(qemu_io('-f', 'raw', '-c', 'map', backing_img),
87                         qemu_io('-f', iotests.imgfmt, '-c', 'map', mid_img),
88                         'image file map does not match backing file after streaming')
89
90    def test_stream_pause(self):
91        self.assert_no_active_block_jobs()
92
93        self.vm.pause_drive('drive0')
94        result = self.vm.qmp('block-stream', device='drive0')
95        self.assert_qmp(result, 'return', {})
96
97        self.pause_job('drive0', wait=False)
98        self.vm.resume_drive('drive0')
99        self.pause_wait('drive0')
100
101        result = self.vm.qmp('query-block-jobs')
102        offset = self.dictpath(result, 'return[0]/offset')
103
104        time.sleep(0.5)
105        result = self.vm.qmp('query-block-jobs')
106        self.assert_qmp(result, 'return[0]/offset', offset)
107
108        result = self.vm.qmp('block-job-resume', device='drive0')
109        self.assert_qmp(result, 'return', {})
110
111        self.wait_until_completed()
112
113        self.assert_no_active_block_jobs()
114        self.vm.shutdown()
115
116        self.assertEqual(qemu_io('-f', 'raw', '-c', 'map', backing_img),
117                         qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img),
118                         'image file map does not match backing file after streaming')
119
120    def test_stream_no_op(self):
121        self.assert_no_active_block_jobs()
122
123        # The image map is empty before the operation
124        empty_map = qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', test_img)
125
126        # This is a no-op: no data should ever be copied from the base image
127        result = self.vm.qmp('block-stream', device='drive0', base=mid_img)
128        self.assert_qmp(result, 'return', {})
129
130        self.wait_until_completed()
131
132        self.assert_no_active_block_jobs()
133        self.vm.shutdown()
134
135        self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img),
136                         empty_map, 'image file map changed after a no-op')
137
138    def test_stream_partial(self):
139        self.assert_no_active_block_jobs()
140
141        result = self.vm.qmp('block-stream', device='drive0', base=backing_img)
142        self.assert_qmp(result, 'return', {})
143
144        self.wait_until_completed()
145
146        self.assert_no_active_block_jobs()
147        self.vm.shutdown()
148
149        self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', mid_img),
150                         qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img),
151                         'image file map does not match backing file after streaming')
152
153    def test_device_not_found(self):
154        result = self.vm.qmp('block-stream', device='nonexistent')
155        self.assert_qmp(result, 'error/desc',
156            'Cannot find device=nonexistent nor node_name=nonexistent')
157
158    def test_job_id_missing(self):
159        result = self.vm.qmp('block-stream', device='mid')
160        self.assert_qmp(result, 'error/desc', "Invalid job ID ''")
161
162    def test_read_only(self):
163        # Create a new file that we can attach (we need a read-only top)
164        with iotests.FilePath('ro-top.img') as ro_top_path:
165            qemu_img('create', '-f', iotests.imgfmt, ro_top_path,
166                     str(self.image_len))
167
168            result = self.vm.qmp('blockdev-add',
169                                 node_name='ro-top',
170                                 driver=iotests.imgfmt,
171                                 read_only=True,
172                                 file={
173                                     'driver': 'file',
174                                     'filename': ro_top_path,
175                                     'read-only': True
176                                 },
177                                 backing='mid')
178            self.assert_qmp(result, 'return', {})
179
180            result = self.vm.qmp('block-stream', job_id='stream',
181                                 device='ro-top', base_node='base')
182            self.assert_qmp(result, 'error/desc', 'Block node is read-only')
183
184            result = self.vm.qmp('blockdev-del', node_name='ro-top')
185            self.assert_qmp(result, 'return', {})
186
187
188class TestParallelOps(iotests.QMPTestCase):
189    num_ops = 4 # Number of parallel block-stream operations
190    num_imgs = num_ops * 2 + 1
191    image_len = num_ops * 4 * 1024 * 1024
192    imgs = []
193
194    def setUp(self):
195        opts = []
196        self.imgs = []
197
198        # Initialize file names and command-line options
199        for i in range(self.num_imgs):
200            img_depth = self.num_imgs - i - 1
201            opts.append("backing." * img_depth + "node-name=node%d" % i)
202            self.imgs.append(os.path.join(iotests.test_dir, 'img-%d.img' % i))
203
204        # Create all images
205        iotests.create_image(self.imgs[0], self.image_len)
206        for i in range(1, self.num_imgs):
207            qemu_img('create', '-f', iotests.imgfmt,
208                     '-o', 'backing_file=%s' % self.imgs[i-1],
209                     '-F', 'raw' if i == 1 else iotests.imgfmt, self.imgs[i])
210
211        # Put data into the images we are copying data from
212        odd_img_indexes = [x for x in reversed(range(self.num_imgs)) if x % 2 == 1]
213        for i in range(len(odd_img_indexes)):
214            # Alternate between 2MB and 4MB.
215            # This way jobs will not finish in the same order they were created
216            num_mb = 2 + 2 * (i % 2)
217            qemu_io('-f', iotests.imgfmt,
218                    '-c', 'write -P 0xFF %dM %dM' % (i * 4, num_mb),
219                    self.imgs[odd_img_indexes[i]])
220
221        # Attach the drive to the VM
222        self.vm = iotests.VM()
223        self.vm.add_drive(self.imgs[-1], ','.join(opts))
224        self.vm.launch()
225
226    def tearDown(self):
227        self.vm.shutdown()
228        for img in self.imgs:
229            os.remove(img)
230
231    # Test that it's possible to run several block-stream operations
232    # in parallel in the same snapshot chain
233    @unittest.skipIf(os.environ.get('QEMU_CHECK_BLOCK_AUTO'), 'disabled in CI')
234    def test_stream_parallel(self):
235        self.assert_no_active_block_jobs()
236
237        # Check that the maps don't match before the streaming operations
238        for i in range(2, self.num_imgs, 2):
239            self.assertNotEqual(qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.imgs[i]),
240                                qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.imgs[i-1]),
241                                'image file map matches backing file before streaming')
242
243        # Create all streaming jobs
244        pending_jobs = []
245        for i in range(2, self.num_imgs, 2):
246            node_name = 'node%d' % i
247            job_id = 'stream-%s' % node_name
248            pending_jobs.append(job_id)
249            result = self.vm.qmp('block-stream', device=node_name,
250                                 job_id=job_id, bottom=f'node{i-1}',
251                                 speed=1024)
252            self.assert_qmp(result, 'return', {})
253
254        for job in pending_jobs:
255            result = self.vm.qmp('block-job-set-speed', device=job, speed=0)
256            self.assert_qmp(result, 'return', {})
257
258        # Wait for all jobs to be finished.
259        while len(pending_jobs) > 0:
260            for event in self.vm.get_qmp_events(wait=True):
261                if event['event'] == 'BLOCK_JOB_COMPLETED':
262                    job_id = self.dictpath(event, 'data/device')
263                    self.assertTrue(job_id in pending_jobs)
264                    self.assert_qmp_absent(event, 'data/error')
265                    pending_jobs.remove(job_id)
266
267        self.assert_no_active_block_jobs()
268        self.vm.shutdown()
269
270        # Check that all maps match now
271        for i in range(2, self.num_imgs, 2):
272            self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[i]),
273                             qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[i-1]),
274                             'image file map does not match backing file after streaming')
275
276    # Test that it's not possible to perform two block-stream
277    # operations if there are nodes involved in both.
278    def test_overlapping_1(self):
279        self.assert_no_active_block_jobs()
280
281        # Set a speed limit to make sure that this job blocks the rest
282        result = self.vm.qmp('block-stream', device='node4',
283                             job_id='stream-node4', base=self.imgs[1],
284                             filter_node_name='stream-filter', speed=1024*1024)
285        self.assert_qmp(result, 'return', {})
286
287        result = self.vm.qmp('block-stream', device='node5', job_id='stream-node5', base=self.imgs[2])
288        self.assert_qmp(result, 'error/desc',
289            "Node 'stream-filter' is busy: block device is in use by block job: stream")
290
291        result = self.vm.qmp('block-stream', device='node3', job_id='stream-node3', base=self.imgs[2])
292        self.assert_qmp(result, 'error/desc',
293            "Node 'node3' is busy: block device is in use by block job: stream")
294
295        result = self.vm.qmp('block-stream', device='node4', job_id='stream-node4-v2')
296        self.assert_qmp(result, 'error/desc',
297            "Node 'node4' is busy: block device is in use by block job: stream")
298
299        # block-commit should also fail if it touches nodes used by the stream job
300        result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[4], job_id='commit-node4')
301        self.assert_qmp(result, 'error/desc',
302            "Node 'stream-filter' is busy: block device is in use by block job: stream")
303
304        result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[1], top=self.imgs[3], job_id='commit-node1')
305        self.assert_qmp(result, 'error/desc',
306            "Node 'node3' is busy: block device is in use by block job: stream")
307
308        # This fails because it needs to modify the backing string in node2, which is blocked
309        result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[0], top=self.imgs[1], job_id='commit-node0')
310        self.assert_qmp(result, 'error/desc',
311            "Node 'node2' is busy: block device is in use by block job: stream")
312
313        result = self.vm.qmp('block-job-set-speed', device='stream-node4', speed=0)
314        self.assert_qmp(result, 'return', {})
315
316        self.wait_until_completed(drive='stream-node4')
317        self.assert_no_active_block_jobs()
318
319    # Similar to test_overlapping_1, but with block-commit
320    # blocking the other jobs
321    def test_overlapping_2(self):
322        self.assertLessEqual(9, self.num_imgs)
323        self.assert_no_active_block_jobs()
324
325        # Set a speed limit to make sure that this job blocks the rest
326        result = self.vm.qmp('block-commit', device='drive0', top=self.imgs[5], base=self.imgs[3], job_id='commit-node3', speed=1024*1024)
327        self.assert_qmp(result, 'return', {})
328
329        result = self.vm.qmp('block-stream', device='node3', job_id='stream-node3')
330        self.assert_qmp(result, 'error/desc',
331            "Node 'node3' is busy: block device is in use by block job: commit")
332
333        result = self.vm.qmp('block-stream', device='node6', base=self.imgs[2], job_id='stream-node6')
334        self.assert_qmp(result, 'error/desc',
335            "Node 'node5' is busy: block device is in use by block job: commit")
336
337        result = self.vm.qmp('block-stream', device='node4', base=self.imgs[2], job_id='stream-node4')
338        self.assert_qmp(result, 'error/desc',
339            "Node 'node4' is busy: block device is in use by block job: commit")
340
341        result = self.vm.qmp('block-stream', device='node6', base=self.imgs[4], job_id='stream-node6-v2')
342        self.assert_qmp(result, 'error/desc',
343            "Node 'node5' is busy: block device is in use by block job: commit")
344
345        # This fails because block-commit currently blocks the active layer even if it's not used
346        result = self.vm.qmp('block-stream', device='drive0', base=self.imgs[5], job_id='stream-drive0')
347        self.assert_qmp(result, 'error/desc',
348            "Node 'drive0' is busy: block device is in use by block job: commit")
349
350        result = self.vm.qmp('block-job-set-speed', device='commit-node3', speed=0)
351        self.assert_qmp(result, 'return', {})
352
353        self.wait_until_completed(drive='commit-node3')
354
355    # Similar to test_overlapping_2, but here block-commit doesn't use the 'top' parameter.
356    # Internally this uses a mirror block job, hence the separate test case.
357    def test_overlapping_3(self):
358        self.assertLessEqual(8, self.num_imgs)
359        self.assert_no_active_block_jobs()
360
361        # Set a speed limit to make sure that this job blocks the rest
362        result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[3], job_id='commit-drive0', speed=1024*1024)
363        self.assert_qmp(result, 'return', {})
364
365        result = self.vm.qmp('block-stream', device='node5', base=self.imgs[3], job_id='stream-node6')
366        self.assert_qmp(result, 'error/desc',
367            "Node 'node5' is busy: block device is in use by block job: commit")
368
369        result = self.vm.qmp('block-job-set-speed', device='commit-drive0', speed=0)
370        self.assert_qmp(result, 'return', {})
371
372        event = self.vm.event_wait(name='BLOCK_JOB_READY')
373        self.assert_qmp(event, 'data/device', 'commit-drive0')
374        self.assert_qmp(event, 'data/type', 'commit')
375        self.assert_qmp_absent(event, 'data/error')
376
377        result = self.vm.qmp('block-job-complete', device='commit-drive0')
378        self.assert_qmp(result, 'return', {})
379
380        self.wait_until_completed(drive='commit-drive0')
381
382    # In this case the base node of the stream job is the same as the
383    # top node of commit job. Since this results in the commit filter
384    # node being part of the stream chain, this is not allowed.
385    def test_overlapping_4(self):
386        self.assert_no_active_block_jobs()
387
388        # Commit from node2 into node0
389        result = self.vm.qmp('block-commit', device='drive0',
390                             top=self.imgs[2], base=self.imgs[0],
391                             filter_node_name='commit-filter', speed=1024*1024)
392        self.assert_qmp(result, 'return', {})
393
394        # Stream from node2 into node4
395        result = self.vm.qmp('block-stream', device='node4', base_node='node2', job_id='node4')
396        self.assert_qmp(result, 'error/desc',
397            "Cannot freeze 'backing' link to 'commit-filter'")
398
399        result = self.vm.qmp('block-job-set-speed', device='drive0', speed=0)
400        self.assert_qmp(result, 'return', {})
401
402        self.wait_until_completed()
403        self.assert_no_active_block_jobs()
404
405    # In this case the base node of the stream job is the commit job's
406    # filter node.  stream does not have a real dependency on its base
407    # node, so even though commit removes it when it is done, there is
408    # no conflict.
409    def test_overlapping_5(self):
410        self.assert_no_active_block_jobs()
411
412        # Commit from node2 into node0
413        result = self.vm.qmp('block-commit', device='drive0',
414                             top_node='node2', base_node='node0',
415                             filter_node_name='commit-filter', speed=1024*1024)
416        self.assert_qmp(result, 'return', {})
417
418        # Stream from node2 into node4
419        result = self.vm.qmp('block-stream', device='node4',
420                             base_node='commit-filter', job_id='node4')
421        self.assert_qmp(result, 'return', {})
422
423        result = self.vm.qmp('block-job-set-speed', device='drive0', speed=0)
424        self.assert_qmp(result, 'return', {})
425
426        self.vm.run_job(job='drive0', auto_dismiss=True)
427        self.vm.run_job(job='node4', auto_dismiss=True)
428        self.assert_no_active_block_jobs()
429
430    # Test a block-stream and a block-commit job in parallel
431    # Here the stream job is supposed to finish quickly in order to reproduce
432    # the scenario that triggers the bug fixed in 3d5d319e1221 and 1a63a907507
433    def test_stream_commit_1(self):
434        self.assertLessEqual(8, self.num_imgs)
435        self.assert_no_active_block_jobs()
436
437        # Stream from node0 into node2
438        result = self.vm.qmp('block-stream', device='node2', base_node='node0', job_id='node2')
439        self.assert_qmp(result, 'return', {})
440
441        # Commit from the active layer into node3
442        result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[3])
443        self.assert_qmp(result, 'return', {})
444
445        # Wait for all jobs to be finished.
446        pending_jobs = ['node2', 'drive0']
447        while len(pending_jobs) > 0:
448            for event in self.vm.get_qmp_events(wait=True):
449                if event['event'] == 'BLOCK_JOB_COMPLETED':
450                    node_name = self.dictpath(event, 'data/device')
451                    self.assertTrue(node_name in pending_jobs)
452                    self.assert_qmp_absent(event, 'data/error')
453                    pending_jobs.remove(node_name)
454                if event['event'] == 'BLOCK_JOB_READY':
455                    self.assert_qmp(event, 'data/device', 'drive0')
456                    self.assert_qmp(event, 'data/type', 'commit')
457                    self.assert_qmp_absent(event, 'data/error')
458                    self.assertTrue('drive0' in pending_jobs)
459                    self.vm.qmp('block-job-complete', device='drive0')
460
461        self.assert_no_active_block_jobs()
462
463    # This is similar to test_stream_commit_1 but both jobs are slowed
464    # down so they can run in parallel for a little while.
465    def test_stream_commit_2(self):
466        self.assertLessEqual(8, self.num_imgs)
467        self.assert_no_active_block_jobs()
468
469        # Stream from node0 into node4
470        result = self.vm.qmp('block-stream', device='node4', base_node='node0', job_id='node4', speed=1024*1024)
471        self.assert_qmp(result, 'return', {})
472
473        # Commit from the active layer into node5
474        result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[5], speed=1024*1024)
475        self.assert_qmp(result, 'return', {})
476
477        for job in ['drive0', 'node4']:
478            result = self.vm.qmp('block-job-set-speed', device=job, speed=0)
479            self.assert_qmp(result, 'return', {})
480
481        # Wait for all jobs to be finished.
482        pending_jobs = ['node4', 'drive0']
483        while len(pending_jobs) > 0:
484            for event in self.vm.get_qmp_events(wait=True):
485                if event['event'] == 'BLOCK_JOB_COMPLETED':
486                    node_name = self.dictpath(event, 'data/device')
487                    self.assertTrue(node_name in pending_jobs)
488                    self.assert_qmp_absent(event, 'data/error')
489                    pending_jobs.remove(node_name)
490                if event['event'] == 'BLOCK_JOB_READY':
491                    self.assert_qmp(event, 'data/device', 'drive0')
492                    self.assert_qmp(event, 'data/type', 'commit')
493                    self.assert_qmp_absent(event, 'data/error')
494                    self.assertTrue('drive0' in pending_jobs)
495                    self.vm.qmp('block-job-complete', device='drive0')
496
497        self.assert_no_active_block_jobs()
498
499    # Test the base_node parameter
500    def test_stream_base_node_name(self):
501        self.assert_no_active_block_jobs()
502
503        self.assertNotEqual(qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.imgs[4]),
504                            qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.imgs[3]),
505                            'image file map matches backing file before streaming')
506
507        # Error: the base node does not exist
508        result = self.vm.qmp('block-stream', device='node4', base_node='none', job_id='stream')
509        self.assert_qmp(result, 'error/desc',
510            'Cannot find device= nor node_name=none')
511
512        # Error: the base node is not a backing file of the top node
513        result = self.vm.qmp('block-stream', device='node4', base_node='node6', job_id='stream')
514        self.assert_qmp(result, 'error/desc',
515            "Node 'node6' is not a backing image of 'node4'")
516
517        # Error: the base node is the same as the top node
518        result = self.vm.qmp('block-stream', device='node4', base_node='node4', job_id='stream')
519        self.assert_qmp(result, 'error/desc',
520            "Node 'node4' is not a backing image of 'node4'")
521
522        # Error: cannot specify 'base' and 'base-node' at the same time
523        result = self.vm.qmp('block-stream', device='node4', base=self.imgs[2], base_node='node2', job_id='stream')
524        self.assert_qmp(result, 'error/desc',
525            "'base' and 'base-node' cannot be specified at the same time")
526
527        # Success: the base node is a backing file of the top node
528        result = self.vm.qmp('block-stream', device='node4', base_node='node2', job_id='stream')
529        self.assert_qmp(result, 'return', {})
530
531        self.wait_until_completed(drive='stream')
532
533        self.assert_no_active_block_jobs()
534        self.vm.shutdown()
535
536        self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[4]),
537                         qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[3]),
538                         'image file map matches backing file after streaming')
539
540class TestQuorum(iotests.QMPTestCase):
541    num_children = 3
542    children = []
543    backing = []
544
545    @iotests.skip_if_unsupported(['quorum'])
546    def setUp(self):
547        opts = ['driver=quorum', 'vote-threshold=2']
548
549        # Initialize file names and command-line options
550        for i in range(self.num_children):
551            child_img = os.path.join(iotests.test_dir, 'img-%d.img' % i)
552            backing_img = os.path.join(iotests.test_dir, 'backing-%d.img' % i)
553            self.children.append(child_img)
554            self.backing.append(backing_img)
555            qemu_img('create', '-f', iotests.imgfmt, backing_img, '1M')
556            qemu_io('-f', iotests.imgfmt,
557                    '-c', 'write -P 0x55 0 1024', backing_img)
558            qemu_img('create', '-f', iotests.imgfmt,
559                     '-o', 'backing_file=%s' % backing_img,
560                     '-F', iotests.imgfmt, child_img)
561            opts.append("children.%d.file.filename=%s" % (i, child_img))
562            opts.append("children.%d.node-name=node%d" % (i, i))
563
564        # Attach the drive to the VM
565        self.vm = iotests.VM()
566        self.vm.add_drive(path = None, opts = ','.join(opts))
567        self.vm.launch()
568
569    def tearDown(self):
570        self.vm.shutdown()
571        for img in self.children:
572            os.remove(img)
573        for img in self.backing:
574            os.remove(img)
575
576    def test_stream_quorum(self):
577        self.assertNotEqual(qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.children[0]),
578                            qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.backing[0]),
579                            'image file map matches backing file before streaming')
580
581        self.assert_no_active_block_jobs()
582
583        result = self.vm.qmp('block-stream', device='node0', job_id='stream-node0')
584        self.assert_qmp(result, 'return', {})
585
586        self.wait_until_completed(drive='stream-node0')
587
588        self.assert_no_active_block_jobs()
589        self.vm.shutdown()
590
591        self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', self.children[0]),
592                         qemu_io('-f', iotests.imgfmt, '-c', 'map', self.backing[0]),
593                         'image file map does not match backing file after streaming')
594
595class TestSmallerBackingFile(iotests.QMPTestCase):
596    backing_len = 1 * 1024 * 1024 # MB
597    image_len = 2 * backing_len
598
599    def setUp(self):
600        iotests.create_image(backing_img, self.backing_len)
601        qemu_img('create', '-f', iotests.imgfmt,
602                 '-o', 'backing_file=%s' % backing_img,
603                 '-F', 'raw', test_img, str(self.image_len))
604        self.vm = iotests.VM().add_drive(test_img)
605        self.vm.launch()
606
607    # If this hangs, then you are missing a fix to complete streaming when the
608    # end of the backing file is reached.
609    def test_stream(self):
610        self.assert_no_active_block_jobs()
611
612        result = self.vm.qmp('block-stream', device='drive0')
613        self.assert_qmp(result, 'return', {})
614
615        self.wait_until_completed()
616
617        self.assert_no_active_block_jobs()
618        self.vm.shutdown()
619
620class TestErrors(iotests.QMPTestCase):
621    image_len = 2 * 1024 * 1024 # MB
622
623    # this should match STREAM_BUFFER_SIZE/512 in block/stream.c
624    STREAM_BUFFER_SIZE = 512 * 1024
625
626    def create_blkdebug_file(self, name, event, errno):
627        file = open(name, 'w')
628        file.write('''
629[inject-error]
630state = "1"
631event = "%s"
632errno = "%d"
633immediately = "off"
634once = "on"
635sector = "%d"
636
637[set-state]
638state = "1"
639event = "%s"
640new_state = "2"
641
642[set-state]
643state = "2"
644event = "%s"
645new_state = "1"
646''' % (event, errno, self.STREAM_BUFFER_SIZE // 512, event, event))
647        file.close()
648
649class TestEIO(TestErrors):
650    def setUp(self):
651        self.blkdebug_file = backing_img + ".blkdebug"
652        iotests.create_image(backing_img, TestErrors.image_len)
653        self.create_blkdebug_file(self.blkdebug_file, "read_aio", 5)
654        qemu_img('create', '-f', iotests.imgfmt,
655                 '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw'
656                       % (self.blkdebug_file, backing_img),
657                 test_img)
658        self.vm = iotests.VM().add_drive(test_img)
659        self.vm.launch()
660
661    def tearDown(self):
662        self.vm.shutdown()
663        os.remove(test_img)
664        os.remove(backing_img)
665        os.remove(self.blkdebug_file)
666
667    def test_report(self):
668        self.assert_no_active_block_jobs()
669
670        result = self.vm.qmp('block-stream', device='drive0')
671        self.assert_qmp(result, 'return', {})
672
673        completed = False
674        error = False
675        while not completed:
676            for event in self.vm.get_qmp_events(wait=True):
677                if event['event'] == 'BLOCK_JOB_ERROR':
678                    self.assert_qmp(event, 'data/device', 'drive0')
679                    self.assert_qmp(event, 'data/operation', 'read')
680                    error = True
681                elif event['event'] == 'BLOCK_JOB_COMPLETED':
682                    self.assertTrue(error, 'job completed unexpectedly')
683                    self.assert_qmp(event, 'data/type', 'stream')
684                    self.assert_qmp(event, 'data/device', 'drive0')
685                    self.assert_qmp(event, 'data/error', 'Input/output error')
686                    self.assert_qmp(event, 'data/offset', self.STREAM_BUFFER_SIZE)
687                    self.assert_qmp(event, 'data/len', self.image_len)
688                    completed = True
689                elif event['event'] == 'JOB_STATUS_CHANGE':
690                    self.assert_qmp(event, 'data/id', 'drive0')
691
692        self.assert_no_active_block_jobs()
693        self.vm.shutdown()
694
695    def test_ignore(self):
696        self.assert_no_active_block_jobs()
697
698        result = self.vm.qmp('block-stream', device='drive0', on_error='ignore')
699        self.assert_qmp(result, 'return', {})
700
701        error = False
702        completed = False
703        while not completed:
704            for event in self.vm.get_qmp_events(wait=True):
705                if event['event'] == 'BLOCK_JOB_ERROR':
706                    error = True
707                    self.assert_qmp(event, 'data/device', 'drive0')
708                    self.assert_qmp(event, 'data/operation', 'read')
709                    result = self.vm.qmp('query-block-jobs')
710                    if result == {'return': []}:
711                        # Job finished too quickly
712                        continue
713                    self.assert_qmp(result, 'return[0]/paused', False)
714                elif event['event'] == 'BLOCK_JOB_COMPLETED':
715                    self.assertTrue(error, 'job completed unexpectedly')
716                    self.assert_qmp(event, 'data/type', 'stream')
717                    self.assert_qmp(event, 'data/device', 'drive0')
718                    self.assert_qmp(event, 'data/error', 'Input/output error')
719                    self.assert_qmp(event, 'data/offset', self.image_len)
720                    self.assert_qmp(event, 'data/len', self.image_len)
721                    completed = True
722                elif event['event'] == 'JOB_STATUS_CHANGE':
723                    self.assert_qmp(event, 'data/id', 'drive0')
724
725        self.assert_no_active_block_jobs()
726        self.vm.shutdown()
727
728    def test_stop(self):
729        self.assert_no_active_block_jobs()
730
731        result = self.vm.qmp('block-stream', device='drive0', on_error='stop')
732        self.assert_qmp(result, 'return', {})
733
734        error = False
735        completed = False
736        while not completed:
737            for event in self.vm.get_qmp_events(wait=True):
738                if event['event'] == 'BLOCK_JOB_ERROR':
739                    error = True
740                    self.assert_qmp(event, 'data/device', 'drive0')
741                    self.assert_qmp(event, 'data/operation', 'read')
742
743                    result = self.vm.qmp('query-block-jobs')
744                    self.assert_qmp(result, 'return[0]/paused', True)
745                    self.assert_qmp(result, 'return[0]/offset', self.STREAM_BUFFER_SIZE)
746                    self.assert_qmp(result, 'return[0]/io-status', 'failed')
747
748                    result = self.vm.qmp('block-job-resume', device='drive0')
749                    self.assert_qmp(result, 'return', {})
750
751                    result = self.vm.qmp('query-block-jobs')
752                    if result == {'return': []}:
753                        # Race; likely already finished. Check.
754                        continue
755                    self.assert_qmp(result, 'return[0]/paused', False)
756                    self.assert_qmp(result, 'return[0]/io-status', 'ok')
757                elif event['event'] == 'BLOCK_JOB_COMPLETED':
758                    self.assertTrue(error, 'job completed unexpectedly')
759                    self.assert_qmp(event, 'data/type', 'stream')
760                    self.assert_qmp(event, 'data/device', 'drive0')
761                    self.assert_qmp_absent(event, 'data/error')
762                    self.assert_qmp(event, 'data/offset', self.image_len)
763                    self.assert_qmp(event, 'data/len', self.image_len)
764                    completed = True
765                elif event['event'] == 'JOB_STATUS_CHANGE':
766                    self.assert_qmp(event, 'data/id', 'drive0')
767
768        self.assert_no_active_block_jobs()
769        self.vm.shutdown()
770
771    def test_enospc(self):
772        self.assert_no_active_block_jobs()
773
774        result = self.vm.qmp('block-stream', device='drive0', on_error='enospc')
775        self.assert_qmp(result, 'return', {})
776
777        completed = False
778        error = False
779        while not completed:
780            for event in self.vm.get_qmp_events(wait=True):
781                if event['event'] == 'BLOCK_JOB_ERROR':
782                    self.assert_qmp(event, 'data/device', 'drive0')
783                    self.assert_qmp(event, 'data/operation', 'read')
784                    error = True
785                elif event['event'] == 'BLOCK_JOB_COMPLETED':
786                    self.assertTrue(error, 'job completed unexpectedly')
787                    self.assert_qmp(event, 'data/type', 'stream')
788                    self.assert_qmp(event, 'data/device', 'drive0')
789                    self.assert_qmp(event, 'data/error', 'Input/output error')
790                    self.assert_qmp(event, 'data/offset', self.STREAM_BUFFER_SIZE)
791                    self.assert_qmp(event, 'data/len', self.image_len)
792                    completed = True
793                elif event['event'] == 'JOB_STATUS_CHANGE':
794                    self.assert_qmp(event, 'data/id', 'drive0')
795
796        self.assert_no_active_block_jobs()
797        self.vm.shutdown()
798
799class TestENOSPC(TestErrors):
800    def setUp(self):
801        self.blkdebug_file = backing_img + ".blkdebug"
802        iotests.create_image(backing_img, TestErrors.image_len)
803        self.create_blkdebug_file(self.blkdebug_file, "read_aio", 28)
804        qemu_img('create', '-f', iotests.imgfmt,
805                 '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw'
806                       % (self.blkdebug_file, backing_img),
807                 test_img)
808        self.vm = iotests.VM().add_drive(test_img)
809        self.vm.launch()
810
811    def tearDown(self):
812        self.vm.shutdown()
813        os.remove(test_img)
814        os.remove(backing_img)
815        os.remove(self.blkdebug_file)
816
817    def test_enospc(self):
818        self.assert_no_active_block_jobs()
819
820        result = self.vm.qmp('block-stream', device='drive0', on_error='enospc')
821        self.assert_qmp(result, 'return', {})
822
823        error = False
824        completed = False
825        while not completed:
826            for event in self.vm.get_qmp_events(wait=True):
827                if event['event'] == 'BLOCK_JOB_ERROR':
828                    self.assert_qmp(event, 'data/device', 'drive0')
829                    self.assert_qmp(event, 'data/operation', 'read')
830                    error = True
831
832                    result = self.vm.qmp('query-block-jobs')
833                    self.assert_qmp(result, 'return[0]/paused', True)
834                    self.assert_qmp(result, 'return[0]/offset', self.STREAM_BUFFER_SIZE)
835                    self.assert_qmp(result, 'return[0]/io-status', 'nospace')
836
837                    result = self.vm.qmp('block-job-resume', device='drive0')
838                    self.assert_qmp(result, 'return', {})
839
840                    result = self.vm.qmp('query-block-jobs')
841                    if result == {'return': []}:
842                        # Race; likely already finished. Check.
843                        continue
844                    self.assert_qmp(result, 'return[0]/paused', False)
845                    self.assert_qmp(result, 'return[0]/io-status', 'ok')
846                elif event['event'] == 'BLOCK_JOB_COMPLETED':
847                    self.assertTrue(error, 'job completed unexpectedly')
848                    self.assert_qmp(event, 'data/type', 'stream')
849                    self.assert_qmp(event, 'data/device', 'drive0')
850                    self.assert_qmp_absent(event, 'data/error')
851                    self.assert_qmp(event, 'data/offset', self.image_len)
852                    self.assert_qmp(event, 'data/len', self.image_len)
853                    completed = True
854                elif event['event'] == 'JOB_STATUS_CHANGE':
855                    self.assert_qmp(event, 'data/id', 'drive0')
856
857        self.assert_no_active_block_jobs()
858        self.vm.shutdown()
859
860class TestStreamStop(iotests.QMPTestCase):
861    image_len = 8 * 1024 * 1024 * 1024 # GB
862
863    def setUp(self):
864        qemu_img('create', backing_img, str(TestStreamStop.image_len))
865        qemu_io('-f', 'raw', '-c', 'write -P 0x1 0 32M', backing_img)
866        qemu_img('create', '-f', iotests.imgfmt,
867                 '-o', 'backing_file=%s' % backing_img,
868                 '-F', 'raw', test_img)
869        qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x1 32M 32M', test_img)
870        self.vm = iotests.VM().add_drive("blkdebug::" + test_img)
871        self.vm.launch()
872
873    def tearDown(self):
874        self.vm.shutdown()
875        os.remove(test_img)
876        os.remove(backing_img)
877
878    def test_stream_stop(self):
879        self.assert_no_active_block_jobs()
880
881        self.vm.pause_drive('drive0')
882        result = self.vm.qmp('block-stream', device='drive0')
883        self.assert_qmp(result, 'return', {})
884
885        time.sleep(0.1)
886        events = self.vm.get_qmp_events(wait=False)
887        for e in events:
888            self.assert_qmp(e, 'event', 'JOB_STATUS_CHANGE')
889            self.assert_qmp(e, 'data/id', 'drive0')
890
891        self.cancel_and_wait(resume=True)
892
893class TestSetSpeed(iotests.QMPTestCase):
894    image_len = 80 * 1024 * 1024 # MB
895
896    def setUp(self):
897        qemu_img('create', backing_img, str(TestSetSpeed.image_len))
898        qemu_io('-f', 'raw', '-c', 'write -P 0x1 0 32M', backing_img)
899        qemu_img('create', '-f', iotests.imgfmt,
900                 '-o', 'backing_file=%s' % backing_img,
901                 '-F', 'raw', test_img)
902        qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x1 32M 32M', test_img)
903        self.vm = iotests.VM().add_drive('blkdebug::' + test_img)
904        self.vm.launch()
905
906    def tearDown(self):
907        self.vm.shutdown()
908        os.remove(test_img)
909        os.remove(backing_img)
910
911    # This is a short performance test which is not run by default.
912    # Invoke "IMGFMT=qed ./030 TestSetSpeed.perf_test_throughput"
913    def perf_test_throughput(self):
914        self.assert_no_active_block_jobs()
915
916        result = self.vm.qmp('block-stream', device='drive0')
917        self.assert_qmp(result, 'return', {})
918
919        result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024)
920        self.assert_qmp(result, 'return', {})
921
922        self.wait_until_completed()
923
924        self.assert_no_active_block_jobs()
925
926    def test_set_speed(self):
927        self.assert_no_active_block_jobs()
928
929        self.vm.pause_drive('drive0')
930        result = self.vm.qmp('block-stream', device='drive0')
931        self.assert_qmp(result, 'return', {})
932
933        # Default speed is 0
934        result = self.vm.qmp('query-block-jobs')
935        self.assert_qmp(result, 'return[0]/device', 'drive0')
936        self.assert_qmp(result, 'return[0]/speed', 0)
937
938        result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024)
939        self.assert_qmp(result, 'return', {})
940
941        # Ensure the speed we set was accepted
942        result = self.vm.qmp('query-block-jobs')
943        self.assert_qmp(result, 'return[0]/device', 'drive0')
944        self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024)
945
946        self.cancel_and_wait(resume=True)
947        self.vm.pause_drive('drive0')
948
949        # Check setting speed in block-stream works
950        result = self.vm.qmp('block-stream', device='drive0', speed=4 * 1024 * 1024)
951        self.assert_qmp(result, 'return', {})
952
953        result = self.vm.qmp('query-block-jobs')
954        self.assert_qmp(result, 'return[0]/device', 'drive0')
955        self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024)
956
957        self.cancel_and_wait(resume=True)
958
959    def test_set_speed_invalid(self):
960        self.assert_no_active_block_jobs()
961
962        result = self.vm.qmp('block-stream', device='drive0', speed=-1)
963        self.assert_qmp(result, 'error/desc', "Parameter 'speed' expects a non-negative value")
964
965        self.assert_no_active_block_jobs()
966
967        self.vm.pause_drive('drive0')
968        result = self.vm.qmp('block-stream', device='drive0')
969        self.assert_qmp(result, 'return', {})
970
971        result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1)
972        self.assert_qmp(result, 'error/desc', "Parameter 'speed' expects a non-negative value")
973
974        self.cancel_and_wait(resume=True)
975
976if __name__ == '__main__':
977    iotests.main(supported_fmts=['qcow2', 'qed'],
978                 supported_protocols=['file'])
979