3 # Tests for image streaming.
5 # Copyright (C) 2012 IBM Corp.
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 from iotests import qemu_img, qemu_io
26 backing_img = os.path.join(iotests.test_dir, 'backing.img')
27 mid_img = os.path.join(iotests.test_dir, 'mid.img')
28 test_img = os.path.join(iotests.test_dir, 'test.img')
30 class TestSingleDrive(iotests.QMPTestCase):
31 image_len = 1 * 1024 * 1024 # MB
34 iotests.create_image(backing_img, TestSingleDrive.image_len)
35 qemu_img('create', '-f', iotests.imgfmt,
36 '-o', 'backing_file=%s' % backing_img,
38 qemu_img('create', '-f', iotests.imgfmt,
39 '-o', 'backing_file=%s' % mid_img,
40 '-F', iotests.imgfmt, test_img)
41 qemu_io('-f', 'raw', '-c', 'write -P 0x1 0 512', backing_img)
42 qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x1 524288 512', mid_img)
43 self.vm = iotests.VM().add_drive("blkdebug::" + test_img,
44 "backing.node-name=mid," +
45 "backing.backing.node-name=base")
52 os.remove(backing_img)
54 def test_stream(self):
55 self.assert_no_active_block_jobs()
57 result = self.vm.qmp('block-stream', device='drive0')
58 self.assert_qmp(result, 'return', {})
60 self.wait_until_completed()
62 self.assert_no_active_block_jobs()
65 self.assertEqual(qemu_io('-f', 'raw', '-c', 'map', backing_img),
66 qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img),
67 'image file map does not match backing file after streaming')
69 def test_stream_intermediate(self):
70 self.assert_no_active_block_jobs()
72 self.assertNotEqual(qemu_io('-f', 'raw', '-rU', '-c', 'map', backing_img),
73 qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', mid_img),
74 'image file map matches backing file before streaming')
76 result = self.vm.qmp('block-stream', device='mid', job_id='stream-mid')
77 self.assert_qmp(result, 'return', {})
79 self.wait_until_completed(drive='stream-mid')
81 self.assert_no_active_block_jobs()
84 self.assertEqual(qemu_io('-f', 'raw', '-c', 'map', backing_img),
85 qemu_io('-f', iotests.imgfmt, '-c', 'map', mid_img),
86 'image file map does not match backing file after streaming')
88 def test_stream_pause(self):
89 self.assert_no_active_block_jobs()
91 self.vm.pause_drive('drive0')
92 result = self.vm.qmp('block-stream', device='drive0')
93 self.assert_qmp(result, 'return', {})
95 self.pause_job('drive0', wait=False)
96 self.vm.resume_drive('drive0')
97 self.pause_wait('drive0')
99 result = self.vm.qmp('query-block-jobs')
100 offset = self.dictpath(result, 'return[0]/offset')
103 result = self.vm.qmp('query-block-jobs')
104 self.assert_qmp(result, 'return[0]/offset', offset)
106 result = self.vm.qmp('block-job-resume', device='drive0')
107 self.assert_qmp(result, 'return', {})
109 self.wait_until_completed()
111 self.assert_no_active_block_jobs()
114 self.assertEqual(qemu_io('-f', 'raw', '-c', 'map', backing_img),
115 qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img),
116 'image file map does not match backing file after streaming')
118 def test_stream_no_op(self):
119 self.assert_no_active_block_jobs()
121 # The image map is empty before the operation
122 empty_map = qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', test_img)
124 # This is a no-op: no data should ever be copied from the base image
125 result = self.vm.qmp('block-stream', device='drive0', base=mid_img)
126 self.assert_qmp(result, 'return', {})
128 self.wait_until_completed()
130 self.assert_no_active_block_jobs()
133 self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img),
134 empty_map, 'image file map changed after a no-op')
136 def test_stream_partial(self):
137 self.assert_no_active_block_jobs()
139 result = self.vm.qmp('block-stream', device='drive0', base=backing_img)
140 self.assert_qmp(result, 'return', {})
142 self.wait_until_completed()
144 self.assert_no_active_block_jobs()
147 self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', mid_img),
148 qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img),
149 'image file map does not match backing file after streaming')
151 def test_device_not_found(self):
152 result = self.vm.qmp('block-stream', device='nonexistent')
153 self.assert_qmp(result, 'error/desc',
154 'Cannot find device=nonexistent nor node_name=nonexistent')
156 def test_job_id_missing(self):
157 result = self.vm.qmp('block-stream', device='mid')
158 self.assert_qmp(result, 'error/desc', "Invalid job ID ''")
160 def test_read_only(self):
161 # Create a new file that we can attach (we need a read-only top)
162 with iotests.FilePath('ro-top.img') as ro_top_path:
163 qemu_img('create', '-f', iotests.imgfmt, ro_top_path,
166 result = self.vm.qmp('blockdev-add',
168 driver=iotests.imgfmt,
172 'filename': ro_top_path,
176 self.assert_qmp(result, 'return', {})
178 result = self.vm.qmp('block-stream', job_id='stream',
179 device='ro-top', base_node='base')
180 self.assert_qmp(result, 'error/desc', 'Block node is read-only')
182 result = self.vm.qmp('blockdev-del', node_name='ro-top')
183 self.assert_qmp(result, 'return', {})
186 class TestParallelOps(iotests.QMPTestCase):
187 num_ops = 4 # Number of parallel block-stream operations
188 num_imgs = num_ops * 2 + 1
189 image_len = num_ops * 4 * 1024 * 1024
196 # Initialize file names and command-line options
197 for i in range(self.num_imgs):
198 img_depth = self.num_imgs - i - 1
199 opts.append("backing." * img_depth + "node-name=node%d" % i)
200 self.imgs.append(os.path.join(iotests.test_dir, 'img-%d.img' % i))
203 iotests.create_image(self.imgs[0], self.image_len)
204 for i in range(1, self.num_imgs):
205 qemu_img('create', '-f', iotests.imgfmt,
206 '-o', 'backing_file=%s' % self.imgs[i-1],
207 '-F', 'raw' if i == 1 else iotests.imgfmt, self.imgs[i])
209 # Put data into the images we are copying data from
210 odd_img_indexes = [x for x in reversed(range(self.num_imgs)) if x % 2 == 1]
211 for i in range(len(odd_img_indexes)):
212 # Alternate between 2MB and 4MB.
213 # This way jobs will not finish in the same order they were created
214 num_mb = 2 + 2 * (i % 2)
215 qemu_io('-f', iotests.imgfmt,
216 '-c', 'write -P 0xFF %dM %dM' % (i * 4, num_mb),
217 self.imgs[odd_img_indexes[i]])
219 # Attach the drive to the VM
220 self.vm = iotests.VM()
221 self.vm.add_drive(self.imgs[-1], ','.join(opts))
226 for img in self.imgs:
229 # Test that it's possible to run several block-stream operations
230 # in parallel in the same snapshot chain
231 def test_stream_parallel(self):
232 self.assert_no_active_block_jobs()
234 # Check that the maps don't match before the streaming operations
235 for i in range(2, self.num_imgs, 2):
236 self.assertNotEqual(qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.imgs[i]),
237 qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.imgs[i-1]),
238 'image file map matches backing file before streaming')
240 # Create all streaming jobs
242 for i in range(2, self.num_imgs, 2):
243 node_name = 'node%d' % i
244 job_id = 'stream-%s' % node_name
245 pending_jobs.append(job_id)
246 result = self.vm.qmp('block-stream', device=node_name, job_id=job_id, base=self.imgs[i-2], speed=1024)
247 self.assert_qmp(result, 'return', {})
249 for job in pending_jobs:
250 result = self.vm.qmp('block-job-set-speed', device=job, speed=0)
251 self.assert_qmp(result, 'return', {})
253 # Wait for all jobs to be finished.
254 while len(pending_jobs) > 0:
255 for event in self.vm.get_qmp_events(wait=True):
256 if event['event'] == 'BLOCK_JOB_COMPLETED':
257 job_id = self.dictpath(event, 'data/device')
258 self.assertTrue(job_id in pending_jobs)
259 self.assert_qmp_absent(event, 'data/error')
260 pending_jobs.remove(job_id)
262 self.assert_no_active_block_jobs()
265 # Check that all maps match now
266 for i in range(2, self.num_imgs, 2):
267 self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[i]),
268 qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[i-1]),
269 'image file map does not match backing file after streaming')
271 # Test that it's not possible to perform two block-stream
272 # operations if there are nodes involved in both.
273 def test_overlapping_1(self):
274 self.assert_no_active_block_jobs()
276 # Set a speed limit to make sure that this job blocks the rest
277 result = self.vm.qmp('block-stream', device='node4', job_id='stream-node4', base=self.imgs[1], speed=1024*1024)
278 self.assert_qmp(result, 'return', {})
280 result = self.vm.qmp('block-stream', device='node5', job_id='stream-node5', base=self.imgs[2])
281 self.assert_qmp(result, 'error/desc',
282 "Node 'node4' is busy: block device is in use by block job: stream")
284 result = self.vm.qmp('block-stream', device='node3', job_id='stream-node3', base=self.imgs[2])
285 self.assert_qmp(result, 'error/desc',
286 "Node 'node3' is busy: block device is in use by block job: stream")
288 result = self.vm.qmp('block-stream', device='node4', job_id='stream-node4-v2')
289 self.assert_qmp(result, 'error/desc',
290 "Node 'node4' is busy: block device is in use by block job: stream")
292 # block-commit should also fail if it touches nodes used by the stream job
293 result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[4], job_id='commit-node4')
294 self.assert_qmp(result, 'error/desc',
295 "Node 'node4' is busy: block device is in use by block job: stream")
297 result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[1], top=self.imgs[3], job_id='commit-node1')
298 self.assert_qmp(result, 'error/desc',
299 "Node 'node3' is busy: block device is in use by block job: stream")
301 # This fails because it needs to modify the backing string in node2, which is blocked
302 result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[0], top=self.imgs[1], job_id='commit-node0')
303 self.assert_qmp(result, 'error/desc',
304 "Node 'node2' is busy: block device is in use by block job: stream")
306 result = self.vm.qmp('block-job-set-speed', device='stream-node4', speed=0)
307 self.assert_qmp(result, 'return', {})
309 self.wait_until_completed(drive='stream-node4')
310 self.assert_no_active_block_jobs()
312 # Similar to test_overlapping_1, but with block-commit
313 # blocking the other jobs
314 def test_overlapping_2(self):
315 self.assertLessEqual(9, self.num_imgs)
316 self.assert_no_active_block_jobs()
318 # Set a speed limit to make sure that this job blocks the rest
319 result = self.vm.qmp('block-commit', device='drive0', top=self.imgs[5], base=self.imgs[3], job_id='commit-node3', speed=1024*1024)
320 self.assert_qmp(result, 'return', {})
322 result = self.vm.qmp('block-stream', device='node3', job_id='stream-node3')
323 self.assert_qmp(result, 'error/desc',
324 "Node 'node3' is busy: block device is in use by block job: commit")
326 result = self.vm.qmp('block-stream', device='node6', base=self.imgs[2], job_id='stream-node6')
327 self.assert_qmp(result, 'error/desc',
328 "Node 'node5' is busy: block device is in use by block job: commit")
330 result = self.vm.qmp('block-stream', device='node4', base=self.imgs[2], job_id='stream-node4')
331 self.assert_qmp(result, 'error/desc',
332 "Node 'node4' is busy: block device is in use by block job: commit")
334 result = self.vm.qmp('block-stream', device='node6', base=self.imgs[4], job_id='stream-node6-v2')
335 self.assert_qmp(result, 'error/desc',
336 "Node 'node5' is busy: block device is in use by block job: commit")
338 # This fails because block-commit currently blocks the active layer even if it's not used
339 result = self.vm.qmp('block-stream', device='drive0', base=self.imgs[5], job_id='stream-drive0')
340 self.assert_qmp(result, 'error/desc',
341 "Node 'drive0' is busy: block device is in use by block job: commit")
343 result = self.vm.qmp('block-job-set-speed', device='commit-node3', speed=0)
344 self.assert_qmp(result, 'return', {})
346 self.wait_until_completed(drive='commit-node3')
348 # Similar to test_overlapping_2, but here block-commit doesn't use the 'top' parameter.
349 # Internally this uses a mirror block job, hence the separate test case.
350 def test_overlapping_3(self):
351 self.assertLessEqual(8, self.num_imgs)
352 self.assert_no_active_block_jobs()
354 # Set a speed limit to make sure that this job blocks the rest
355 result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[3], job_id='commit-drive0', speed=1024*1024)
356 self.assert_qmp(result, 'return', {})
358 result = self.vm.qmp('block-stream', device='node5', base=self.imgs[3], job_id='stream-node6')
359 self.assert_qmp(result, 'error/desc',
360 "Node 'node5' is busy: block device is in use by block job: commit")
362 result = self.vm.qmp('block-job-set-speed', device='commit-drive0', speed=0)
363 self.assert_qmp(result, 'return', {})
365 event = self.vm.event_wait(name='BLOCK_JOB_READY')
366 self.assert_qmp(event, 'data/device', 'commit-drive0')
367 self.assert_qmp(event, 'data/type', 'commit')
368 self.assert_qmp_absent(event, 'data/error')
370 result = self.vm.qmp('block-job-complete', device='commit-drive0')
371 self.assert_qmp(result, 'return', {})
373 self.wait_until_completed(drive='commit-drive0')
375 # In this case the base node of the stream job is the same as the
376 # top node of commit job. Since this results in the commit filter
377 # node being part of the stream chain, this is not allowed.
378 def test_overlapping_4(self):
379 self.assert_no_active_block_jobs()
381 # Commit from node2 into node0
382 result = self.vm.qmp('block-commit', device='drive0',
383 top=self.imgs[2], base=self.imgs[0],
384 filter_node_name='commit-filter', speed=1024*1024)
385 self.assert_qmp(result, 'return', {})
387 # Stream from node2 into node4
388 result = self.vm.qmp('block-stream', device='node4', base_node='node2', job_id='node4')
389 self.assert_qmp(result, 'error/desc',
390 "Cannot freeze 'backing' link to 'commit-filter'")
392 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=0)
393 self.assert_qmp(result, 'return', {})
395 self.wait_until_completed()
396 self.assert_no_active_block_jobs()
398 # In this case the base node of the stream job is the commit job's
399 # filter node. stream does not have a real dependency on its base
400 # node, so even though commit removes it when it is done, there is
402 def test_overlapping_5(self):
403 self.assert_no_active_block_jobs()
405 # Commit from node2 into node0
406 result = self.vm.qmp('block-commit', device='drive0',
407 top_node='node2', base_node='node0',
408 filter_node_name='commit-filter', speed=1024*1024)
409 self.assert_qmp(result, 'return', {})
411 # Stream from node2 into node4
412 result = self.vm.qmp('block-stream', device='node4',
413 base_node='commit-filter', job_id='node4')
414 self.assert_qmp(result, 'return', {})
416 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=0)
417 self.assert_qmp(result, 'return', {})
419 self.vm.run_job(job='drive0', auto_dismiss=True)
420 self.vm.run_job(job='node4', auto_dismiss=True)
421 self.assert_no_active_block_jobs()
423 # Test a block-stream and a block-commit job in parallel
424 # Here the stream job is supposed to finish quickly in order to reproduce
425 # the scenario that triggers the bug fixed in 3d5d319e1221 and 1a63a907507
426 def test_stream_commit_1(self):
427 self.assertLessEqual(8, self.num_imgs)
428 self.assert_no_active_block_jobs()
430 # Stream from node0 into node2
431 result = self.vm.qmp('block-stream', device='node2', base_node='node0', job_id='node2')
432 self.assert_qmp(result, 'return', {})
434 # Commit from the active layer into node3
435 result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[3])
436 self.assert_qmp(result, 'return', {})
438 # Wait for all jobs to be finished.
439 pending_jobs = ['node2', 'drive0']
440 while len(pending_jobs) > 0:
441 for event in self.vm.get_qmp_events(wait=True):
442 if event['event'] == 'BLOCK_JOB_COMPLETED':
443 node_name = self.dictpath(event, 'data/device')
444 self.assertTrue(node_name in pending_jobs)
445 self.assert_qmp_absent(event, 'data/error')
446 pending_jobs.remove(node_name)
447 if event['event'] == 'BLOCK_JOB_READY':
448 self.assert_qmp(event, 'data/device', 'drive0')
449 self.assert_qmp(event, 'data/type', 'commit')
450 self.assert_qmp_absent(event, 'data/error')
451 self.assertTrue('drive0' in pending_jobs)
452 self.vm.qmp('block-job-complete', device='drive0')
454 self.assert_no_active_block_jobs()
456 # This is similar to test_stream_commit_1 but both jobs are slowed
457 # down so they can run in parallel for a little while.
458 def test_stream_commit_2(self):
459 self.assertLessEqual(8, self.num_imgs)
460 self.assert_no_active_block_jobs()
462 # Stream from node0 into node4
463 result = self.vm.qmp('block-stream', device='node4', base_node='node0', job_id='node4', speed=1024*1024)
464 self.assert_qmp(result, 'return', {})
466 # Commit from the active layer into node5
467 result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[5], speed=1024*1024)
468 self.assert_qmp(result, 'return', {})
470 for job in ['drive0', 'node4']:
471 result = self.vm.qmp('block-job-set-speed', device=job, speed=0)
472 self.assert_qmp(result, 'return', {})
474 # Wait for all jobs to be finished.
475 pending_jobs = ['node4', 'drive0']
476 while len(pending_jobs) > 0:
477 for event in self.vm.get_qmp_events(wait=True):
478 if event['event'] == 'BLOCK_JOB_COMPLETED':
479 node_name = self.dictpath(event, 'data/device')
480 self.assertTrue(node_name in pending_jobs)
481 self.assert_qmp_absent(event, 'data/error')
482 pending_jobs.remove(node_name)
483 if event['event'] == 'BLOCK_JOB_READY':
484 self.assert_qmp(event, 'data/device', 'drive0')
485 self.assert_qmp(event, 'data/type', 'commit')
486 self.assert_qmp_absent(event, 'data/error')
487 self.assertTrue('drive0' in pending_jobs)
488 self.vm.qmp('block-job-complete', device='drive0')
490 self.assert_no_active_block_jobs()
492 # Test the base_node parameter
493 def test_stream_base_node_name(self):
494 self.assert_no_active_block_jobs()
496 self.assertNotEqual(qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.imgs[4]),
497 qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.imgs[3]),
498 'image file map matches backing file before streaming')
500 # Error: the base node does not exist
501 result = self.vm.qmp('block-stream', device='node4', base_node='none', job_id='stream')
502 self.assert_qmp(result, 'error/desc',
503 'Cannot find device= nor node_name=none')
505 # Error: the base node is not a backing file of the top node
506 result = self.vm.qmp('block-stream', device='node4', base_node='node6', job_id='stream')
507 self.assert_qmp(result, 'error/desc',
508 "Node 'node6' is not a backing image of 'node4'")
510 # Error: the base node is the same as the top node
511 result = self.vm.qmp('block-stream', device='node4', base_node='node4', job_id='stream')
512 self.assert_qmp(result, 'error/desc',
513 "Node 'node4' is not a backing image of 'node4'")
515 # Error: cannot specify 'base' and 'base-node' at the same time
516 result = self.vm.qmp('block-stream', device='node4', base=self.imgs[2], base_node='node2', job_id='stream')
517 self.assert_qmp(result, 'error/desc',
518 "'base' and 'base-node' cannot be specified at the same time")
520 # Success: the base node is a backing file of the top node
521 result = self.vm.qmp('block-stream', device='node4', base_node='node2', job_id='stream')
522 self.assert_qmp(result, 'return', {})
524 self.wait_until_completed(drive='stream')
526 self.assert_no_active_block_jobs()
529 self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[4]),
530 qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[3]),
531 'image file map matches backing file after streaming')
533 class TestQuorum(iotests.QMPTestCase):
538 @iotests.skip_if_unsupported(['quorum'])
540 opts = ['driver=quorum', 'vote-threshold=2']
542 # Initialize file names and command-line options
543 for i in range(self.num_children):
544 child_img = os.path.join(iotests.test_dir, 'img-%d.img' % i)
545 backing_img = os.path.join(iotests.test_dir, 'backing-%d.img' % i)
546 self.children.append(child_img)
547 self.backing.append(backing_img)
548 qemu_img('create', '-f', iotests.imgfmt, backing_img, '1M')
549 qemu_io('-f', iotests.imgfmt,
550 '-c', 'write -P 0x55 0 1024', backing_img)
551 qemu_img('create', '-f', iotests.imgfmt,
552 '-o', 'backing_file=%s' % backing_img,
553 '-F', iotests.imgfmt, child_img)
554 opts.append("children.%d.file.filename=%s" % (i, child_img))
555 opts.append("children.%d.node-name=node%d" % (i, i))
557 # Attach the drive to the VM
558 self.vm = iotests.VM()
559 self.vm.add_drive(path = None, opts = ','.join(opts))
564 for img in self.children:
566 for img in self.backing:
569 def test_stream_quorum(self):
570 self.assertNotEqual(qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.children[0]),
571 qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.backing[0]),
572 'image file map matches backing file before streaming')
574 self.assert_no_active_block_jobs()
576 result = self.vm.qmp('block-stream', device='node0', job_id='stream-node0')
577 self.assert_qmp(result, 'return', {})
579 self.wait_until_completed(drive='stream-node0')
581 self.assert_no_active_block_jobs()
584 self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', self.children[0]),
585 qemu_io('-f', iotests.imgfmt, '-c', 'map', self.backing[0]),
586 'image file map does not match backing file after streaming')
588 class TestSmallerBackingFile(iotests.QMPTestCase):
589 backing_len = 1 * 1024 * 1024 # MB
590 image_len = 2 * backing_len
593 iotests.create_image(backing_img, self.backing_len)
594 qemu_img('create', '-f', iotests.imgfmt,
595 '-o', 'backing_file=%s' % backing_img,
596 '-F', 'raw', test_img, str(self.image_len))
597 self.vm = iotests.VM().add_drive(test_img)
600 # If this hangs, then you are missing a fix to complete streaming when the
601 # end of the backing file is reached.
602 def test_stream(self):
603 self.assert_no_active_block_jobs()
605 result = self.vm.qmp('block-stream', device='drive0')
606 self.assert_qmp(result, 'return', {})
608 self.wait_until_completed()
610 self.assert_no_active_block_jobs()
613 class TestErrors(iotests.QMPTestCase):
614 image_len = 2 * 1024 * 1024 # MB
616 # this should match STREAM_BUFFER_SIZE/512 in block/stream.c
617 STREAM_BUFFER_SIZE = 512 * 1024
619 def create_blkdebug_file(self, name, event, errno):
620 file = open(name, 'w')
639 ''' % (event, errno, self.STREAM_BUFFER_SIZE // 512, event, event))
642 class TestEIO(TestErrors):
644 self.blkdebug_file = backing_img + ".blkdebug"
645 iotests.create_image(backing_img, TestErrors.image_len)
646 self.create_blkdebug_file(self.blkdebug_file, "read_aio", 5)
647 qemu_img('create', '-f', iotests.imgfmt,
648 '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw'
649 % (self.blkdebug_file, backing_img),
651 self.vm = iotests.VM().add_drive(test_img)
657 os.remove(backing_img)
658 os.remove(self.blkdebug_file)
660 def test_report(self):
661 self.assert_no_active_block_jobs()
663 result = self.vm.qmp('block-stream', device='drive0')
664 self.assert_qmp(result, 'return', {})
669 for event in self.vm.get_qmp_events(wait=True):
670 if event['event'] == 'BLOCK_JOB_ERROR':
671 self.assert_qmp(event, 'data/device', 'drive0')
672 self.assert_qmp(event, 'data/operation', 'read')
674 elif event['event'] == 'BLOCK_JOB_COMPLETED':
675 self.assertTrue(error, 'job completed unexpectedly')
676 self.assert_qmp(event, 'data/type', 'stream')
677 self.assert_qmp(event, 'data/device', 'drive0')
678 self.assert_qmp(event, 'data/error', 'Input/output error')
679 self.assert_qmp(event, 'data/offset', self.STREAM_BUFFER_SIZE)
680 self.assert_qmp(event, 'data/len', self.image_len)
682 elif event['event'] == 'JOB_STATUS_CHANGE':
683 self.assert_qmp(event, 'data/id', 'drive0')
685 self.assert_no_active_block_jobs()
688 def test_ignore(self):
689 self.assert_no_active_block_jobs()
691 result = self.vm.qmp('block-stream', device='drive0', on_error='ignore')
692 self.assert_qmp(result, 'return', {})
697 for event in self.vm.get_qmp_events(wait=True):
698 if event['event'] == 'BLOCK_JOB_ERROR':
700 self.assert_qmp(event, 'data/device', 'drive0')
701 self.assert_qmp(event, 'data/operation', 'read')
702 result = self.vm.qmp('query-block-jobs')
703 if result == {'return': []}:
704 # Job finished too quickly
706 self.assert_qmp(result, 'return[0]/paused', False)
707 elif event['event'] == 'BLOCK_JOB_COMPLETED':
708 self.assertTrue(error, 'job completed unexpectedly')
709 self.assert_qmp(event, 'data/type', 'stream')
710 self.assert_qmp(event, 'data/device', 'drive0')
711 self.assert_qmp(event, 'data/error', 'Input/output error')
712 self.assert_qmp(event, 'data/offset', self.image_len)
713 self.assert_qmp(event, 'data/len', self.image_len)
715 elif event['event'] == 'JOB_STATUS_CHANGE':
716 self.assert_qmp(event, 'data/id', 'drive0')
718 self.assert_no_active_block_jobs()
722 self.assert_no_active_block_jobs()
724 result = self.vm.qmp('block-stream', device='drive0', on_error='stop')
725 self.assert_qmp(result, 'return', {})
730 for event in self.vm.get_qmp_events(wait=True):
731 if event['event'] == 'BLOCK_JOB_ERROR':
733 self.assert_qmp(event, 'data/device', 'drive0')
734 self.assert_qmp(event, 'data/operation', 'read')
736 result = self.vm.qmp('query-block-jobs')
737 self.assert_qmp(result, 'return[0]/paused', True)
738 self.assert_qmp(result, 'return[0]/offset', self.STREAM_BUFFER_SIZE)
739 self.assert_qmp(result, 'return[0]/io-status', 'failed')
741 result = self.vm.qmp('block-job-resume', device='drive0')
742 self.assert_qmp(result, 'return', {})
744 result = self.vm.qmp('query-block-jobs')
745 if result == {'return': []}:
746 # Race; likely already finished. Check.
748 self.assert_qmp(result, 'return[0]/paused', False)
749 self.assert_qmp(result, 'return[0]/io-status', 'ok')
750 elif event['event'] == 'BLOCK_JOB_COMPLETED':
751 self.assertTrue(error, 'job completed unexpectedly')
752 self.assert_qmp(event, 'data/type', 'stream')
753 self.assert_qmp(event, 'data/device', 'drive0')
754 self.assert_qmp_absent(event, 'data/error')
755 self.assert_qmp(event, 'data/offset', self.image_len)
756 self.assert_qmp(event, 'data/len', self.image_len)
758 elif event['event'] == 'JOB_STATUS_CHANGE':
759 self.assert_qmp(event, 'data/id', 'drive0')
761 self.assert_no_active_block_jobs()
764 def test_enospc(self):
765 self.assert_no_active_block_jobs()
767 result = self.vm.qmp('block-stream', device='drive0', on_error='enospc')
768 self.assert_qmp(result, 'return', {})
773 for event in self.vm.get_qmp_events(wait=True):
774 if event['event'] == 'BLOCK_JOB_ERROR':
775 self.assert_qmp(event, 'data/device', 'drive0')
776 self.assert_qmp(event, 'data/operation', 'read')
778 elif event['event'] == 'BLOCK_JOB_COMPLETED':
779 self.assertTrue(error, 'job completed unexpectedly')
780 self.assert_qmp(event, 'data/type', 'stream')
781 self.assert_qmp(event, 'data/device', 'drive0')
782 self.assert_qmp(event, 'data/error', 'Input/output error')
783 self.assert_qmp(event, 'data/offset', self.STREAM_BUFFER_SIZE)
784 self.assert_qmp(event, 'data/len', self.image_len)
786 elif event['event'] == 'JOB_STATUS_CHANGE':
787 self.assert_qmp(event, 'data/id', 'drive0')
789 self.assert_no_active_block_jobs()
792 class TestENOSPC(TestErrors):
794 self.blkdebug_file = backing_img + ".blkdebug"
795 iotests.create_image(backing_img, TestErrors.image_len)
796 self.create_blkdebug_file(self.blkdebug_file, "read_aio", 28)
797 qemu_img('create', '-f', iotests.imgfmt,
798 '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw'
799 % (self.blkdebug_file, backing_img),
801 self.vm = iotests.VM().add_drive(test_img)
807 os.remove(backing_img)
808 os.remove(self.blkdebug_file)
810 def test_enospc(self):
811 self.assert_no_active_block_jobs()
813 result = self.vm.qmp('block-stream', device='drive0', on_error='enospc')
814 self.assert_qmp(result, 'return', {})
819 for event in self.vm.get_qmp_events(wait=True):
820 if event['event'] == 'BLOCK_JOB_ERROR':
821 self.assert_qmp(event, 'data/device', 'drive0')
822 self.assert_qmp(event, 'data/operation', 'read')
825 result = self.vm.qmp('query-block-jobs')
826 self.assert_qmp(result, 'return[0]/paused', True)
827 self.assert_qmp(result, 'return[0]/offset', self.STREAM_BUFFER_SIZE)
828 self.assert_qmp(result, 'return[0]/io-status', 'nospace')
830 result = self.vm.qmp('block-job-resume', device='drive0')
831 self.assert_qmp(result, 'return', {})
833 result = self.vm.qmp('query-block-jobs')
834 if result == {'return': []}:
835 # Race; likely already finished. Check.
837 self.assert_qmp(result, 'return[0]/paused', False)
838 self.assert_qmp(result, 'return[0]/io-status', 'ok')
839 elif event['event'] == 'BLOCK_JOB_COMPLETED':
840 self.assertTrue(error, 'job completed unexpectedly')
841 self.assert_qmp(event, 'data/type', 'stream')
842 self.assert_qmp(event, 'data/device', 'drive0')
843 self.assert_qmp_absent(event, 'data/error')
844 self.assert_qmp(event, 'data/offset', self.image_len)
845 self.assert_qmp(event, 'data/len', self.image_len)
847 elif event['event'] == 'JOB_STATUS_CHANGE':
848 self.assert_qmp(event, 'data/id', 'drive0')
850 self.assert_no_active_block_jobs()
853 class TestStreamStop(iotests.QMPTestCase):
854 image_len = 8 * 1024 * 1024 * 1024 # GB
857 qemu_img('create', backing_img, str(TestStreamStop.image_len))
858 qemu_io('-f', 'raw', '-c', 'write -P 0x1 0 32M', backing_img)
859 qemu_img('create', '-f', iotests.imgfmt,
860 '-o', 'backing_file=%s' % backing_img,
861 '-F', 'raw', test_img)
862 qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x1 32M 32M', test_img)
863 self.vm = iotests.VM().add_drive("blkdebug::" + test_img)
869 os.remove(backing_img)
871 def test_stream_stop(self):
872 self.assert_no_active_block_jobs()
874 self.vm.pause_drive('drive0')
875 result = self.vm.qmp('block-stream', device='drive0')
876 self.assert_qmp(result, 'return', {})
879 events = self.vm.get_qmp_events(wait=False)
881 self.assert_qmp(e, 'event', 'JOB_STATUS_CHANGE')
882 self.assert_qmp(e, 'data/id', 'drive0')
884 self.cancel_and_wait(resume=True)
886 class TestSetSpeed(iotests.QMPTestCase):
887 image_len = 80 * 1024 * 1024 # MB
890 qemu_img('create', backing_img, str(TestSetSpeed.image_len))
891 qemu_io('-f', 'raw', '-c', 'write -P 0x1 0 32M', backing_img)
892 qemu_img('create', '-f', iotests.imgfmt,
893 '-o', 'backing_file=%s' % backing_img,
894 '-F', 'raw', test_img)
895 qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x1 32M 32M', test_img)
896 self.vm = iotests.VM().add_drive('blkdebug::' + test_img)
902 os.remove(backing_img)
904 # This is a short performance test which is not run by default.
905 # Invoke "IMGFMT=qed ./030 TestSetSpeed.perf_test_throughput"
906 def perf_test_throughput(self):
907 self.assert_no_active_block_jobs()
909 result = self.vm.qmp('block-stream', device='drive0')
910 self.assert_qmp(result, 'return', {})
912 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024)
913 self.assert_qmp(result, 'return', {})
915 self.wait_until_completed()
917 self.assert_no_active_block_jobs()
919 def test_set_speed(self):
920 self.assert_no_active_block_jobs()
922 self.vm.pause_drive('drive0')
923 result = self.vm.qmp('block-stream', device='drive0')
924 self.assert_qmp(result, 'return', {})
927 result = self.vm.qmp('query-block-jobs')
928 self.assert_qmp(result, 'return[0]/device', 'drive0')
929 self.assert_qmp(result, 'return[0]/speed', 0)
931 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024)
932 self.assert_qmp(result, 'return', {})
934 # Ensure the speed we set was accepted
935 result = self.vm.qmp('query-block-jobs')
936 self.assert_qmp(result, 'return[0]/device', 'drive0')
937 self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024)
939 self.cancel_and_wait(resume=True)
940 self.vm.pause_drive('drive0')
942 # Check setting speed in block-stream works
943 result = self.vm.qmp('block-stream', device='drive0', speed=4 * 1024 * 1024)
944 self.assert_qmp(result, 'return', {})
946 result = self.vm.qmp('query-block-jobs')
947 self.assert_qmp(result, 'return[0]/device', 'drive0')
948 self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024)
950 self.cancel_and_wait(resume=True)
952 def test_set_speed_invalid(self):
953 self.assert_no_active_block_jobs()
955 result = self.vm.qmp('block-stream', device='drive0', speed=-1)
956 self.assert_qmp(result, 'error/desc', "Parameter 'speed' expects a non-negative value")
958 self.assert_no_active_block_jobs()
960 self.vm.pause_drive('drive0')
961 result = self.vm.qmp('block-stream', device='drive0')
962 self.assert_qmp(result, 'return', {})
964 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1)
965 self.assert_qmp(result, 'error/desc', "Parameter 'speed' expects a non-negative value")
967 self.cancel_and_wait(resume=True)
969 if __name__ == '__main__':
970 iotests.main(supported_fmts=['qcow2', 'qed'],
971 supported_protocols=['file'])