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
, '-o', 'backing_file=%s' % backing_img
, mid_img
)
36 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'backing_file=%s' % mid_img
, test_img
)
37 qemu_io('-f', 'raw', '-c', 'write -P 0x1 0 512', backing_img
)
38 qemu_io('-f', iotests
.imgfmt
, '-c', 'write -P 0x1 524288 512', mid_img
)
39 self
.vm
= iotests
.VM().add_drive("blkdebug::" + test_img
,
40 "backing.node-name=mid," +
41 "backing.backing.node-name=base")
48 os
.remove(backing_img
)
50 def test_stream(self
):
51 self
.assert_no_active_block_jobs()
53 result
= self
.vm
.qmp('block-stream', device
='drive0')
54 self
.assert_qmp(result
, 'return', {})
56 self
.wait_until_completed()
58 self
.assert_no_active_block_jobs()
61 self
.assertEqual(qemu_io('-f', 'raw', '-c', 'map', backing_img
),
62 qemu_io('-f', iotests
.imgfmt
, '-c', 'map', test_img
),
63 'image file map does not match backing file after streaming')
65 def test_stream_intermediate(self
):
66 self
.assert_no_active_block_jobs()
68 self
.assertNotEqual(qemu_io('-f', 'raw', '-rU', '-c', 'map', backing_img
),
69 qemu_io('-f', iotests
.imgfmt
, '-rU', '-c', 'map', mid_img
),
70 'image file map matches backing file before streaming')
72 result
= self
.vm
.qmp('block-stream', device
='mid', job_id
='stream-mid')
73 self
.assert_qmp(result
, 'return', {})
75 self
.wait_until_completed(drive
='stream-mid')
77 self
.assert_no_active_block_jobs()
80 self
.assertEqual(qemu_io('-f', 'raw', '-c', 'map', backing_img
),
81 qemu_io('-f', iotests
.imgfmt
, '-c', 'map', mid_img
),
82 'image file map does not match backing file after streaming')
84 def test_stream_pause(self
):
85 self
.assert_no_active_block_jobs()
87 self
.vm
.pause_drive('drive0')
88 result
= self
.vm
.qmp('block-stream', device
='drive0')
89 self
.assert_qmp(result
, 'return', {})
91 self
.pause_job('drive0', wait
=False)
92 self
.vm
.resume_drive('drive0')
93 self
.pause_wait('drive0')
95 result
= self
.vm
.qmp('query-block-jobs')
96 offset
= self
.dictpath(result
, 'return[0]/offset')
99 result
= self
.vm
.qmp('query-block-jobs')
100 self
.assert_qmp(result
, 'return[0]/offset', offset
)
102 result
= self
.vm
.qmp('block-job-resume', device
='drive0')
103 self
.assert_qmp(result
, 'return', {})
105 self
.wait_until_completed()
107 self
.assert_no_active_block_jobs()
110 self
.assertEqual(qemu_io('-f', 'raw', '-c', 'map', backing_img
),
111 qemu_io('-f', iotests
.imgfmt
, '-c', 'map', test_img
),
112 'image file map does not match backing file after streaming')
114 def test_stream_no_op(self
):
115 self
.assert_no_active_block_jobs()
117 # The image map is empty before the operation
118 empty_map
= qemu_io('-f', iotests
.imgfmt
, '-rU', '-c', 'map', test_img
)
120 # This is a no-op: no data should ever be copied from the base image
121 result
= self
.vm
.qmp('block-stream', device
='drive0', base
=mid_img
)
122 self
.assert_qmp(result
, 'return', {})
124 self
.wait_until_completed()
126 self
.assert_no_active_block_jobs()
129 self
.assertEqual(qemu_io('-f', iotests
.imgfmt
, '-c', 'map', test_img
),
130 empty_map
, 'image file map changed after a no-op')
132 def test_stream_partial(self
):
133 self
.assert_no_active_block_jobs()
135 result
= self
.vm
.qmp('block-stream', device
='drive0', base
=backing_img
)
136 self
.assert_qmp(result
, 'return', {})
138 self
.wait_until_completed()
140 self
.assert_no_active_block_jobs()
143 self
.assertEqual(qemu_io('-f', iotests
.imgfmt
, '-c', 'map', mid_img
),
144 qemu_io('-f', iotests
.imgfmt
, '-c', 'map', test_img
),
145 'image file map does not match backing file after streaming')
147 def test_device_not_found(self
):
148 result
= self
.vm
.qmp('block-stream', device
='nonexistent')
149 self
.assert_qmp(result
, 'error/desc',
150 'Cannot find device=nonexistent nor node_name=nonexistent')
152 def test_job_id_missing(self
):
153 result
= self
.vm
.qmp('block-stream', device
='mid')
154 self
.assert_qmp(result
, 'error/desc', "Invalid job ID ''")
156 def test_read_only(self
):
157 # Create a new file that we can attach (we need a read-only top)
158 with iotests
.FilePath('ro-top.img') as ro_top_path
:
159 qemu_img('create', '-f', iotests
.imgfmt
, ro_top_path
,
162 result
= self
.vm
.qmp('blockdev-add',
164 driver
=iotests
.imgfmt
,
168 'filename': ro_top_path
,
172 self
.assert_qmp(result
, 'return', {})
174 result
= self
.vm
.qmp('block-stream', job_id
='stream',
175 device
='ro-top', base_node
='base')
176 self
.assert_qmp(result
, 'error/desc', 'Block node is read-only')
178 result
= self
.vm
.qmp('blockdev-del', node_name
='ro-top')
179 self
.assert_qmp(result
, 'return', {})
182 class TestParallelOps(iotests
.QMPTestCase
):
183 num_ops
= 4 # Number of parallel block-stream operations
184 num_imgs
= num_ops
* 2 + 1
185 image_len
= num_ops
* 4 * 1024 * 1024
192 # Initialize file names and command-line options
193 for i
in range(self
.num_imgs
):
194 img_depth
= self
.num_imgs
- i
- 1
195 opts
.append("backing." * img_depth
+ "node-name=node%d" % i
)
196 self
.imgs
.append(os
.path
.join(iotests
.test_dir
, 'img-%d.img' % i
))
199 iotests
.create_image(self
.imgs
[0], self
.image_len
)
200 for i
in range(1, self
.num_imgs
):
201 qemu_img('create', '-f', iotests
.imgfmt
,
202 '-o', 'backing_file=%s' % self
.imgs
[i
-1], self
.imgs
[i
])
204 # Put data into the images we are copying data from
205 odd_img_indexes
= [x
for x
in reversed(range(self
.num_imgs
)) if x
% 2 == 1]
206 for i
in range(len(odd_img_indexes
)):
207 # Alternate between 2MB and 4MB.
208 # This way jobs will not finish in the same order they were created
209 num_mb
= 2 + 2 * (i
% 2)
210 qemu_io('-f', iotests
.imgfmt
,
211 '-c', 'write -P 0xFF %dM %dM' % (i
* 4, num_mb
),
212 self
.imgs
[odd_img_indexes
[i
]])
214 # Attach the drive to the VM
215 self
.vm
= iotests
.VM()
216 self
.vm
.add_drive(self
.imgs
[-1], ','.join(opts
))
221 for img
in self
.imgs
:
224 # Test that it's possible to run several block-stream operations
225 # in parallel in the same snapshot chain
226 def test_stream_parallel(self
):
227 self
.assert_no_active_block_jobs()
229 # Check that the maps don't match before the streaming operations
230 for i
in range(2, self
.num_imgs
, 2):
231 self
.assertNotEqual(qemu_io('-f', iotests
.imgfmt
, '-rU', '-c', 'map', self
.imgs
[i
]),
232 qemu_io('-f', iotests
.imgfmt
, '-rU', '-c', 'map', self
.imgs
[i
-1]),
233 'image file map matches backing file before streaming')
235 # Create all streaming jobs
237 for i
in range(2, self
.num_imgs
, 2):
238 node_name
= 'node%d' % i
239 job_id
= 'stream-%s' % node_name
240 pending_jobs
.append(job_id
)
241 result
= self
.vm
.qmp('block-stream', device
=node_name
, job_id
=job_id
, base
=self
.imgs
[i
-2], speed
=512*1024)
242 self
.assert_qmp(result
, 'return', {})
244 for job
in pending_jobs
:
245 result
= self
.vm
.qmp('block-job-set-speed', device
=job
, speed
=0)
246 self
.assert_qmp(result
, 'return', {})
248 # Wait for all jobs to be finished.
249 while len(pending_jobs
) > 0:
250 for event
in self
.vm
.get_qmp_events(wait
=True):
251 if event
['event'] == 'BLOCK_JOB_COMPLETED':
252 job_id
= self
.dictpath(event
, 'data/device')
253 self
.assertTrue(job_id
in pending_jobs
)
254 self
.assert_qmp_absent(event
, 'data/error')
255 pending_jobs
.remove(job_id
)
257 self
.assert_no_active_block_jobs()
260 # Check that all maps match now
261 for i
in range(2, self
.num_imgs
, 2):
262 self
.assertEqual(qemu_io('-f', iotests
.imgfmt
, '-c', 'map', self
.imgs
[i
]),
263 qemu_io('-f', iotests
.imgfmt
, '-c', 'map', self
.imgs
[i
-1]),
264 'image file map does not match backing file after streaming')
266 # Test that it's not possible to perform two block-stream
267 # operations if there are nodes involved in both.
268 def test_overlapping_1(self
):
269 self
.assert_no_active_block_jobs()
271 # Set a speed limit to make sure that this job blocks the rest
272 result
= self
.vm
.qmp('block-stream', device
='node4', job_id
='stream-node4', base
=self
.imgs
[1], speed
=1024*1024)
273 self
.assert_qmp(result
, 'return', {})
275 result
= self
.vm
.qmp('block-stream', device
='node5', job_id
='stream-node5', base
=self
.imgs
[2])
276 self
.assert_qmp(result
, 'error/desc',
277 "Node 'node4' is busy: block device is in use by block job: stream")
279 result
= self
.vm
.qmp('block-stream', device
='node3', job_id
='stream-node3', base
=self
.imgs
[2])
280 self
.assert_qmp(result
, 'error/desc',
281 "Node 'node3' is busy: block device is in use by block job: stream")
283 result
= self
.vm
.qmp('block-stream', device
='node4', job_id
='stream-node4-v2')
284 self
.assert_qmp(result
, 'error/desc',
285 "Node 'node4' is busy: block device is in use by block job: stream")
287 # block-commit should also fail if it touches nodes used by the stream job
288 result
= self
.vm
.qmp('block-commit', device
='drive0', base
=self
.imgs
[4], job_id
='commit-node4')
289 self
.assert_qmp(result
, 'error/desc',
290 "Node 'node4' is busy: block device is in use by block job: stream")
292 result
= self
.vm
.qmp('block-commit', device
='drive0', base
=self
.imgs
[1], top
=self
.imgs
[3], job_id
='commit-node1')
293 self
.assert_qmp(result
, 'error/desc',
294 "Node 'node3' is busy: block device is in use by block job: stream")
296 # This fails because it needs to modify the backing string in node2, which is blocked
297 result
= self
.vm
.qmp('block-commit', device
='drive0', base
=self
.imgs
[0], top
=self
.imgs
[1], job_id
='commit-node0')
298 self
.assert_qmp(result
, 'error/desc',
299 "Node 'node2' is busy: block device is in use by block job: stream")
301 result
= self
.vm
.qmp('block-job-set-speed', device
='stream-node4', speed
=0)
302 self
.assert_qmp(result
, 'return', {})
304 self
.wait_until_completed(drive
='stream-node4')
305 self
.assert_no_active_block_jobs()
307 # Similar to test_overlapping_1, but with block-commit
308 # blocking the other jobs
309 def test_overlapping_2(self
):
310 self
.assertLessEqual(9, self
.num_imgs
)
311 self
.assert_no_active_block_jobs()
313 # Set a speed limit to make sure that this job blocks the rest
314 result
= self
.vm
.qmp('block-commit', device
='drive0', top
=self
.imgs
[5], base
=self
.imgs
[3], job_id
='commit-node3', speed
=1024*1024)
315 self
.assert_qmp(result
, 'return', {})
317 result
= self
.vm
.qmp('block-stream', device
='node3', job_id
='stream-node3')
318 self
.assert_qmp(result
, 'error/desc',
319 "Node 'node3' is busy: block device is in use by block job: commit")
321 result
= self
.vm
.qmp('block-stream', device
='node6', base
=self
.imgs
[2], job_id
='stream-node6')
322 self
.assert_qmp(result
, 'error/desc',
323 "Node 'node5' is busy: block device is in use by block job: commit")
325 result
= self
.vm
.qmp('block-stream', device
='node4', base
=self
.imgs
[2], job_id
='stream-node4')
326 self
.assert_qmp(result
, 'error/desc',
327 "Node 'node4' is busy: block device is in use by block job: commit")
329 result
= self
.vm
.qmp('block-stream', device
='node6', base
=self
.imgs
[4], job_id
='stream-node6-v2')
330 self
.assert_qmp(result
, 'error/desc',
331 "Node 'node5' is busy: block device is in use by block job: commit")
333 # This fails because block-commit currently blocks the active layer even if it's not used
334 result
= self
.vm
.qmp('block-stream', device
='drive0', base
=self
.imgs
[5], job_id
='stream-drive0')
335 self
.assert_qmp(result
, 'error/desc',
336 "Node 'drive0' is busy: block device is in use by block job: commit")
338 result
= self
.vm
.qmp('block-job-set-speed', device
='commit-node3', speed
=0)
339 self
.assert_qmp(result
, 'return', {})
341 self
.wait_until_completed(drive
='commit-node3')
343 # Similar to test_overlapping_2, but here block-commit doesn't use the 'top' parameter.
344 # Internally this uses a mirror block job, hence the separate test case.
345 def test_overlapping_3(self
):
346 self
.assertLessEqual(8, self
.num_imgs
)
347 self
.assert_no_active_block_jobs()
349 # Set a speed limit to make sure that this job blocks the rest
350 result
= self
.vm
.qmp('block-commit', device
='drive0', base
=self
.imgs
[3], job_id
='commit-drive0', speed
=1024*1024)
351 self
.assert_qmp(result
, 'return', {})
353 result
= self
.vm
.qmp('block-stream', device
='node5', base
=self
.imgs
[3], job_id
='stream-node6')
354 self
.assert_qmp(result
, 'error/desc',
355 "Node 'node5' is busy: block device is in use by block job: commit")
357 event
= self
.vm
.event_wait(name
='BLOCK_JOB_READY')
358 self
.assert_qmp(event
, 'data/device', 'commit-drive0')
359 self
.assert_qmp(event
, 'data/type', 'commit')
360 self
.assert_qmp_absent(event
, 'data/error')
362 result
= self
.vm
.qmp('block-job-set-speed', device
='commit-drive0', speed
=0)
363 self
.assert_qmp(result
, 'return', {})
365 result
= self
.vm
.qmp('block-job-complete', device
='commit-drive0')
366 self
.assert_qmp(result
, 'return', {})
368 self
.wait_until_completed(drive
='commit-drive0')
370 # In this case the base node of the stream job is the same as the
371 # top node of commit job. Since this results in the commit filter
372 # node being part of the stream chain, this is not allowed.
373 def test_overlapping_4(self
):
374 self
.assert_no_active_block_jobs()
376 # Commit from node2 into node0
377 result
= self
.vm
.qmp('block-commit', device
='drive0',
378 top
=self
.imgs
[2], base
=self
.imgs
[0],
379 filter_node_name
='commit-filter', speed
=1024*1024)
380 self
.assert_qmp(result
, 'return', {})
382 # Stream from node2 into node4
383 result
= self
.vm
.qmp('block-stream', device
='node4', base_node
='node2', job_id
='node4')
384 self
.assert_qmp(result
, 'error/desc',
385 "Cannot freeze 'backing' link to 'commit-filter'")
387 result
= self
.vm
.qmp('block-job-set-speed', device
='drive0', speed
=0)
388 self
.assert_qmp(result
, 'return', {})
390 self
.wait_until_completed()
391 self
.assert_no_active_block_jobs()
393 # In this case the base node of the stream job is the commit job's
394 # filter node. stream does not have a real dependency on its base
395 # node, so even though commit removes it when it is done, there is
397 def test_overlapping_5(self
):
398 self
.assert_no_active_block_jobs()
400 # Commit from node2 into node0
401 result
= self
.vm
.qmp('block-commit', device
='drive0',
402 top_node
='node2', base_node
='node0',
403 filter_node_name
='commit-filter', speed
=1024*1024)
404 self
.assert_qmp(result
, 'return', {})
406 # Stream from node2 into node4
407 result
= self
.vm
.qmp('block-stream', device
='node4',
408 base_node
='commit-filter', job_id
='node4')
409 self
.assert_qmp(result
, 'return', {})
411 result
= self
.vm
.qmp('block-job-set-speed', device
='drive0', speed
=0)
412 self
.assert_qmp(result
, 'return', {})
414 self
.vm
.run_job(job
='drive0', auto_dismiss
=True, use_log
=False)
415 self
.vm
.run_job(job
='node4', auto_dismiss
=True, use_log
=False)
416 self
.assert_no_active_block_jobs()
418 # Test a block-stream and a block-commit job in parallel
419 # Here the stream job is supposed to finish quickly in order to reproduce
420 # the scenario that triggers the bug fixed in 3d5d319e1221 and 1a63a907507
421 def test_stream_commit_1(self
):
422 self
.assertLessEqual(8, self
.num_imgs
)
423 self
.assert_no_active_block_jobs()
425 # Stream from node0 into node2
426 result
= self
.vm
.qmp('block-stream', device
='node2', base_node
='node0', job_id
='node2')
427 self
.assert_qmp(result
, 'return', {})
429 # Commit from the active layer into node3
430 result
= self
.vm
.qmp('block-commit', device
='drive0', base
=self
.imgs
[3])
431 self
.assert_qmp(result
, 'return', {})
433 # Wait for all jobs to be finished.
434 pending_jobs
= ['node2', 'drive0']
435 while len(pending_jobs
) > 0:
436 for event
in self
.vm
.get_qmp_events(wait
=True):
437 if event
['event'] == 'BLOCK_JOB_COMPLETED':
438 node_name
= self
.dictpath(event
, 'data/device')
439 self
.assertTrue(node_name
in pending_jobs
)
440 self
.assert_qmp_absent(event
, 'data/error')
441 pending_jobs
.remove(node_name
)
442 if event
['event'] == 'BLOCK_JOB_READY':
443 self
.assert_qmp(event
, 'data/device', 'drive0')
444 self
.assert_qmp(event
, 'data/type', 'commit')
445 self
.assert_qmp_absent(event
, 'data/error')
446 self
.assertTrue('drive0' in pending_jobs
)
447 self
.vm
.qmp('block-job-complete', device
='drive0')
449 self
.assert_no_active_block_jobs()
451 # This is similar to test_stream_commit_1 but both jobs are slowed
452 # down so they can run in parallel for a little while.
453 def test_stream_commit_2(self
):
454 self
.assertLessEqual(8, self
.num_imgs
)
455 self
.assert_no_active_block_jobs()
457 # Stream from node0 into node4
458 result
= self
.vm
.qmp('block-stream', device
='node4', base_node
='node0', job_id
='node4', speed
=1024*1024)
459 self
.assert_qmp(result
, 'return', {})
461 # Commit from the active layer into node5
462 result
= self
.vm
.qmp('block-commit', device
='drive0', base
=self
.imgs
[5], speed
=1024*1024)
463 self
.assert_qmp(result
, 'return', {})
465 for job
in ['drive0', 'node4']:
466 result
= self
.vm
.qmp('block-job-set-speed', device
=job
, speed
=0)
467 self
.assert_qmp(result
, 'return', {})
469 # Wait for all jobs to be finished.
470 pending_jobs
= ['node4', 'drive0']
471 while len(pending_jobs
) > 0:
472 for event
in self
.vm
.get_qmp_events(wait
=True):
473 if event
['event'] == 'BLOCK_JOB_COMPLETED':
474 node_name
= self
.dictpath(event
, 'data/device')
475 self
.assertTrue(node_name
in pending_jobs
)
476 self
.assert_qmp_absent(event
, 'data/error')
477 pending_jobs
.remove(node_name
)
478 if event
['event'] == 'BLOCK_JOB_READY':
479 self
.assert_qmp(event
, 'data/device', 'drive0')
480 self
.assert_qmp(event
, 'data/type', 'commit')
481 self
.assert_qmp_absent(event
, 'data/error')
482 self
.assertTrue('drive0' in pending_jobs
)
483 self
.vm
.qmp('block-job-complete', device
='drive0')
485 self
.assert_no_active_block_jobs()
487 # Test the base_node parameter
488 def test_stream_base_node_name(self
):
489 self
.assert_no_active_block_jobs()
491 self
.assertNotEqual(qemu_io('-f', iotests
.imgfmt
, '-rU', '-c', 'map', self
.imgs
[4]),
492 qemu_io('-f', iotests
.imgfmt
, '-rU', '-c', 'map', self
.imgs
[3]),
493 'image file map matches backing file before streaming')
495 # Error: the base node does not exist
496 result
= self
.vm
.qmp('block-stream', device
='node4', base_node
='none', job_id
='stream')
497 self
.assert_qmp(result
, 'error/desc',
498 'Cannot find device= nor node_name=none')
500 # Error: the base node is not a backing file of the top node
501 result
= self
.vm
.qmp('block-stream', device
='node4', base_node
='node6', job_id
='stream')
502 self
.assert_qmp(result
, 'error/desc',
503 "Node 'node6' is not a backing image of 'node4'")
505 # Error: the base node is the same as the top node
506 result
= self
.vm
.qmp('block-stream', device
='node4', base_node
='node4', job_id
='stream')
507 self
.assert_qmp(result
, 'error/desc',
508 "Node 'node4' is not a backing image of 'node4'")
510 # Error: cannot specify 'base' and 'base-node' at the same time
511 result
= self
.vm
.qmp('block-stream', device
='node4', base
=self
.imgs
[2], base_node
='node2', job_id
='stream')
512 self
.assert_qmp(result
, 'error/desc',
513 "'base' and 'base-node' cannot be specified at the same time")
515 # Success: the base node is a backing file of the top node
516 result
= self
.vm
.qmp('block-stream', device
='node4', base_node
='node2', job_id
='stream')
517 self
.assert_qmp(result
, 'return', {})
519 self
.wait_until_completed(drive
='stream')
521 self
.assert_no_active_block_jobs()
524 self
.assertEqual(qemu_io('-f', iotests
.imgfmt
, '-c', 'map', self
.imgs
[4]),
525 qemu_io('-f', iotests
.imgfmt
, '-c', 'map', self
.imgs
[3]),
526 'image file map matches backing file after streaming')
528 class TestQuorum(iotests
.QMPTestCase
):
534 opts
= ['driver=quorum', 'vote-threshold=2']
536 # Initialize file names and command-line options
537 for i
in range(self
.num_children
):
538 child_img
= os
.path
.join(iotests
.test_dir
, 'img-%d.img' % i
)
539 backing_img
= os
.path
.join(iotests
.test_dir
, 'backing-%d.img' % i
)
540 self
.children
.append(child_img
)
541 self
.backing
.append(backing_img
)
542 qemu_img('create', '-f', iotests
.imgfmt
, backing_img
, '1M')
543 qemu_io('-f', iotests
.imgfmt
,
544 '-c', 'write -P 0x55 0 1024', backing_img
)
545 qemu_img('create', '-f', iotests
.imgfmt
,
546 '-o', 'backing_file=%s' % backing_img
, child_img
)
547 opts
.append("children.%d.file.filename=%s" % (i
, child_img
))
548 opts
.append("children.%d.node-name=node%d" % (i
, i
))
550 # Attach the drive to the VM
551 self
.vm
= iotests
.VM()
552 self
.vm
.add_drive(path
= None, opts
= ','.join(opts
))
557 for img
in self
.children
:
559 for img
in self
.backing
:
562 def test_stream_quorum(self
):
563 if not iotests
.supports_quorum():
566 self
.assertNotEqual(qemu_io('-f', iotests
.imgfmt
, '-rU', '-c', 'map', self
.children
[0]),
567 qemu_io('-f', iotests
.imgfmt
, '-rU', '-c', 'map', self
.backing
[0]),
568 'image file map matches backing file before streaming')
570 self
.assert_no_active_block_jobs()
572 result
= self
.vm
.qmp('block-stream', device
='node0', job_id
='stream-node0')
573 self
.assert_qmp(result
, 'return', {})
575 self
.wait_until_completed(drive
='stream-node0')
577 self
.assert_no_active_block_jobs()
580 self
.assertEqual(qemu_io('-f', iotests
.imgfmt
, '-c', 'map', self
.children
[0]),
581 qemu_io('-f', iotests
.imgfmt
, '-c', 'map', self
.backing
[0]),
582 'image file map does not match backing file after streaming')
584 class TestSmallerBackingFile(iotests
.QMPTestCase
):
585 backing_len
= 1 * 1024 * 1024 # MB
586 image_len
= 2 * backing_len
589 iotests
.create_image(backing_img
, self
.backing_len
)
590 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'backing_file=%s' % backing_img
, test_img
, str(self
.image_len
))
591 self
.vm
= iotests
.VM().add_drive(test_img
)
594 # If this hangs, then you are missing a fix to complete streaming when the
595 # end of the backing file is reached.
596 def test_stream(self
):
597 self
.assert_no_active_block_jobs()
599 result
= self
.vm
.qmp('block-stream', device
='drive0')
600 self
.assert_qmp(result
, 'return', {})
602 self
.wait_until_completed()
604 self
.assert_no_active_block_jobs()
607 class TestErrors(iotests
.QMPTestCase
):
608 image_len
= 2 * 1024 * 1024 # MB
610 # this should match STREAM_BUFFER_SIZE/512 in block/stream.c
611 STREAM_BUFFER_SIZE
= 512 * 1024
613 def create_blkdebug_file(self
, name
, event
, errno
):
614 file = open(name
, 'w')
633 ''' % (event
, errno
, self
.STREAM_BUFFER_SIZE
// 512, event
, event
))
636 class TestEIO(TestErrors
):
638 self
.blkdebug_file
= backing_img
+ ".blkdebug"
639 iotests
.create_image(backing_img
, TestErrors
.image_len
)
640 self
.create_blkdebug_file(self
.blkdebug_file
, "read_aio", 5)
641 qemu_img('create', '-f', iotests
.imgfmt
,
642 '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw'
643 % (self
.blkdebug_file
, backing_img
),
645 self
.vm
= iotests
.VM().add_drive(test_img
)
651 os
.remove(backing_img
)
652 os
.remove(self
.blkdebug_file
)
654 def test_report(self
):
655 self
.assert_no_active_block_jobs()
657 result
= self
.vm
.qmp('block-stream', device
='drive0')
658 self
.assert_qmp(result
, 'return', {})
663 for event
in self
.vm
.get_qmp_events(wait
=True):
664 if event
['event'] == 'BLOCK_JOB_ERROR':
665 self
.assert_qmp(event
, 'data/device', 'drive0')
666 self
.assert_qmp(event
, 'data/operation', 'read')
668 elif event
['event'] == 'BLOCK_JOB_COMPLETED':
669 self
.assertTrue(error
, 'job completed unexpectedly')
670 self
.assert_qmp(event
, 'data/type', 'stream')
671 self
.assert_qmp(event
, 'data/device', 'drive0')
672 self
.assert_qmp(event
, 'data/error', 'Input/output error')
673 self
.assert_qmp(event
, 'data/offset', self
.STREAM_BUFFER_SIZE
)
674 self
.assert_qmp(event
, 'data/len', self
.image_len
)
676 elif event
['event'] == 'JOB_STATUS_CHANGE':
677 self
.assert_qmp(event
, 'data/id', 'drive0')
679 self
.assert_no_active_block_jobs()
682 def test_ignore(self
):
683 self
.assert_no_active_block_jobs()
685 result
= self
.vm
.qmp('block-stream', device
='drive0', on_error
='ignore')
686 self
.assert_qmp(result
, 'return', {})
691 for event
in self
.vm
.get_qmp_events(wait
=True):
692 if event
['event'] == 'BLOCK_JOB_ERROR':
694 self
.assert_qmp(event
, 'data/device', 'drive0')
695 self
.assert_qmp(event
, 'data/operation', 'read')
696 result
= self
.vm
.qmp('query-block-jobs')
697 if result
== {'return': []}:
698 # Job finished too quickly
700 self
.assert_qmp(result
, 'return[0]/paused', False)
701 elif event
['event'] == 'BLOCK_JOB_COMPLETED':
702 self
.assertTrue(error
, 'job completed unexpectedly')
703 self
.assert_qmp(event
, 'data/type', 'stream')
704 self
.assert_qmp(event
, 'data/device', 'drive0')
705 self
.assert_qmp(event
, 'data/error', 'Input/output error')
706 self
.assert_qmp(event
, 'data/offset', self
.image_len
)
707 self
.assert_qmp(event
, 'data/len', self
.image_len
)
709 elif event
['event'] == 'JOB_STATUS_CHANGE':
710 self
.assert_qmp(event
, 'data/id', 'drive0')
712 self
.assert_no_active_block_jobs()
716 self
.assert_no_active_block_jobs()
718 result
= self
.vm
.qmp('block-stream', device
='drive0', on_error
='stop')
719 self
.assert_qmp(result
, 'return', {})
724 for event
in self
.vm
.get_qmp_events(wait
=True):
725 if event
['event'] == 'BLOCK_JOB_ERROR':
727 self
.assert_qmp(event
, 'data/device', 'drive0')
728 self
.assert_qmp(event
, 'data/operation', 'read')
730 result
= self
.vm
.qmp('query-block-jobs')
731 self
.assert_qmp(result
, 'return[0]/paused', True)
732 self
.assert_qmp(result
, 'return[0]/offset', self
.STREAM_BUFFER_SIZE
)
733 self
.assert_qmp(result
, 'return[0]/io-status', 'failed')
735 result
= self
.vm
.qmp('block-job-resume', device
='drive0')
736 self
.assert_qmp(result
, 'return', {})
738 result
= self
.vm
.qmp('query-block-jobs')
739 if result
== {'return': []}:
740 # Race; likely already finished. Check.
742 self
.assert_qmp(result
, 'return[0]/paused', False)
743 self
.assert_qmp(result
, 'return[0]/io-status', 'ok')
744 elif event
['event'] == 'BLOCK_JOB_COMPLETED':
745 self
.assertTrue(error
, 'job completed unexpectedly')
746 self
.assert_qmp(event
, 'data/type', 'stream')
747 self
.assert_qmp(event
, 'data/device', 'drive0')
748 self
.assert_qmp_absent(event
, 'data/error')
749 self
.assert_qmp(event
, 'data/offset', self
.image_len
)
750 self
.assert_qmp(event
, 'data/len', self
.image_len
)
752 elif event
['event'] == 'JOB_STATUS_CHANGE':
753 self
.assert_qmp(event
, 'data/id', 'drive0')
755 self
.assert_no_active_block_jobs()
758 def test_enospc(self
):
759 self
.assert_no_active_block_jobs()
761 result
= self
.vm
.qmp('block-stream', device
='drive0', on_error
='enospc')
762 self
.assert_qmp(result
, 'return', {})
767 for event
in self
.vm
.get_qmp_events(wait
=True):
768 if event
['event'] == 'BLOCK_JOB_ERROR':
769 self
.assert_qmp(event
, 'data/device', 'drive0')
770 self
.assert_qmp(event
, 'data/operation', 'read')
772 elif event
['event'] == 'BLOCK_JOB_COMPLETED':
773 self
.assertTrue(error
, 'job completed unexpectedly')
774 self
.assert_qmp(event
, 'data/type', 'stream')
775 self
.assert_qmp(event
, 'data/device', 'drive0')
776 self
.assert_qmp(event
, 'data/error', 'Input/output error')
777 self
.assert_qmp(event
, 'data/offset', self
.STREAM_BUFFER_SIZE
)
778 self
.assert_qmp(event
, 'data/len', self
.image_len
)
780 elif event
['event'] == 'JOB_STATUS_CHANGE':
781 self
.assert_qmp(event
, 'data/id', 'drive0')
783 self
.assert_no_active_block_jobs()
786 class TestENOSPC(TestErrors
):
788 self
.blkdebug_file
= backing_img
+ ".blkdebug"
789 iotests
.create_image(backing_img
, TestErrors
.image_len
)
790 self
.create_blkdebug_file(self
.blkdebug_file
, "read_aio", 28)
791 qemu_img('create', '-f', iotests
.imgfmt
,
792 '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw'
793 % (self
.blkdebug_file
, backing_img
),
795 self
.vm
= iotests
.VM().add_drive(test_img
)
801 os
.remove(backing_img
)
802 os
.remove(self
.blkdebug_file
)
804 def test_enospc(self
):
805 self
.assert_no_active_block_jobs()
807 result
= self
.vm
.qmp('block-stream', device
='drive0', on_error
='enospc')
808 self
.assert_qmp(result
, 'return', {})
813 for event
in self
.vm
.get_qmp_events(wait
=True):
814 if event
['event'] == 'BLOCK_JOB_ERROR':
815 self
.assert_qmp(event
, 'data/device', 'drive0')
816 self
.assert_qmp(event
, 'data/operation', 'read')
819 result
= self
.vm
.qmp('query-block-jobs')
820 self
.assert_qmp(result
, 'return[0]/paused', True)
821 self
.assert_qmp(result
, 'return[0]/offset', self
.STREAM_BUFFER_SIZE
)
822 self
.assert_qmp(result
, 'return[0]/io-status', 'nospace')
824 result
= self
.vm
.qmp('block-job-resume', device
='drive0')
825 self
.assert_qmp(result
, 'return', {})
827 result
= self
.vm
.qmp('query-block-jobs')
828 if result
== {'return': []}:
829 # Race; likely already finished. Check.
831 self
.assert_qmp(result
, 'return[0]/paused', False)
832 self
.assert_qmp(result
, 'return[0]/io-status', 'ok')
833 elif event
['event'] == 'BLOCK_JOB_COMPLETED':
834 self
.assertTrue(error
, 'job completed unexpectedly')
835 self
.assert_qmp(event
, 'data/type', 'stream')
836 self
.assert_qmp(event
, 'data/device', 'drive0')
837 self
.assert_qmp_absent(event
, 'data/error')
838 self
.assert_qmp(event
, 'data/offset', self
.image_len
)
839 self
.assert_qmp(event
, 'data/len', self
.image_len
)
841 elif event
['event'] == 'JOB_STATUS_CHANGE':
842 self
.assert_qmp(event
, 'data/id', 'drive0')
844 self
.assert_no_active_block_jobs()
847 class TestStreamStop(iotests
.QMPTestCase
):
848 image_len
= 8 * 1024 * 1024 * 1024 # GB
851 qemu_img('create', backing_img
, str(TestStreamStop
.image_len
))
852 qemu_io('-f', 'raw', '-c', 'write -P 0x1 0 32M', backing_img
)
853 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'backing_file=%s' % backing_img
, test_img
)
854 qemu_io('-f', iotests
.imgfmt
, '-c', 'write -P 0x1 32M 32M', test_img
)
855 self
.vm
= iotests
.VM().add_drive("blkdebug::" + test_img
)
861 os
.remove(backing_img
)
863 def test_stream_stop(self
):
864 self
.assert_no_active_block_jobs()
866 self
.vm
.pause_drive('drive0')
867 result
= self
.vm
.qmp('block-stream', device
='drive0')
868 self
.assert_qmp(result
, 'return', {})
871 events
= self
.vm
.get_qmp_events(wait
=False)
873 self
.assert_qmp(e
, 'event', 'JOB_STATUS_CHANGE')
874 self
.assert_qmp(e
, 'data/id', 'drive0')
876 self
.cancel_and_wait(resume
=True)
878 class TestSetSpeed(iotests
.QMPTestCase
):
879 image_len
= 80 * 1024 * 1024 # MB
882 qemu_img('create', backing_img
, str(TestSetSpeed
.image_len
))
883 qemu_io('-f', 'raw', '-c', 'write -P 0x1 0 32M', backing_img
)
884 qemu_img('create', '-f', iotests
.imgfmt
, '-o', 'backing_file=%s' % backing_img
, test_img
)
885 qemu_io('-f', iotests
.imgfmt
, '-c', 'write -P 0x1 32M 32M', test_img
)
886 self
.vm
= iotests
.VM().add_drive('blkdebug::' + test_img
)
892 os
.remove(backing_img
)
894 # This is a short performance test which is not run by default.
895 # Invoke "IMGFMT=qed ./030 TestSetSpeed.perf_test_throughput"
896 def perf_test_throughput(self
):
897 self
.assert_no_active_block_jobs()
899 result
= self
.vm
.qmp('block-stream', device
='drive0')
900 self
.assert_qmp(result
, 'return', {})
902 result
= self
.vm
.qmp('block-job-set-speed', device
='drive0', speed
=8 * 1024 * 1024)
903 self
.assert_qmp(result
, 'return', {})
905 self
.wait_until_completed()
907 self
.assert_no_active_block_jobs()
909 def test_set_speed(self
):
910 self
.assert_no_active_block_jobs()
912 self
.vm
.pause_drive('drive0')
913 result
= self
.vm
.qmp('block-stream', device
='drive0')
914 self
.assert_qmp(result
, 'return', {})
917 result
= self
.vm
.qmp('query-block-jobs')
918 self
.assert_qmp(result
, 'return[0]/device', 'drive0')
919 self
.assert_qmp(result
, 'return[0]/speed', 0)
921 result
= self
.vm
.qmp('block-job-set-speed', device
='drive0', speed
=8 * 1024 * 1024)
922 self
.assert_qmp(result
, 'return', {})
924 # Ensure the speed we set was accepted
925 result
= self
.vm
.qmp('query-block-jobs')
926 self
.assert_qmp(result
, 'return[0]/device', 'drive0')
927 self
.assert_qmp(result
, 'return[0]/speed', 8 * 1024 * 1024)
929 self
.cancel_and_wait(resume
=True)
930 self
.vm
.pause_drive('drive0')
932 # Check setting speed in block-stream works
933 result
= self
.vm
.qmp('block-stream', device
='drive0', speed
=4 * 1024 * 1024)
934 self
.assert_qmp(result
, 'return', {})
936 result
= self
.vm
.qmp('query-block-jobs')
937 self
.assert_qmp(result
, 'return[0]/device', 'drive0')
938 self
.assert_qmp(result
, 'return[0]/speed', 4 * 1024 * 1024)
940 self
.cancel_and_wait(resume
=True)
942 def test_set_speed_invalid(self
):
943 self
.assert_no_active_block_jobs()
945 result
= self
.vm
.qmp('block-stream', device
='drive0', speed
=-1)
946 self
.assert_qmp(result
, 'error/desc', "Invalid parameter 'speed'")
948 self
.assert_no_active_block_jobs()
950 self
.vm
.pause_drive('drive0')
951 result
= self
.vm
.qmp('block-stream', device
='drive0')
952 self
.assert_qmp(result
, 'return', {})
954 result
= self
.vm
.qmp('block-job-set-speed', device
='drive0', speed
=-1)
955 self
.assert_qmp(result
, 'error/desc', "Invalid parameter 'speed'")
957 self
.cancel_and_wait(resume
=True)
959 if __name__
== '__main__':
960 iotests
.main(supported_fmts
=['qcow2', 'qed'],
961 supported_protocols
=['file'])