xref: /qemu/tests/qemu-iotests/055 (revision d4eb5038)
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
24import time
25import os
26import iotests
27from iotests import qemu_img, qemu_io
28
29test_img = os.path.join(iotests.test_dir, 'test.img')
30target_img = os.path.join(iotests.test_dir, 'target.img')
31blockdev_target_img = os.path.join(iotests.test_dir, 'blockdev-target.img')
32
33image_len = 64 * 1024 * 1024 # MB
34
35def 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
44def tearDownModule():
45    os.remove(test_img)
46
47
48class 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
197class 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.
279class 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
477class 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
588class 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
597if __name__ == '__main__':
598    iotests.main(supported_fmts=['raw', 'qcow2'],
599                 supported_protocols=['file'])
600