master
text 599 lines 21.3 KB
Raw
1 #!/usr/bin/env python3
2 # group: rw
3 #
4 # Tests for drive-backup and blockdev-backup
5 #
6 # Copyright (C) 2013, 2014 Red Hat, Inc.
7 #
8 # Based on 041.
9 #
10 # This program is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
14 #
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
19 #
20 # You should have received a copy of the GNU General Public License
21 # along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #
23
24 import time
25 import os
26 import iotests
27 from iotests import qemu_img, qemu_io
28
29 test_img = os.path.join(iotests.test_dir, 'test.img')
30 target_img = os.path.join(iotests.test_dir, 'target.img')
31 blockdev_target_img = os.path.join(iotests.test_dir, 'blockdev-target.img')
32
33 image_len = 64 * 1024 * 1024 # MB
34
35 def setUpModule():
36 qemu_img('create', '-f', iotests.imgfmt, test_img, str(image_len))
37 qemu_io('-f', iotests.imgfmt, '-c', 'write -P0x11 0 64k', test_img)
38 qemu_io('-f', iotests.imgfmt, '-c', 'write -P0x00 64k 128k', test_img)
39 qemu_io('-f', iotests.imgfmt, '-c', 'write -P0x22 162k 32k', test_img)
40 qemu_io('-f', iotests.imgfmt, '-c', 'write -P0xd5 1M 32k', test_img)
41 qemu_io('-f', iotests.imgfmt, '-c', 'write -P0xdc 32M 124k', test_img)
42 qemu_io('-f', iotests.imgfmt, '-c', 'write -P0x33 67043328 64k', test_img)
43
44 def tearDownModule():
45 os.remove(test_img)
46
47
48 class TestSingleDrive(iotests.QMPTestCase):
49 def setUp(self):
50 qemu_img('create', '-f', iotests.imgfmt, blockdev_target_img, str(image_len))
51
52 self.vm = iotests.VM()
53 self.vm.add_drive('blkdebug::' + test_img, 'node-name=source')
54 self.vm.add_drive(blockdev_target_img, 'node-name=target',
55 interface="none")
56 if iotests.qemu_default_machine == 'pc':
57 self.vm.add_drive(None, 'media=cdrom', 'ide')
58 self.vm.launch()
59
60 def tearDown(self):
61 self.vm.shutdown()
62 os.remove(blockdev_target_img)
63 try:
64 os.remove(target_img)
65 except OSError:
66 pass
67
68 def do_test_cancel(self, cmd, target):
69 self.assert_no_active_block_jobs()
70
71 self.vm.pause_drive('drive0')
72 self.vm.cmd(cmd, device='drive0', target=target, sync='full')
73
74 event = self.cancel_and_wait(resume=True)
75 self.assert_qmp(event, 'data/type', 'backup')
76
77 def test_cancel_drive_backup(self):
78 self.do_test_cancel('drive-backup', target_img)
79
80 def test_cancel_blockdev_backup(self):
81 self.do_test_cancel('blockdev-backup', 'drive1')
82
83 def do_test_pause(self, cmd, target, image):
84 self.assert_no_active_block_jobs()
85
86 self.vm.pause_drive('drive0')
87 self.vm.cmd(cmd, device='drive0',
88 target=target, sync='full')
89
90 self.pause_job('drive0', wait=False)
91 self.vm.resume_drive('drive0')
92 self.pause_wait('drive0')
93
94 result = self.vm.qmp('query-block-jobs')
95 offset = self.dictpath(result, 'return[0]/offset')
96
97 time.sleep(0.5)
98 result = self.vm.qmp('query-block-jobs')
99 self.assert_qmp(result, 'return[0]/offset', offset)
100
101 self.vm.cmd('block-job-resume', device='drive0')
102
103 self.wait_until_completed()
104
105 self.vm.shutdown()
106 self.assertTrue(iotests.compare_images(test_img, image),
107 'target image does not match source after backup')
108
109 def test_pause_drive_backup(self):
110 self.do_test_pause('drive-backup', target_img, target_img)
111
112 def test_pause_blockdev_backup(self):
113 self.do_test_pause('blockdev-backup', 'drive1', blockdev_target_img)
114
115 def do_test_resize_blockdev_backup(self, device, node):
116 def pre_finalize():
117 result = self.vm.qmp('block_resize', device=device, size=65536)
118 self.assert_qmp(result, 'error/class', 'GenericError')
119
120 result = self.vm.qmp('block_resize', node_name=node, size=65536)
121 self.assert_qmp(result, 'error/class', 'GenericError')
122
123 self.vm.cmd('blockdev-backup', job_id='job0', device='drive0',
124 target='drive1', sync='full', auto_finalize=False,
125 auto_dismiss=False)
126
127 self.vm.run_job('job0', auto_finalize=False, pre_finalize=pre_finalize)
128
129 def test_source_resize_blockdev_backup(self):
130 self.do_test_resize_blockdev_backup('drive0', 'source')
131
132 def test_target_resize_blockdev_backup(self):
133 self.do_test_resize_blockdev_backup('drive1', 'target')
134
135 def do_test_target_size(self, size):
136 self.vm.cmd('block_resize', device='drive1', size=size)
137
138 result = self.vm.qmp('blockdev-backup', job_id='job0', device='drive0',
139 target='drive1', sync='full')
140 self.assert_qmp(result, 'error/class', 'GenericError')
141
142 def test_small_target(self):
143 self.do_test_target_size(image_len // 2)
144
145 def test_large_target(self):
146 self.do_test_target_size(image_len * 2)
147
148 def test_medium_not_found(self):
149 if iotests.qemu_default_machine != 'pc':
150 return
151
152 result = self.vm.qmp('drive-backup', device='drive2', # CD-ROM
153 target=target_img, sync='full')
154 self.assert_qmp(result, 'error/class', 'GenericError')
155
156 def test_medium_not_found_blockdev_backup(self):
157 if iotests.qemu_default_machine != 'pc':
158 return
159
160 result = self.vm.qmp('blockdev-backup', device='drive2', # CD-ROM
161 target='drive1', sync='full')
162 self.assert_qmp(result, 'error/class', 'GenericError')
163
164 def test_image_not_found(self):
165 result = self.vm.qmp('drive-backup', device='drive0',
166 target=target_img, sync='full', mode='existing')
167 self.assert_qmp(result, 'error/class', 'GenericError')
168
169 def test_invalid_format(self):
170 result = self.vm.qmp('drive-backup', device='drive0',
171 target=target_img, sync='full',
172 format='spaghetti-noodles')
173 self.assert_qmp(result, 'error/class', 'GenericError')
174
175 def do_test_device_not_found(self, cmd, **args):
176 result = self.vm.qmp(cmd, **args)
177 self.assert_qmp(result, 'error/class', 'GenericError')
178
179 def test_device_not_found(self):
180 self.do_test_device_not_found('drive-backup', device='nonexistent',
181 target=target_img, sync='full')
182
183 self.do_test_device_not_found('blockdev-backup', device='nonexistent',
184 target='drive0', sync='full')
185
186 self.do_test_device_not_found('blockdev-backup', device='drive0',
187 target='nonexistent', sync='full')
188
189 self.do_test_device_not_found('blockdev-backup', device='nonexistent',
190 target='nonexistent', sync='full')
191
192 def test_target_is_source(self):
193 result = self.vm.qmp('blockdev-backup', device='drive0',
194 target='drive0', sync='full')
195 self.assert_qmp(result, 'error/class', 'GenericError')
196
197 class TestSetSpeed(iotests.QMPTestCase):
198 def setUp(self):
199 qemu_img('create', '-f', iotests.imgfmt, blockdev_target_img, str(image_len))
200
201 self.vm = iotests.VM().add_drive('blkdebug::' + test_img)
202 self.vm.add_drive(blockdev_target_img, interface="none")
203 self.vm.launch()
204
205 def tearDown(self):
206 self.vm.shutdown()
207 os.remove(blockdev_target_img)
208 try:
209 os.remove(target_img)
210 except OSError:
211 pass
212
213 def do_test_set_speed(self, cmd, target):
214 self.assert_no_active_block_jobs()
215
216 self.vm.pause_drive('drive0')
217 self.vm.cmd(cmd, device='drive0', target=target, sync='full')
218
219 # Default speed is 0
220 result = self.vm.qmp('query-block-jobs')
221 self.assert_qmp(result, 'return[0]/device', 'drive0')
222 self.assert_qmp(result, 'return[0]/speed', 0)
223
224 self.vm.cmd('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024)
225
226 # Ensure the speed we set was accepted
227 result = self.vm.qmp('query-block-jobs')
228 self.assert_qmp(result, 'return[0]/device', 'drive0')
229 self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024)
230
231 event = self.cancel_and_wait(resume=True)
232 self.assert_qmp(event, 'data/type', 'backup')
233
234 # Check setting speed option works
235 self.vm.pause_drive('drive0')
236 self.vm.cmd(cmd, device='drive0',
237 target=target, sync='full', speed=4*1024*1024)
238
239 result = self.vm.qmp('query-block-jobs')
240 self.assert_qmp(result, 'return[0]/device', 'drive0')
241 self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024)
242
243 event = self.cancel_and_wait(resume=True)
244 self.assert_qmp(event, 'data/type', 'backup')
245
246 def test_set_speed_drive_backup(self):
247 self.do_test_set_speed('drive-backup', target_img)
248
249 def test_set_speed_blockdev_backup(self):
250 self.do_test_set_speed('blockdev-backup', 'drive1')
251
252 def do_test_set_speed_invalid(self, cmd, target):
253 self.assert_no_active_block_jobs()
254
255 result = self.vm.qmp(cmd, device='drive0',
256 target=target, sync='full', speed=-1)
257 self.assert_qmp(result, 'error/class', 'GenericError')
258
259 self.assert_no_active_block_jobs()
260
261 self.vm.pause_drive('drive0')
262 self.vm.cmd(cmd, device='drive0',
263 target=target, sync='full')
264
265 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1)
266 self.assert_qmp(result, 'error/class', 'GenericError')
267
268 event = self.cancel_and_wait(resume=True)
269 self.assert_qmp(event, 'data/type', 'backup')
270
271 def test_set_speed_invalid_drive_backup(self):
272 self.do_test_set_speed_invalid('drive-backup', target_img)
273
274 def test_set_speed_invalid_blockdev_backup(self):
275 self.do_test_set_speed_invalid('blockdev-backup', 'drive1')
276
277 # Note: We cannot use pause_drive() here, or the transaction command
278 # would stall. Instead, we limit the block job speed here.
279 class TestSingleTransaction(iotests.QMPTestCase):
280 def setUp(self):
281 qemu_img('create', '-f', iotests.imgfmt, blockdev_target_img, str(image_len))
282
283 self.vm = iotests.VM().add_drive(test_img)
284 self.vm.add_drive(blockdev_target_img, interface="none")
285 if iotests.qemu_default_machine == 'pc':
286 self.vm.add_drive(None, 'media=cdrom', 'ide')
287 self.vm.launch()
288
289 def tearDown(self):
290 self.vm.shutdown()
291 os.remove(blockdev_target_img)
292 try:
293 os.remove(target_img)
294 except OSError:
295 pass
296
297 def do_test_cancel(self, cmd, target):
298 self.assert_no_active_block_jobs()
299
300 self.vm.cmd('transaction', actions=[{
301 'type': cmd,
302 'data': { 'device': 'drive0',
303 'target': target,
304 'sync': 'full',
305 'speed': 64 * 1024 },
306 }
307 ])
308
309 event = self.cancel_and_wait()
310 self.assert_qmp(event, 'data/type', 'backup')
311
312 def test_cancel_drive_backup(self):
313 self.do_test_cancel('drive-backup', target_img)
314
315 def test_cancel_blockdev_backup(self):
316 self.do_test_cancel('blockdev-backup', 'drive1')
317
318 def do_test_pause(self, cmd, target, image):
319 self.assert_no_active_block_jobs()
320
321 self.vm.cmd('transaction', actions=[{
322 'type': cmd,
323 'data': { 'device': 'drive0',
324 'target': target,
325 'sync': 'full',
326 'speed': 64 * 1024 },
327 }
328 ])
329
330 self.pause_job('drive0', wait=False)
331
332 self.vm.cmd('block-job-set-speed', device='drive0', speed=0)
333
334 self.pause_wait('drive0')
335
336 result = self.vm.qmp('query-block-jobs')
337 offset = self.dictpath(result, 'return[0]/offset')
338
339 time.sleep(0.5)
340 result = self.vm.qmp('query-block-jobs')
341 self.assert_qmp(result, 'return[0]/offset', offset)
342
343 self.vm.cmd('block-job-resume', device='drive0')
344
345 self.wait_until_completed()
346
347 self.vm.shutdown()
348 self.assertTrue(iotests.compare_images(test_img, image),
349 'target image does not match source after backup')
350
351 def test_pause_drive_backup(self):
352 self.do_test_pause('drive-backup', target_img, target_img)
353
354 def test_pause_blockdev_backup(self):
355 self.do_test_pause('blockdev-backup', 'drive1', blockdev_target_img)
356
357 def do_test_medium_not_found(self, cmd, target):
358 if iotests.qemu_default_machine != 'pc':
359 return
360
361 result = self.vm.qmp('transaction', actions=[{
362 'type': cmd,
363 'data': { 'device': 'drive2', # CD-ROM
364 'target': target,
365 'sync': 'full' },
366 }
367 ])
368 self.assert_qmp(result, 'error/class', 'GenericError')
369
370 def test_medium_not_found_drive_backup(self):
371 self.do_test_medium_not_found('drive-backup', target_img)
372
373 def test_medium_not_found_blockdev_backup(self):
374 self.do_test_medium_not_found('blockdev-backup', 'drive1')
375
376 def test_image_not_found(self):
377 result = self.vm.qmp('transaction', actions=[{
378 'type': 'drive-backup',
379 'data': { 'device': 'drive0',
380 'mode': 'existing',
381 'target': target_img,
382 'sync': 'full' },
383 }
384 ])
385 self.assert_qmp(result, 'error/class', 'GenericError')
386
387 def test_device_not_found(self):
388 result = self.vm.qmp('transaction', actions=[{
389 'type': 'drive-backup',
390 'data': { 'device': 'nonexistent',
391 'mode': 'existing',
392 'target': target_img,
393 'sync': 'full' },
394 }
395 ])
396 self.assert_qmp(result, 'error/class', 'GenericError')
397
398 result = self.vm.qmp('transaction', actions=[{
399 'type': 'blockdev-backup',
400 'data': { 'device': 'nonexistent',
401 'target': 'drive1',
402 'sync': 'full' },
403 }
404 ])
405 self.assert_qmp(result, 'error/class', 'GenericError')
406
407 result = self.vm.qmp('transaction', actions=[{
408 'type': 'blockdev-backup',
409 'data': { 'device': 'drive0',
410 'target': 'nonexistent',
411 'sync': 'full' },
412 }
413 ])
414 self.assert_qmp(result, 'error/class', 'GenericError')
415
416 result = self.vm.qmp('transaction', actions=[{
417 'type': 'blockdev-backup',
418 'data': { 'device': 'nonexistent',
419 'target': 'nonexistent',
420 'sync': 'full' },
421 }
422 ])
423 self.assert_qmp(result, 'error/class', 'GenericError')
424
425 def test_target_is_source(self):
426 result = self.vm.qmp('transaction', actions=[{
427 'type': 'blockdev-backup',
428 'data': { 'device': 'drive0',
429 'target': 'drive0',
430 'sync': 'full' },
431 }
432 ])
433 self.assert_qmp(result, 'error/class', 'GenericError')
434
435 def test_abort(self):
436 result = self.vm.qmp('transaction', actions=[{
437 'type': 'drive-backup',
438 'data': { 'device': 'nonexistent',
439 'mode': 'existing',
440 'target': target_img,
441 'sync': 'full' },
442 }, {
443 'type': 'Abort',
444 'data': {},
445 }
446 ])
447 self.assert_qmp(result, 'error/class', 'GenericError')
448 self.assert_no_active_block_jobs()
449
450 result = self.vm.qmp('transaction', actions=[{
451 'type': 'blockdev-backup',
452 'data': { 'device': 'nonexistent',
453 'target': 'drive1',
454 'sync': 'full' },
455 }, {
456 'type': 'Abort',
457 'data': {},
458 }
459 ])
460 self.assert_qmp(result, 'error/class', 'GenericError')
461 self.assert_no_active_block_jobs()
462
463 result = self.vm.qmp('transaction', actions=[{
464 'type': 'blockdev-backup',
465 'data': { 'device': 'drive0',
466 'target': 'nonexistent',
467 'sync': 'full' },
468 }, {
469 'type': 'Abort',
470 'data': {},
471 }
472 ])
473 self.assert_qmp(result, 'error/class', 'GenericError')
474 self.assert_no_active_block_jobs()
475
476
477 class TestCompressedToQcow2(iotests.QMPTestCase):
478 image_len = 64 * 1024 * 1024 # MB
479 target_fmt = {'type': 'qcow2', 'args': (), 'drive-opts': ''}
480
481 def tearDown(self):
482 self.vm.shutdown()
483 os.remove(blockdev_target_img)
484 try:
485 os.remove(target_img)
486 except OSError:
487 pass
488
489 def do_prepare_drives(self, attach_target):
490 self.vm = iotests.VM().add_drive('blkdebug::' + test_img,
491 opts=self.target_fmt['drive-opts'])
492
493 qemu_img('create', '-f', self.target_fmt['type'], blockdev_target_img,
494 str(self.image_len), *self.target_fmt['args'])
495 if attach_target:
496 self.vm.add_drive(blockdev_target_img,
497 img_format=self.target_fmt['type'],
498 interface="none",
499 opts=self.target_fmt['drive-opts'])
500
501 self.vm.launch()
502
503 def do_test_compress_complete(self, cmd, attach_target, **args):
504 self.do_prepare_drives(attach_target)
505
506 self.assert_no_active_block_jobs()
507
508 self.vm.cmd(cmd, device='drive0', sync='full', compress=True, **args)
509
510 self.wait_until_completed()
511
512 self.vm.shutdown()
513 self.assertTrue(iotests.compare_images(test_img, blockdev_target_img,
514 iotests.imgfmt,
515 self.target_fmt['type']),
516 'target image does not match source after backup')
517
518 def test_complete_compress_drive_backup(self):
519 self.do_test_compress_complete('drive-backup', False,
520 target=blockdev_target_img,
521 mode='existing')
522
523 def test_complete_compress_blockdev_backup(self):
524 self.do_test_compress_complete('blockdev-backup',
525 True, target='drive1')
526
527 def do_test_compress_cancel(self, cmd, attach_target, **args):
528 self.do_prepare_drives(attach_target)
529
530 self.assert_no_active_block_jobs()
531
532 self.vm.pause_drive('drive0')
533 self.vm.cmd(cmd, device='drive0', sync='full', compress=True, **args)
534
535 event = self.cancel_and_wait(resume=True)
536 self.assert_qmp(event, 'data/type', 'backup')
537
538 self.vm.shutdown()
539
540 def test_compress_cancel_drive_backup(self):
541 self.do_test_compress_cancel('drive-backup', False,
542 target=blockdev_target_img,
543 mode='existing')
544
545 def test_compress_cancel_blockdev_backup(self):
546 self.do_test_compress_cancel('blockdev-backup', True,
547 target='drive1')
548
549 def do_test_compress_pause(self, cmd, attach_target, **args):
550 self.do_prepare_drives(attach_target)
551
552 self.assert_no_active_block_jobs()
553
554 self.vm.pause_drive('drive0')
555 self.vm.cmd(cmd, device='drive0', sync='full', compress=True, **args)
556
557 self.pause_job('drive0', wait=False)
558 self.vm.resume_drive('drive0')
559 self.pause_wait('drive0')
560
561 result = self.vm.qmp('query-block-jobs')
562 offset = self.dictpath(result, 'return[0]/offset')
563
564 time.sleep(0.5)
565 result = self.vm.qmp('query-block-jobs')
566 self.assert_qmp(result, 'return[0]/offset', offset)
567
568 self.vm.cmd('block-job-resume', device='drive0')
569
570 self.wait_until_completed()
571
572 self.vm.shutdown()
573 self.assertTrue(iotests.compare_images(test_img, blockdev_target_img,
574 iotests.imgfmt,
575 self.target_fmt['type']),
576 'target image does not match source after backup')
577
578 def test_compress_pause_drive_backup(self):
579 self.do_test_compress_pause('drive-backup', False,
580 target=blockdev_target_img,
581 mode='existing')
582
583 def test_compress_pause_blockdev_backup(self):
584 self.do_test_compress_pause('blockdev-backup', True,
585 target='drive1')
586
587
588 class TestCompressedToVmdk(TestCompressedToQcow2):
589 target_fmt = {'type': 'vmdk', 'args': ('-o', 'subformat=streamOptimized'),
590 'drive-opts': 'cache.no-flush=on'}
591
592 @iotests.skip_if_unsupported(['vmdk'])
593 def setUp(self):
594 pass
595
596
597 if __name__ == '__main__':
598 iotests.main(supported_fmts=['raw', 'qcow2'],
599 supported_protocols=['file'])