xref: /qemu/tests/qemu-iotests/055 (revision bfa3ab61)
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        self.vm.launch()
46
47    def tearDown(self):
48        self.vm.shutdown()
49        os.remove(test_img)
50        os.remove(blockdev_target_img)
51        try:
52            os.remove(target_img)
53        except OSError:
54            pass
55
56    def do_test_cancel(self, cmd, target):
57        self.assert_no_active_block_jobs()
58
59        result = self.vm.qmp(cmd, device='drive0', target=target, sync='full')
60        self.assert_qmp(result, 'return', {})
61
62        event = self.cancel_and_wait()
63        self.assert_qmp(event, 'data/type', 'backup')
64
65    def test_cancel_drive_backup(self):
66        self.do_test_cancel('drive-backup', target_img)
67
68    def test_cancel_blockdev_backup(self):
69        self.do_test_cancel('blockdev-backup', 'drive1')
70
71    def do_test_pause(self, cmd, target, image):
72        self.assert_no_active_block_jobs()
73
74        self.vm.pause_drive('drive0')
75        result = self.vm.qmp(cmd, device='drive0',
76                             target=target, sync='full')
77        self.assert_qmp(result, 'return', {})
78
79        result = self.vm.qmp('block-job-pause', device='drive0')
80        self.assert_qmp(result, 'return', {})
81
82        self.vm.resume_drive('drive0')
83        time.sleep(1)
84        result = self.vm.qmp('query-block-jobs')
85        offset = self.dictpath(result, 'return[0]/offset')
86
87        time.sleep(1)
88        result = self.vm.qmp('query-block-jobs')
89        self.assert_qmp(result, 'return[0]/offset', offset)
90
91        result = self.vm.qmp('block-job-resume', device='drive0')
92        self.assert_qmp(result, 'return', {})
93
94        self.wait_until_completed()
95
96        self.vm.shutdown()
97        self.assertTrue(iotests.compare_images(test_img, image),
98                        'target image does not match source after backup')
99
100    def test_pause_drive_backup(self):
101        self.do_test_pause('drive-backup', target_img, target_img)
102
103    def test_pause_blockdev_backup(self):
104        self.do_test_pause('blockdev-backup', 'drive1', blockdev_target_img)
105
106    def test_medium_not_found(self):
107        result = self.vm.qmp('drive-backup', device='ide1-cd0',
108                             target=target_img, sync='full')
109        self.assert_qmp(result, 'error/class', 'GenericError')
110
111    def test_medium_not_found_blockdev_backup(self):
112        result = self.vm.qmp('blockdev-backup', device='ide1-cd0',
113                             target='drive1', sync='full')
114        self.assert_qmp(result, 'error/class', 'GenericError')
115
116    def test_image_not_found(self):
117        result = self.vm.qmp('drive-backup', device='drive0',
118                             target=target_img, sync='full', mode='existing')
119        self.assert_qmp(result, 'error/class', 'GenericError')
120
121    def test_invalid_format(self):
122        result = self.vm.qmp('drive-backup', device='drive0',
123                             target=target_img, sync='full',
124                             format='spaghetti-noodles')
125        self.assert_qmp(result, 'error/class', 'GenericError')
126
127    def do_test_device_not_found(self, cmd, **args):
128        result = self.vm.qmp(cmd, **args)
129        if cmd == 'drive-backup':
130            self.assert_qmp(result, 'error/class', 'DeviceNotFound')
131        else:
132            self.assert_qmp(result, 'error/class', 'GenericError')
133
134    def test_device_not_found(self):
135        self.do_test_device_not_found('drive-backup', device='nonexistent',
136                                      target=target_img, sync='full')
137
138        self.do_test_device_not_found('blockdev-backup', device='nonexistent',
139                                      target='drive0', sync='full')
140
141        self.do_test_device_not_found('blockdev-backup', device='drive0',
142                                      target='nonexistent', sync='full')
143
144        self.do_test_device_not_found('blockdev-backup', device='nonexistent',
145                                      target='nonexistent', sync='full')
146
147    def test_target_is_source(self):
148        result = self.vm.qmp('blockdev-backup', device='drive0',
149                             target='drive0', sync='full')
150        self.assert_qmp(result, 'error/class', 'GenericError')
151
152class TestSetSpeed(iotests.QMPTestCase):
153    image_len = 80 * 1024 * 1024 # MB
154
155    def setUp(self):
156        qemu_img('create', '-f', iotests.imgfmt, test_img, str(TestSetSpeed.image_len))
157        qemu_io('-f', iotests.imgfmt, '-c', 'write -P1 0 512', test_img)
158        qemu_img('create', '-f', iotests.imgfmt, blockdev_target_img, str(TestSingleDrive.image_len))
159
160        self.vm = iotests.VM().add_drive(test_img).add_drive(blockdev_target_img)
161        self.vm.launch()
162
163    def tearDown(self):
164        self.vm.shutdown()
165        os.remove(test_img)
166        os.remove(blockdev_target_img)
167        try:
168            os.remove(target_img)
169        except OSError:
170            pass
171
172    def do_test_set_speed(self, cmd, target):
173        self.assert_no_active_block_jobs()
174
175        self.vm.pause_drive('drive0')
176        result = self.vm.qmp(cmd, device='drive0', target=target, sync='full')
177        self.assert_qmp(result, 'return', {})
178
179        # Default speed is 0
180        result = self.vm.qmp('query-block-jobs')
181        self.assert_qmp(result, 'return[0]/device', 'drive0')
182        self.assert_qmp(result, 'return[0]/speed', 0)
183
184        result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024)
185        self.assert_qmp(result, 'return', {})
186
187        # Ensure the speed we set was accepted
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', 8 * 1024 * 1024)
191
192        event = self.cancel_and_wait(resume=True)
193        self.assert_qmp(event, 'data/type', 'backup')
194
195        # Check setting speed option works
196        self.vm.pause_drive('drive0')
197        result = self.vm.qmp(cmd, device='drive0',
198                             target=target, sync='full', speed=4*1024*1024)
199        self.assert_qmp(result, 'return', {})
200
201        result = self.vm.qmp('query-block-jobs')
202        self.assert_qmp(result, 'return[0]/device', 'drive0')
203        self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024)
204
205        event = self.cancel_and_wait(resume=True)
206        self.assert_qmp(event, 'data/type', 'backup')
207
208    def test_set_speed_drive_backup(self):
209        self.do_test_set_speed('drive-backup', target_img)
210
211    def test_set_speed_blockdev_backup(self):
212        self.do_test_set_speed('blockdev-backup', 'drive1')
213
214    def do_test_set_speed_invalid(self, cmd, target):
215        self.assert_no_active_block_jobs()
216
217        result = self.vm.qmp(cmd, device='drive0',
218                             target=target, sync='full', speed=-1)
219        self.assert_qmp(result, 'error/class', 'GenericError')
220
221        self.assert_no_active_block_jobs()
222
223        self.vm.pause_drive('drive0')
224        result = self.vm.qmp(cmd, device='drive0',
225                             target=target, sync='full')
226        self.assert_qmp(result, 'return', {})
227
228        result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1)
229        self.assert_qmp(result, 'error/class', 'GenericError')
230
231        event = self.cancel_and_wait(resume=True)
232        self.assert_qmp(event, 'data/type', 'backup')
233
234    def test_set_speed_invalid_drive_backup(self):
235        self.do_test_set_speed_invalid('drive-backup', target_img)
236
237    def test_set_speed_invalid_blockdev_backup(self):
238        self.do_test_set_speed_invalid('blockdev-backup',  'drive1')
239
240class TestSingleTransaction(iotests.QMPTestCase):
241    image_len = 64 * 1024 * 1024 # MB
242
243    def setUp(self):
244        qemu_img('create', '-f', iotests.imgfmt, test_img, str(TestSingleTransaction.image_len))
245        qemu_io('-f', iotests.imgfmt, '-c', 'write -P0x5d 0 64k', test_img)
246        qemu_io('-f', iotests.imgfmt, '-c', 'write -P0xd5 1M 32k', test_img)
247        qemu_io('-f', iotests.imgfmt, '-c', 'write -P0xdc 32M 124k', test_img)
248        qemu_io('-f', iotests.imgfmt, '-c', 'write -P0xdc 67043328 64k', test_img)
249        qemu_img('create', '-f', iotests.imgfmt, blockdev_target_img, str(TestSingleDrive.image_len))
250
251        self.vm = iotests.VM().add_drive(test_img).add_drive(blockdev_target_img)
252        self.vm.launch()
253
254    def tearDown(self):
255        self.vm.shutdown()
256        os.remove(test_img)
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        result = self.vm.qmp('transaction', actions=[{
327                'type': cmd,
328                'data': { 'device': 'ide1-cd0',
329                          'target': target,
330                          'sync': 'full' },
331            }
332        ])
333        self.assert_qmp(result, 'error/class', 'GenericError')
334
335    def test_medium_not_found_drive_backup(self):
336        self.do_test_medium_not_found('drive-backup', target_img)
337
338    def test_medium_not_found_blockdev_backup(self):
339        self.do_test_medium_not_found('blockdev-backup', 'drive1')
340
341    def test_image_not_found(self):
342        result = self.vm.qmp('transaction', actions=[{
343                'type': 'drive-backup',
344                'data': { 'device': 'drive0',
345                          'mode': 'existing',
346                          'target': target_img,
347                          'sync': 'full' },
348            }
349        ])
350        self.assert_qmp(result, 'error/class', 'GenericError')
351
352    def test_device_not_found(self):
353        result = self.vm.qmp('transaction', actions=[{
354                'type': 'drive-backup',
355                'data': { 'device': 'nonexistent',
356                          'mode': 'existing',
357                          'target': target_img,
358                          'sync': 'full' },
359            }
360        ])
361        self.assert_qmp(result, 'error/class', 'DeviceNotFound')
362
363        result = self.vm.qmp('transaction', actions=[{
364                'type': 'blockdev-backup',
365                'data': { 'device': 'nonexistent',
366                          'target': 'drive1',
367                          'sync': 'full' },
368            }
369        ])
370        self.assert_qmp(result, 'error/class', 'GenericError')
371
372        result = self.vm.qmp('transaction', actions=[{
373                'type': 'blockdev-backup',
374                'data': { 'device': 'drive0',
375                          'target': 'nonexistent',
376                          'sync': 'full' },
377            }
378        ])
379        self.assert_qmp(result, 'error/class', 'GenericError')
380
381        result = self.vm.qmp('transaction', actions=[{
382                'type': 'blockdev-backup',
383                'data': { 'device': 'nonexistent',
384                          'target': 'nonexistent',
385                          'sync': 'full' },
386            }
387        ])
388        self.assert_qmp(result, 'error/class', 'GenericError')
389
390    def test_target_is_source(self):
391        result = self.vm.qmp('transaction', actions=[{
392                'type': 'blockdev-backup',
393                'data': { 'device': 'drive0',
394                          'target': 'drive0',
395                          'sync': 'full' },
396            }
397        ])
398        self.assert_qmp(result, 'error/class', 'GenericError')
399
400    def test_abort(self):
401        result = self.vm.qmp('transaction', actions=[{
402                'type': 'drive-backup',
403                'data': { 'device': 'nonexistent',
404                          'mode': 'existing',
405                          'target': target_img,
406                          'sync': 'full' },
407            }, {
408                'type': 'Abort',
409                'data': {},
410            }
411        ])
412        self.assert_qmp(result, 'error/class', 'GenericError')
413        self.assert_no_active_block_jobs()
414
415        result = self.vm.qmp('transaction', actions=[{
416                'type': 'blockdev-backup',
417                'data': { 'device': 'nonexistent',
418                          'target': 'drive1',
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': 'drive0',
431                          'target': 'nonexistent',
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
441if __name__ == '__main__':
442    iotests.main(supported_fmts=['raw', 'qcow2'])
443