xref: /qemu/tests/qemu-iotests/124 (revision 1e2b1f64)
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,
42fc6c796fSJohn 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):
52eed87583SKevin Wolf    return transaction_action('drive-backup', job_id=device, device=device,
53eed87583SKevin Wolf                              target=target, **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
941b19bb9dSJohn Snowclass TestIncrementalBackupBase(iotests.QMPTestCase):
951b19bb9dSJohn Snow    def __init__(self, *args):
961b19bb9dSJohn Snow        super(TestIncrementalBackupBase, self).__init__(*args)
979f7264f5SJohn Snow        self.bitmaps = list()
989f7264f5SJohn Snow        self.files = list()
999f7264f5SJohn Snow        self.drives = list()
1009f7264f5SJohn Snow        self.vm = iotests.VM()
1019f7264f5SJohn Snow        self.err_img = os.path.join(iotests.test_dir, 'err.%s' % iotests.imgfmt)
1029f7264f5SJohn Snow
1031b19bb9dSJohn Snow
1041b19bb9dSJohn Snow    def setUp(self):
1059f7264f5SJohn Snow        # Create a base image with a distinctive patterning
1069f7264f5SJohn Snow        drive0 = self.add_node('drive0')
1079f7264f5SJohn Snow        self.img_create(drive0['file'], drive0['fmt'])
1089f7264f5SJohn Snow        self.vm.add_drive(drive0['file'])
1091b19bb9dSJohn Snow        self.write_default_pattern(drive0['file'])
1101b19bb9dSJohn Snow        self.vm.launch()
1111b19bb9dSJohn Snow
1121b19bb9dSJohn Snow
1131b19bb9dSJohn Snow    def write_default_pattern(self, target):
1141b19bb9dSJohn Snow        io_write_patterns(target, (('0x41', 0, 512),
1159f7264f5SJohn Snow                                   ('0xd5', '1M', '32k'),
1169f7264f5SJohn Snow                                   ('0xdc', '32M', '124k')))
1179f7264f5SJohn Snow
1189f7264f5SJohn Snow
1199f7264f5SJohn Snow    def add_node(self, node_id, fmt=iotests.imgfmt, path=None, backup=None):
1209f7264f5SJohn Snow        if path is None:
1219f7264f5SJohn Snow            path = os.path.join(iotests.test_dir, '%s.%s' % (node_id, fmt))
1229f7264f5SJohn Snow        if backup is None:
1239f7264f5SJohn Snow            backup = os.path.join(iotests.test_dir,
1249f7264f5SJohn Snow                                  '%s.full.backup.%s' % (node_id, fmt))
1259f7264f5SJohn Snow
1269f7264f5SJohn Snow        self.drives.append({
1279f7264f5SJohn Snow            'id': node_id,
1289f7264f5SJohn Snow            'file': path,
1299f7264f5SJohn Snow            'backup': backup,
1309f7264f5SJohn Snow            'fmt': fmt })
1319f7264f5SJohn Snow        return self.drives[-1]
1329f7264f5SJohn Snow
1339f7264f5SJohn Snow
1349f7264f5SJohn Snow    def img_create(self, img, fmt=iotests.imgfmt, size='64M',
135cc199b16SJohn Snow                   parent=None, parentFormat=None, **kwargs):
136cc199b16SJohn Snow        optargs = []
137cc199b16SJohn Snow        for k,v in kwargs.iteritems():
138cc199b16SJohn Snow            optargs = optargs + ['-o', '%s=%s' % (k,v)]
139cc199b16SJohn Snow        args = ['create', '-f', fmt] + optargs + [img, size]
1409f7264f5SJohn Snow        if parent:
1419f7264f5SJohn Snow            if parentFormat is None:
1429f7264f5SJohn Snow                parentFormat = fmt
143cc199b16SJohn Snow            args = args + ['-b', parent, '-F', parentFormat]
144cc199b16SJohn Snow        iotests.qemu_img(*args)
1459f7264f5SJohn Snow        self.files.append(img)
1469f7264f5SJohn Snow
147a3d71595SJohn Snow
148a3d71595SJohn Snow    def do_qmp_backup(self, error='Input/output error', **kwargs):
149a3d71595SJohn Snow        res = self.vm.qmp('drive-backup', **kwargs)
150a3d71595SJohn Snow        self.assert_qmp(res, 'return', {})
151fc6c796fSJohn Snow        return self.wait_qmp_backup(kwargs['device'], error)
152a3d71595SJohn Snow
153fc6c796fSJohn Snow
154fc6c796fSJohn Snow    def wait_qmp_backup(self, device, error='Input/output error'):
155a3d71595SJohn Snow        event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED",
156fc6c796fSJohn Snow                                   match={'data': {'device': device}})
157ff793890SJohn Snow        self.assertNotEqual(event, None)
158a3d71595SJohn Snow
159a3d71595SJohn Snow        try:
160a3d71595SJohn Snow            failure = self.dictpath(event, 'data/error')
161a3d71595SJohn Snow        except AssertionError:
162a3d71595SJohn Snow            # Backup succeeded.
163a3d71595SJohn Snow            self.assert_qmp(event, 'data/offset', event['data']['len'])
164a3d71595SJohn Snow            return True
165a3d71595SJohn Snow        else:
166a3d71595SJohn Snow            # Backup failed.
167a3d71595SJohn Snow            self.assert_qmp(event, 'data/error', error)
168a3d71595SJohn Snow            return False
169a3d71595SJohn Snow
170a3d71595SJohn Snow
171fc6c796fSJohn Snow    def wait_qmp_backup_cancelled(self, device):
172fc6c796fSJohn Snow        event = self.vm.event_wait(name='BLOCK_JOB_CANCELLED',
173fc6c796fSJohn Snow                                   match={'data': {'device': device}})
174fc6c796fSJohn Snow        self.assertNotEqual(event, None)
175fc6c796fSJohn Snow
176fc6c796fSJohn Snow
177a3d71595SJohn Snow    def create_anchor_backup(self, drive=None):
178a3d71595SJohn Snow        if drive is None:
179a3d71595SJohn Snow            drive = self.drives[-1]
180eed87583SKevin Wolf        res = self.do_qmp_backup(job_id=drive['id'],
181eed87583SKevin Wolf                                 device=drive['id'], sync='full',
182a3d71595SJohn Snow                                 format=drive['fmt'], target=drive['backup'])
183a3d71595SJohn Snow        self.assertTrue(res)
184a3d71595SJohn Snow        self.files.append(drive['backup'])
185a3d71595SJohn Snow        return drive['backup']
186a3d71595SJohn Snow
187a3d71595SJohn Snow
188a3d71595SJohn Snow    def make_reference_backup(self, bitmap=None):
189a3d71595SJohn Snow        if bitmap is None:
190a3d71595SJohn Snow            bitmap = self.bitmaps[-1]
191a3d71595SJohn Snow        _, reference = bitmap.last_target()
192eed87583SKevin Wolf        res = self.do_qmp_backup(job_id=bitmap.drive['id'],
193eed87583SKevin Wolf                                 device=bitmap.drive['id'], sync='full',
194a3d71595SJohn Snow                                 format=bitmap.drive['fmt'], target=reference)
195a3d71595SJohn Snow        self.assertTrue(res)
196a3d71595SJohn Snow
197a3d71595SJohn Snow
19859fc5d84SJohn Snow    def add_bitmap(self, name, drive, **kwargs):
199a3d71595SJohn Snow        bitmap = Bitmap(name, drive)
200a3d71595SJohn Snow        self.bitmaps.append(bitmap)
201a3d71595SJohn Snow        result = self.vm.qmp('block-dirty-bitmap-add', node=drive['id'],
20259fc5d84SJohn Snow                             name=bitmap.name, **kwargs)
203a3d71595SJohn Snow        self.assert_qmp(result, 'return', {})
204a3d71595SJohn Snow        return bitmap
205a3d71595SJohn Snow
206a3d71595SJohn Snow
207a3d71595SJohn Snow    def prepare_backup(self, bitmap=None, parent=None):
208a3d71595SJohn Snow        if bitmap is None:
209a3d71595SJohn Snow            bitmap = self.bitmaps[-1]
210a3d71595SJohn Snow        if parent is None:
211a3d71595SJohn Snow            parent, _ = bitmap.last_target()
212a3d71595SJohn Snow
213a3d71595SJohn Snow        target, _ = bitmap.new_target()
214a3d71595SJohn Snow        self.img_create(target, bitmap.drive['fmt'], parent=parent)
215a3d71595SJohn Snow        return target
216a3d71595SJohn Snow
217a3d71595SJohn Snow
218a3d71595SJohn Snow    def create_incremental(self, bitmap=None, parent=None,
219a3d71595SJohn Snow                           parentFormat=None, validate=True):
220a3d71595SJohn Snow        if bitmap is None:
221a3d71595SJohn Snow            bitmap = self.bitmaps[-1]
222a3d71595SJohn Snow        if parent is None:
223a3d71595SJohn Snow            parent, _ = bitmap.last_target()
224a3d71595SJohn Snow
225a3d71595SJohn Snow        target = self.prepare_backup(bitmap, parent)
226eed87583SKevin Wolf        res = self.do_qmp_backup(job_id=bitmap.drive['id'],
227eed87583SKevin Wolf                                 device=bitmap.drive['id'],
2284b80ab2bSJohn Snow                                 sync='incremental', bitmap=bitmap.name,
229a3d71595SJohn Snow                                 format=bitmap.drive['fmt'], target=target,
230a3d71595SJohn Snow                                 mode='existing')
231a3d71595SJohn Snow        if not res:
232a3d71595SJohn Snow            bitmap.del_target();
233a3d71595SJohn Snow            self.assertFalse(validate)
234a3d71595SJohn Snow        else:
235a3d71595SJohn Snow            self.make_reference_backup(bitmap)
236a3d71595SJohn Snow        return res
237a3d71595SJohn Snow
238a3d71595SJohn Snow
239a3d71595SJohn Snow    def check_backups(self):
240a3d71595SJohn Snow        for bitmap in self.bitmaps:
241a3d71595SJohn Snow            for incremental, reference in bitmap.backups:
242a3d71595SJohn Snow                self.assertTrue(iotests.compare_images(incremental, reference))
243a3d71595SJohn Snow            last = bitmap.last_target()[0]
244a3d71595SJohn Snow            self.assertTrue(iotests.compare_images(last, bitmap.drive['file']))
245a3d71595SJohn Snow
246a3d71595SJohn Snow
247a3d71595SJohn Snow    def hmp_io_writes(self, drive, patterns):
248a3d71595SJohn Snow        for pattern in patterns:
249a3d71595SJohn Snow            self.vm.hmp_qemu_io(drive, 'write -P%s %s %s' % pattern)
250a3d71595SJohn Snow        self.vm.hmp_qemu_io(drive, 'flush')
251a3d71595SJohn Snow
252a3d71595SJohn Snow
25359fc5d84SJohn Snow    def do_incremental_simple(self, **kwargs):
254a3d71595SJohn Snow        self.create_anchor_backup()
25559fc5d84SJohn Snow        self.add_bitmap('bitmap0', self.drives[0], **kwargs)
256a3d71595SJohn Snow
257a3d71595SJohn Snow        # Sanity: Create a "hollow" incremental backup
258a3d71595SJohn Snow        self.create_incremental()
259a3d71595SJohn Snow        # Three writes: One complete overwrite, one new segment,
260a3d71595SJohn Snow        # and one partial overlap.
261a3d71595SJohn Snow        self.hmp_io_writes(self.drives[0]['id'], (('0xab', 0, 512),
262a3d71595SJohn Snow                                                  ('0xfe', '16M', '256k'),
263a3d71595SJohn Snow                                                  ('0x64', '32736k', '64k')))
264a3d71595SJohn Snow        self.create_incremental()
265a3d71595SJohn Snow        # Three more writes, one of each kind, like above
266a3d71595SJohn Snow        self.hmp_io_writes(self.drives[0]['id'], (('0x9a', 0, 512),
267a3d71595SJohn Snow                                                  ('0x55', '8M', '352k'),
268a3d71595SJohn Snow                                                  ('0x78', '15872k', '1M')))
269a3d71595SJohn Snow        self.create_incremental()
270a3d71595SJohn Snow        self.vm.shutdown()
271a3d71595SJohn Snow        self.check_backups()
272a3d71595SJohn Snow
273a3d71595SJohn Snow
2741b19bb9dSJohn Snow    def tearDown(self):
2751b19bb9dSJohn Snow        self.vm.shutdown()
2761b19bb9dSJohn Snow        for bitmap in self.bitmaps:
2771b19bb9dSJohn Snow            bitmap.cleanup()
2781b19bb9dSJohn Snow        for filename in self.files:
2791b19bb9dSJohn Snow            try_remove(filename)
2801b19bb9dSJohn Snow
2811b19bb9dSJohn Snow
2821b19bb9dSJohn Snow
2831b19bb9dSJohn Snowclass TestIncrementalBackup(TestIncrementalBackupBase):
28459fc5d84SJohn Snow    def test_incremental_simple(self):
28559fc5d84SJohn Snow        '''
28659fc5d84SJohn Snow        Test: Create and verify three incremental backups.
28759fc5d84SJohn Snow
28859fc5d84SJohn Snow        Create a bitmap and a full backup before VM execution begins,
28959fc5d84SJohn Snow        then create a series of three incremental backups "during execution,"
29059fc5d84SJohn Snow        i.e.; after IO requests begin modifying the drive.
29159fc5d84SJohn Snow        '''
29259fc5d84SJohn Snow        return self.do_incremental_simple()
29359fc5d84SJohn Snow
29459fc5d84SJohn Snow
29559fc5d84SJohn Snow    def test_small_granularity(self):
29659fc5d84SJohn Snow        '''
29759fc5d84SJohn Snow        Test: Create and verify backups made with a small granularity bitmap.
29859fc5d84SJohn Snow
29959fc5d84SJohn Snow        Perform the same test as test_incremental_simple, but with a granularity
30059fc5d84SJohn Snow        of only 32KiB instead of the present default of 64KiB.
30159fc5d84SJohn Snow        '''
30259fc5d84SJohn Snow        return self.do_incremental_simple(granularity=32768)
30359fc5d84SJohn Snow
30459fc5d84SJohn Snow
30559fc5d84SJohn Snow    def test_large_granularity(self):
30659fc5d84SJohn Snow        '''
30759fc5d84SJohn Snow        Test: Create and verify backups made with a large granularity bitmap.
30859fc5d84SJohn Snow
30959fc5d84SJohn Snow        Perform the same test as test_incremental_simple, but with a granularity
31059fc5d84SJohn Snow        of 128KiB instead of the present default of 64KiB.
31159fc5d84SJohn Snow        '''
31259fc5d84SJohn Snow        return self.do_incremental_simple(granularity=131072)
31359fc5d84SJohn Snow
31459fc5d84SJohn Snow
315cc199b16SJohn Snow    def test_larger_cluster_target(self):
316cc199b16SJohn Snow        '''
317cc199b16SJohn Snow        Test: Create and verify backups made to a larger cluster size target.
318cc199b16SJohn Snow
319cc199b16SJohn Snow        With a default granularity of 64KiB, verify that backups made to a
320cc199b16SJohn Snow        larger cluster size target of 128KiB without a backing file works.
321cc199b16SJohn Snow        '''
322cc199b16SJohn Snow        drive0 = self.drives[0]
323cc199b16SJohn Snow
324cc199b16SJohn Snow        # Create a cluster_size=128k full backup / "anchor" backup
325cc199b16SJohn Snow        self.img_create(drive0['backup'], cluster_size='128k')
326cc199b16SJohn Snow        self.assertTrue(self.do_qmp_backup(device=drive0['id'], sync='full',
327cc199b16SJohn Snow                                           format=drive0['fmt'],
328cc199b16SJohn Snow                                           target=drive0['backup'],
329cc199b16SJohn Snow                                           mode='existing'))
330cc199b16SJohn Snow
331cc199b16SJohn Snow        # Create bitmap and dirty it with some new writes.
332cc199b16SJohn Snow        # overwrite [32736, 32799] which will dirty bitmap clusters at
333cc199b16SJohn Snow        # 32M-64K and 32M. 32M+64K will be left undirtied.
334cc199b16SJohn Snow        bitmap0 = self.add_bitmap('bitmap0', drive0)
335cc199b16SJohn Snow        self.hmp_io_writes(drive0['id'],
336cc199b16SJohn Snow                           (('0xab', 0, 512),
337cc199b16SJohn Snow                            ('0xfe', '16M', '256k'),
338cc199b16SJohn Snow                            ('0x64', '32736k', '64k')))
339*1e2b1f64SEric Blake        # Check the dirty bitmap stats
340*1e2b1f64SEric Blake        result = self.vm.qmp('query-block')
341*1e2b1f64SEric Blake        self.assert_qmp(result, 'return[0]/dirty-bitmaps[0]/name', 'bitmap0')
342*1e2b1f64SEric Blake        self.assert_qmp(result, 'return[0]/dirty-bitmaps[0]/count', 458752)
343*1e2b1f64SEric Blake        self.assert_qmp(result, 'return[0]/dirty-bitmaps[0]/granularity', 65536)
344*1e2b1f64SEric Blake        self.assert_qmp(result, 'return[0]/dirty-bitmaps[0]/status', 'active')
345cc199b16SJohn Snow
346cc199b16SJohn Snow        # Prepare a cluster_size=128k backup target without a backing file.
347cc199b16SJohn Snow        (target, _) = bitmap0.new_target()
348cc199b16SJohn Snow        self.img_create(target, bitmap0.drive['fmt'], cluster_size='128k')
349cc199b16SJohn Snow
350cc199b16SJohn Snow        # Perform Incremental Backup
351cc199b16SJohn Snow        self.assertTrue(self.do_qmp_backup(device=bitmap0.drive['id'],
352cc199b16SJohn Snow                                           sync='incremental',
353cc199b16SJohn Snow                                           bitmap=bitmap0.name,
354cc199b16SJohn Snow                                           format=bitmap0.drive['fmt'],
355cc199b16SJohn Snow                                           target=target,
356cc199b16SJohn Snow                                           mode='existing'))
357cc199b16SJohn Snow        self.make_reference_backup(bitmap0)
358cc199b16SJohn Snow
359cc199b16SJohn Snow        # Add the backing file, then compare and exit.
360cc199b16SJohn Snow        iotests.qemu_img('rebase', '-f', drive0['fmt'], '-u', '-b',
361cc199b16SJohn Snow                         drive0['backup'], '-F', drive0['fmt'], target)
362cc199b16SJohn Snow        self.vm.shutdown()
363cc199b16SJohn Snow        self.check_backups()
364cc199b16SJohn Snow
365cc199b16SJohn Snow
366749ad5e8SJohn Snow    def test_incremental_transaction(self):
367749ad5e8SJohn Snow        '''Test: Verify backups made from transactionally created bitmaps.
368749ad5e8SJohn Snow
369749ad5e8SJohn Snow        Create a bitmap "before" VM execution begins, then create a second
370749ad5e8SJohn Snow        bitmap AFTER writes have already occurred. Use transactions to create
371749ad5e8SJohn Snow        a full backup and synchronize both bitmaps to this backup.
372749ad5e8SJohn Snow        Create an incremental backup through both bitmaps and verify that
373749ad5e8SJohn Snow        both backups match the current drive0 image.
374749ad5e8SJohn Snow        '''
375749ad5e8SJohn Snow
376749ad5e8SJohn Snow        drive0 = self.drives[0]
377749ad5e8SJohn Snow        bitmap0 = self.add_bitmap('bitmap0', drive0)
378749ad5e8SJohn Snow        self.hmp_io_writes(drive0['id'], (('0xab', 0, 512),
379749ad5e8SJohn Snow                                          ('0xfe', '16M', '256k'),
380749ad5e8SJohn Snow                                          ('0x64', '32736k', '64k')))
381749ad5e8SJohn Snow        bitmap1 = self.add_bitmap('bitmap1', drive0)
382749ad5e8SJohn Snow
383749ad5e8SJohn Snow        result = self.vm.qmp('transaction', actions=[
384749ad5e8SJohn Snow            transaction_bitmap_clear(bitmap0.drive['id'], bitmap0.name),
385749ad5e8SJohn Snow            transaction_bitmap_clear(bitmap1.drive['id'], bitmap1.name),
386749ad5e8SJohn Snow            transaction_drive_backup(drive0['id'], drive0['backup'],
387749ad5e8SJohn Snow                                     sync='full', format=drive0['fmt'])
388749ad5e8SJohn Snow        ])
389749ad5e8SJohn Snow        self.assert_qmp(result, 'return', {})
390749ad5e8SJohn Snow        self.wait_until_completed(drive0['id'])
391749ad5e8SJohn Snow        self.files.append(drive0['backup'])
392749ad5e8SJohn Snow
393749ad5e8SJohn Snow        self.hmp_io_writes(drive0['id'], (('0x9a', 0, 512),
394749ad5e8SJohn Snow                                          ('0x55', '8M', '352k'),
395749ad5e8SJohn Snow                                          ('0x78', '15872k', '1M')))
396749ad5e8SJohn Snow        # Both bitmaps should be correctly in sync.
397749ad5e8SJohn Snow        self.create_incremental(bitmap0)
398749ad5e8SJohn Snow        self.create_incremental(bitmap1)
399749ad5e8SJohn Snow        self.vm.shutdown()
400749ad5e8SJohn Snow        self.check_backups()
401749ad5e8SJohn Snow
402749ad5e8SJohn Snow
4030aef09b9SJohn Snow    def do_transaction_failure_test(self, race=False):
404fc6c796fSJohn Snow        # Create a second drive, with pattern:
405fc6c796fSJohn Snow        drive1 = self.add_node('drive1')
406fc6c796fSJohn Snow        self.img_create(drive1['file'], drive1['fmt'])
407fc6c796fSJohn Snow        io_write_patterns(drive1['file'], (('0x14', 0, 512),
408fc6c796fSJohn Snow                                           ('0x5d', '1M', '32k'),
409fc6c796fSJohn Snow                                           ('0xcd', '32M', '124k')))
410fc6c796fSJohn Snow
411fc6c796fSJohn Snow        # Create a blkdebug interface to this img as 'drive1'
4120153d2f5SKevin Wolf        result = self.vm.qmp('blockdev-add',
4130153d2f5SKevin Wolf            node_name=drive1['id'],
4140153d2f5SKevin Wolf            driver=drive1['fmt'],
4150153d2f5SKevin Wolf            file={
416fc6c796fSJohn Snow                'driver': 'blkdebug',
417fc6c796fSJohn Snow                'image': {
418fc6c796fSJohn Snow                    'driver': 'file',
419fc6c796fSJohn Snow                    'filename': drive1['file']
420fc6c796fSJohn Snow                },
421fc6c796fSJohn Snow                'set-state': [{
422fc6c796fSJohn Snow                    'event': 'flush_to_disk',
423fc6c796fSJohn Snow                    'state': 1,
424fc6c796fSJohn Snow                    'new_state': 2
425fc6c796fSJohn Snow                }],
426fc6c796fSJohn Snow                'inject-error': [{
427fc6c796fSJohn Snow                    'event': 'read_aio',
428fc6c796fSJohn Snow                    'errno': 5,
429fc6c796fSJohn Snow                    'state': 2,
430fc6c796fSJohn Snow                    'immediately': False,
431fc6c796fSJohn Snow                    'once': True
432fc6c796fSJohn Snow                }],
433fc6c796fSJohn Snow            }
4340153d2f5SKevin Wolf        )
435fc6c796fSJohn Snow        self.assert_qmp(result, 'return', {})
436fc6c796fSJohn Snow
437fc6c796fSJohn Snow        # Create bitmaps and full backups for both drives
438fc6c796fSJohn Snow        drive0 = self.drives[0]
439fc6c796fSJohn Snow        dr0bm0 = self.add_bitmap('bitmap0', drive0)
440fc6c796fSJohn Snow        dr1bm0 = self.add_bitmap('bitmap0', drive1)
441fc6c796fSJohn Snow        self.create_anchor_backup(drive0)
442fc6c796fSJohn Snow        self.create_anchor_backup(drive1)
443fc6c796fSJohn Snow        self.assert_no_active_block_jobs()
444fc6c796fSJohn Snow        self.assertFalse(self.vm.get_qmp_events(wait=False))
445fc6c796fSJohn Snow
446fc6c796fSJohn Snow        # Emulate some writes
4470aef09b9SJohn Snow        if not race:
448fc6c796fSJohn Snow            self.hmp_io_writes(drive0['id'], (('0xab', 0, 512),
449fc6c796fSJohn Snow                                              ('0xfe', '16M', '256k'),
450fc6c796fSJohn Snow                                              ('0x64', '32736k', '64k')))
451fc6c796fSJohn Snow        self.hmp_io_writes(drive1['id'], (('0xba', 0, 512),
452fc6c796fSJohn Snow                                          ('0xef', '16M', '256k'),
453fc6c796fSJohn Snow                                          ('0x46', '32736k', '64k')))
454fc6c796fSJohn Snow
455fc6c796fSJohn Snow        # Create incremental backup targets
456fc6c796fSJohn Snow        target0 = self.prepare_backup(dr0bm0)
457fc6c796fSJohn Snow        target1 = self.prepare_backup(dr1bm0)
458fc6c796fSJohn Snow
459fc6c796fSJohn Snow        # Ask for a new incremental backup per-each drive,
4600aef09b9SJohn Snow        # expecting drive1's backup to fail. In the 'race' test,
4610aef09b9SJohn Snow        # we expect drive1 to attempt to cancel the empty drive0 job.
462fc6c796fSJohn Snow        transaction = [
463fc6c796fSJohn Snow            transaction_drive_backup(drive0['id'], target0, sync='incremental',
464fc6c796fSJohn Snow                                     format=drive0['fmt'], mode='existing',
465fc6c796fSJohn Snow                                     bitmap=dr0bm0.name),
466fc6c796fSJohn Snow            transaction_drive_backup(drive1['id'], target1, sync='incremental',
467fc6c796fSJohn Snow                                     format=drive1['fmt'], mode='existing',
468fc6c796fSJohn Snow                                     bitmap=dr1bm0.name)
469fc6c796fSJohn Snow        ]
470fc6c796fSJohn Snow        result = self.vm.qmp('transaction', actions=transaction,
471fc6c796fSJohn Snow                             properties={'completion-mode': 'grouped'} )
472fc6c796fSJohn Snow        self.assert_qmp(result, 'return', {})
473fc6c796fSJohn Snow
474fc6c796fSJohn Snow        # Observe that drive0's backup is cancelled and drive1 completes with
475fc6c796fSJohn Snow        # an error.
476fc6c796fSJohn Snow        self.wait_qmp_backup_cancelled(drive0['id'])
477fc6c796fSJohn Snow        self.assertFalse(self.wait_qmp_backup(drive1['id']))
478fc6c796fSJohn Snow        error = self.vm.event_wait('BLOCK_JOB_ERROR')
479fc6c796fSJohn Snow        self.assert_qmp(error, 'data', {'device': drive1['id'],
480fc6c796fSJohn Snow                                        'action': 'report',
481fc6c796fSJohn Snow                                        'operation': 'read'})
482fc6c796fSJohn Snow        self.assertFalse(self.vm.get_qmp_events(wait=False))
483fc6c796fSJohn Snow        self.assert_no_active_block_jobs()
484fc6c796fSJohn Snow
485fc6c796fSJohn Snow        # Delete drive0's successful target and eliminate our record of the
4860aef09b9SJohn Snow        # unsuccessful drive1 target.
487fc6c796fSJohn Snow        dr0bm0.del_target()
488fc6c796fSJohn Snow        dr1bm0.del_target()
4890aef09b9SJohn Snow        if race:
4900aef09b9SJohn Snow            # Don't re-run the transaction, we only wanted to test the race.
4910aef09b9SJohn Snow            self.vm.shutdown()
4920aef09b9SJohn Snow            return
4930aef09b9SJohn Snow
4940aef09b9SJohn Snow        # Re-run the same transaction:
495fc6c796fSJohn Snow        target0 = self.prepare_backup(dr0bm0)
496fc6c796fSJohn Snow        target1 = self.prepare_backup(dr1bm0)
497fc6c796fSJohn Snow
498fc6c796fSJohn Snow        # Re-run the exact same transaction.
499fc6c796fSJohn Snow        result = self.vm.qmp('transaction', actions=transaction,
500fc6c796fSJohn Snow                             properties={'completion-mode':'grouped'})
501fc6c796fSJohn Snow        self.assert_qmp(result, 'return', {})
502fc6c796fSJohn Snow
503fc6c796fSJohn Snow        # Both should complete successfully this time.
504fc6c796fSJohn Snow        self.assertTrue(self.wait_qmp_backup(drive0['id']))
505fc6c796fSJohn Snow        self.assertTrue(self.wait_qmp_backup(drive1['id']))
506fc6c796fSJohn Snow        self.make_reference_backup(dr0bm0)
507fc6c796fSJohn Snow        self.make_reference_backup(dr1bm0)
508fc6c796fSJohn Snow        self.assertFalse(self.vm.get_qmp_events(wait=False))
509fc6c796fSJohn Snow        self.assert_no_active_block_jobs()
510fc6c796fSJohn Snow
511fc6c796fSJohn Snow        # And the images should of course validate.
512fc6c796fSJohn Snow        self.vm.shutdown()
513fc6c796fSJohn Snow        self.check_backups()
514fc6c796fSJohn Snow
5150aef09b9SJohn Snow    def test_transaction_failure(self):
5160aef09b9SJohn Snow        '''Test: Verify backups made from a transaction that partially fails.
5170aef09b9SJohn Snow
5180aef09b9SJohn Snow        Add a second drive with its own unique pattern, and add a bitmap to each
5190aef09b9SJohn Snow        drive. Use blkdebug to interfere with the backup on just one drive and
5200aef09b9SJohn Snow        attempt to create a coherent incremental backup across both drives.
5210aef09b9SJohn Snow
5220aef09b9SJohn Snow        verify a failure in one but not both, then delete the failed stubs and
5230aef09b9SJohn Snow        re-run the same transaction.
5240aef09b9SJohn Snow
5250aef09b9SJohn Snow        verify that both incrementals are created successfully.
5260aef09b9SJohn Snow        '''
5270aef09b9SJohn Snow        self.do_transaction_failure_test()
5280aef09b9SJohn Snow
5290aef09b9SJohn Snow    def test_transaction_failure_race(self):
5300aef09b9SJohn Snow        '''Test: Verify that transactions with jobs that have no data to
5310aef09b9SJohn Snow        transfer do not cause race conditions in the cancellation of the entire
5320aef09b9SJohn Snow        transaction job group.
5330aef09b9SJohn Snow        '''
5340aef09b9SJohn Snow        self.do_transaction_failure_test(race=True)
5350aef09b9SJohn Snow
536fc6c796fSJohn Snow
5379f7264f5SJohn Snow    def test_sync_dirty_bitmap_missing(self):
5389f7264f5SJohn Snow        self.assert_no_active_block_jobs()
5399f7264f5SJohn Snow        self.files.append(self.err_img)
5409f7264f5SJohn Snow        result = self.vm.qmp('drive-backup', device=self.drives[0]['id'],
5414b80ab2bSJohn Snow                             sync='incremental', format=self.drives[0]['fmt'],
5429f7264f5SJohn Snow                             target=self.err_img)
5439f7264f5SJohn Snow        self.assert_qmp(result, 'error/class', 'GenericError')
5449f7264f5SJohn Snow
5459f7264f5SJohn Snow
5469f7264f5SJohn Snow    def test_sync_dirty_bitmap_not_found(self):
5479f7264f5SJohn Snow        self.assert_no_active_block_jobs()
5489f7264f5SJohn Snow        self.files.append(self.err_img)
5499f7264f5SJohn Snow        result = self.vm.qmp('drive-backup', device=self.drives[0]['id'],
5504b80ab2bSJohn Snow                             sync='incremental', bitmap='unknown',
5519f7264f5SJohn Snow                             format=self.drives[0]['fmt'], target=self.err_img)
5529f7264f5SJohn Snow        self.assert_qmp(result, 'error/class', 'GenericError')
5539f7264f5SJohn Snow
5549f7264f5SJohn Snow
55559fc5d84SJohn Snow    def test_sync_dirty_bitmap_bad_granularity(self):
55659fc5d84SJohn Snow        '''
55759fc5d84SJohn Snow        Test: Test what happens if we provide an improper granularity.
55859fc5d84SJohn Snow
55959fc5d84SJohn Snow        The granularity must always be a power of 2.
56059fc5d84SJohn Snow        '''
56159fc5d84SJohn Snow        self.assert_no_active_block_jobs()
56259fc5d84SJohn Snow        self.assertRaises(AssertionError, self.add_bitmap,
56359fc5d84SJohn Snow                          'bitmap0', self.drives[0],
56459fc5d84SJohn Snow                          granularity=64000)
56559fc5d84SJohn Snow
56659fc5d84SJohn Snow
567ce2cbc49SJohn Snowclass TestIncrementalBackupBlkdebug(TestIncrementalBackupBase):
568ce2cbc49SJohn Snow    '''Incremental backup tests that utilize a BlkDebug filter on drive0.'''
569ce2cbc49SJohn Snow
57035cea223SJohn Snow    def setUp(self):
57135cea223SJohn Snow        drive0 = self.add_node('drive0')
57235cea223SJohn Snow        self.img_create(drive0['file'], drive0['fmt'])
57335cea223SJohn Snow        self.write_default_pattern(drive0['file'])
57435cea223SJohn Snow        self.vm.launch()
57535cea223SJohn Snow
576ce2cbc49SJohn Snow    def test_incremental_failure(self):
577ce2cbc49SJohn Snow        '''Test: Verify backups made after a failure are correct.
578ce2cbc49SJohn Snow
579ce2cbc49SJohn Snow        Simulate a failure during an incremental backup block job,
580ce2cbc49SJohn Snow        emulate additional writes, then create another incremental backup
581ce2cbc49SJohn Snow        afterwards and verify that the backup created is correct.
582ce2cbc49SJohn Snow        '''
583ce2cbc49SJohn Snow
58435cea223SJohn Snow        drive0 = self.drives[0]
5850153d2f5SKevin Wolf        result = self.vm.qmp('blockdev-add',
5860153d2f5SKevin Wolf            node_name=drive0['id'],
5870153d2f5SKevin Wolf            driver=drive0['fmt'],
5880153d2f5SKevin Wolf            file={
589ce2cbc49SJohn Snow                'driver': 'blkdebug',
590ce2cbc49SJohn Snow                'image': {
591ce2cbc49SJohn Snow                    'driver': 'file',
59235cea223SJohn Snow                    'filename': drive0['file']
593ce2cbc49SJohn Snow                },
594ce2cbc49SJohn Snow                'set-state': [{
595ce2cbc49SJohn Snow                    'event': 'flush_to_disk',
596ce2cbc49SJohn Snow                    'state': 1,
597ce2cbc49SJohn Snow                    'new_state': 2
598ce2cbc49SJohn Snow                }],
599ce2cbc49SJohn Snow                'inject-error': [{
600ce2cbc49SJohn Snow                    'event': 'read_aio',
601ce2cbc49SJohn Snow                    'errno': 5,
602ce2cbc49SJohn Snow                    'state': 2,
603ce2cbc49SJohn Snow                    'immediately': False,
604ce2cbc49SJohn Snow                    'once': True
605ce2cbc49SJohn Snow                }],
606ce2cbc49SJohn Snow            }
6070153d2f5SKevin Wolf        )
608ce2cbc49SJohn Snow        self.assert_qmp(result, 'return', {})
609ce2cbc49SJohn Snow
61035cea223SJohn Snow        self.create_anchor_backup(drive0)
61135cea223SJohn Snow        self.add_bitmap('bitmap0', drive0)
612ce2cbc49SJohn Snow        # Note: at this point, during a normal execution,
613ce2cbc49SJohn Snow        # Assume that the VM resumes and begins issuing IO requests here.
614ce2cbc49SJohn Snow
61535cea223SJohn Snow        self.hmp_io_writes(drive0['id'], (('0xab', 0, 512),
616ce2cbc49SJohn Snow                                          ('0xfe', '16M', '256k'),
617ce2cbc49SJohn Snow                                          ('0x64', '32736k', '64k')))
618ce2cbc49SJohn Snow
619ce2cbc49SJohn Snow        result = self.create_incremental(validate=False)
620ce2cbc49SJohn Snow        self.assertFalse(result)
62135cea223SJohn Snow        self.hmp_io_writes(drive0['id'], (('0x9a', 0, 512),
622ce2cbc49SJohn Snow                                          ('0x55', '8M', '352k'),
623ce2cbc49SJohn Snow                                          ('0x78', '15872k', '1M')))
624ce2cbc49SJohn Snow        self.create_incremental()
625ce2cbc49SJohn Snow        self.vm.shutdown()
626ce2cbc49SJohn Snow        self.check_backups()
627ce2cbc49SJohn Snow
628ce2cbc49SJohn Snow
6299f7264f5SJohn Snowif __name__ == '__main__':
6309f7264f5SJohn Snow    iotests.main(supported_fmts=['qcow2'])
631