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