xref: /qemu/tests/qemu-iotests/055 (revision ac06724a)
1#!/usr/bin/env python
2#
3# Tests for drive-backup and blockdev-backup
4#
5# Copyright (C) 2013, 2014 Red Hat, Inc.
6#
7# Based on 041.
8#
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.
13#
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.
18#
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/>.
21#
22
23import time
24import os
25import iotests
26from iotests import qemu_img, qemu_io
27
28test_img = os.path.join(iotests.test_dir, 'test.img')
29target_img = os.path.join(iotests.test_dir, 'target.img')
30blockdev_target_img = os.path.join(iotests.test_dir, 'blockdev-target.img')
31
32image_len = 64 * 1024 * 1024 # MB
33
34def setUpModule():
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)
42
43def tearDownModule():
44    os.remove(test_img)
45
46
47class TestSingleDrive(iotests.QMPTestCase):
48    def setUp(self):
49        qemu_img('create', '-f', iotests.imgfmt, blockdev_target_img, str(image_len))
50
51        self.vm = iotests.VM().add_drive(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')
55        self.vm.launch()
56
57    def tearDown(self):
58        self.vm.shutdown()
59        os.remove(blockdev_target_img)
60        try:
61            os.remove(target_img)
62        except OSError:
63            pass
64
65    def do_test_cancel(self, cmd, target):
66        self.assert_no_active_block_jobs()
67
68        result = self.vm.qmp(cmd, device='drive0', target=target, sync='full')
69        self.assert_qmp(result, 'return', {})
70
71        event = self.cancel_and_wait()
72        self.assert_qmp(event, 'data/type', 'backup')
73
74    def test_cancel_drive_backup(self):
75        self.do_test_cancel('drive-backup', target_img)
76
77    def test_cancel_blockdev_backup(self):
78        self.do_test_cancel('blockdev-backup', 'drive1')
79
80    def do_test_pause(self, cmd, target, image):
81        self.assert_no_active_block_jobs()
82
83        self.vm.pause_drive('drive0')
84        result = self.vm.qmp(cmd, device='drive0',
85                             target=target, sync='full')
86        self.assert_qmp(result, 'return', {})
87
88        result = self.vm.qmp('block-job-pause', device='drive0')
89        self.assert_qmp(result, 'return', {})
90
91        self.vm.resume_drive('drive0')
92        time.sleep(1)
93        result = self.vm.qmp('query-block-jobs')
94        offset = self.dictpath(result, 'return[0]/offset')
95
96        time.sleep(1)
97        result = self.vm.qmp('query-block-jobs')
98        self.assert_qmp(result, 'return[0]/offset', offset)
99
100        result = self.vm.qmp('block-job-resume', device='drive0')
101        self.assert_qmp(result, 'return', {})
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 test_medium_not_found(self):
116        if iotests.qemu_default_machine != 'pc':
117            return
118
119        result = self.vm.qmp('drive-backup', device='drive2', # CD-ROM
120                             target=target_img, sync='full')
121        self.assert_qmp(result, 'error/class', 'GenericError')
122
123    def test_medium_not_found_blockdev_backup(self):
124        if iotests.qemu_default_machine != 'pc':
125            return
126
127        result = self.vm.qmp('blockdev-backup', device='drive2', # CD-ROM
128                             target='drive1', sync='full')
129        self.assert_qmp(result, 'error/class', 'GenericError')
130
131    def test_image_not_found(self):
132        result = self.vm.qmp('drive-backup', device='drive0',
133                             target=target_img, sync='full', mode='existing')
134        self.assert_qmp(result, 'error/class', 'GenericError')
135
136    def test_invalid_format(self):
137        result = self.vm.qmp('drive-backup', device='drive0',
138                             target=target_img, sync='full',
139                             format='spaghetti-noodles')
140        self.assert_qmp(result, 'error/class', 'GenericError')
141
142    def do_test_device_not_found(self, cmd, **args):
143        result = self.vm.qmp(cmd, **args)
144        self.assert_qmp(result, 'error/class', 'GenericError')
145
146    def test_device_not_found(self):
147        self.do_test_device_not_found('drive-backup', device='nonexistent',
148                                      target=target_img, sync='full')
149
150        self.do_test_device_not_found('blockdev-backup', device='nonexistent',
151                                      target='drive0', sync='full')
152
153        self.do_test_device_not_found('blockdev-backup', device='drive0',
154                                      target='nonexistent', sync='full')
155
156        self.do_test_device_not_found('blockdev-backup', device='nonexistent',
157                                      target='nonexistent', sync='full')
158
159    def test_target_is_source(self):
160        result = self.vm.qmp('blockdev-backup', device='drive0',
161                             target='drive0', sync='full')
162        self.assert_qmp(result, 'error/class', 'GenericError')
163
164class TestSetSpeed(iotests.QMPTestCase):
165    def setUp(self):
166        qemu_img('create', '-f', iotests.imgfmt, blockdev_target_img, str(image_len))
167
168        self.vm = iotests.VM().add_drive(test_img)
169        self.vm.add_drive(blockdev_target_img, interface="none")
170        self.vm.launch()
171
172    def tearDown(self):
173        self.vm.shutdown()
174        os.remove(blockdev_target_img)
175        try:
176            os.remove(target_img)
177        except OSError:
178            pass
179
180    def do_test_set_speed(self, cmd, target):
181        self.assert_no_active_block_jobs()
182
183        self.vm.pause_drive('drive0')
184        result = self.vm.qmp(cmd, device='drive0', target=target, sync='full')
185        self.assert_qmp(result, 'return', {})
186
187        # Default speed is 0
188        result = self.vm.qmp('query-block-jobs')
189        self.assert_qmp(result, 'return[0]/device', 'drive0')
190        self.assert_qmp(result, 'return[0]/speed', 0)
191
192        result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024)
193        self.assert_qmp(result, 'return', {})
194
195        # Ensure the speed we set was accepted
196        result = self.vm.qmp('query-block-jobs')
197        self.assert_qmp(result, 'return[0]/device', 'drive0')
198        self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024)
199
200        event = self.cancel_and_wait(resume=True)
201        self.assert_qmp(event, 'data/type', 'backup')
202
203        # Check setting speed option works
204        self.vm.pause_drive('drive0')
205        result = self.vm.qmp(cmd, device='drive0',
206                             target=target, sync='full', speed=4*1024*1024)
207        self.assert_qmp(result, 'return', {})
208
209        result = self.vm.qmp('query-block-jobs')
210        self.assert_qmp(result, 'return[0]/device', 'drive0')
211        self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024)
212
213        event = self.cancel_and_wait(resume=True)
214        self.assert_qmp(event, 'data/type', 'backup')
215
216    def test_set_speed_drive_backup(self):
217        self.do_test_set_speed('drive-backup', target_img)
218
219    def test_set_speed_blockdev_backup(self):
220        self.do_test_set_speed('blockdev-backup', 'drive1')
221
222    def do_test_set_speed_invalid(self, cmd, target):
223        self.assert_no_active_block_jobs()
224
225        result = self.vm.qmp(cmd, device='drive0',
226                             target=target, sync='full', speed=-1)
227        self.assert_qmp(result, 'error/class', 'GenericError')
228
229        self.assert_no_active_block_jobs()
230
231        self.vm.pause_drive('drive0')
232        result = self.vm.qmp(cmd, device='drive0',
233                             target=target, sync='full')
234        self.assert_qmp(result, 'return', {})
235
236        result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1)
237        self.assert_qmp(result, 'error/class', 'GenericError')
238
239        event = self.cancel_and_wait(resume=True)
240        self.assert_qmp(event, 'data/type', 'backup')
241
242    def test_set_speed_invalid_drive_backup(self):
243        self.do_test_set_speed_invalid('drive-backup', target_img)
244
245    def test_set_speed_invalid_blockdev_backup(self):
246        self.do_test_set_speed_invalid('blockdev-backup',  'drive1')
247
248class TestSingleTransaction(iotests.QMPTestCase):
249    def setUp(self):
250        qemu_img('create', '-f', iotests.imgfmt, blockdev_target_img, str(image_len))
251
252        self.vm = iotests.VM().add_drive(test_img)
253        self.vm.add_drive(blockdev_target_img, interface="none")
254        if iotests.qemu_default_machine == 'pc':
255            self.vm.add_drive(None, 'media=cdrom', 'ide')
256        self.vm.launch()
257
258    def tearDown(self):
259        self.vm.shutdown()
260        os.remove(blockdev_target_img)
261        try:
262            os.remove(target_img)
263        except OSError:
264            pass
265
266    def do_test_cancel(self, cmd, target):
267        self.assert_no_active_block_jobs()
268
269        result = self.vm.qmp('transaction', actions=[{
270                'type': cmd,
271                'data': { 'device': 'drive0',
272                          'target': target,
273                          'sync': 'full' },
274            }
275        ])
276
277        self.assert_qmp(result, 'return', {})
278
279        event = self.cancel_and_wait()
280        self.assert_qmp(event, 'data/type', 'backup')
281
282    def test_cancel_drive_backup(self):
283        self.do_test_cancel('drive-backup', target_img)
284
285    def test_cancel_blockdev_backup(self):
286        self.do_test_cancel('blockdev-backup', 'drive1')
287
288    def do_test_pause(self, cmd, target, image):
289        self.assert_no_active_block_jobs()
290
291        self.vm.pause_drive('drive0')
292        result = self.vm.qmp('transaction', actions=[{
293                'type': cmd,
294                'data': { 'device': 'drive0',
295                          'target': target,
296                          'sync': 'full' },
297            }
298        ])
299        self.assert_qmp(result, 'return', {})
300
301        result = self.vm.qmp('block-job-pause', device='drive0')
302        self.assert_qmp(result, 'return', {})
303
304        self.vm.resume_drive('drive0')
305        time.sleep(1)
306        result = self.vm.qmp('query-block-jobs')
307        offset = self.dictpath(result, 'return[0]/offset')
308
309        time.sleep(1)
310        result = self.vm.qmp('query-block-jobs')
311        self.assert_qmp(result, 'return[0]/offset', offset)
312
313        result = self.vm.qmp('block-job-resume', device='drive0')
314        self.assert_qmp(result, 'return', {})
315
316        self.wait_until_completed()
317
318        self.vm.shutdown()
319        self.assertTrue(iotests.compare_images(test_img, image),
320                        'target image does not match source after backup')
321
322    def test_pause_drive_backup(self):
323        self.do_test_pause('drive-backup', target_img, target_img)
324
325    def test_pause_blockdev_backup(self):
326        self.do_test_pause('blockdev-backup', 'drive1', blockdev_target_img)
327
328    def do_test_medium_not_found(self, cmd, target):
329        if iotests.qemu_default_machine != 'pc':
330            return
331
332        result = self.vm.qmp('transaction', actions=[{
333                'type': cmd,
334                'data': { 'device': 'drive2', # CD-ROM
335                          'target': target,
336                          'sync': 'full' },
337            }
338        ])
339        self.assert_qmp(result, 'error/class', 'GenericError')
340
341    def test_medium_not_found_drive_backup(self):
342        self.do_test_medium_not_found('drive-backup', target_img)
343
344    def test_medium_not_found_blockdev_backup(self):
345        self.do_test_medium_not_found('blockdev-backup', 'drive1')
346
347    def test_image_not_found(self):
348        result = self.vm.qmp('transaction', actions=[{
349                'type': 'drive-backup',
350                'data': { 'device': 'drive0',
351                          'mode': 'existing',
352                          'target': target_img,
353                          'sync': 'full' },
354            }
355        ])
356        self.assert_qmp(result, 'error/class', 'GenericError')
357
358    def test_device_not_found(self):
359        result = self.vm.qmp('transaction', actions=[{
360                'type': 'drive-backup',
361                'data': { 'device': 'nonexistent',
362                          'mode': 'existing',
363                          'target': target_img,
364                          'sync': 'full' },
365            }
366        ])
367        self.assert_qmp(result, 'error/class', 'GenericError')
368
369        result = self.vm.qmp('transaction', actions=[{
370                'type': 'blockdev-backup',
371                'data': { 'device': 'nonexistent',
372                          'target': 'drive1',
373                          'sync': 'full' },
374            }
375        ])
376        self.assert_qmp(result, 'error/class', 'GenericError')
377
378        result = self.vm.qmp('transaction', actions=[{
379                'type': 'blockdev-backup',
380                'data': { 'device': 'drive0',
381                          'target': 'nonexistent',
382                          'sync': 'full' },
383            }
384        ])
385        self.assert_qmp(result, 'error/class', 'GenericError')
386
387        result = self.vm.qmp('transaction', actions=[{
388                'type': 'blockdev-backup',
389                'data': { 'device': 'nonexistent',
390                          'target': 'nonexistent',
391                          'sync': 'full' },
392            }
393        ])
394        self.assert_qmp(result, 'error/class', 'GenericError')
395
396    def test_target_is_source(self):
397        result = self.vm.qmp('transaction', actions=[{
398                'type': 'blockdev-backup',
399                'data': { 'device': 'drive0',
400                          'target': 'drive0',
401                          'sync': 'full' },
402            }
403        ])
404        self.assert_qmp(result, 'error/class', 'GenericError')
405
406    def test_abort(self):
407        result = self.vm.qmp('transaction', actions=[{
408                'type': 'drive-backup',
409                'data': { 'device': 'nonexistent',
410                          'mode': 'existing',
411                          'target': target_img,
412                          'sync': 'full' },
413            }, {
414                'type': 'Abort',
415                'data': {},
416            }
417        ])
418        self.assert_qmp(result, 'error/class', 'GenericError')
419        self.assert_no_active_block_jobs()
420
421        result = self.vm.qmp('transaction', actions=[{
422                'type': 'blockdev-backup',
423                'data': { 'device': 'nonexistent',
424                          'target': 'drive1',
425                          'sync': 'full' },
426            }, {
427                'type': 'Abort',
428                'data': {},
429            }
430        ])
431        self.assert_qmp(result, 'error/class', 'GenericError')
432        self.assert_no_active_block_jobs()
433
434        result = self.vm.qmp('transaction', actions=[{
435                'type': 'blockdev-backup',
436                'data': { 'device': 'drive0',
437                          'target': 'nonexistent',
438                          'sync': 'full' },
439            }, {
440                'type': 'Abort',
441                'data': {},
442            }
443        ])
444        self.assert_qmp(result, 'error/class', 'GenericError')
445        self.assert_no_active_block_jobs()
446
447
448class TestDriveCompression(iotests.QMPTestCase):
449    image_len = 64 * 1024 * 1024 # MB
450    fmt_supports_compression = [{'type': 'qcow2', 'args': ()},
451                                {'type': 'vmdk', 'args': ('-o', 'subformat=streamOptimized')}]
452
453    def tearDown(self):
454        self.vm.shutdown()
455        os.remove(blockdev_target_img)
456        try:
457            os.remove(target_img)
458        except OSError:
459            pass
460
461    def do_prepare_drives(self, fmt, args, attach_target):
462        self.vm = iotests.VM().add_drive(test_img)
463
464        qemu_img('create', '-f', fmt, blockdev_target_img,
465                 str(TestDriveCompression.image_len), *args)
466        if attach_target:
467            self.vm.add_drive(blockdev_target_img, format=fmt, interface="none")
468
469        self.vm.launch()
470
471    def do_test_compress_complete(self, cmd, format, attach_target, **args):
472        self.do_prepare_drives(format['type'], format['args'], attach_target)
473
474        self.assert_no_active_block_jobs()
475
476        result = self.vm.qmp(cmd, device='drive0', sync='full', compress=True, **args)
477        self.assert_qmp(result, 'return', {})
478
479        self.wait_until_completed()
480
481        self.vm.shutdown()
482        self.assertTrue(iotests.compare_images(test_img, blockdev_target_img,
483                                               iotests.imgfmt, format['type']),
484                        'target image does not match source after backup')
485
486    def test_complete_compress_drive_backup(self):
487        for format in TestDriveCompression.fmt_supports_compression:
488            self.do_test_compress_complete('drive-backup', format, False,
489                                           target=blockdev_target_img, mode='existing')
490
491    def test_complete_compress_blockdev_backup(self):
492        for format in TestDriveCompression.fmt_supports_compression:
493            self.do_test_compress_complete('blockdev-backup', format, True,
494                                           target='drive1')
495
496    def do_test_compress_cancel(self, cmd, format, attach_target, **args):
497        self.do_prepare_drives(format['type'], format['args'], attach_target)
498
499        self.assert_no_active_block_jobs()
500
501        result = self.vm.qmp(cmd, device='drive0', sync='full', compress=True, **args)
502        self.assert_qmp(result, 'return', {})
503
504        event = self.cancel_and_wait()
505        self.assert_qmp(event, 'data/type', 'backup')
506
507        self.vm.shutdown()
508
509    def test_compress_cancel_drive_backup(self):
510        for format in TestDriveCompression.fmt_supports_compression:
511            self.do_test_compress_cancel('drive-backup', format, False,
512                                         target=blockdev_target_img, mode='existing')
513
514    def test_compress_cancel_blockdev_backup(self):
515       for format in TestDriveCompression.fmt_supports_compression:
516            self.do_test_compress_cancel('blockdev-backup', format, True,
517                                         target='drive1')
518
519    def do_test_compress_pause(self, cmd, format, attach_target, **args):
520        self.do_prepare_drives(format['type'], format['args'], attach_target)
521
522        self.assert_no_active_block_jobs()
523
524        self.vm.pause_drive('drive0')
525        result = self.vm.qmp(cmd, device='drive0', sync='full', compress=True, **args)
526        self.assert_qmp(result, 'return', {})
527
528        result = self.vm.qmp('block-job-pause', device='drive0')
529        self.assert_qmp(result, 'return', {})
530
531        self.vm.resume_drive('drive0')
532        time.sleep(1)
533        result = self.vm.qmp('query-block-jobs')
534        offset = self.dictpath(result, 'return[0]/offset')
535
536        time.sleep(1)
537        result = self.vm.qmp('query-block-jobs')
538        self.assert_qmp(result, 'return[0]/offset', offset)
539
540        result = self.vm.qmp('block-job-resume', device='drive0')
541        self.assert_qmp(result, 'return', {})
542
543        self.wait_until_completed()
544
545        self.vm.shutdown()
546        self.assertTrue(iotests.compare_images(test_img, blockdev_target_img,
547                                               iotests.imgfmt, format['type']),
548                        'target image does not match source after backup')
549
550    def test_compress_pause_drive_backup(self):
551        for format in TestDriveCompression.fmt_supports_compression:
552            self.do_test_compress_pause('drive-backup', format, False,
553                                        target=blockdev_target_img, mode='existing')
554
555    def test_compress_pause_blockdev_backup(self):
556        for format in TestDriveCompression.fmt_supports_compression:
557            self.do_test_compress_pause('blockdev-backup', format, True,
558                                        target='drive1')
559
560if __name__ == '__main__':
561    iotests.main(supported_fmts=['raw', 'qcow2'])
562