3 # Tests for drive-backup and blockdev-backup
5 # Copyright (C) 2013, 2014 Red Hat, Inc.
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 from iotests
import qemu_img
, qemu_io
28 test_img
= os
.path
.join(iotests
.test_dir
, 'test.img')
29 target_img
= os
.path
.join(iotests
.test_dir
, 'target.img')
30 blockdev_target_img
= os
.path
.join(iotests
.test_dir
, 'blockdev-target.img')
32 image_len
= 64 * 1024 * 1024 # MB
35 qemu_img('create', '-f', iotests
.imgfmt
, test_img
, str(image_len
))
36 qemu_io('-f', iotests
.imgfmt
, '-c', 'write -P0x11 0 64k', test_img
)
37 qemu_io('-f', iotests
.imgfmt
, '-c', 'write -P0x00 64k 128k', test_img
)
38 qemu_io('-f', iotests
.imgfmt
, '-c', 'write -P0x22 162k 32k', test_img
)
39 qemu_io('-f', iotests
.imgfmt
, '-c', 'write -P0xd5 1M 32k', test_img
)
40 qemu_io('-f', iotests
.imgfmt
, '-c', 'write -P0xdc 32M 124k', test_img
)
41 qemu_io('-f', iotests
.imgfmt
, '-c', 'write -P0x33 67043328 64k', test_img
)
47 class TestSingleDrive(iotests
.QMPTestCase
):
49 qemu_img('create', '-f', iotests
.imgfmt
, blockdev_target_img
, str(image_len
))
51 self
.vm
= iotests
.VM().add_drive('blkdebug::' + test_img
)
52 self
.vm
.add_drive(blockdev_target_img
, interface
="none")
53 if iotests
.qemu_default_machine
== 'pc':
54 self
.vm
.add_drive(None, 'media=cdrom', 'ide')
59 os
.remove(blockdev_target_img
)
65 def do_test_cancel(self
, cmd
, target
):
66 self
.assert_no_active_block_jobs()
68 self
.vm
.pause_drive('drive0')
69 result
= self
.vm
.qmp(cmd
, device
='drive0', target
=target
, sync
='full')
70 self
.assert_qmp(result
, 'return', {})
72 event
= self
.cancel_and_wait(resume
=True)
73 self
.assert_qmp(event
, 'data/type', 'backup')
75 def test_cancel_drive_backup(self
):
76 self
.do_test_cancel('drive-backup', target_img
)
78 def test_cancel_blockdev_backup(self
):
79 self
.do_test_cancel('blockdev-backup', 'drive1')
81 def do_test_pause(self
, cmd
, target
, image
):
82 self
.assert_no_active_block_jobs()
84 self
.vm
.pause_drive('drive0')
85 result
= self
.vm
.qmp(cmd
, device
='drive0',
86 target
=target
, sync
='full')
87 self
.assert_qmp(result
, 'return', {})
89 result
= self
.vm
.qmp('block-job-pause', device
='drive0')
90 self
.assert_qmp(result
, 'return', {})
92 self
.vm
.resume_drive('drive0')
93 self
.pause_job('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()
108 self
.assertTrue(iotests
.compare_images(test_img
, image
),
109 'target image does not match source after backup')
111 def test_pause_drive_backup(self
):
112 self
.do_test_pause('drive-backup', target_img
, target_img
)
114 def test_pause_blockdev_backup(self
):
115 self
.do_test_pause('blockdev-backup', 'drive1', blockdev_target_img
)
117 def test_medium_not_found(self
):
118 if iotests
.qemu_default_machine
!= 'pc':
121 result
= self
.vm
.qmp('drive-backup', device
='drive2', # CD-ROM
122 target
=target_img
, sync
='full')
123 self
.assert_qmp(result
, 'error/class', 'GenericError')
125 def test_medium_not_found_blockdev_backup(self
):
126 if iotests
.qemu_default_machine
!= 'pc':
129 result
= self
.vm
.qmp('blockdev-backup', device
='drive2', # CD-ROM
130 target
='drive1', sync
='full')
131 self
.assert_qmp(result
, 'error/class', 'GenericError')
133 def test_image_not_found(self
):
134 result
= self
.vm
.qmp('drive-backup', device
='drive0',
135 target
=target_img
, sync
='full', mode
='existing')
136 self
.assert_qmp(result
, 'error/class', 'GenericError')
138 def test_invalid_format(self
):
139 result
= self
.vm
.qmp('drive-backup', device
='drive0',
140 target
=target_img
, sync
='full',
141 format
='spaghetti-noodles')
142 self
.assert_qmp(result
, 'error/class', 'GenericError')
144 def do_test_device_not_found(self
, cmd
, **args
):
145 result
= self
.vm
.qmp(cmd
, **args
)
146 self
.assert_qmp(result
, 'error/class', 'GenericError')
148 def test_device_not_found(self
):
149 self
.do_test_device_not_found('drive-backup', device
='nonexistent',
150 target
=target_img
, sync
='full')
152 self
.do_test_device_not_found('blockdev-backup', device
='nonexistent',
153 target
='drive0', sync
='full')
155 self
.do_test_device_not_found('blockdev-backup', device
='drive0',
156 target
='nonexistent', sync
='full')
158 self
.do_test_device_not_found('blockdev-backup', device
='nonexistent',
159 target
='nonexistent', sync
='full')
161 def test_target_is_source(self
):
162 result
= self
.vm
.qmp('blockdev-backup', device
='drive0',
163 target
='drive0', sync
='full')
164 self
.assert_qmp(result
, 'error/class', 'GenericError')
166 class TestSetSpeed(iotests
.QMPTestCase
):
168 qemu_img('create', '-f', iotests
.imgfmt
, blockdev_target_img
, str(image_len
))
170 self
.vm
= iotests
.VM().add_drive('blkdebug::' + test_img
)
171 self
.vm
.add_drive(blockdev_target_img
, interface
="none")
176 os
.remove(blockdev_target_img
)
178 os
.remove(target_img
)
182 def do_test_set_speed(self
, cmd
, target
):
183 self
.assert_no_active_block_jobs()
185 self
.vm
.pause_drive('drive0')
186 result
= self
.vm
.qmp(cmd
, device
='drive0', target
=target
, sync
='full')
187 self
.assert_qmp(result
, 'return', {})
190 result
= self
.vm
.qmp('query-block-jobs')
191 self
.assert_qmp(result
, 'return[0]/device', 'drive0')
192 self
.assert_qmp(result
, 'return[0]/speed', 0)
194 result
= self
.vm
.qmp('block-job-set-speed', device
='drive0', speed
=8 * 1024 * 1024)
195 self
.assert_qmp(result
, 'return', {})
197 # Ensure the speed we set was accepted
198 result
= self
.vm
.qmp('query-block-jobs')
199 self
.assert_qmp(result
, 'return[0]/device', 'drive0')
200 self
.assert_qmp(result
, 'return[0]/speed', 8 * 1024 * 1024)
202 event
= self
.cancel_and_wait(resume
=True)
203 self
.assert_qmp(event
, 'data/type', 'backup')
205 # Check setting speed option works
206 self
.vm
.pause_drive('drive0')
207 result
= self
.vm
.qmp(cmd
, device
='drive0',
208 target
=target
, sync
='full', speed
=4*1024*1024)
209 self
.assert_qmp(result
, 'return', {})
211 result
= self
.vm
.qmp('query-block-jobs')
212 self
.assert_qmp(result
, 'return[0]/device', 'drive0')
213 self
.assert_qmp(result
, 'return[0]/speed', 4 * 1024 * 1024)
215 event
= self
.cancel_and_wait(resume
=True)
216 self
.assert_qmp(event
, 'data/type', 'backup')
218 def test_set_speed_drive_backup(self
):
219 self
.do_test_set_speed('drive-backup', target_img
)
221 def test_set_speed_blockdev_backup(self
):
222 self
.do_test_set_speed('blockdev-backup', 'drive1')
224 def do_test_set_speed_invalid(self
, cmd
, target
):
225 self
.assert_no_active_block_jobs()
227 result
= self
.vm
.qmp(cmd
, device
='drive0',
228 target
=target
, sync
='full', speed
=-1)
229 self
.assert_qmp(result
, 'error/class', 'GenericError')
231 self
.assert_no_active_block_jobs()
233 self
.vm
.pause_drive('drive0')
234 result
= self
.vm
.qmp(cmd
, device
='drive0',
235 target
=target
, sync
='full')
236 self
.assert_qmp(result
, 'return', {})
238 result
= self
.vm
.qmp('block-job-set-speed', device
='drive0', speed
=-1)
239 self
.assert_qmp(result
, 'error/class', 'GenericError')
241 event
= self
.cancel_and_wait(resume
=True)
242 self
.assert_qmp(event
, 'data/type', 'backup')
244 def test_set_speed_invalid_drive_backup(self
):
245 self
.do_test_set_speed_invalid('drive-backup', target_img
)
247 def test_set_speed_invalid_blockdev_backup(self
):
248 self
.do_test_set_speed_invalid('blockdev-backup', 'drive1')
250 # Note: We cannot use pause_drive() here, or the transaction command
251 # would stall. Instead, we limit the block job speed here.
252 class TestSingleTransaction(iotests
.QMPTestCase
):
254 qemu_img('create', '-f', iotests
.imgfmt
, blockdev_target_img
, str(image_len
))
256 self
.vm
= iotests
.VM().add_drive(test_img
)
257 self
.vm
.add_drive(blockdev_target_img
, interface
="none")
258 if iotests
.qemu_default_machine
== 'pc':
259 self
.vm
.add_drive(None, 'media=cdrom', 'ide')
264 os
.remove(blockdev_target_img
)
266 os
.remove(target_img
)
270 def do_test_cancel(self
, cmd
, target
):
271 self
.assert_no_active_block_jobs()
273 result
= self
.vm
.qmp('transaction', actions
=[{
275 'data': { 'device': 'drive0',
278 'speed': 64 * 1024 },
282 self
.assert_qmp(result
, 'return', {})
284 event
= self
.cancel_and_wait()
285 self
.assert_qmp(event
, 'data/type', 'backup')
287 def test_cancel_drive_backup(self
):
288 self
.do_test_cancel('drive-backup', target_img
)
290 def test_cancel_blockdev_backup(self
):
291 self
.do_test_cancel('blockdev-backup', 'drive1')
293 def do_test_pause(self
, cmd
, target
, image
):
294 self
.assert_no_active_block_jobs()
296 result
= self
.vm
.qmp('transaction', actions
=[{
298 'data': { 'device': 'drive0',
301 'speed': 64 * 1024 },
304 self
.assert_qmp(result
, 'return', {})
306 result
= self
.vm
.qmp('block-job-pause', device
='drive0')
307 self
.assert_qmp(result
, 'return', {})
309 result
= self
.vm
.qmp('block-job-set-speed', device
='drive0', speed
=0)
310 self
.assert_qmp(result
, 'return', {})
312 self
.pause_job('drive0')
314 result
= self
.vm
.qmp('query-block-jobs')
315 offset
= self
.dictpath(result
, 'return[0]/offset')
318 result
= self
.vm
.qmp('query-block-jobs')
319 self
.assert_qmp(result
, 'return[0]/offset', offset
)
321 result
= self
.vm
.qmp('block-job-resume', device
='drive0')
322 self
.assert_qmp(result
, 'return', {})
324 self
.wait_until_completed()
327 self
.assertTrue(iotests
.compare_images(test_img
, image
),
328 'target image does not match source after backup')
330 def test_pause_drive_backup(self
):
331 self
.do_test_pause('drive-backup', target_img
, target_img
)
333 def test_pause_blockdev_backup(self
):
334 self
.do_test_pause('blockdev-backup', 'drive1', blockdev_target_img
)
336 def do_test_medium_not_found(self
, cmd
, target
):
337 if iotests
.qemu_default_machine
!= 'pc':
340 result
= self
.vm
.qmp('transaction', actions
=[{
342 'data': { 'device': 'drive2', # CD-ROM
347 self
.assert_qmp(result
, 'error/class', 'GenericError')
349 def test_medium_not_found_drive_backup(self
):
350 self
.do_test_medium_not_found('drive-backup', target_img
)
352 def test_medium_not_found_blockdev_backup(self
):
353 self
.do_test_medium_not_found('blockdev-backup', 'drive1')
355 def test_image_not_found(self
):
356 result
= self
.vm
.qmp('transaction', actions
=[{
357 'type': 'drive-backup',
358 'data': { 'device': 'drive0',
360 'target': target_img
,
364 self
.assert_qmp(result
, 'error/class', 'GenericError')
366 def test_device_not_found(self
):
367 result
= self
.vm
.qmp('transaction', actions
=[{
368 'type': 'drive-backup',
369 'data': { 'device': 'nonexistent',
371 'target': target_img
,
375 self
.assert_qmp(result
, 'error/class', 'GenericError')
377 result
= self
.vm
.qmp('transaction', actions
=[{
378 'type': 'blockdev-backup',
379 'data': { 'device': 'nonexistent',
384 self
.assert_qmp(result
, 'error/class', 'GenericError')
386 result
= self
.vm
.qmp('transaction', actions
=[{
387 'type': 'blockdev-backup',
388 'data': { 'device': 'drive0',
389 'target': 'nonexistent',
393 self
.assert_qmp(result
, 'error/class', 'GenericError')
395 result
= self
.vm
.qmp('transaction', actions
=[{
396 'type': 'blockdev-backup',
397 'data': { 'device': 'nonexistent',
398 'target': 'nonexistent',
402 self
.assert_qmp(result
, 'error/class', 'GenericError')
404 def test_target_is_source(self
):
405 result
= self
.vm
.qmp('transaction', actions
=[{
406 'type': 'blockdev-backup',
407 'data': { 'device': 'drive0',
412 self
.assert_qmp(result
, 'error/class', 'GenericError')
414 def test_abort(self
):
415 result
= self
.vm
.qmp('transaction', actions
=[{
416 'type': 'drive-backup',
417 'data': { 'device': 'nonexistent',
419 'target': target_img
,
426 self
.assert_qmp(result
, 'error/class', 'GenericError')
427 self
.assert_no_active_block_jobs()
429 result
= self
.vm
.qmp('transaction', actions
=[{
430 'type': 'blockdev-backup',
431 'data': { 'device': 'nonexistent',
439 self
.assert_qmp(result
, 'error/class', 'GenericError')
440 self
.assert_no_active_block_jobs()
442 result
= self
.vm
.qmp('transaction', actions
=[{
443 'type': 'blockdev-backup',
444 'data': { 'device': 'drive0',
445 'target': 'nonexistent',
452 self
.assert_qmp(result
, 'error/class', 'GenericError')
453 self
.assert_no_active_block_jobs()
456 class TestDriveCompression(iotests
.QMPTestCase
):
457 image_len
= 64 * 1024 * 1024 # MB
458 fmt_supports_compression
= [{'type': 'qcow2', 'args': ()},
459 {'type': 'vmdk', 'args': ('-o', 'subformat=streamOptimized')}]
463 os
.remove(blockdev_target_img
)
465 os
.remove(target_img
)
469 def do_prepare_drives(self
, fmt
, args
, attach_target
):
470 self
.vm
= iotests
.VM().add_drive('blkdebug::' + test_img
)
472 qemu_img('create', '-f', fmt
, blockdev_target_img
,
473 str(TestDriveCompression
.image_len
), *args
)
475 self
.vm
.add_drive(blockdev_target_img
, format
=fmt
, interface
="none")
479 def do_test_compress_complete(self
, cmd
, format
, attach_target
, **args
):
480 self
.do_prepare_drives(format
['type'], format
['args'], attach_target
)
482 self
.assert_no_active_block_jobs()
484 result
= self
.vm
.qmp(cmd
, device
='drive0', sync
='full', compress
=True, **args
)
485 self
.assert_qmp(result
, 'return', {})
487 self
.wait_until_completed()
490 self
.assertTrue(iotests
.compare_images(test_img
, blockdev_target_img
,
491 iotests
.imgfmt
, format
['type']),
492 'target image does not match source after backup')
494 def test_complete_compress_drive_backup(self
):
495 for format
in TestDriveCompression
.fmt_supports_compression
:
496 self
.do_test_compress_complete('drive-backup', format
, False,
497 target
=blockdev_target_img
, mode
='existing')
499 def test_complete_compress_blockdev_backup(self
):
500 for format
in TestDriveCompression
.fmt_supports_compression
:
501 self
.do_test_compress_complete('blockdev-backup', format
, True,
504 def do_test_compress_cancel(self
, cmd
, format
, attach_target
, **args
):
505 self
.do_prepare_drives(format
['type'], format
['args'], attach_target
)
507 self
.assert_no_active_block_jobs()
509 self
.vm
.pause_drive('drive0')
510 result
= self
.vm
.qmp(cmd
, device
='drive0', sync
='full', compress
=True, **args
)
511 self
.assert_qmp(result
, 'return', {})
513 event
= self
.cancel_and_wait(resume
=True)
514 self
.assert_qmp(event
, 'data/type', 'backup')
518 def test_compress_cancel_drive_backup(self
):
519 for format
in TestDriveCompression
.fmt_supports_compression
:
520 self
.do_test_compress_cancel('drive-backup', format
, False,
521 target
=blockdev_target_img
, mode
='existing')
523 def test_compress_cancel_blockdev_backup(self
):
524 for format
in TestDriveCompression
.fmt_supports_compression
:
525 self
.do_test_compress_cancel('blockdev-backup', format
, True,
528 def do_test_compress_pause(self
, cmd
, format
, attach_target
, **args
):
529 self
.do_prepare_drives(format
['type'], format
['args'], attach_target
)
531 self
.assert_no_active_block_jobs()
533 self
.vm
.pause_drive('drive0')
534 result
= self
.vm
.qmp(cmd
, device
='drive0', sync
='full', compress
=True, **args
)
535 self
.assert_qmp(result
, 'return', {})
537 result
= self
.vm
.qmp('block-job-pause', device
='drive0')
538 self
.assert_qmp(result
, 'return', {})
540 self
.vm
.resume_drive('drive0')
541 self
.pause_job('drive0')
543 result
= self
.vm
.qmp('query-block-jobs')
544 offset
= self
.dictpath(result
, 'return[0]/offset')
547 result
= self
.vm
.qmp('query-block-jobs')
548 self
.assert_qmp(result
, 'return[0]/offset', offset
)
550 result
= self
.vm
.qmp('block-job-resume', device
='drive0')
551 self
.assert_qmp(result
, 'return', {})
553 self
.wait_until_completed()
556 self
.assertTrue(iotests
.compare_images(test_img
, blockdev_target_img
,
557 iotests
.imgfmt
, format
['type']),
558 'target image does not match source after backup')
560 def test_compress_pause_drive_backup(self
):
561 for format
in TestDriveCompression
.fmt_supports_compression
:
562 self
.do_test_compress_pause('drive-backup', format
, False,
563 target
=blockdev_target_img
, mode
='existing')
565 def test_compress_pause_blockdev_backup(self
):
566 for format
in TestDriveCompression
.fmt_supports_compression
:
567 self
.do_test_compress_pause('blockdev-backup', format
, True,
570 if __name__
== '__main__':
571 iotests
.main(supported_fmts
=['raw', 'qcow2'])