xref: /qemu/tests/qemu-iotests/056 (revision b49f4755)
1#!/usr/bin/env python3
2# group: rw backing
3#
4# Tests for drive-backup
5#
6# Copyright (C) 2013 Red Hat, Inc.
7#
8# Based on 041.
9#
10# This program is free software; you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation; either version 2 of the License, or
13# (at your option) any later version.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with this program.  If not, see <http://www.gnu.org/licenses/>.
22#
23
24import time
25import os
26import iotests
27from iotests import qemu_img, qemu_io, create_image
28
29backing_img = os.path.join(iotests.test_dir, 'backing.img')
30test_img = os.path.join(iotests.test_dir, 'test.img')
31target_img = os.path.join(iotests.test_dir, 'target.img')
32
33def img_create(img, fmt=iotests.imgfmt, size='64M', **kwargs):
34    fullname = os.path.join(iotests.test_dir, '%s.%s' % (img, fmt))
35    optargs = []
36    for k,v in kwargs.items():
37        optargs = optargs + ['-o', '%s=%s' % (k,v)]
38    args = ['create', '-f', fmt] + optargs + [fullname, size]
39    iotests.qemu_img(*args)
40    return fullname
41
42def try_remove(img):
43    try:
44        os.remove(img)
45    except OSError:
46        pass
47
48def io_write_patterns(img, patterns):
49    for pattern in patterns:
50        iotests.qemu_io('-c', 'write -P%s %s %s' % pattern, img)
51
52
53class TestSyncModesNoneAndTop(iotests.QMPTestCase):
54    image_len = 64 * 1024 * 1024 # MB
55
56    def setUp(self):
57        create_image(backing_img, TestSyncModesNoneAndTop.image_len)
58        qemu_img('create', '-f', iotests.imgfmt,
59                 '-o', 'backing_file=%s' % backing_img, '-F', 'raw', test_img)
60        qemu_io('-c', 'write -P0x41 0 512', test_img)
61        qemu_io('-c', 'write -P0xd5 1M 32k', test_img)
62        qemu_io('-c', 'write -P0xdc 32M 124k', test_img)
63        qemu_io('-c', 'write -P0xdc 67043328 64k', test_img)
64        self.vm = iotests.VM().add_drive(test_img)
65        self.vm.launch()
66
67    def tearDown(self):
68        self.vm.shutdown()
69        os.remove(test_img)
70        os.remove(backing_img)
71        try:
72            os.remove(target_img)
73        except OSError:
74            pass
75
76    def test_complete_top(self):
77        self.assert_no_active_block_jobs()
78        self.vm.cmd('drive-backup', device='drive0', sync='top',
79                    format=iotests.imgfmt, target=target_img)
80
81        self.wait_until_completed(check_offset=False)
82
83        self.assert_no_active_block_jobs()
84        self.vm.shutdown()
85        self.assertTrue(iotests.compare_images(test_img, target_img),
86                        'target image does not match source after backup')
87
88    def test_cancel_sync_none(self):
89        self.assert_no_active_block_jobs()
90
91        self.vm.cmd('drive-backup', device='drive0',
92                    sync='none', target=target_img)
93        time.sleep(1)
94        self.vm.hmp_qemu_io('drive0', 'write -P0x5e 0 512')
95        self.vm.hmp_qemu_io('drive0', 'aio_flush')
96        # Verify that the original contents exist in the target image.
97
98        event = self.cancel_and_wait()
99        self.assert_qmp(event, 'data/type', 'backup')
100
101        self.vm.shutdown()
102        time.sleep(1)
103        qemu_io('-c', 'read -P0x41 0 512', target_img)
104
105class TestBeforeWriteNotifier(iotests.QMPTestCase):
106    def setUp(self):
107        self.vm = iotests.VM().add_drive_raw("file=blkdebug::null-co://,id=drive0,align=65536,driver=blkdebug")
108        self.vm.launch()
109
110    def tearDown(self):
111        self.vm.shutdown()
112        os.remove(target_img)
113
114    def test_before_write_notifier(self):
115        self.vm.pause_drive("drive0")
116        self.vm.cmd('drive-backup', device='drive0',
117                    sync='full', target=target_img,
118                    format="file", speed=1)
119        self.vm.cmd('block-job-pause', device="drive0")
120        # Speed is low enough that this must be an uncopied range, which will
121        # trigger the before write notifier
122        self.vm.hmp_qemu_io('drive0', 'aio_write -P 1 512512 512')
123        self.vm.resume_drive("drive0")
124        self.vm.cmd('block-job-resume', device="drive0")
125        event = self.cancel_and_wait()
126        self.assert_qmp(event, 'data/type', 'backup')
127
128class BackupTest(iotests.QMPTestCase):
129    def setUp(self):
130        self.vm = iotests.VM()
131        self.test_img = img_create('test')
132        self.dest_img = img_create('dest')
133        self.dest_img2 = img_create('dest2')
134        self.ref_img = img_create('ref')
135        self.vm.add_drive(self.test_img)
136        self.vm.launch()
137
138    def tearDown(self):
139        self.vm.shutdown()
140        try_remove(self.test_img)
141        try_remove(self.dest_img)
142        try_remove(self.dest_img2)
143        try_remove(self.ref_img)
144
145    def hmp_io_writes(self, drive, patterns):
146        for pattern in patterns:
147            self.vm.hmp_qemu_io(drive, 'write -P%s %s %s' % pattern)
148        self.vm.hmp_qemu_io(drive, 'flush')
149
150    def qmp_backup_and_wait(self, cmd='drive-backup', serror=None,
151                            aerror=None, **kwargs):
152        if not self.qmp_backup(cmd, serror, **kwargs):
153            return False
154        return self.qmp_backup_wait(kwargs['device'], aerror)
155
156    def qmp_backup(self, cmd='drive-backup',
157                   error=None, **kwargs):
158        self.assertTrue('device' in kwargs)
159        res = self.vm.qmp(cmd, **kwargs)
160        if error:
161            self.assert_qmp(res, 'error/desc', error)
162            return False
163        self.assert_qmp(res, 'return', {})
164        return True
165
166    def qmp_backup_wait(self, device, error=None):
167        event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED",
168                                   match={'data': {'device': device}})
169        self.assertNotEqual(event, None)
170        try:
171            failure = self.dictpath(event, 'data/error')
172        except AssertionError:
173            # Backup succeeded.
174            self.assert_qmp(event, 'data/offset', event['data']['len'])
175            return True
176        else:
177            # Failure.
178            self.assert_qmp(event, 'data/error', qerror)
179            return False
180
181    def test_overlapping_writes(self):
182        # Write something to back up
183        self.hmp_io_writes('drive0', [('42', '0M', '2M')])
184
185        # Create a reference backup
186        self.qmp_backup_and_wait(device='drive0', format=iotests.imgfmt,
187                                 sync='full', target=self.ref_img,
188                                 auto_dismiss=False)
189        self.vm.cmd('block-job-dismiss', id='drive0')
190
191        # Now to the test backup: We simulate the following guest
192        # writes:
193        # (1) [1M + 64k, 1M + 128k): Afterwards, everything in that
194        #     area should be in the target image, and we must not copy
195        #     it again (because the source image has changed now)
196        #     (64k is the job's cluster size)
197        # (2) [1M, 2M): The backup job must not get overeager.  It
198        #     must copy [1M, 1M + 64k) and [1M + 128k, 2M) separately,
199        #     but not the area in between.
200
201        self.qmp_backup(device='drive0', format=iotests.imgfmt, sync='full',
202                        target=self.dest_img, speed=1, auto_dismiss=False)
203
204        self.hmp_io_writes('drive0', [('23', '%ik' % (1024 + 64), '64k'),
205                                      ('66', '1M', '1M')])
206
207        # Let the job complete
208        self.vm.cmd('block-job-set-speed', device='drive0', speed=0)
209        self.qmp_backup_wait('drive0')
210        self.vm.cmd('block-job-dismiss', id='drive0')
211
212        self.assertTrue(iotests.compare_images(self.ref_img, self.dest_img),
213                        'target image does not match reference image')
214
215    def test_dismiss_false(self):
216        res = self.vm.qmp('query-block-jobs')
217        self.assert_qmp(res, 'return', [])
218        self.qmp_backup_and_wait(device='drive0', format=iotests.imgfmt,
219                                 sync='full', target=self.dest_img,
220                                 auto_dismiss=True)
221        res = self.vm.qmp('query-block-jobs')
222        self.assert_qmp(res, 'return', [])
223
224    def test_dismiss_true(self):
225        res = self.vm.qmp('query-block-jobs')
226        self.assert_qmp(res, 'return', [])
227        self.qmp_backup_and_wait(device='drive0', format=iotests.imgfmt,
228                                 sync='full', target=self.dest_img,
229                                 auto_dismiss=False)
230        res = self.vm.qmp('query-block-jobs')
231        self.assert_qmp(res, 'return[0]/status', 'concluded')
232        self.vm.cmd('block-job-dismiss', id='drive0')
233        res = self.vm.qmp('query-block-jobs')
234        self.assert_qmp(res, 'return', [])
235
236    def test_dismiss_bad_id(self):
237        res = self.vm.qmp('query-block-jobs')
238        self.assert_qmp(res, 'return', [])
239        res = self.vm.qmp('block-job-dismiss', id='foobar')
240        self.assert_qmp(res, 'error/class', 'DeviceNotActive')
241
242    def test_dismiss_collision(self):
243        res = self.vm.qmp('query-block-jobs')
244        self.assert_qmp(res, 'return', [])
245        self.qmp_backup_and_wait(device='drive0', format=iotests.imgfmt,
246                                 sync='full', target=self.dest_img,
247                                 auto_dismiss=False)
248        res = self.vm.qmp('query-block-jobs')
249        self.assert_qmp(res, 'return[0]/status', 'concluded')
250        # Leave zombie job un-dismissed, observe a failure:
251        res = self.qmp_backup_and_wait(serror="Job ID 'drive0' already in use",
252                                       device='drive0', format=iotests.imgfmt,
253                                       sync='full', target=self.dest_img2,
254                                       auto_dismiss=False)
255        self.assertEqual(res, False)
256        # OK, dismiss the zombie.
257        self.vm.cmd('block-job-dismiss', id='drive0')
258        res = self.vm.qmp('query-block-jobs')
259        self.assert_qmp(res, 'return', [])
260        # Ensure it's really gone.
261        self.qmp_backup_and_wait(device='drive0', format=iotests.imgfmt,
262                                 sync='full', target=self.dest_img2,
263                                 auto_dismiss=False)
264
265    def dismissal_failure(self, dismissal_opt):
266        res = self.vm.qmp('query-block-jobs')
267        self.assert_qmp(res, 'return', [])
268        # Give blkdebug something to chew on
269        self.hmp_io_writes('drive0',
270                           (('0x9a', 0, 512),
271                           ('0x55', '8M', '352k'),
272                           ('0x78', '15872k', '1M')))
273        # Add destination node via blkdebug
274        self.vm.cmd('blockdev-add',
275                    node_name='target0',
276                    driver=iotests.imgfmt,
277                    file={
278                        'driver': 'blkdebug',
279                        'image': {
280                            'driver': 'file',
281                            'filename': self.dest_img
282                        },
283                        'inject-error': [{
284                            'event': 'write_aio',
285                            'errno': 5,
286                            'immediately': False,
287                            'once': True
288                        }],
289                    })
290
291        res = self.qmp_backup(cmd='blockdev-backup',
292                              device='drive0', target='target0',
293                              on_target_error='stop',
294                              sync='full',
295                              auto_dismiss=dismissal_opt)
296        self.assertTrue(res)
297        event = self.vm.event_wait(name="BLOCK_JOB_ERROR",
298                                   match={'data': {'device': 'drive0'}})
299        self.assertNotEqual(event, None)
300        # OK, job should pause, but it can't do it immediately, as it can't
301        # cancel other parallel requests (which didn't fail)
302        with iotests.Timeout(60, "Timeout waiting for backup actually paused"):
303            while True:
304                res = self.vm.qmp('query-block-jobs')
305                if res['return'][0]['status'] == 'paused':
306                    break
307        self.assert_qmp(res, 'return[0]/status', 'paused')
308        res = self.vm.qmp('block-job-dismiss', id='drive0')
309        self.assert_qmp(res, 'error/desc',
310                        "Job 'drive0' in state 'paused' cannot accept"
311                        " command verb 'dismiss'")
312        res = self.vm.qmp('query-block-jobs')
313        self.assert_qmp(res, 'return[0]/status', 'paused')
314        # OK, unstick job and move forward.
315        self.vm.cmd('block-job-resume', device='drive0')
316        # And now we need to wait for it to conclude;
317        res = self.qmp_backup_wait(device='drive0')
318        self.assertTrue(res)
319        if not dismissal_opt:
320            # Job should now be languishing:
321            res = self.vm.qmp('query-block-jobs')
322            self.assert_qmp(res, 'return[0]/status', 'concluded')
323            self.vm.cmd('block-job-dismiss', id='drive0')
324            res = self.vm.qmp('query-block-jobs')
325            self.assert_qmp(res, 'return', [])
326
327    def test_dismiss_premature(self):
328        self.dismissal_failure(False)
329
330    def test_dismiss_erroneous(self):
331        self.dismissal_failure(True)
332
333if __name__ == '__main__':
334    iotests.main(supported_fmts=['qcow2', 'qed'],
335                 supported_protocols=['file'])
336