xref: /qemu/tests/qemu-iotests/055 (revision 67cc32eb)
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
32class TestSingleDrive(iotests.QMPTestCase):
33    image_len = 64 * 1024 * 1024 # MB
34
35    def setUp(self):
36        # Write data to the image so we can compare later
37        qemu_img('create', '-f', iotests.imgfmt, test_img, str(TestSingleDrive.image_len))
38        qemu_io('-f', iotests.imgfmt, '-c', 'write -P0x5d 0 64k', 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 -P0xdc 67043328 64k', test_img)
42        qemu_img('create', '-f', iotests.imgfmt, blockdev_target_img, str(TestSingleDrive.image_len))
43
44        self.vm = iotests.VM().add_drive(test_img).add_drive(blockdev_target_img)
45        if iotests.qemu_default_machine == 'pc':
46            self.vm.add_drive(None, 'media=cdrom', 'ide')
47        self.vm.launch()
48
49    def tearDown(self):
50        self.vm.shutdown()
51        os.remove(test_img)
52        os.remove(blockdev_target_img)
53        try:
54            os.remove(target_img)
55        except OSError:
56            pass
57
58    def do_test_cancel(self, cmd, target):
59        self.assert_no_active_block_jobs()
60
61        result = self.vm.qmp(cmd, device='drive0', target=target, sync='full')
62        self.assert_qmp(result, 'return', {})
63
64        event = self.cancel_and_wait()
65        self.assert_qmp(event, 'data/type', 'backup')
66
67    def test_cancel_drive_backup(self):
68        self.do_test_cancel('drive-backup', target_img)
69
70    def test_cancel_blockdev_backup(self):
71        self.do_test_cancel('blockdev-backup', 'drive1')
72
73    def do_test_pause(self, cmd, target, image):
74        self.assert_no_active_block_jobs()
75
76        self.vm.pause_drive('drive0')
77        result = self.vm.qmp(cmd, device='drive0',
78                             target=target, sync='full')
79        self.assert_qmp(result, 'return', {})
80
81        result = self.vm.qmp('block-job-pause', device='drive0')
82        self.assert_qmp(result, 'return', {})
83
84        self.vm.resume_drive('drive0')
85        time.sleep(1)
86        result = self.vm.qmp('query-block-jobs')
87        offset = self.dictpath(result, 'return[0]/offset')
88
89        time.sleep(1)
90        result = self.vm.qmp('query-block-jobs')
91        self.assert_qmp(result, 'return[0]/offset', offset)
92
93        result = self.vm.qmp('block-job-resume', device='drive0')
94        self.assert_qmp(result, 'return', {})
95
96        self.wait_until_completed()
97
98        self.vm.shutdown()
99        self.assertTrue(iotests.compare_images(test_img, image),
100                        'target image does not match source after backup')
101
102    def test_pause_drive_backup(self):
103        self.do_test_pause('drive-backup', target_img, target_img)
104
105    def test_pause_blockdev_backup(self):
106        self.do_test_pause('blockdev-backup', 'drive1', blockdev_target_img)
107
108    def test_medium_not_found(self):
109        if iotests.qemu_default_machine != 'pc':
110            return
111
112        result = self.vm.qmp('drive-backup', device='drive2', # CD-ROM
113                             target=target_img, sync='full')
114        self.assert_qmp(result, 'error/class', 'GenericError')
115
116    def test_medium_not_found_blockdev_backup(self):
117        if iotests.qemu_default_machine != 'pc':
118            return
119
120        result = self.vm.qmp('blockdev-backup', device='drive2', # CD-ROM
121                             target='drive1', sync='full')
122        self.assert_qmp(result, 'error/class', 'GenericError')
123
124    def test_image_not_found(self):
125        result = self.vm.qmp('drive-backup', device='drive0',
126                             target=target_img, sync='full', mode='existing')
127        self.assert_qmp(result, 'error/class', 'GenericError')
128
129    def test_invalid_format(self):
130        result = self.vm.qmp('drive-backup', device='drive0',
131                             target=target_img, sync='full',
132                             format='spaghetti-noodles')
133        self.assert_qmp(result, 'error/class', 'GenericError')
134
135    def do_test_device_not_found(self, cmd, **args):
136        result = self.vm.qmp(cmd, **args)
137        if cmd == 'drive-backup':
138            self.assert_qmp(result, 'error/class', 'DeviceNotFound')
139        else:
140            self.assert_qmp(result, 'error/class', 'GenericError')
141
142    def test_device_not_found(self):
143        self.do_test_device_not_found('drive-backup', device='nonexistent',
144                                      target=target_img, sync='full')
145
146        self.do_test_device_not_found('blockdev-backup', device='nonexistent',
147                                      target='drive0', sync='full')
148
149        self.do_test_device_not_found('blockdev-backup', device='drive0',
150                                      target='nonexistent', sync='full')
151
152        self.do_test_device_not_found('blockdev-backup', device='nonexistent',
153                                      target='nonexistent', sync='full')
154
155    def test_target_is_source(self):
156        result = self.vm.qmp('blockdev-backup', device='drive0',
157                             target='drive0', sync='full')
158        self.assert_qmp(result, 'error/class', 'GenericError')
159
160class TestSetSpeed(iotests.QMPTestCase):
161    image_len = 80 * 1024 * 1024 # MB
162
163    def setUp(self):
164        qemu_img('create', '-f', iotests.imgfmt, test_img, str(TestSetSpeed.image_len))
165        qemu_io('-f', iotests.imgfmt, '-c', 'write -P1 0 512', test_img)
166        qemu_img('create', '-f', iotests.imgfmt, blockdev_target_img, str(TestSingleDrive.image_len))
167
168        self.vm = iotests.VM().add_drive(test_img).add_drive(blockdev_target_img)
169        self.vm.launch()
170
171    def tearDown(self):
172        self.vm.shutdown()
173        os.remove(test_img)
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    image_len = 64 * 1024 * 1024 # MB
250
251    def setUp(self):
252        qemu_img('create', '-f', iotests.imgfmt, test_img, str(TestSingleTransaction.image_len))
253        qemu_io('-f', iotests.imgfmt, '-c', 'write -P0x5d 0 64k', test_img)
254        qemu_io('-f', iotests.imgfmt, '-c', 'write -P0xd5 1M 32k', test_img)
255        qemu_io('-f', iotests.imgfmt, '-c', 'write -P0xdc 32M 124k', test_img)
256        qemu_io('-f', iotests.imgfmt, '-c', 'write -P0xdc 67043328 64k', test_img)
257        qemu_img('create', '-f', iotests.imgfmt, blockdev_target_img, str(TestSingleDrive.image_len))
258
259        self.vm = iotests.VM().add_drive(test_img).add_drive(blockdev_target_img)
260        if iotests.qemu_default_machine == 'pc':
261            self.vm.add_drive(None, 'media=cdrom', 'ide')
262        self.vm.launch()
263
264    def tearDown(self):
265        self.vm.shutdown()
266        os.remove(test_img)
267        os.remove(blockdev_target_img)
268        try:
269            os.remove(target_img)
270        except OSError:
271            pass
272
273    def do_test_cancel(self, cmd, target):
274        self.assert_no_active_block_jobs()
275
276        result = self.vm.qmp('transaction', actions=[{
277                'type': cmd,
278                'data': { 'device': 'drive0',
279                          'target': target,
280                          'sync': 'full' },
281            }
282        ])
283
284        self.assert_qmp(result, 'return', {})
285
286        event = self.cancel_and_wait()
287        self.assert_qmp(event, 'data/type', 'backup')
288
289    def test_cancel_drive_backup(self):
290        self.do_test_cancel('drive-backup', target_img)
291
292    def test_cancel_blockdev_backup(self):
293        self.do_test_cancel('blockdev-backup', 'drive1')
294
295    def do_test_pause(self, cmd, target, image):
296        self.assert_no_active_block_jobs()
297
298        self.vm.pause_drive('drive0')
299        result = self.vm.qmp('transaction', actions=[{
300                'type': cmd,
301                'data': { 'device': 'drive0',
302                          'target': target,
303                          'sync': 'full' },
304            }
305        ])
306        self.assert_qmp(result, 'return', {})
307
308        result = self.vm.qmp('block-job-pause', device='drive0')
309        self.assert_qmp(result, 'return', {})
310
311        self.vm.resume_drive('drive0')
312        time.sleep(1)
313        result = self.vm.qmp('query-block-jobs')
314        offset = self.dictpath(result, 'return[0]/offset')
315
316        time.sleep(1)
317        result = self.vm.qmp('query-block-jobs')
318        self.assert_qmp(result, 'return[0]/offset', offset)
319
320        result = self.vm.qmp('block-job-resume', device='drive0')
321        self.assert_qmp(result, 'return', {})
322
323        self.wait_until_completed()
324
325        self.vm.shutdown()
326        self.assertTrue(iotests.compare_images(test_img, image),
327                        'target image does not match source after backup')
328
329    def test_pause_drive_backup(self):
330        self.do_test_pause('drive-backup', target_img, target_img)
331
332    def test_pause_blockdev_backup(self):
333        self.do_test_pause('blockdev-backup', 'drive1', blockdev_target_img)
334
335    def do_test_medium_not_found(self, cmd, target):
336        if iotests.qemu_default_machine != 'pc':
337            return
338
339        result = self.vm.qmp('transaction', actions=[{
340                'type': cmd,
341                'data': { 'device': 'drive2', # CD-ROM
342                          'target': target,
343                          'sync': 'full' },
344            }
345        ])
346        self.assert_qmp(result, 'error/class', 'GenericError')
347
348    def test_medium_not_found_drive_backup(self):
349        self.do_test_medium_not_found('drive-backup', target_img)
350
351    def test_medium_not_found_blockdev_backup(self):
352        self.do_test_medium_not_found('blockdev-backup', 'drive1')
353
354    def test_image_not_found(self):
355        result = self.vm.qmp('transaction', actions=[{
356                'type': 'drive-backup',
357                'data': { 'device': 'drive0',
358                          'mode': 'existing',
359                          'target': target_img,
360                          'sync': 'full' },
361            }
362        ])
363        self.assert_qmp(result, 'error/class', 'GenericError')
364
365    def test_device_not_found(self):
366        result = self.vm.qmp('transaction', actions=[{
367                'type': 'drive-backup',
368                'data': { 'device': 'nonexistent',
369                          'mode': 'existing',
370                          'target': target_img,
371                          'sync': 'full' },
372            }
373        ])
374        self.assert_qmp(result, 'error/class', 'DeviceNotFound')
375
376        result = self.vm.qmp('transaction', actions=[{
377                'type': 'blockdev-backup',
378                'data': { 'device': 'nonexistent',
379                          'target': 'drive1',
380                          'sync': 'full' },
381            }
382        ])
383        self.assert_qmp(result, 'error/class', 'GenericError')
384
385        result = self.vm.qmp('transaction', actions=[{
386                'type': 'blockdev-backup',
387                'data': { 'device': 'drive0',
388                          'target': 'nonexistent',
389                          'sync': 'full' },
390            }
391        ])
392        self.assert_qmp(result, 'error/class', 'GenericError')
393
394        result = self.vm.qmp('transaction', actions=[{
395                'type': 'blockdev-backup',
396                'data': { 'device': 'nonexistent',
397                          'target': 'nonexistent',
398                          'sync': 'full' },
399            }
400        ])
401        self.assert_qmp(result, 'error/class', 'GenericError')
402
403    def test_target_is_source(self):
404        result = self.vm.qmp('transaction', actions=[{
405                'type': 'blockdev-backup',
406                'data': { 'device': 'drive0',
407                          'target': 'drive0',
408                          'sync': 'full' },
409            }
410        ])
411        self.assert_qmp(result, 'error/class', 'GenericError')
412
413    def test_abort(self):
414        result = self.vm.qmp('transaction', actions=[{
415                'type': 'drive-backup',
416                'data': { 'device': 'nonexistent',
417                          'mode': 'existing',
418                          'target': target_img,
419                          'sync': 'full' },
420            }, {
421                'type': 'Abort',
422                'data': {},
423            }
424        ])
425        self.assert_qmp(result, 'error/class', 'GenericError')
426        self.assert_no_active_block_jobs()
427
428        result = self.vm.qmp('transaction', actions=[{
429                'type': 'blockdev-backup',
430                'data': { 'device': 'nonexistent',
431                          'target': 'drive1',
432                          'sync': 'full' },
433            }, {
434                'type': 'Abort',
435                'data': {},
436            }
437        ])
438        self.assert_qmp(result, 'error/class', 'GenericError')
439        self.assert_no_active_block_jobs()
440
441        result = self.vm.qmp('transaction', actions=[{
442                'type': 'blockdev-backup',
443                'data': { 'device': 'drive0',
444                          'target': 'nonexistent',
445                          'sync': 'full' },
446            }, {
447                'type': 'Abort',
448                'data': {},
449            }
450        ])
451        self.assert_qmp(result, 'error/class', 'GenericError')
452        self.assert_no_active_block_jobs()
453
454if __name__ == '__main__':
455    iotests.main(supported_fmts=['raw', 'qcow2'])
456