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