xref: /openbmc/qemu/tests/qemu-iotests/124 (revision 103cbc77)
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,
4268474776SMax Reitz        'data': dict((k.replace('_', '-'), v) for k, v in kwargs.items())
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 = []
13768474776SMax Reitz        for k,v in kwargs.items():
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
1541dac83f1SKevin Wolf    def ignore_job_status_change_events(self):
1551dac83f1SKevin Wolf        while True:
1561dac83f1SKevin Wolf            e = self.vm.event_wait(name="JOB_STATUS_CHANGE")
1571dac83f1SKevin Wolf            if e['data']['status'] == 'null':
1581dac83f1SKevin Wolf                break
1591dac83f1SKevin Wolf
160fc6c796fSJohn Snow    def wait_qmp_backup(self, device, error='Input/output error'):
161a3d71595SJohn Snow        event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED",
162fc6c796fSJohn Snow                                   match={'data': {'device': device}})
163ff793890SJohn Snow        self.assertNotEqual(event, None)
1641dac83f1SKevin Wolf        self.ignore_job_status_change_events()
165a3d71595SJohn Snow
166a3d71595SJohn Snow        try:
167a3d71595SJohn Snow            failure = self.dictpath(event, 'data/error')
168a3d71595SJohn Snow        except AssertionError:
169a3d71595SJohn Snow            # Backup succeeded.
170a3d71595SJohn Snow            self.assert_qmp(event, 'data/offset', event['data']['len'])
171a3d71595SJohn Snow            return True
172a3d71595SJohn Snow        else:
173a3d71595SJohn Snow            # Backup failed.
174a3d71595SJohn Snow            self.assert_qmp(event, 'data/error', error)
175a3d71595SJohn Snow            return False
176a3d71595SJohn Snow
177a3d71595SJohn Snow
178fc6c796fSJohn Snow    def wait_qmp_backup_cancelled(self, device):
179fc6c796fSJohn Snow        event = self.vm.event_wait(name='BLOCK_JOB_CANCELLED',
180fc6c796fSJohn Snow                                   match={'data': {'device': device}})
181fc6c796fSJohn Snow        self.assertNotEqual(event, None)
1821dac83f1SKevin Wolf        self.ignore_job_status_change_events()
183fc6c796fSJohn Snow
184fc6c796fSJohn Snow
185a3d71595SJohn Snow    def create_anchor_backup(self, drive=None):
186a3d71595SJohn Snow        if drive is None:
187a3d71595SJohn Snow            drive = self.drives[-1]
188eed87583SKevin Wolf        res = self.do_qmp_backup(job_id=drive['id'],
189eed87583SKevin Wolf                                 device=drive['id'], sync='full',
190a3d71595SJohn Snow                                 format=drive['fmt'], target=drive['backup'])
191a3d71595SJohn Snow        self.assertTrue(res)
192a3d71595SJohn Snow        self.files.append(drive['backup'])
193a3d71595SJohn Snow        return drive['backup']
194a3d71595SJohn Snow
195a3d71595SJohn Snow
196a3d71595SJohn Snow    def make_reference_backup(self, bitmap=None):
197a3d71595SJohn Snow        if bitmap is None:
198a3d71595SJohn Snow            bitmap = self.bitmaps[-1]
199a3d71595SJohn Snow        _, reference = bitmap.last_target()
200eed87583SKevin Wolf        res = self.do_qmp_backup(job_id=bitmap.drive['id'],
201eed87583SKevin Wolf                                 device=bitmap.drive['id'], sync='full',
202a3d71595SJohn Snow                                 format=bitmap.drive['fmt'], target=reference)
203a3d71595SJohn Snow        self.assertTrue(res)
204a3d71595SJohn Snow
205a3d71595SJohn Snow
20659fc5d84SJohn Snow    def add_bitmap(self, name, drive, **kwargs):
207a3d71595SJohn Snow        bitmap = Bitmap(name, drive)
208a3d71595SJohn Snow        self.bitmaps.append(bitmap)
209a3d71595SJohn Snow        result = self.vm.qmp('block-dirty-bitmap-add', node=drive['id'],
21059fc5d84SJohn Snow                             name=bitmap.name, **kwargs)
211a3d71595SJohn Snow        self.assert_qmp(result, 'return', {})
212a3d71595SJohn Snow        return bitmap
213a3d71595SJohn Snow
214a3d71595SJohn Snow
2158a9cb864SMax Reitz    def prepare_backup(self, bitmap=None, parent=None, **kwargs):
216a3d71595SJohn Snow        if bitmap is None:
217a3d71595SJohn Snow            bitmap = self.bitmaps[-1]
218a3d71595SJohn Snow        if parent is None:
219a3d71595SJohn Snow            parent, _ = bitmap.last_target()
220a3d71595SJohn Snow
221a3d71595SJohn Snow        target, _ = bitmap.new_target()
2228a9cb864SMax Reitz        self.img_create(target, bitmap.drive['fmt'], parent=parent,
2238a9cb864SMax Reitz                        **kwargs)
224a3d71595SJohn Snow        return target
225a3d71595SJohn Snow
226a3d71595SJohn Snow
227a3d71595SJohn Snow    def create_incremental(self, bitmap=None, parent=None,
2288a9cb864SMax Reitz                           parentFormat=None, validate=True,
2298a9cb864SMax Reitz                           target=None):
230a3d71595SJohn Snow        if bitmap is None:
231a3d71595SJohn Snow            bitmap = self.bitmaps[-1]
232a3d71595SJohn Snow        if parent is None:
233a3d71595SJohn Snow            parent, _ = bitmap.last_target()
234a3d71595SJohn Snow
2358a9cb864SMax Reitz        if target is None:
236a3d71595SJohn Snow            target = self.prepare_backup(bitmap, parent)
237eed87583SKevin Wolf        res = self.do_qmp_backup(job_id=bitmap.drive['id'],
238eed87583SKevin Wolf                                 device=bitmap.drive['id'],
2394b80ab2bSJohn Snow                                 sync='incremental', bitmap=bitmap.name,
240a3d71595SJohn Snow                                 format=bitmap.drive['fmt'], target=target,
241a3d71595SJohn Snow                                 mode='existing')
242a3d71595SJohn Snow        if not res:
243a3d71595SJohn Snow            bitmap.del_target();
244a3d71595SJohn Snow            self.assertFalse(validate)
245a3d71595SJohn Snow        else:
246a3d71595SJohn Snow            self.make_reference_backup(bitmap)
247a3d71595SJohn Snow        return res
248a3d71595SJohn Snow
249a3d71595SJohn Snow
250a3d71595SJohn Snow    def check_backups(self):
251a3d71595SJohn Snow        for bitmap in self.bitmaps:
252a3d71595SJohn Snow            for incremental, reference in bitmap.backups:
253a3d71595SJohn Snow                self.assertTrue(iotests.compare_images(incremental, reference))
254a3d71595SJohn Snow            last = bitmap.last_target()[0]
255a3d71595SJohn Snow            self.assertTrue(iotests.compare_images(last, bitmap.drive['file']))
256a3d71595SJohn Snow
257a3d71595SJohn Snow
258a3d71595SJohn Snow    def hmp_io_writes(self, drive, patterns):
259a3d71595SJohn Snow        for pattern in patterns:
260a3d71595SJohn Snow            self.vm.hmp_qemu_io(drive, 'write -P%s %s %s' % pattern)
261a3d71595SJohn Snow        self.vm.hmp_qemu_io(drive, 'flush')
262a3d71595SJohn Snow
263a3d71595SJohn Snow
26459fc5d84SJohn Snow    def do_incremental_simple(self, **kwargs):
265a3d71595SJohn Snow        self.create_anchor_backup()
26659fc5d84SJohn Snow        self.add_bitmap('bitmap0', self.drives[0], **kwargs)
267a3d71595SJohn Snow
268a3d71595SJohn Snow        # Sanity: Create a "hollow" incremental backup
269a3d71595SJohn Snow        self.create_incremental()
270a3d71595SJohn Snow        # Three writes: One complete overwrite, one new segment,
271a3d71595SJohn Snow        # and one partial overlap.
272a3d71595SJohn Snow        self.hmp_io_writes(self.drives[0]['id'], (('0xab', 0, 512),
273a3d71595SJohn Snow                                                  ('0xfe', '16M', '256k'),
274a3d71595SJohn Snow                                                  ('0x64', '32736k', '64k')))
275a3d71595SJohn Snow        self.create_incremental()
276a3d71595SJohn Snow        # Three more writes, one of each kind, like above
277a3d71595SJohn Snow        self.hmp_io_writes(self.drives[0]['id'], (('0x9a', 0, 512),
278a3d71595SJohn Snow                                                  ('0x55', '8M', '352k'),
279a3d71595SJohn Snow                                                  ('0x78', '15872k', '1M')))
280a3d71595SJohn Snow        self.create_incremental()
281a3d71595SJohn Snow        self.vm.shutdown()
282a3d71595SJohn Snow        self.check_backups()
283a3d71595SJohn Snow
284a3d71595SJohn Snow
2851b19bb9dSJohn Snow    def tearDown(self):
2861b19bb9dSJohn Snow        self.vm.shutdown()
2871b19bb9dSJohn Snow        for bitmap in self.bitmaps:
2881b19bb9dSJohn Snow            bitmap.cleanup()
2891b19bb9dSJohn Snow        for filename in self.files:
2901b19bb9dSJohn Snow            try_remove(filename)
2911b19bb9dSJohn Snow
2921b19bb9dSJohn Snow
2931b19bb9dSJohn Snow
2941b19bb9dSJohn Snowclass TestIncrementalBackup(TestIncrementalBackupBase):
29559fc5d84SJohn Snow    def test_incremental_simple(self):
29659fc5d84SJohn Snow        '''
29759fc5d84SJohn Snow        Test: Create and verify three incremental backups.
29859fc5d84SJohn Snow
29959fc5d84SJohn Snow        Create a bitmap and a full backup before VM execution begins,
30059fc5d84SJohn Snow        then create a series of three incremental backups "during execution,"
30159fc5d84SJohn Snow        i.e.; after IO requests begin modifying the drive.
30259fc5d84SJohn Snow        '''
30359fc5d84SJohn Snow        return self.do_incremental_simple()
30459fc5d84SJohn Snow
30559fc5d84SJohn Snow
30659fc5d84SJohn Snow    def test_small_granularity(self):
30759fc5d84SJohn Snow        '''
30859fc5d84SJohn Snow        Test: Create and verify backups made with a small granularity bitmap.
30959fc5d84SJohn Snow
31059fc5d84SJohn Snow        Perform the same test as test_incremental_simple, but with a granularity
31159fc5d84SJohn Snow        of only 32KiB instead of the present default of 64KiB.
31259fc5d84SJohn Snow        '''
31359fc5d84SJohn Snow        return self.do_incremental_simple(granularity=32768)
31459fc5d84SJohn Snow
31559fc5d84SJohn Snow
31659fc5d84SJohn Snow    def test_large_granularity(self):
31759fc5d84SJohn Snow        '''
31859fc5d84SJohn Snow        Test: Create and verify backups made with a large granularity bitmap.
31959fc5d84SJohn Snow
32059fc5d84SJohn Snow        Perform the same test as test_incremental_simple, but with a granularity
32159fc5d84SJohn Snow        of 128KiB instead of the present default of 64KiB.
32259fc5d84SJohn Snow        '''
32359fc5d84SJohn Snow        return self.do_incremental_simple(granularity=131072)
32459fc5d84SJohn Snow
32559fc5d84SJohn Snow
326cc199b16SJohn Snow    def test_larger_cluster_target(self):
327cc199b16SJohn Snow        '''
328cc199b16SJohn Snow        Test: Create and verify backups made to a larger cluster size target.
329cc199b16SJohn Snow
330cc199b16SJohn Snow        With a default granularity of 64KiB, verify that backups made to a
331cc199b16SJohn Snow        larger cluster size target of 128KiB without a backing file works.
332cc199b16SJohn Snow        '''
333cc199b16SJohn Snow        drive0 = self.drives[0]
334cc199b16SJohn Snow
335cc199b16SJohn Snow        # Create a cluster_size=128k full backup / "anchor" backup
336cc199b16SJohn Snow        self.img_create(drive0['backup'], cluster_size='128k')
337cc199b16SJohn Snow        self.assertTrue(self.do_qmp_backup(device=drive0['id'], sync='full',
338cc199b16SJohn Snow                                           format=drive0['fmt'],
339cc199b16SJohn Snow                                           target=drive0['backup'],
340cc199b16SJohn Snow                                           mode='existing'))
341cc199b16SJohn Snow
342cc199b16SJohn Snow        # Create bitmap and dirty it with some new writes.
343cc199b16SJohn Snow        # overwrite [32736, 32799] which will dirty bitmap clusters at
344cc199b16SJohn Snow        # 32M-64K and 32M. 32M+64K will be left undirtied.
345cc199b16SJohn Snow        bitmap0 = self.add_bitmap('bitmap0', drive0)
346cc199b16SJohn Snow        self.hmp_io_writes(drive0['id'],
347cc199b16SJohn Snow                           (('0xab', 0, 512),
348cc199b16SJohn Snow                            ('0xfe', '16M', '256k'),
349cc199b16SJohn Snow                            ('0x64', '32736k', '64k')))
3501e2b1f64SEric Blake        # Check the dirty bitmap stats
3511e2b1f64SEric Blake        result = self.vm.qmp('query-block')
3521e2b1f64SEric Blake        self.assert_qmp(result, 'return[0]/dirty-bitmaps[0]/name', 'bitmap0')
3531e2b1f64SEric Blake        self.assert_qmp(result, 'return[0]/dirty-bitmaps[0]/count', 458752)
3541e2b1f64SEric Blake        self.assert_qmp(result, 'return[0]/dirty-bitmaps[0]/granularity', 65536)
3551e2b1f64SEric Blake        self.assert_qmp(result, 'return[0]/dirty-bitmaps[0]/status', 'active')
356f67cf661SEric Blake        self.assert_qmp(result, 'return[0]/dirty-bitmaps[0]/persistent', False)
357cc199b16SJohn Snow
358cc199b16SJohn Snow        # Prepare a cluster_size=128k backup target without a backing file.
359cc199b16SJohn Snow        (target, _) = bitmap0.new_target()
360cc199b16SJohn Snow        self.img_create(target, bitmap0.drive['fmt'], cluster_size='128k')
361cc199b16SJohn Snow
362cc199b16SJohn Snow        # Perform Incremental Backup
363cc199b16SJohn Snow        self.assertTrue(self.do_qmp_backup(device=bitmap0.drive['id'],
364cc199b16SJohn Snow                                           sync='incremental',
365cc199b16SJohn Snow                                           bitmap=bitmap0.name,
366cc199b16SJohn Snow                                           format=bitmap0.drive['fmt'],
367cc199b16SJohn Snow                                           target=target,
368cc199b16SJohn Snow                                           mode='existing'))
369cc199b16SJohn Snow        self.make_reference_backup(bitmap0)
370cc199b16SJohn Snow
371cc199b16SJohn Snow        # Add the backing file, then compare and exit.
372cc199b16SJohn Snow        iotests.qemu_img('rebase', '-f', drive0['fmt'], '-u', '-b',
373cc199b16SJohn Snow                         drive0['backup'], '-F', drive0['fmt'], target)
374cc199b16SJohn Snow        self.vm.shutdown()
375cc199b16SJohn Snow        self.check_backups()
376cc199b16SJohn Snow
377cc199b16SJohn Snow
378749ad5e8SJohn Snow    def test_incremental_transaction(self):
379749ad5e8SJohn Snow        '''Test: Verify backups made from transactionally created bitmaps.
380749ad5e8SJohn Snow
381749ad5e8SJohn Snow        Create a bitmap "before" VM execution begins, then create a second
382749ad5e8SJohn Snow        bitmap AFTER writes have already occurred. Use transactions to create
383749ad5e8SJohn Snow        a full backup and synchronize both bitmaps to this backup.
384749ad5e8SJohn Snow        Create an incremental backup through both bitmaps and verify that
385749ad5e8SJohn Snow        both backups match the current drive0 image.
386749ad5e8SJohn Snow        '''
387749ad5e8SJohn Snow
388749ad5e8SJohn Snow        drive0 = self.drives[0]
389749ad5e8SJohn Snow        bitmap0 = self.add_bitmap('bitmap0', drive0)
390749ad5e8SJohn Snow        self.hmp_io_writes(drive0['id'], (('0xab', 0, 512),
391749ad5e8SJohn Snow                                          ('0xfe', '16M', '256k'),
392749ad5e8SJohn Snow                                          ('0x64', '32736k', '64k')))
393749ad5e8SJohn Snow        bitmap1 = self.add_bitmap('bitmap1', drive0)
394749ad5e8SJohn Snow
395749ad5e8SJohn Snow        result = self.vm.qmp('transaction', actions=[
396749ad5e8SJohn Snow            transaction_bitmap_clear(bitmap0.drive['id'], bitmap0.name),
397749ad5e8SJohn Snow            transaction_bitmap_clear(bitmap1.drive['id'], bitmap1.name),
398749ad5e8SJohn Snow            transaction_drive_backup(drive0['id'], drive0['backup'],
399749ad5e8SJohn Snow                                     sync='full', format=drive0['fmt'])
400749ad5e8SJohn Snow        ])
401749ad5e8SJohn Snow        self.assert_qmp(result, 'return', {})
402749ad5e8SJohn Snow        self.wait_until_completed(drive0['id'])
403749ad5e8SJohn Snow        self.files.append(drive0['backup'])
404749ad5e8SJohn Snow
405749ad5e8SJohn Snow        self.hmp_io_writes(drive0['id'], (('0x9a', 0, 512),
406749ad5e8SJohn Snow                                          ('0x55', '8M', '352k'),
407749ad5e8SJohn Snow                                          ('0x78', '15872k', '1M')))
408749ad5e8SJohn Snow        # Both bitmaps should be correctly in sync.
409749ad5e8SJohn Snow        self.create_incremental(bitmap0)
410749ad5e8SJohn Snow        self.create_incremental(bitmap1)
411749ad5e8SJohn Snow        self.vm.shutdown()
412749ad5e8SJohn Snow        self.check_backups()
413749ad5e8SJohn Snow
414749ad5e8SJohn Snow
4150aef09b9SJohn Snow    def do_transaction_failure_test(self, race=False):
416fc6c796fSJohn Snow        # Create a second drive, with pattern:
417fc6c796fSJohn Snow        drive1 = self.add_node('drive1')
418fc6c796fSJohn Snow        self.img_create(drive1['file'], drive1['fmt'])
419fc6c796fSJohn Snow        io_write_patterns(drive1['file'], (('0x14', 0, 512),
420fc6c796fSJohn Snow                                           ('0x5d', '1M', '32k'),
421fc6c796fSJohn Snow                                           ('0xcd', '32M', '124k')))
422fc6c796fSJohn Snow
423fc6c796fSJohn Snow        # Create a blkdebug interface to this img as 'drive1'
4240153d2f5SKevin Wolf        result = self.vm.qmp('blockdev-add',
4250153d2f5SKevin Wolf            node_name=drive1['id'],
4260153d2f5SKevin Wolf            driver=drive1['fmt'],
4270153d2f5SKevin Wolf            file={
428fc6c796fSJohn Snow                'driver': 'blkdebug',
429fc6c796fSJohn Snow                'image': {
430fc6c796fSJohn Snow                    'driver': 'file',
431fc6c796fSJohn Snow                    'filename': drive1['file']
432fc6c796fSJohn Snow                },
433fc6c796fSJohn Snow                'set-state': [{
434fc6c796fSJohn Snow                    'event': 'flush_to_disk',
435fc6c796fSJohn Snow                    'state': 1,
436fc6c796fSJohn Snow                    'new_state': 2
437fc6c796fSJohn Snow                }],
438fc6c796fSJohn Snow                'inject-error': [{
439fc6c796fSJohn Snow                    'event': 'read_aio',
440fc6c796fSJohn Snow                    'errno': 5,
441fc6c796fSJohn Snow                    'state': 2,
442fc6c796fSJohn Snow                    'immediately': False,
443fc6c796fSJohn Snow                    'once': True
444fc6c796fSJohn Snow                }],
445fc6c796fSJohn Snow            }
4460153d2f5SKevin Wolf        )
447fc6c796fSJohn Snow        self.assert_qmp(result, 'return', {})
448fc6c796fSJohn Snow
449fc6c796fSJohn Snow        # Create bitmaps and full backups for both drives
450fc6c796fSJohn Snow        drive0 = self.drives[0]
451fc6c796fSJohn Snow        dr0bm0 = self.add_bitmap('bitmap0', drive0)
452fc6c796fSJohn Snow        dr1bm0 = self.add_bitmap('bitmap0', drive1)
453fc6c796fSJohn Snow        self.create_anchor_backup(drive0)
454fc6c796fSJohn Snow        self.create_anchor_backup(drive1)
455fc6c796fSJohn Snow        self.assert_no_active_block_jobs()
456fc6c796fSJohn Snow        self.assertFalse(self.vm.get_qmp_events(wait=False))
457fc6c796fSJohn Snow
458fc6c796fSJohn Snow        # Emulate some writes
4590aef09b9SJohn Snow        if not race:
460fc6c796fSJohn Snow            self.hmp_io_writes(drive0['id'], (('0xab', 0, 512),
461fc6c796fSJohn Snow                                              ('0xfe', '16M', '256k'),
462fc6c796fSJohn Snow                                              ('0x64', '32736k', '64k')))
463fc6c796fSJohn Snow        self.hmp_io_writes(drive1['id'], (('0xba', 0, 512),
464fc6c796fSJohn Snow                                          ('0xef', '16M', '256k'),
465fc6c796fSJohn Snow                                          ('0x46', '32736k', '64k')))
466fc6c796fSJohn Snow
467fc6c796fSJohn Snow        # Create incremental backup targets
468fc6c796fSJohn Snow        target0 = self.prepare_backup(dr0bm0)
469fc6c796fSJohn Snow        target1 = self.prepare_backup(dr1bm0)
470fc6c796fSJohn Snow
471fc6c796fSJohn Snow        # Ask for a new incremental backup per-each drive,
4720aef09b9SJohn Snow        # expecting drive1's backup to fail. In the 'race' test,
4730aef09b9SJohn Snow        # we expect drive1 to attempt to cancel the empty drive0 job.
474fc6c796fSJohn Snow        transaction = [
475fc6c796fSJohn Snow            transaction_drive_backup(drive0['id'], target0, sync='incremental',
476fc6c796fSJohn Snow                                     format=drive0['fmt'], mode='existing',
477fc6c796fSJohn Snow                                     bitmap=dr0bm0.name),
478fc6c796fSJohn Snow            transaction_drive_backup(drive1['id'], target1, sync='incremental',
479fc6c796fSJohn Snow                                     format=drive1['fmt'], mode='existing',
480fc6c796fSJohn Snow                                     bitmap=dr1bm0.name)
481fc6c796fSJohn Snow        ]
482fc6c796fSJohn Snow        result = self.vm.qmp('transaction', actions=transaction,
483fc6c796fSJohn Snow                             properties={'completion-mode': 'grouped'} )
484fc6c796fSJohn Snow        self.assert_qmp(result, 'return', {})
485fc6c796fSJohn Snow
486fc6c796fSJohn Snow        # Observe that drive0's backup is cancelled and drive1 completes with
487fc6c796fSJohn Snow        # an error.
488fc6c796fSJohn Snow        self.wait_qmp_backup_cancelled(drive0['id'])
489fc6c796fSJohn Snow        self.assertFalse(self.wait_qmp_backup(drive1['id']))
490fc6c796fSJohn Snow        error = self.vm.event_wait('BLOCK_JOB_ERROR')
491fc6c796fSJohn Snow        self.assert_qmp(error, 'data', {'device': drive1['id'],
492fc6c796fSJohn Snow                                        'action': 'report',
493fc6c796fSJohn Snow                                        'operation': 'read'})
494fc6c796fSJohn Snow        self.assertFalse(self.vm.get_qmp_events(wait=False))
495fc6c796fSJohn Snow        self.assert_no_active_block_jobs()
496fc6c796fSJohn Snow
497fc6c796fSJohn Snow        # Delete drive0's successful target and eliminate our record of the
4980aef09b9SJohn Snow        # unsuccessful drive1 target.
499fc6c796fSJohn Snow        dr0bm0.del_target()
500fc6c796fSJohn Snow        dr1bm0.del_target()
5010aef09b9SJohn Snow        if race:
5020aef09b9SJohn Snow            # Don't re-run the transaction, we only wanted to test the race.
5030aef09b9SJohn Snow            self.vm.shutdown()
5040aef09b9SJohn Snow            return
5050aef09b9SJohn Snow
5060aef09b9SJohn Snow        # Re-run the same transaction:
507fc6c796fSJohn Snow        target0 = self.prepare_backup(dr0bm0)
508fc6c796fSJohn Snow        target1 = self.prepare_backup(dr1bm0)
509fc6c796fSJohn Snow
510fc6c796fSJohn Snow        # Re-run the exact same transaction.
511fc6c796fSJohn Snow        result = self.vm.qmp('transaction', actions=transaction,
512fc6c796fSJohn Snow                             properties={'completion-mode':'grouped'})
513fc6c796fSJohn Snow        self.assert_qmp(result, 'return', {})
514fc6c796fSJohn Snow
515fc6c796fSJohn Snow        # Both should complete successfully this time.
516fc6c796fSJohn Snow        self.assertTrue(self.wait_qmp_backup(drive0['id']))
517fc6c796fSJohn Snow        self.assertTrue(self.wait_qmp_backup(drive1['id']))
518fc6c796fSJohn Snow        self.make_reference_backup(dr0bm0)
519fc6c796fSJohn Snow        self.make_reference_backup(dr1bm0)
520fc6c796fSJohn Snow        self.assertFalse(self.vm.get_qmp_events(wait=False))
521fc6c796fSJohn Snow        self.assert_no_active_block_jobs()
522fc6c796fSJohn Snow
523fc6c796fSJohn Snow        # And the images should of course validate.
524fc6c796fSJohn Snow        self.vm.shutdown()
525fc6c796fSJohn Snow        self.check_backups()
526fc6c796fSJohn Snow
5270aef09b9SJohn Snow    def test_transaction_failure(self):
5280aef09b9SJohn Snow        '''Test: Verify backups made from a transaction that partially fails.
5290aef09b9SJohn Snow
5300aef09b9SJohn Snow        Add a second drive with its own unique pattern, and add a bitmap to each
5310aef09b9SJohn Snow        drive. Use blkdebug to interfere with the backup on just one drive and
5320aef09b9SJohn Snow        attempt to create a coherent incremental backup across both drives.
5330aef09b9SJohn Snow
5340aef09b9SJohn Snow        verify a failure in one but not both, then delete the failed stubs and
5350aef09b9SJohn Snow        re-run the same transaction.
5360aef09b9SJohn Snow
5370aef09b9SJohn Snow        verify that both incrementals are created successfully.
5380aef09b9SJohn Snow        '''
5390aef09b9SJohn Snow        self.do_transaction_failure_test()
5400aef09b9SJohn Snow
5410aef09b9SJohn Snow    def test_transaction_failure_race(self):
5420aef09b9SJohn Snow        '''Test: Verify that transactions with jobs that have no data to
5430aef09b9SJohn Snow        transfer do not cause race conditions in the cancellation of the entire
5440aef09b9SJohn Snow        transaction job group.
5450aef09b9SJohn Snow        '''
5460aef09b9SJohn Snow        self.do_transaction_failure_test(race=True)
5470aef09b9SJohn Snow
548fc6c796fSJohn Snow
5499f7264f5SJohn Snow    def test_sync_dirty_bitmap_missing(self):
5509f7264f5SJohn Snow        self.assert_no_active_block_jobs()
5519f7264f5SJohn Snow        self.files.append(self.err_img)
5529f7264f5SJohn Snow        result = self.vm.qmp('drive-backup', device=self.drives[0]['id'],
5534b80ab2bSJohn Snow                             sync='incremental', format=self.drives[0]['fmt'],
5549f7264f5SJohn Snow                             target=self.err_img)
5559f7264f5SJohn Snow        self.assert_qmp(result, 'error/class', 'GenericError')
5569f7264f5SJohn Snow
5579f7264f5SJohn Snow
5589f7264f5SJohn Snow    def test_sync_dirty_bitmap_not_found(self):
5599f7264f5SJohn Snow        self.assert_no_active_block_jobs()
5609f7264f5SJohn Snow        self.files.append(self.err_img)
5619f7264f5SJohn Snow        result = self.vm.qmp('drive-backup', device=self.drives[0]['id'],
5624b80ab2bSJohn Snow                             sync='incremental', bitmap='unknown',
5639f7264f5SJohn Snow                             format=self.drives[0]['fmt'], target=self.err_img)
5649f7264f5SJohn Snow        self.assert_qmp(result, 'error/class', 'GenericError')
5659f7264f5SJohn Snow
5669f7264f5SJohn Snow
56759fc5d84SJohn Snow    def test_sync_dirty_bitmap_bad_granularity(self):
56859fc5d84SJohn Snow        '''
56959fc5d84SJohn Snow        Test: Test what happens if we provide an improper granularity.
57059fc5d84SJohn Snow
57159fc5d84SJohn Snow        The granularity must always be a power of 2.
57259fc5d84SJohn Snow        '''
57359fc5d84SJohn Snow        self.assert_no_active_block_jobs()
57459fc5d84SJohn Snow        self.assertRaises(AssertionError, self.add_bitmap,
57559fc5d84SJohn Snow                          'bitmap0', self.drives[0],
57659fc5d84SJohn Snow                          granularity=64000)
57759fc5d84SJohn Snow
5788a9cb864SMax Reitz    def test_growing_before_backup(self):
5798a9cb864SMax Reitz        '''
5808a9cb864SMax Reitz        Test: Add a bitmap, truncate the image, write past the old
5818a9cb864SMax Reitz              end, do a backup.
5828a9cb864SMax Reitz
5838a9cb864SMax Reitz        Incremental backup should not ignore dirty bits past the old
5848a9cb864SMax Reitz        image end.
5858a9cb864SMax Reitz        '''
5868a9cb864SMax Reitz        self.assert_no_active_block_jobs()
5878a9cb864SMax Reitz
5888a9cb864SMax Reitz        self.create_anchor_backup()
5898a9cb864SMax Reitz
5908a9cb864SMax Reitz        self.add_bitmap('bitmap0', self.drives[0])
5918a9cb864SMax Reitz
5928a9cb864SMax Reitz        res = self.vm.qmp('block_resize', device=self.drives[0]['id'],
5938a9cb864SMax Reitz                          size=(65 * 1048576))
5948a9cb864SMax Reitz        self.assert_qmp(res, 'return', {})
5958a9cb864SMax Reitz
5968a9cb864SMax Reitz        # Dirty the image past the old end
5978a9cb864SMax Reitz        self.vm.hmp_qemu_io(self.drives[0]['id'], 'write 64M 64k')
5988a9cb864SMax Reitz
5998a9cb864SMax Reitz        target = self.prepare_backup(size='65M')
6008a9cb864SMax Reitz        self.create_incremental(target=target)
6018a9cb864SMax Reitz
6028a9cb864SMax Reitz        self.vm.shutdown()
6038a9cb864SMax Reitz        self.check_backups()
6048a9cb864SMax Reitz
60559fc5d84SJohn Snow
606ce2cbc49SJohn Snowclass TestIncrementalBackupBlkdebug(TestIncrementalBackupBase):
607ce2cbc49SJohn Snow    '''Incremental backup tests that utilize a BlkDebug filter on drive0.'''
608ce2cbc49SJohn Snow
60935cea223SJohn Snow    def setUp(self):
61035cea223SJohn Snow        drive0 = self.add_node('drive0')
61135cea223SJohn Snow        self.img_create(drive0['file'], drive0['fmt'])
61235cea223SJohn Snow        self.write_default_pattern(drive0['file'])
61335cea223SJohn Snow        self.vm.launch()
61435cea223SJohn Snow
615ce2cbc49SJohn Snow    def test_incremental_failure(self):
616ce2cbc49SJohn Snow        '''Test: Verify backups made after a failure are correct.
617ce2cbc49SJohn Snow
618ce2cbc49SJohn Snow        Simulate a failure during an incremental backup block job,
619ce2cbc49SJohn Snow        emulate additional writes, then create another incremental backup
620ce2cbc49SJohn Snow        afterwards and verify that the backup created is correct.
621ce2cbc49SJohn Snow        '''
622ce2cbc49SJohn Snow
62335cea223SJohn Snow        drive0 = self.drives[0]
6240153d2f5SKevin Wolf        result = self.vm.qmp('blockdev-add',
6250153d2f5SKevin Wolf            node_name=drive0['id'],
6260153d2f5SKevin Wolf            driver=drive0['fmt'],
6270153d2f5SKevin Wolf            file={
628ce2cbc49SJohn Snow                'driver': 'blkdebug',
629ce2cbc49SJohn Snow                'image': {
630ce2cbc49SJohn Snow                    'driver': 'file',
63135cea223SJohn Snow                    'filename': drive0['file']
632ce2cbc49SJohn Snow                },
633ce2cbc49SJohn Snow                'set-state': [{
634ce2cbc49SJohn Snow                    'event': 'flush_to_disk',
635ce2cbc49SJohn Snow                    'state': 1,
636ce2cbc49SJohn Snow                    'new_state': 2
637ce2cbc49SJohn Snow                }],
638ce2cbc49SJohn Snow                'inject-error': [{
639ce2cbc49SJohn Snow                    'event': 'read_aio',
640ce2cbc49SJohn Snow                    'errno': 5,
641ce2cbc49SJohn Snow                    'state': 2,
642ce2cbc49SJohn Snow                    'immediately': False,
643ce2cbc49SJohn Snow                    'once': True
644ce2cbc49SJohn Snow                }],
645ce2cbc49SJohn Snow            }
6460153d2f5SKevin Wolf        )
647ce2cbc49SJohn Snow        self.assert_qmp(result, 'return', {})
648ce2cbc49SJohn Snow
64935cea223SJohn Snow        self.create_anchor_backup(drive0)
65035cea223SJohn Snow        self.add_bitmap('bitmap0', drive0)
651ce2cbc49SJohn Snow        # Note: at this point, during a normal execution,
652ce2cbc49SJohn Snow        # Assume that the VM resumes and begins issuing IO requests here.
653ce2cbc49SJohn Snow
65435cea223SJohn Snow        self.hmp_io_writes(drive0['id'], (('0xab', 0, 512),
655ce2cbc49SJohn Snow                                          ('0xfe', '16M', '256k'),
656ce2cbc49SJohn Snow                                          ('0x64', '32736k', '64k')))
657ce2cbc49SJohn Snow
658ce2cbc49SJohn Snow        result = self.create_incremental(validate=False)
659ce2cbc49SJohn Snow        self.assertFalse(result)
66035cea223SJohn Snow        self.hmp_io_writes(drive0['id'], (('0x9a', 0, 512),
661ce2cbc49SJohn Snow                                          ('0x55', '8M', '352k'),
662ce2cbc49SJohn Snow                                          ('0x78', '15872k', '1M')))
663ce2cbc49SJohn Snow        self.create_incremental()
664ce2cbc49SJohn Snow        self.vm.shutdown()
665ce2cbc49SJohn Snow        self.check_backups()
666ce2cbc49SJohn Snow
667c61b198bSJohn Snow    def test_incremental_pause(self):
668c61b198bSJohn Snow        """
669c61b198bSJohn Snow        Test an incremental backup that errors into a pause and is resumed.
670c61b198bSJohn Snow        """
671c61b198bSJohn Snow
672c61b198bSJohn Snow        drive0 = self.drives[0]
673c61b198bSJohn Snow        # NB: The blkdebug script here looks for a "flush, read, read" pattern.
674c61b198bSJohn Snow        # The flush occurs in hmp_io_writes, the first read in device_add, and
675c61b198bSJohn Snow        # the last read during the block job.
676c61b198bSJohn Snow        result = self.vm.qmp('blockdev-add',
677c61b198bSJohn Snow                             node_name=drive0['id'],
678c61b198bSJohn Snow                             driver=drive0['fmt'],
679c61b198bSJohn Snow                             file={
680c61b198bSJohn Snow                                 'driver': 'blkdebug',
681c61b198bSJohn Snow                                 'image': {
682c61b198bSJohn Snow                                     'driver': 'file',
683c61b198bSJohn Snow                                     'filename': drive0['file']
684c61b198bSJohn Snow                                 },
685c61b198bSJohn Snow                                 'set-state': [{
686c61b198bSJohn Snow                                     'event': 'flush_to_disk',
687c61b198bSJohn Snow                                     'state': 1,
688c61b198bSJohn Snow                                     'new_state': 2
689c61b198bSJohn Snow                                 },{
690c61b198bSJohn Snow                                     'event': 'read_aio',
691c61b198bSJohn Snow                                     'state': 2,
692c61b198bSJohn Snow                                     'new_state': 3
693c61b198bSJohn Snow                                 }],
694c61b198bSJohn Snow                                 'inject-error': [{
695c61b198bSJohn Snow                                     'event': 'read_aio',
696c61b198bSJohn Snow                                     'errno': 5,
697c61b198bSJohn Snow                                     'state': 3,
698c61b198bSJohn Snow                                     'immediately': False,
699c61b198bSJohn Snow                                     'once': True
700c61b198bSJohn Snow                                 }],
701c61b198bSJohn Snow                             })
702c61b198bSJohn Snow        self.assert_qmp(result, 'return', {})
703c61b198bSJohn Snow        self.create_anchor_backup(drive0)
704c61b198bSJohn Snow        bitmap = self.add_bitmap('bitmap0', drive0)
705c61b198bSJohn Snow
706c61b198bSJohn Snow        # Emulate guest activity
707c61b198bSJohn Snow        self.hmp_io_writes(drive0['id'], (('0xab', 0, 512),
708c61b198bSJohn Snow                                          ('0xfe', '16M', '256k'),
709c61b198bSJohn Snow                                          ('0x64', '32736k', '64k')))
710c61b198bSJohn Snow
711c61b198bSJohn Snow        # For the purposes of query-block visibility of bitmaps, add a drive
712c61b198bSJohn Snow        # frontend after we've written data; otherwise we can't use hmp-io
713c61b198bSJohn Snow        result = self.vm.qmp("device_add",
714c61b198bSJohn Snow                             id="device0",
715c61b198bSJohn Snow                             drive=drive0['id'],
716c61b198bSJohn Snow                             driver="virtio-blk")
717c61b198bSJohn Snow        self.assert_qmp(result, 'return', {})
718c61b198bSJohn Snow
719c61b198bSJohn Snow        # Bitmap Status Check
720c61b198bSJohn Snow        query = self.vm.qmp('query-block')
721c61b198bSJohn Snow        ret = [bmap for bmap in query['return'][0]['dirty-bitmaps']
722c61b198bSJohn Snow               if bmap.get('name') == bitmap.name][0]
723c61b198bSJohn Snow        self.assert_qmp(ret, 'count', 458752)
724c61b198bSJohn Snow        self.assert_qmp(ret, 'granularity', 65536)
725c61b198bSJohn Snow        self.assert_qmp(ret, 'status', 'active')
726c61b198bSJohn Snow        self.assert_qmp(ret, 'busy', False)
727c61b198bSJohn Snow        self.assert_qmp(ret, 'recording', True)
728c61b198bSJohn Snow
729c61b198bSJohn Snow        # Start backup
730c61b198bSJohn Snow        parent, _ = bitmap.last_target()
731c61b198bSJohn Snow        target = self.prepare_backup(bitmap, parent)
732c61b198bSJohn Snow        res = self.vm.qmp('drive-backup',
733c61b198bSJohn Snow                          job_id=bitmap.drive['id'],
734c61b198bSJohn Snow                          device=bitmap.drive['id'],
735c61b198bSJohn Snow                          sync='incremental',
736c61b198bSJohn Snow                          bitmap=bitmap.name,
737c61b198bSJohn Snow                          format=bitmap.drive['fmt'],
738c61b198bSJohn Snow                          target=target,
739c61b198bSJohn Snow                          mode='existing',
740c61b198bSJohn Snow                          on_source_error='stop')
741c61b198bSJohn Snow        self.assert_qmp(res, 'return', {})
742c61b198bSJohn Snow
743c61b198bSJohn Snow        # Wait for the error
744c61b198bSJohn Snow        event = self.vm.event_wait(name="BLOCK_JOB_ERROR",
745c61b198bSJohn Snow                                   match={"data":{"device":bitmap.drive['id']}})
746c61b198bSJohn Snow        self.assert_qmp(event, 'data', {'device': bitmap.drive['id'],
747c61b198bSJohn Snow                                        'action': 'stop',
748c61b198bSJohn Snow                                        'operation': 'read'})
749c61b198bSJohn Snow
750c61b198bSJohn Snow        # Bitmap Status Check
751c61b198bSJohn Snow        query = self.vm.qmp('query-block')
752c61b198bSJohn Snow        ret = [bmap for bmap in query['return'][0]['dirty-bitmaps']
753c61b198bSJohn Snow               if bmap.get('name') == bitmap.name][0]
754c61b198bSJohn Snow        self.assert_qmp(ret, 'count', 458752)
755c61b198bSJohn Snow        self.assert_qmp(ret, 'granularity', 65536)
756c61b198bSJohn Snow        self.assert_qmp(ret, 'status', 'frozen')
757c61b198bSJohn Snow        self.assert_qmp(ret, 'busy', True)
758c61b198bSJohn Snow        self.assert_qmp(ret, 'recording', True)
759c61b198bSJohn Snow
760c61b198bSJohn Snow        # Resume and check incremental backup for consistency
761c61b198bSJohn Snow        res = self.vm.qmp('block-job-resume', device=bitmap.drive['id'])
762c61b198bSJohn Snow        self.assert_qmp(res, 'return', {})
763c61b198bSJohn Snow        self.wait_qmp_backup(bitmap.drive['id'])
764c61b198bSJohn Snow
765c61b198bSJohn Snow        # Bitmap Status Check
766c61b198bSJohn Snow        query = self.vm.qmp('query-block')
767c61b198bSJohn Snow        ret = [bmap for bmap in query['return'][0]['dirty-bitmaps']
768c61b198bSJohn Snow               if bmap.get('name') == bitmap.name][0]
769c61b198bSJohn Snow        self.assert_qmp(ret, 'count', 0)
770c61b198bSJohn Snow        self.assert_qmp(ret, 'granularity', 65536)
771c61b198bSJohn Snow        self.assert_qmp(ret, 'status', 'active')
772c61b198bSJohn Snow        self.assert_qmp(ret, 'busy', False)
773c61b198bSJohn Snow        self.assert_qmp(ret, 'recording', True)
774c61b198bSJohn Snow
775c61b198bSJohn Snow        # Finalize / Cleanup
776c61b198bSJohn Snow        self.make_reference_backup(bitmap)
777c61b198bSJohn Snow        self.vm.shutdown()
778c61b198bSJohn Snow        self.check_backups()
779c61b198bSJohn Snow
780ce2cbc49SJohn Snow
7819f7264f5SJohn Snowif __name__ == '__main__':
782*103cbc77SMax Reitz    iotests.main(supported_fmts=['qcow2'],
783*103cbc77SMax Reitz                 supported_protocols=['file'])
784