xref: /openbmc/qemu/tests/qemu-iotests/124 (revision fc6c796f)
19f7264f5SJohn Snow#!/usr/bin/env python
29f7264f5SJohn Snow#
39f7264f5SJohn Snow# Tests for incremental drive-backup
49f7264f5SJohn Snow#
59f7264f5SJohn Snow# Copyright (C) 2015 John Snow for Red Hat, Inc.
69f7264f5SJohn Snow#
79f7264f5SJohn Snow# Based on 056.
89f7264f5SJohn Snow#
99f7264f5SJohn Snow# This program is free software; you can redistribute it and/or modify
109f7264f5SJohn Snow# it under the terms of the GNU General Public License as published by
119f7264f5SJohn Snow# the Free Software Foundation; either version 2 of the License, or
129f7264f5SJohn Snow# (at your option) any later version.
139f7264f5SJohn Snow#
149f7264f5SJohn Snow# This program is distributed in the hope that it will be useful,
159f7264f5SJohn Snow# but WITHOUT ANY WARRANTY; without even the implied warranty of
169f7264f5SJohn Snow# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
179f7264f5SJohn Snow# GNU General Public License for more details.
189f7264f5SJohn Snow#
199f7264f5SJohn Snow# You should have received a copy of the GNU General Public License
209f7264f5SJohn Snow# along with this program.  If not, see <http://www.gnu.org/licenses/>.
219f7264f5SJohn Snow#
229f7264f5SJohn Snow
239f7264f5SJohn Snowimport os
249f7264f5SJohn Snowimport iotests
259f7264f5SJohn Snow
269f7264f5SJohn Snow
279f7264f5SJohn Snowdef io_write_patterns(img, patterns):
289f7264f5SJohn Snow    for pattern in patterns:
299f7264f5SJohn Snow        iotests.qemu_io('-c', 'write -P%s %s %s' % pattern, img)
309f7264f5SJohn Snow
319f7264f5SJohn Snow
32a3d71595SJohn Snowdef try_remove(img):
33a3d71595SJohn Snow    try:
34a3d71595SJohn Snow        os.remove(img)
35a3d71595SJohn Snow    except OSError:
36a3d71595SJohn Snow        pass
37a3d71595SJohn Snow
38a3d71595SJohn Snow
39749ad5e8SJohn Snowdef transaction_action(action, **kwargs):
40749ad5e8SJohn Snow    return {
41749ad5e8SJohn Snow        'type': action,
42*fc6c796fSJohn Snow        'data': dict((k.replace('_', '-'), v) for k, v in kwargs.iteritems())
43749ad5e8SJohn Snow    }
44749ad5e8SJohn Snow
45749ad5e8SJohn Snow
46749ad5e8SJohn Snowdef transaction_bitmap_clear(node, name, **kwargs):
47749ad5e8SJohn Snow    return transaction_action('block-dirty-bitmap-clear',
48749ad5e8SJohn Snow                              node=node, name=name, **kwargs)
49749ad5e8SJohn Snow
50749ad5e8SJohn Snow
51749ad5e8SJohn Snowdef transaction_drive_backup(device, target, **kwargs):
52749ad5e8SJohn Snow    return transaction_action('drive-backup', device=device, target=target,
53749ad5e8SJohn Snow                              **kwargs)
54749ad5e8SJohn Snow
55749ad5e8SJohn Snow
56a3d71595SJohn Snowclass Bitmap:
57a3d71595SJohn Snow    def __init__(self, name, drive):
58a3d71595SJohn Snow        self.name = name
59a3d71595SJohn Snow        self.drive = drive
60a3d71595SJohn Snow        self.num = 0
61a3d71595SJohn Snow        self.backups = list()
62a3d71595SJohn Snow
63a3d71595SJohn Snow    def base_target(self):
64a3d71595SJohn Snow        return (self.drive['backup'], None)
65a3d71595SJohn Snow
66a3d71595SJohn Snow    def new_target(self, num=None):
67a3d71595SJohn Snow        if num is None:
68a3d71595SJohn Snow            num = self.num
69a3d71595SJohn Snow        self.num = num + 1
70a3d71595SJohn Snow        base = os.path.join(iotests.test_dir,
71a3d71595SJohn Snow                            "%s.%s." % (self.drive['id'], self.name))
72a3d71595SJohn Snow        suff = "%i.%s" % (num, self.drive['fmt'])
73a3d71595SJohn Snow        target = base + "inc" + suff
74a3d71595SJohn Snow        reference = base + "ref" + suff
75a3d71595SJohn Snow        self.backups.append((target, reference))
76a3d71595SJohn Snow        return (target, reference)
77a3d71595SJohn Snow
78a3d71595SJohn Snow    def last_target(self):
79a3d71595SJohn Snow        if self.backups:
80a3d71595SJohn Snow            return self.backups[-1]
81a3d71595SJohn Snow        return self.base_target()
82a3d71595SJohn Snow
83a3d71595SJohn Snow    def del_target(self):
84a3d71595SJohn Snow        for image in self.backups.pop():
85a3d71595SJohn Snow            try_remove(image)
86a3d71595SJohn Snow        self.num -= 1
87a3d71595SJohn Snow
88a3d71595SJohn Snow    def cleanup(self):
89a3d71595SJohn Snow        for backup in self.backups:
90a3d71595SJohn Snow            for image in backup:
91a3d71595SJohn Snow                try_remove(image)
92a3d71595SJohn Snow
93a3d71595SJohn Snow
949f7264f5SJohn Snowclass TestIncrementalBackup(iotests.QMPTestCase):
959f7264f5SJohn Snow    def setUp(self):
969f7264f5SJohn Snow        self.bitmaps = list()
979f7264f5SJohn Snow        self.files = list()
989f7264f5SJohn Snow        self.drives = list()
999f7264f5SJohn Snow        self.vm = iotests.VM()
1009f7264f5SJohn Snow        self.err_img = os.path.join(iotests.test_dir, 'err.%s' % iotests.imgfmt)
1019f7264f5SJohn Snow
1029f7264f5SJohn Snow        # Create a base image with a distinctive patterning
1039f7264f5SJohn Snow        drive0 = self.add_node('drive0')
1049f7264f5SJohn Snow        self.img_create(drive0['file'], drive0['fmt'])
1059f7264f5SJohn Snow        self.vm.add_drive(drive0['file'])
1069f7264f5SJohn Snow        io_write_patterns(drive0['file'], (('0x41', 0, 512),
1079f7264f5SJohn Snow                                           ('0xd5', '1M', '32k'),
1089f7264f5SJohn Snow                                           ('0xdc', '32M', '124k')))
1099f7264f5SJohn Snow        self.vm.launch()
1109f7264f5SJohn Snow
1119f7264f5SJohn Snow
1129f7264f5SJohn Snow    def add_node(self, node_id, fmt=iotests.imgfmt, path=None, backup=None):
1139f7264f5SJohn Snow        if path is None:
1149f7264f5SJohn Snow            path = os.path.join(iotests.test_dir, '%s.%s' % (node_id, fmt))
1159f7264f5SJohn Snow        if backup is None:
1169f7264f5SJohn Snow            backup = os.path.join(iotests.test_dir,
1179f7264f5SJohn Snow                                  '%s.full.backup.%s' % (node_id, fmt))
1189f7264f5SJohn Snow
1199f7264f5SJohn Snow        self.drives.append({
1209f7264f5SJohn Snow            'id': node_id,
1219f7264f5SJohn Snow            'file': path,
1229f7264f5SJohn Snow            'backup': backup,
1239f7264f5SJohn Snow            'fmt': fmt })
1249f7264f5SJohn Snow        return self.drives[-1]
1259f7264f5SJohn Snow
1269f7264f5SJohn Snow
1279f7264f5SJohn Snow    def img_create(self, img, fmt=iotests.imgfmt, size='64M',
1289f7264f5SJohn Snow                   parent=None, parentFormat=None):
1299f7264f5SJohn Snow        if parent:
1309f7264f5SJohn Snow            if parentFormat is None:
1319f7264f5SJohn Snow                parentFormat = fmt
1329f7264f5SJohn Snow            iotests.qemu_img('create', '-f', fmt, img, size,
1339f7264f5SJohn Snow                             '-b', parent, '-F', parentFormat)
1349f7264f5SJohn Snow        else:
1359f7264f5SJohn Snow            iotests.qemu_img('create', '-f', fmt, img, size)
1369f7264f5SJohn Snow        self.files.append(img)
1379f7264f5SJohn Snow
138a3d71595SJohn Snow
139a3d71595SJohn Snow    def do_qmp_backup(self, error='Input/output error', **kwargs):
140a3d71595SJohn Snow        res = self.vm.qmp('drive-backup', **kwargs)
141a3d71595SJohn Snow        self.assert_qmp(res, 'return', {})
142*fc6c796fSJohn Snow        return self.wait_qmp_backup(kwargs['device'], error)
143a3d71595SJohn Snow
144*fc6c796fSJohn Snow
145*fc6c796fSJohn Snow    def wait_qmp_backup(self, device, error='Input/output error'):
146a3d71595SJohn Snow        event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED",
147*fc6c796fSJohn Snow                                   match={'data': {'device': device}})
148ff793890SJohn Snow        self.assertNotEqual(event, None)
149a3d71595SJohn Snow
150a3d71595SJohn Snow        try:
151a3d71595SJohn Snow            failure = self.dictpath(event, 'data/error')
152a3d71595SJohn Snow        except AssertionError:
153a3d71595SJohn Snow            # Backup succeeded.
154a3d71595SJohn Snow            self.assert_qmp(event, 'data/offset', event['data']['len'])
155a3d71595SJohn Snow            return True
156a3d71595SJohn Snow        else:
157a3d71595SJohn Snow            # Backup failed.
158a3d71595SJohn Snow            self.assert_qmp(event, 'data/error', error)
159a3d71595SJohn Snow            return False
160a3d71595SJohn Snow
161a3d71595SJohn Snow
162*fc6c796fSJohn Snow    def wait_qmp_backup_cancelled(self, device):
163*fc6c796fSJohn Snow        event = self.vm.event_wait(name='BLOCK_JOB_CANCELLED',
164*fc6c796fSJohn Snow                                   match={'data': {'device': device}})
165*fc6c796fSJohn Snow        self.assertNotEqual(event, None)
166*fc6c796fSJohn Snow
167*fc6c796fSJohn Snow
168a3d71595SJohn Snow    def create_anchor_backup(self, drive=None):
169a3d71595SJohn Snow        if drive is None:
170a3d71595SJohn Snow            drive = self.drives[-1]
171a3d71595SJohn Snow        res = self.do_qmp_backup(device=drive['id'], sync='full',
172a3d71595SJohn Snow                                 format=drive['fmt'], target=drive['backup'])
173a3d71595SJohn Snow        self.assertTrue(res)
174a3d71595SJohn Snow        self.files.append(drive['backup'])
175a3d71595SJohn Snow        return drive['backup']
176a3d71595SJohn Snow
177a3d71595SJohn Snow
178a3d71595SJohn Snow    def make_reference_backup(self, bitmap=None):
179a3d71595SJohn Snow        if bitmap is None:
180a3d71595SJohn Snow            bitmap = self.bitmaps[-1]
181a3d71595SJohn Snow        _, reference = bitmap.last_target()
182a3d71595SJohn Snow        res = self.do_qmp_backup(device=bitmap.drive['id'], sync='full',
183a3d71595SJohn Snow                                 format=bitmap.drive['fmt'], target=reference)
184a3d71595SJohn Snow        self.assertTrue(res)
185a3d71595SJohn Snow
186a3d71595SJohn Snow
18759fc5d84SJohn Snow    def add_bitmap(self, name, drive, **kwargs):
188a3d71595SJohn Snow        bitmap = Bitmap(name, drive)
189a3d71595SJohn Snow        self.bitmaps.append(bitmap)
190a3d71595SJohn Snow        result = self.vm.qmp('block-dirty-bitmap-add', node=drive['id'],
19159fc5d84SJohn Snow                             name=bitmap.name, **kwargs)
192a3d71595SJohn Snow        self.assert_qmp(result, 'return', {})
193a3d71595SJohn Snow        return bitmap
194a3d71595SJohn Snow
195a3d71595SJohn Snow
196a3d71595SJohn Snow    def prepare_backup(self, bitmap=None, parent=None):
197a3d71595SJohn Snow        if bitmap is None:
198a3d71595SJohn Snow            bitmap = self.bitmaps[-1]
199a3d71595SJohn Snow        if parent is None:
200a3d71595SJohn Snow            parent, _ = bitmap.last_target()
201a3d71595SJohn Snow
202a3d71595SJohn Snow        target, _ = bitmap.new_target()
203a3d71595SJohn Snow        self.img_create(target, bitmap.drive['fmt'], parent=parent)
204a3d71595SJohn Snow        return target
205a3d71595SJohn Snow
206a3d71595SJohn Snow
207a3d71595SJohn Snow    def create_incremental(self, bitmap=None, parent=None,
208a3d71595SJohn Snow                           parentFormat=None, validate=True):
209a3d71595SJohn Snow        if bitmap is None:
210a3d71595SJohn Snow            bitmap = self.bitmaps[-1]
211a3d71595SJohn Snow        if parent is None:
212a3d71595SJohn Snow            parent, _ = bitmap.last_target()
213a3d71595SJohn Snow
214a3d71595SJohn Snow        target = self.prepare_backup(bitmap, parent)
215a3d71595SJohn Snow        res = self.do_qmp_backup(device=bitmap.drive['id'],
2164b80ab2bSJohn Snow                                 sync='incremental', bitmap=bitmap.name,
217a3d71595SJohn Snow                                 format=bitmap.drive['fmt'], target=target,
218a3d71595SJohn Snow                                 mode='existing')
219a3d71595SJohn Snow        if not res:
220a3d71595SJohn Snow            bitmap.del_target();
221a3d71595SJohn Snow            self.assertFalse(validate)
222a3d71595SJohn Snow        else:
223a3d71595SJohn Snow            self.make_reference_backup(bitmap)
224a3d71595SJohn Snow        return res
225a3d71595SJohn Snow
226a3d71595SJohn Snow
227a3d71595SJohn Snow    def check_backups(self):
228a3d71595SJohn Snow        for bitmap in self.bitmaps:
229a3d71595SJohn Snow            for incremental, reference in bitmap.backups:
230a3d71595SJohn Snow                self.assertTrue(iotests.compare_images(incremental, reference))
231a3d71595SJohn Snow            last = bitmap.last_target()[0]
232a3d71595SJohn Snow            self.assertTrue(iotests.compare_images(last, bitmap.drive['file']))
233a3d71595SJohn Snow
234a3d71595SJohn Snow
235a3d71595SJohn Snow    def hmp_io_writes(self, drive, patterns):
236a3d71595SJohn Snow        for pattern in patterns:
237a3d71595SJohn Snow            self.vm.hmp_qemu_io(drive, 'write -P%s %s %s' % pattern)
238a3d71595SJohn Snow        self.vm.hmp_qemu_io(drive, 'flush')
239a3d71595SJohn Snow
240a3d71595SJohn Snow
24159fc5d84SJohn Snow    def do_incremental_simple(self, **kwargs):
242a3d71595SJohn Snow        self.create_anchor_backup()
24359fc5d84SJohn Snow        self.add_bitmap('bitmap0', self.drives[0], **kwargs)
244a3d71595SJohn Snow
245a3d71595SJohn Snow        # Sanity: Create a "hollow" incremental backup
246a3d71595SJohn Snow        self.create_incremental()
247a3d71595SJohn Snow        # Three writes: One complete overwrite, one new segment,
248a3d71595SJohn Snow        # and one partial overlap.
249a3d71595SJohn Snow        self.hmp_io_writes(self.drives[0]['id'], (('0xab', 0, 512),
250a3d71595SJohn Snow                                                  ('0xfe', '16M', '256k'),
251a3d71595SJohn Snow                                                  ('0x64', '32736k', '64k')))
252a3d71595SJohn Snow        self.create_incremental()
253a3d71595SJohn Snow        # Three more writes, one of each kind, like above
254a3d71595SJohn Snow        self.hmp_io_writes(self.drives[0]['id'], (('0x9a', 0, 512),
255a3d71595SJohn Snow                                                  ('0x55', '8M', '352k'),
256a3d71595SJohn Snow                                                  ('0x78', '15872k', '1M')))
257a3d71595SJohn Snow        self.create_incremental()
258a3d71595SJohn Snow        self.vm.shutdown()
259a3d71595SJohn Snow        self.check_backups()
260a3d71595SJohn Snow
261a3d71595SJohn Snow
26259fc5d84SJohn Snow    def test_incremental_simple(self):
26359fc5d84SJohn Snow        '''
26459fc5d84SJohn Snow        Test: Create and verify three incremental backups.
26559fc5d84SJohn Snow
26659fc5d84SJohn Snow        Create a bitmap and a full backup before VM execution begins,
26759fc5d84SJohn Snow        then create a series of three incremental backups "during execution,"
26859fc5d84SJohn Snow        i.e.; after IO requests begin modifying the drive.
26959fc5d84SJohn Snow        '''
27059fc5d84SJohn Snow        return self.do_incremental_simple()
27159fc5d84SJohn Snow
27259fc5d84SJohn Snow
27359fc5d84SJohn Snow    def test_small_granularity(self):
27459fc5d84SJohn Snow        '''
27559fc5d84SJohn Snow        Test: Create and verify backups made with a small granularity bitmap.
27659fc5d84SJohn Snow
27759fc5d84SJohn Snow        Perform the same test as test_incremental_simple, but with a granularity
27859fc5d84SJohn Snow        of only 32KiB instead of the present default of 64KiB.
27959fc5d84SJohn Snow        '''
28059fc5d84SJohn Snow        return self.do_incremental_simple(granularity=32768)
28159fc5d84SJohn Snow
28259fc5d84SJohn Snow
28359fc5d84SJohn Snow    def test_large_granularity(self):
28459fc5d84SJohn Snow        '''
28559fc5d84SJohn Snow        Test: Create and verify backups made with a large granularity bitmap.
28659fc5d84SJohn Snow
28759fc5d84SJohn Snow        Perform the same test as test_incremental_simple, but with a granularity
28859fc5d84SJohn Snow        of 128KiB instead of the present default of 64KiB.
28959fc5d84SJohn Snow        '''
29059fc5d84SJohn Snow        return self.do_incremental_simple(granularity=131072)
29159fc5d84SJohn Snow
29259fc5d84SJohn Snow
293749ad5e8SJohn Snow    def test_incremental_transaction(self):
294749ad5e8SJohn Snow        '''Test: Verify backups made from transactionally created bitmaps.
295749ad5e8SJohn Snow
296749ad5e8SJohn Snow        Create a bitmap "before" VM execution begins, then create a second
297749ad5e8SJohn Snow        bitmap AFTER writes have already occurred. Use transactions to create
298749ad5e8SJohn Snow        a full backup and synchronize both bitmaps to this backup.
299749ad5e8SJohn Snow        Create an incremental backup through both bitmaps and verify that
300749ad5e8SJohn Snow        both backups match the current drive0 image.
301749ad5e8SJohn Snow        '''
302749ad5e8SJohn Snow
303749ad5e8SJohn Snow        drive0 = self.drives[0]
304749ad5e8SJohn Snow        bitmap0 = self.add_bitmap('bitmap0', drive0)
305749ad5e8SJohn Snow        self.hmp_io_writes(drive0['id'], (('0xab', 0, 512),
306749ad5e8SJohn Snow                                          ('0xfe', '16M', '256k'),
307749ad5e8SJohn Snow                                          ('0x64', '32736k', '64k')))
308749ad5e8SJohn Snow        bitmap1 = self.add_bitmap('bitmap1', drive0)
309749ad5e8SJohn Snow
310749ad5e8SJohn Snow        result = self.vm.qmp('transaction', actions=[
311749ad5e8SJohn Snow            transaction_bitmap_clear(bitmap0.drive['id'], bitmap0.name),
312749ad5e8SJohn Snow            transaction_bitmap_clear(bitmap1.drive['id'], bitmap1.name),
313749ad5e8SJohn Snow            transaction_drive_backup(drive0['id'], drive0['backup'],
314749ad5e8SJohn Snow                                     sync='full', format=drive0['fmt'])
315749ad5e8SJohn Snow        ])
316749ad5e8SJohn Snow        self.assert_qmp(result, 'return', {})
317749ad5e8SJohn Snow        self.wait_until_completed(drive0['id'])
318749ad5e8SJohn Snow        self.files.append(drive0['backup'])
319749ad5e8SJohn Snow
320749ad5e8SJohn Snow        self.hmp_io_writes(drive0['id'], (('0x9a', 0, 512),
321749ad5e8SJohn Snow                                          ('0x55', '8M', '352k'),
322749ad5e8SJohn Snow                                          ('0x78', '15872k', '1M')))
323749ad5e8SJohn Snow        # Both bitmaps should be correctly in sync.
324749ad5e8SJohn Snow        self.create_incremental(bitmap0)
325749ad5e8SJohn Snow        self.create_incremental(bitmap1)
326749ad5e8SJohn Snow        self.vm.shutdown()
327749ad5e8SJohn Snow        self.check_backups()
328749ad5e8SJohn Snow
329749ad5e8SJohn Snow
33024618f53SJohn Snow    def test_incremental_failure(self):
33124618f53SJohn Snow        '''Test: Verify backups made after a failure are correct.
33224618f53SJohn Snow
33324618f53SJohn Snow        Simulate a failure during an incremental backup block job,
33424618f53SJohn Snow        emulate additional writes, then create another incremental backup
33524618f53SJohn Snow        afterwards and verify that the backup created is correct.
33624618f53SJohn Snow        '''
33724618f53SJohn Snow
33824618f53SJohn Snow        # Create a blkdebug interface to this img as 'drive1',
33924618f53SJohn Snow        # but don't actually create a new image.
34024618f53SJohn Snow        drive1 = self.add_node('drive1', self.drives[0]['fmt'],
34124618f53SJohn Snow                               path=self.drives[0]['file'],
34224618f53SJohn Snow                               backup=self.drives[0]['backup'])
34324618f53SJohn Snow        result = self.vm.qmp('blockdev-add', options={
34424618f53SJohn Snow            'id': drive1['id'],
34524618f53SJohn Snow            'driver': drive1['fmt'],
34624618f53SJohn Snow            'file': {
34724618f53SJohn Snow                'driver': 'blkdebug',
34824618f53SJohn Snow                'image': {
34924618f53SJohn Snow                    'driver': 'file',
35024618f53SJohn Snow                    'filename': drive1['file']
35124618f53SJohn Snow                },
35224618f53SJohn Snow                'set-state': [{
35324618f53SJohn Snow                    'event': 'flush_to_disk',
35424618f53SJohn Snow                    'state': 1,
35524618f53SJohn Snow                    'new_state': 2
35624618f53SJohn Snow                }],
35724618f53SJohn Snow                'inject-error': [{
35824618f53SJohn Snow                    'event': 'read_aio',
35924618f53SJohn Snow                    'errno': 5,
36024618f53SJohn Snow                    'state': 2,
36124618f53SJohn Snow                    'immediately': False,
36224618f53SJohn Snow                    'once': True
36324618f53SJohn Snow                }],
36424618f53SJohn Snow            }
36524618f53SJohn Snow        })
36624618f53SJohn Snow        self.assert_qmp(result, 'return', {})
36724618f53SJohn Snow
36824618f53SJohn Snow        self.create_anchor_backup(self.drives[0])
36924618f53SJohn Snow        self.add_bitmap('bitmap0', drive1)
37024618f53SJohn Snow        # Note: at this point, during a normal execution,
37124618f53SJohn Snow        # Assume that the VM resumes and begins issuing IO requests here.
37224618f53SJohn Snow
37324618f53SJohn Snow        self.hmp_io_writes(drive1['id'], (('0xab', 0, 512),
37424618f53SJohn Snow                                          ('0xfe', '16M', '256k'),
37524618f53SJohn Snow                                          ('0x64', '32736k', '64k')))
37624618f53SJohn Snow
37724618f53SJohn Snow        result = self.create_incremental(validate=False)
37824618f53SJohn Snow        self.assertFalse(result)
37924618f53SJohn Snow        self.hmp_io_writes(drive1['id'], (('0x9a', 0, 512),
38024618f53SJohn Snow                                          ('0x55', '8M', '352k'),
38124618f53SJohn Snow                                          ('0x78', '15872k', '1M')))
38224618f53SJohn Snow        self.create_incremental()
38324618f53SJohn Snow        self.vm.shutdown()
38424618f53SJohn Snow        self.check_backups()
38524618f53SJohn Snow
38624618f53SJohn Snow
387*fc6c796fSJohn Snow    def test_transaction_failure(self):
388*fc6c796fSJohn Snow        '''Test: Verify backups made from a transaction that partially fails.
389*fc6c796fSJohn Snow
390*fc6c796fSJohn Snow        Add a second drive with its own unique pattern, and add a bitmap to each
391*fc6c796fSJohn Snow        drive. Use blkdebug to interfere with the backup on just one drive and
392*fc6c796fSJohn Snow        attempt to create a coherent incremental backup across both drives.
393*fc6c796fSJohn Snow
394*fc6c796fSJohn Snow        verify a failure in one but not both, then delete the failed stubs and
395*fc6c796fSJohn Snow        re-run the same transaction.
396*fc6c796fSJohn Snow
397*fc6c796fSJohn Snow        verify that both incrementals are created successfully.
398*fc6c796fSJohn Snow        '''
399*fc6c796fSJohn Snow
400*fc6c796fSJohn Snow        # Create a second drive, with pattern:
401*fc6c796fSJohn Snow        drive1 = self.add_node('drive1')
402*fc6c796fSJohn Snow        self.img_create(drive1['file'], drive1['fmt'])
403*fc6c796fSJohn Snow        io_write_patterns(drive1['file'], (('0x14', 0, 512),
404*fc6c796fSJohn Snow                                           ('0x5d', '1M', '32k'),
405*fc6c796fSJohn Snow                                           ('0xcd', '32M', '124k')))
406*fc6c796fSJohn Snow
407*fc6c796fSJohn Snow        # Create a blkdebug interface to this img as 'drive1'
408*fc6c796fSJohn Snow        result = self.vm.qmp('blockdev-add', options={
409*fc6c796fSJohn Snow            'id': drive1['id'],
410*fc6c796fSJohn Snow            'driver': drive1['fmt'],
411*fc6c796fSJohn Snow            'file': {
412*fc6c796fSJohn Snow                'driver': 'blkdebug',
413*fc6c796fSJohn Snow                'image': {
414*fc6c796fSJohn Snow                    'driver': 'file',
415*fc6c796fSJohn Snow                    'filename': drive1['file']
416*fc6c796fSJohn Snow                },
417*fc6c796fSJohn Snow                'set-state': [{
418*fc6c796fSJohn Snow                    'event': 'flush_to_disk',
419*fc6c796fSJohn Snow                    'state': 1,
420*fc6c796fSJohn Snow                    'new_state': 2
421*fc6c796fSJohn Snow                }],
422*fc6c796fSJohn Snow                'inject-error': [{
423*fc6c796fSJohn Snow                    'event': 'read_aio',
424*fc6c796fSJohn Snow                    'errno': 5,
425*fc6c796fSJohn Snow                    'state': 2,
426*fc6c796fSJohn Snow                    'immediately': False,
427*fc6c796fSJohn Snow                    'once': True
428*fc6c796fSJohn Snow                }],
429*fc6c796fSJohn Snow            }
430*fc6c796fSJohn Snow        })
431*fc6c796fSJohn Snow        self.assert_qmp(result, 'return', {})
432*fc6c796fSJohn Snow
433*fc6c796fSJohn Snow        # Create bitmaps and full backups for both drives
434*fc6c796fSJohn Snow        drive0 = self.drives[0]
435*fc6c796fSJohn Snow        dr0bm0 = self.add_bitmap('bitmap0', drive0)
436*fc6c796fSJohn Snow        dr1bm0 = self.add_bitmap('bitmap0', drive1)
437*fc6c796fSJohn Snow        self.create_anchor_backup(drive0)
438*fc6c796fSJohn Snow        self.create_anchor_backup(drive1)
439*fc6c796fSJohn Snow        self.assert_no_active_block_jobs()
440*fc6c796fSJohn Snow        self.assertFalse(self.vm.get_qmp_events(wait=False))
441*fc6c796fSJohn Snow
442*fc6c796fSJohn Snow        # Emulate some writes
443*fc6c796fSJohn Snow        self.hmp_io_writes(drive0['id'], (('0xab', 0, 512),
444*fc6c796fSJohn Snow                                          ('0xfe', '16M', '256k'),
445*fc6c796fSJohn Snow                                          ('0x64', '32736k', '64k')))
446*fc6c796fSJohn Snow        self.hmp_io_writes(drive1['id'], (('0xba', 0, 512),
447*fc6c796fSJohn Snow                                          ('0xef', '16M', '256k'),
448*fc6c796fSJohn Snow                                          ('0x46', '32736k', '64k')))
449*fc6c796fSJohn Snow
450*fc6c796fSJohn Snow        # Create incremental backup targets
451*fc6c796fSJohn Snow        target0 = self.prepare_backup(dr0bm0)
452*fc6c796fSJohn Snow        target1 = self.prepare_backup(dr1bm0)
453*fc6c796fSJohn Snow
454*fc6c796fSJohn Snow        # Ask for a new incremental backup per-each drive,
455*fc6c796fSJohn Snow        # expecting drive1's backup to fail:
456*fc6c796fSJohn Snow        transaction = [
457*fc6c796fSJohn Snow            transaction_drive_backup(drive0['id'], target0, sync='incremental',
458*fc6c796fSJohn Snow                                     format=drive0['fmt'], mode='existing',
459*fc6c796fSJohn Snow                                     bitmap=dr0bm0.name),
460*fc6c796fSJohn Snow            transaction_drive_backup(drive1['id'], target1, sync='incremental',
461*fc6c796fSJohn Snow                                     format=drive1['fmt'], mode='existing',
462*fc6c796fSJohn Snow                                     bitmap=dr1bm0.name)
463*fc6c796fSJohn Snow        ]
464*fc6c796fSJohn Snow        result = self.vm.qmp('transaction', actions=transaction,
465*fc6c796fSJohn Snow                             properties={'completion-mode': 'grouped'} )
466*fc6c796fSJohn Snow        self.assert_qmp(result, 'return', {})
467*fc6c796fSJohn Snow
468*fc6c796fSJohn Snow        # Observe that drive0's backup is cancelled and drive1 completes with
469*fc6c796fSJohn Snow        # an error.
470*fc6c796fSJohn Snow        self.wait_qmp_backup_cancelled(drive0['id'])
471*fc6c796fSJohn Snow        self.assertFalse(self.wait_qmp_backup(drive1['id']))
472*fc6c796fSJohn Snow        error = self.vm.event_wait('BLOCK_JOB_ERROR')
473*fc6c796fSJohn Snow        self.assert_qmp(error, 'data', {'device': drive1['id'],
474*fc6c796fSJohn Snow                                        'action': 'report',
475*fc6c796fSJohn Snow                                        'operation': 'read'})
476*fc6c796fSJohn Snow        self.assertFalse(self.vm.get_qmp_events(wait=False))
477*fc6c796fSJohn Snow        self.assert_no_active_block_jobs()
478*fc6c796fSJohn Snow
479*fc6c796fSJohn Snow        # Delete drive0's successful target and eliminate our record of the
480*fc6c796fSJohn Snow        # unsuccessful drive1 target. Then re-run the same transaction.
481*fc6c796fSJohn Snow        dr0bm0.del_target()
482*fc6c796fSJohn Snow        dr1bm0.del_target()
483*fc6c796fSJohn Snow        target0 = self.prepare_backup(dr0bm0)
484*fc6c796fSJohn Snow        target1 = self.prepare_backup(dr1bm0)
485*fc6c796fSJohn Snow
486*fc6c796fSJohn Snow        # Re-run the exact same transaction.
487*fc6c796fSJohn Snow        result = self.vm.qmp('transaction', actions=transaction,
488*fc6c796fSJohn Snow                             properties={'completion-mode':'grouped'})
489*fc6c796fSJohn Snow        self.assert_qmp(result, 'return', {})
490*fc6c796fSJohn Snow
491*fc6c796fSJohn Snow        # Both should complete successfully this time.
492*fc6c796fSJohn Snow        self.assertTrue(self.wait_qmp_backup(drive0['id']))
493*fc6c796fSJohn Snow        self.assertTrue(self.wait_qmp_backup(drive1['id']))
494*fc6c796fSJohn Snow        self.make_reference_backup(dr0bm0)
495*fc6c796fSJohn Snow        self.make_reference_backup(dr1bm0)
496*fc6c796fSJohn Snow        self.assertFalse(self.vm.get_qmp_events(wait=False))
497*fc6c796fSJohn Snow        self.assert_no_active_block_jobs()
498*fc6c796fSJohn Snow
499*fc6c796fSJohn Snow        # And the images should of course validate.
500*fc6c796fSJohn Snow        self.vm.shutdown()
501*fc6c796fSJohn Snow        self.check_backups()
502*fc6c796fSJohn Snow
503*fc6c796fSJohn Snow
5049f7264f5SJohn Snow    def test_sync_dirty_bitmap_missing(self):
5059f7264f5SJohn Snow        self.assert_no_active_block_jobs()
5069f7264f5SJohn Snow        self.files.append(self.err_img)
5079f7264f5SJohn Snow        result = self.vm.qmp('drive-backup', device=self.drives[0]['id'],
5084b80ab2bSJohn Snow                             sync='incremental', format=self.drives[0]['fmt'],
5099f7264f5SJohn Snow                             target=self.err_img)
5109f7264f5SJohn Snow        self.assert_qmp(result, 'error/class', 'GenericError')
5119f7264f5SJohn Snow
5129f7264f5SJohn Snow
5139f7264f5SJohn Snow    def test_sync_dirty_bitmap_not_found(self):
5149f7264f5SJohn Snow        self.assert_no_active_block_jobs()
5159f7264f5SJohn Snow        self.files.append(self.err_img)
5169f7264f5SJohn Snow        result = self.vm.qmp('drive-backup', device=self.drives[0]['id'],
5174b80ab2bSJohn Snow                             sync='incremental', bitmap='unknown',
5189f7264f5SJohn Snow                             format=self.drives[0]['fmt'], target=self.err_img)
5199f7264f5SJohn Snow        self.assert_qmp(result, 'error/class', 'GenericError')
5209f7264f5SJohn Snow
5219f7264f5SJohn Snow
52259fc5d84SJohn Snow    def test_sync_dirty_bitmap_bad_granularity(self):
52359fc5d84SJohn Snow        '''
52459fc5d84SJohn Snow        Test: Test what happens if we provide an improper granularity.
52559fc5d84SJohn Snow
52659fc5d84SJohn Snow        The granularity must always be a power of 2.
52759fc5d84SJohn Snow        '''
52859fc5d84SJohn Snow        self.assert_no_active_block_jobs()
52959fc5d84SJohn Snow        self.assertRaises(AssertionError, self.add_bitmap,
53059fc5d84SJohn Snow                          'bitmap0', self.drives[0],
53159fc5d84SJohn Snow                          granularity=64000)
53259fc5d84SJohn Snow
53359fc5d84SJohn Snow
5349f7264f5SJohn Snow    def tearDown(self):
5359f7264f5SJohn Snow        self.vm.shutdown()
536a3d71595SJohn Snow        for bitmap in self.bitmaps:
537a3d71595SJohn Snow            bitmap.cleanup()
5389f7264f5SJohn Snow        for filename in self.files:
539a3d71595SJohn Snow            try_remove(filename)
5409f7264f5SJohn Snow
5419f7264f5SJohn Snow
5429f7264f5SJohn Snowif __name__ == '__main__':
5439f7264f5SJohn Snow    iotests.main(supported_fmts=['qcow2'])
544