xref: /openbmc/qemu/tests/qemu-iotests/257 (revision fc272d3ce0094d2c2aff973662536d8d1a6943e5)
1903cb1bfSPhilippe Mathieu-Daudé#!/usr/bin/env python3
29dd003a9SVladimir Sementsov-Ogievskiy# group: rw
3dfdc48d5SJohn Snow#
4dfdc48d5SJohn Snow# Test bitmap-sync backups (incremental, differential, and partials)
5dfdc48d5SJohn Snow#
6dfdc48d5SJohn Snow# Copyright (c) 2019 John Snow for Red Hat, Inc.
7dfdc48d5SJohn Snow#
8dfdc48d5SJohn Snow# This program is free software; you can redistribute it and/or modify
9dfdc48d5SJohn Snow# it under the terms of the GNU General Public License as published by
10dfdc48d5SJohn Snow# the Free Software Foundation; either version 2 of the License, or
11dfdc48d5SJohn Snow# (at your option) any later version.
12dfdc48d5SJohn Snow#
13dfdc48d5SJohn Snow# This program is distributed in the hope that it will be useful,
14dfdc48d5SJohn Snow# but WITHOUT ANY WARRANTY; without even the implied warranty of
15dfdc48d5SJohn Snow# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16dfdc48d5SJohn Snow# GNU General Public License for more details.
17dfdc48d5SJohn Snow#
18dfdc48d5SJohn Snow# You should have received a copy of the GNU General Public License
19dfdc48d5SJohn Snow# along with this program.  If not, see <http://www.gnu.org/licenses/>.
20dfdc48d5SJohn Snow#
21dfdc48d5SJohn Snow# owner=jsnow@redhat.com
22dfdc48d5SJohn Snow
23dfdc48d5SJohn Snowimport math
24dfdc48d5SJohn Snowimport os
25dfdc48d5SJohn Snow
26dfdc48d5SJohn Snowimport iotests
27dfdc48d5SJohn Snowfrom iotests import log, qemu_img
28dfdc48d5SJohn Snow
29dfdc48d5SJohn SnowSIZE = 64 * 1024 * 1024
30dfdc48d5SJohn SnowGRANULARITY = 64 * 1024
31dfdc48d5SJohn Snow
32b0a32befSJohn Snow
33b0a32befSJohn Snowclass Pattern:
34b0a32befSJohn Snow    def __init__(self, byte, offset, size=GRANULARITY):
35b0a32befSJohn Snow        self.byte = byte
36b0a32befSJohn Snow        self.offset = offset
37b0a32befSJohn Snow        self.size = size
38b0a32befSJohn Snow
39b0a32befSJohn Snow    def bits(self, granularity):
40b0a32befSJohn Snow        lower = self.offset // granularity
41b0a32befSJohn Snow        upper = (self.offset + self.size - 1) // granularity
42b0a32befSJohn Snow        return set(range(lower, upper + 1))
43b0a32befSJohn Snow
44dfdc48d5SJohn Snow
45dfdc48d5SJohn Snowclass PatternGroup:
46dfdc48d5SJohn Snow    """Grouping of Pattern objects. Initialize with an iterable of Patterns."""
47dfdc48d5SJohn Snow    def __init__(self, patterns):
48dfdc48d5SJohn Snow        self.patterns = patterns
49dfdc48d5SJohn Snow
50dfdc48d5SJohn Snow    def bits(self, granularity):
51dfdc48d5SJohn Snow        """Calculate the unique bits dirtied by this pattern grouping"""
52dfdc48d5SJohn Snow        res = set()
53dfdc48d5SJohn Snow        for pattern in self.patterns:
54b0a32befSJohn Snow            res |= pattern.bits(granularity)
55dfdc48d5SJohn Snow        return res
56dfdc48d5SJohn Snow
57b0a32befSJohn Snow
58dfdc48d5SJohn SnowGROUPS = [
59dfdc48d5SJohn Snow    PatternGroup([
60dfdc48d5SJohn Snow        # Batch 0: 4 clusters
61b0a32befSJohn Snow        Pattern('0x49', 0x0000000),
62b0a32befSJohn Snow        Pattern('0x6c', 0x0100000),   # 1M
63b0a32befSJohn Snow        Pattern('0x6f', 0x2000000),   # 32M
64b0a32befSJohn Snow        Pattern('0x76', 0x3ff0000)]), # 64M - 64K
65dfdc48d5SJohn Snow    PatternGroup([
66dfdc48d5SJohn Snow        # Batch 1: 6 clusters (3 new)
67b0a32befSJohn Snow        Pattern('0x65', 0x0000000),   # Full overwrite
68b0a32befSJohn Snow        Pattern('0x77', 0x00f8000),   # Partial-left (1M-32K)
69b0a32befSJohn Snow        Pattern('0x72', 0x2008000),   # Partial-right (32M+32K)
70b0a32befSJohn Snow        Pattern('0x69', 0x3fe0000)]), # Adjacent-left (64M - 128K)
71dfdc48d5SJohn Snow    PatternGroup([
72dfdc48d5SJohn Snow        # Batch 2: 7 clusters (3 new)
73b0a32befSJohn Snow        Pattern('0x74', 0x0010000),   # Adjacent-right
74b0a32befSJohn Snow        Pattern('0x69', 0x00e8000),   # Partial-left  (1M-96K)
75b0a32befSJohn Snow        Pattern('0x6e', 0x2018000),   # Partial-right (32M+96K)
76b0a32befSJohn Snow        Pattern('0x67', 0x3fe0000,
77dfdc48d5SJohn Snow                2*GRANULARITY)]),     # Overwrite [(64M-128K)-64M)
78dfdc48d5SJohn Snow    PatternGroup([
79dfdc48d5SJohn Snow        # Batch 3: 8 clusters (5 new)
80dfdc48d5SJohn Snow        # Carefully chosen such that nothing re-dirties the one cluster
81dfdc48d5SJohn Snow        # that copies out successfully before failure in Group #1.
82b0a32befSJohn Snow        Pattern('0xaa', 0x0010000,
83dfdc48d5SJohn Snow                3*GRANULARITY),       # Overwrite and 2x Adjacent-right
84b0a32befSJohn Snow        Pattern('0xbb', 0x00d8000),   # Partial-left (1M-160K)
85b0a32befSJohn Snow        Pattern('0xcc', 0x2028000),   # Partial-right (32M+160K)
86b0a32befSJohn Snow        Pattern('0xdd', 0x3fc0000)]), # New; leaving a gap to the right
87dfdc48d5SJohn Snow]
88dfdc48d5SJohn Snow
8932afa5a1SJohn Snow
9032afa5a1SJohn Snowclass EmulatedBitmap:
9132afa5a1SJohn Snow    def __init__(self, granularity=GRANULARITY):
9232afa5a1SJohn Snow        self._bits = set()
9332afa5a1SJohn Snow        self.granularity = granularity
9432afa5a1SJohn Snow
9532afa5a1SJohn Snow    def dirty_bits(self, bits):
9632afa5a1SJohn Snow        self._bits |= set(bits)
9732afa5a1SJohn Snow
9832afa5a1SJohn Snow    def dirty_group(self, n):
9932afa5a1SJohn Snow        self.dirty_bits(GROUPS[n].bits(self.granularity))
10032afa5a1SJohn Snow
10132afa5a1SJohn Snow    def clear(self):
10232afa5a1SJohn Snow        self._bits = set()
10332afa5a1SJohn Snow
10432afa5a1SJohn Snow    def clear_bits(self, bits):
10532afa5a1SJohn Snow        self._bits -= set(bits)
10632afa5a1SJohn Snow
10732afa5a1SJohn Snow    def clear_bit(self, bit):
10832afa5a1SJohn Snow        self.clear_bits({bit})
10932afa5a1SJohn Snow
11032afa5a1SJohn Snow    def clear_group(self, n):
11132afa5a1SJohn Snow        self.clear_bits(GROUPS[n].bits(self.granularity))
11232afa5a1SJohn Snow
11332afa5a1SJohn Snow    @property
11432afa5a1SJohn Snow    def first_bit(self):
11532afa5a1SJohn Snow        return sorted(self.bits)[0]
11632afa5a1SJohn Snow
11732afa5a1SJohn Snow    @property
11832afa5a1SJohn Snow    def bits(self):
11932afa5a1SJohn Snow        return self._bits
12032afa5a1SJohn Snow
12132afa5a1SJohn Snow    @property
12232afa5a1SJohn Snow    def count(self):
12332afa5a1SJohn Snow        return len(self.bits)
12432afa5a1SJohn Snow
12532afa5a1SJohn Snow    def compare(self, qmp_bitmap):
12632afa5a1SJohn Snow        """
12732afa5a1SJohn Snow        Print a nice human-readable message checking that a bitmap as reported
12832afa5a1SJohn Snow        by the QMP interface has as many bits set as we expect it to.
12932afa5a1SJohn Snow        """
13032afa5a1SJohn Snow
13132afa5a1SJohn Snow        name = qmp_bitmap.get('name', '(anonymous)')
13232afa5a1SJohn Snow        log("= Checking Bitmap {:s} =".format(name))
13332afa5a1SJohn Snow
13432afa5a1SJohn Snow        want = self.count
13532afa5a1SJohn Snow        have = qmp_bitmap['count'] // qmp_bitmap['granularity']
13632afa5a1SJohn Snow
13732afa5a1SJohn Snow        log("expecting {:d} dirty sectors; have {:d}. {:s}".format(
13832afa5a1SJohn Snow            want, have, "OK!" if want == have else "ERROR!"))
13932afa5a1SJohn Snow        log('')
14032afa5a1SJohn Snow
14132afa5a1SJohn Snow
142dfdc48d5SJohn Snowclass Drive:
143dfdc48d5SJohn Snow    """Represents, vaguely, a drive attached to a VM.
144dfdc48d5SJohn Snow    Includes format, graph, and device information."""
145dfdc48d5SJohn Snow
146dfdc48d5SJohn Snow    def __init__(self, path, vm=None):
147dfdc48d5SJohn Snow        self.path = path
148dfdc48d5SJohn Snow        self.vm = vm
149dfdc48d5SJohn Snow        self.fmt = None
150dfdc48d5SJohn Snow        self.size = None
151dfdc48d5SJohn Snow        self.node = None
152dfdc48d5SJohn Snow
153dfdc48d5SJohn Snow    def img_create(self, fmt, size):
154dfdc48d5SJohn Snow        self.fmt = fmt
155dfdc48d5SJohn Snow        self.size = size
156dfdc48d5SJohn Snow        iotests.qemu_img_create('-f', self.fmt, self.path, str(self.size))
157dfdc48d5SJohn Snow
158dfdc48d5SJohn Snow    def create_target(self, name, fmt, size):
159dfdc48d5SJohn Snow        basename = os.path.basename(self.path)
160dfdc48d5SJohn Snow        file_node_name = "file_{}".format(basename)
161dfdc48d5SJohn Snow        vm = self.vm
162dfdc48d5SJohn Snow
163dfdc48d5SJohn Snow        log(vm.command('blockdev-create', job_id='bdc-file-job',
164dfdc48d5SJohn Snow                       options={
165dfdc48d5SJohn Snow                           'driver': 'file',
166dfdc48d5SJohn Snow                           'filename': self.path,
167dfdc48d5SJohn Snow                           'size': 0,
168dfdc48d5SJohn Snow                       }))
169dfdc48d5SJohn Snow        vm.run_job('bdc-file-job')
170dfdc48d5SJohn Snow        log(vm.command('blockdev-add', driver='file',
171dfdc48d5SJohn Snow                       node_name=file_node_name, filename=self.path))
172dfdc48d5SJohn Snow
173dfdc48d5SJohn Snow        log(vm.command('blockdev-create', job_id='bdc-fmt-job',
174dfdc48d5SJohn Snow                       options={
175dfdc48d5SJohn Snow                           'driver': fmt,
176dfdc48d5SJohn Snow                           'file': file_node_name,
177dfdc48d5SJohn Snow                           'size': size,
178dfdc48d5SJohn Snow                       }))
179dfdc48d5SJohn Snow        vm.run_job('bdc-fmt-job')
180dfdc48d5SJohn Snow        log(vm.command('blockdev-add', driver=fmt,
181dfdc48d5SJohn Snow                       node_name=name,
182dfdc48d5SJohn Snow                       file=file_node_name))
183dfdc48d5SJohn Snow        self.fmt = fmt
184dfdc48d5SJohn Snow        self.size = size
185dfdc48d5SJohn Snow        self.node = name
186dfdc48d5SJohn Snow
1870af2a09cSJohn Snowdef blockdev_backup(vm, device, target, sync, **kwargs):
1880af2a09cSJohn Snow    # Strip any arguments explicitly nulled by the caller:
1890af2a09cSJohn Snow    kwargs = {key: val for key, val in kwargs.items() if val is not None}
1900af2a09cSJohn Snow    result = vm.qmp_log('blockdev-backup',
1910af2a09cSJohn Snow                        device=device,
1920af2a09cSJohn Snow                        target=target,
1930af2a09cSJohn Snow                        sync=sync,
19400e30f05SVladimir Sementsov-Ogievskiy                        filter_node_name='backup-top',
1952d0f32e3SVladimir Sementsov-Ogievskiy                        x_perf={'max-workers': 1},
1960af2a09cSJohn Snow                        **kwargs)
1970af2a09cSJohn Snow    return result
1980af2a09cSJohn Snow
1990af2a09cSJohn Snowdef blockdev_backup_mktarget(drive, target_id, filepath, sync, **kwargs):
2000af2a09cSJohn Snow    target_drive = Drive(filepath, vm=drive.vm)
2010af2a09cSJohn Snow    target_drive.create_target(target_id, drive.fmt, drive.size)
202f1648454SVladimir Sementsov-Ogievskiy    blockdev_backup(drive.vm, drive.node, target_id, sync, **kwargs)
2030af2a09cSJohn Snow
204dfdc48d5SJohn Snowdef reference_backup(drive, n, filepath):
205dfdc48d5SJohn Snow    log("--- Reference Backup #{:d} ---\n".format(n))
206dfdc48d5SJohn Snow    target_id = "ref_target_{:d}".format(n)
207dfdc48d5SJohn Snow    job_id = "ref_backup_{:d}".format(n)
2080af2a09cSJohn Snow    blockdev_backup_mktarget(drive, target_id, filepath, "full",
2090af2a09cSJohn Snow                             job_id=job_id)
210dfdc48d5SJohn Snow    drive.vm.run_job(job_id, auto_dismiss=True)
211dfdc48d5SJohn Snow    log('')
212dfdc48d5SJohn Snow
2130af2a09cSJohn Snowdef backup(drive, n, filepath, sync, **kwargs):
2140af2a09cSJohn Snow    log("--- Test Backup #{:d} ---\n".format(n))
2150af2a09cSJohn Snow    target_id = "backup_target_{:d}".format(n)
2160af2a09cSJohn Snow    job_id = "backup_{:d}".format(n)
2170af2a09cSJohn Snow    kwargs.setdefault('auto-finalize', False)
2180af2a09cSJohn Snow    blockdev_backup_mktarget(drive, target_id, filepath, sync,
2190af2a09cSJohn Snow                             job_id=job_id, **kwargs)
220dfdc48d5SJohn Snow    return job_id
221dfdc48d5SJohn Snow
22200e30f05SVladimir Sementsov-Ogievskiydef perform_writes(drive, n, filter_node_name=None):
223dfdc48d5SJohn Snow    log("--- Write #{:d} ---\n".format(n))
224dfdc48d5SJohn Snow    for pattern in GROUPS[n].patterns:
225dfdc48d5SJohn Snow        cmd = "write -P{:s} 0x{:07x} 0x{:x}".format(
226dfdc48d5SJohn Snow            pattern.byte,
227dfdc48d5SJohn Snow            pattern.offset,
228dfdc48d5SJohn Snow            pattern.size)
229dfdc48d5SJohn Snow        log(cmd)
23000e30f05SVladimir Sementsov-Ogievskiy        log(drive.vm.hmp_qemu_io(filter_node_name or drive.node, cmd))
2315c4343b8SVladimir Sementsov-Ogievskiy    bitmaps = drive.vm.query_bitmaps()
2325c4343b8SVladimir Sementsov-Ogievskiy    log({'bitmaps': bitmaps}, indent=2)
233dfdc48d5SJohn Snow    log('')
234dfdc48d5SJohn Snow    return bitmaps
235dfdc48d5SJohn Snow
236dfdc48d5SJohn Snow
237dfdc48d5SJohn Snowdef compare_images(image, reference, baseimg=None, expected_match=True):
238dfdc48d5SJohn Snow    """
239dfdc48d5SJohn Snow    Print a nice human-readable message comparing these images.
240dfdc48d5SJohn Snow    """
241dfdc48d5SJohn Snow    expected_ret = 0 if expected_match else 1
242dfdc48d5SJohn Snow    if baseimg:
243*fc272d3cSJohn Snow        qemu_img("rebase", "-u", "-b", baseimg, '-F', iotests.imgfmt, image)
244dfdc48d5SJohn Snow    ret = qemu_img("compare", image, reference)
245dfdc48d5SJohn Snow    log('qemu_img compare "{:s}" "{:s}" ==> {:s}, {:s}'.format(
246dfdc48d5SJohn Snow        image, reference,
247dfdc48d5SJohn Snow        "Identical" if ret == 0 else "Mismatch",
248dfdc48d5SJohn Snow        "OK!" if ret == expected_ret else "ERROR!"),
249dfdc48d5SJohn Snow        filters=[iotests.filter_testfiles])
250dfdc48d5SJohn Snow
2510af2a09cSJohn Snowdef test_bitmap_sync(bsync_mode, msync_mode='bitmap', failure=None):
252dfdc48d5SJohn Snow    """
253dfdc48d5SJohn Snow    Test bitmap backup routines.
254dfdc48d5SJohn Snow
255dfdc48d5SJohn Snow    :param bsync_mode: Is the Bitmap Sync mode, and can be any of:
256dfdc48d5SJohn Snow        - on-success: This is the "incremental" style mode. Bitmaps are
257dfdc48d5SJohn Snow                      synchronized to what was copied out only on success.
258dfdc48d5SJohn Snow                      (Partial images must be discarded.)
259dfdc48d5SJohn Snow        - never:      This is the "differential" style mode.
260dfdc48d5SJohn Snow                      Bitmaps are never synchronized.
261dfdc48d5SJohn Snow        - always:     This is a "best effort" style mode.
262dfdc48d5SJohn Snow                      Bitmaps are always synchronized, regardless of failure.
263dfdc48d5SJohn Snow                      (Partial images must be kept.)
264dfdc48d5SJohn Snow
265bd5ceebfSJohn Snow    :param msync_mode: The mirror sync mode to use for the first backup.
266bd5ceebfSJohn Snow                       Can be any one of:
267bd5ceebfSJohn Snow        - bitmap: Backups based on bitmap manifest.
268bd5ceebfSJohn Snow        - full:   Full backups.
269bd5ceebfSJohn Snow        - top:    Full backups of the top layer only.
270bd5ceebfSJohn Snow
271dfdc48d5SJohn Snow    :param failure: Is the (optional) failure mode, and can be any of:
272dfdc48d5SJohn Snow        - None:         No failure. Test the normative path. Default.
273dfdc48d5SJohn Snow        - simulated:    Cancel the job right before it completes.
274dfdc48d5SJohn Snow                        This also tests writes "during" the job.
275dfdc48d5SJohn Snow        - intermediate: This tests a job that fails mid-process and produces
276dfdc48d5SJohn Snow                        an incomplete backup. Testing limitations prevent
277dfdc48d5SJohn Snow                        testing competing writes.
278dfdc48d5SJohn Snow    """
2793192fad7SNir Soffer    with iotests.FilePath(
280a242b19eSNir Soffer            'img', 'bsync1', 'bsync2', 'fbackup0', 'fbackup1', 'fbackup2') as \
281a242b19eSNir Soffer            (img_path, bsync1, bsync2, fbackup0, fbackup1, fbackup2), \
282dfdc48d5SJohn Snow         iotests.VM() as vm:
283dfdc48d5SJohn Snow
2840af2a09cSJohn Snow        mode = "Mode {:s}; Bitmap Sync {:s}".format(msync_mode, bsync_mode)
285dfdc48d5SJohn Snow        preposition = "with" if failure else "without"
286dfdc48d5SJohn Snow        cond = "{:s} {:s}".format(preposition,
287dfdc48d5SJohn Snow                                  "{:s} failure".format(failure) if failure
288dfdc48d5SJohn Snow                                  else "failure")
289dfdc48d5SJohn Snow        log("\n=== {:s} {:s} ===\n".format(mode, cond))
290dfdc48d5SJohn Snow
291dfdc48d5SJohn Snow        log('--- Preparing image & VM ---\n')
292dfdc48d5SJohn Snow        drive0 = Drive(img_path, vm=vm)
293dfdc48d5SJohn Snow        drive0.img_create(iotests.imgfmt, SIZE)
29422329f0dSLaurent Vivier        vm.add_device("{},id=scsi0".format('virtio-scsi'))
295dfdc48d5SJohn Snow        vm.launch()
296dfdc48d5SJohn Snow
297dfdc48d5SJohn Snow        file_config = {
298dfdc48d5SJohn Snow            'driver': 'file',
299dfdc48d5SJohn Snow            'filename': drive0.path
300dfdc48d5SJohn Snow        }
301dfdc48d5SJohn Snow
302dfdc48d5SJohn Snow        if failure == 'intermediate':
303dfdc48d5SJohn Snow            file_config = {
304dfdc48d5SJohn Snow                'driver': 'blkdebug',
305dfdc48d5SJohn Snow                'image': file_config,
306dfdc48d5SJohn Snow                'set-state': [{
307dfdc48d5SJohn Snow                    'event': 'flush_to_disk',
308dfdc48d5SJohn Snow                    'state': 1,
309dfdc48d5SJohn Snow                    'new_state': 2
310dfdc48d5SJohn Snow                }, {
311dfdc48d5SJohn Snow                    'event': 'read_aio',
312dfdc48d5SJohn Snow                    'state': 2,
313dfdc48d5SJohn Snow                    'new_state': 3
314dfdc48d5SJohn Snow                }],
315dfdc48d5SJohn Snow                'inject-error': [{
316dfdc48d5SJohn Snow                    'event': 'read_aio',
317dfdc48d5SJohn Snow                    'errno': 5,
318dfdc48d5SJohn Snow                    'state': 3,
319dfdc48d5SJohn Snow                    'immediately': False,
320dfdc48d5SJohn Snow                    'once': True
321dfdc48d5SJohn Snow                }]
322dfdc48d5SJohn Snow            }
323dfdc48d5SJohn Snow
324f1648454SVladimir Sementsov-Ogievskiy        drive0.node = 'drive0'
325dfdc48d5SJohn Snow        vm.qmp_log('blockdev-add',
326dfdc48d5SJohn Snow                   filters=[iotests.filter_qmp_testfiles],
327f1648454SVladimir Sementsov-Ogievskiy                   node_name=drive0.node,
328dfdc48d5SJohn Snow                   driver=drive0.fmt,
329dfdc48d5SJohn Snow                   file=file_config)
330dfdc48d5SJohn Snow        log('')
331dfdc48d5SJohn Snow
332dfdc48d5SJohn Snow        # 0 - Writes and Reference Backup
333dfdc48d5SJohn Snow        perform_writes(drive0, 0)
334dfdc48d5SJohn Snow        reference_backup(drive0, 0, fbackup0)
335dfdc48d5SJohn Snow        log('--- Add Bitmap ---\n')
336f1648454SVladimir Sementsov-Ogievskiy        vm.qmp_log("block-dirty-bitmap-add", node=drive0.node,
337dfdc48d5SJohn Snow                   name="bitmap0", granularity=GRANULARITY)
338dfdc48d5SJohn Snow        log('')
33932afa5a1SJohn Snow        ebitmap = EmulatedBitmap()
340dfdc48d5SJohn Snow
341dfdc48d5SJohn Snow        # 1 - Writes and Reference Backup
342dfdc48d5SJohn Snow        bitmaps = perform_writes(drive0, 1)
34332afa5a1SJohn Snow        ebitmap.dirty_group(1)
3445c4343b8SVladimir Sementsov-Ogievskiy        bitmap = vm.get_bitmap(drive0.node, 'bitmap0', bitmaps=bitmaps)
34532afa5a1SJohn Snow        ebitmap.compare(bitmap)
346dfdc48d5SJohn Snow        reference_backup(drive0, 1, fbackup1)
347dfdc48d5SJohn Snow
3480af2a09cSJohn Snow        # 1 - Test Backup (w/ Optional induced failure)
349dfdc48d5SJohn Snow        if failure == 'intermediate':
350dfdc48d5SJohn Snow            # Activate blkdebug induced failure for second-to-next read
351f1648454SVladimir Sementsov-Ogievskiy            log(vm.hmp_qemu_io(drive0.node, 'flush'))
352dfdc48d5SJohn Snow            log('')
3530af2a09cSJohn Snow        job = backup(drive0, 1, bsync1, msync_mode,
3540af2a09cSJohn Snow                     bitmap="bitmap0", bitmap_mode=bsync_mode)
355dfdc48d5SJohn Snow
356dfdc48d5SJohn Snow        def _callback():
357dfdc48d5SJohn Snow            """Issue writes while the job is open to test bitmap divergence."""
358dfdc48d5SJohn Snow            # Note: when `failure` is 'intermediate', this isn't called.
359dfdc48d5SJohn Snow            log('')
36000e30f05SVladimir Sementsov-Ogievskiy            bitmaps = perform_writes(drive0, 2, filter_node_name='backup-top')
361dfdc48d5SJohn Snow            # Named bitmap (static, should be unchanged)
3625c4343b8SVladimir Sementsov-Ogievskiy            ebitmap.compare(vm.get_bitmap(drive0.node, 'bitmap0',
3635c4343b8SVladimir Sementsov-Ogievskiy                                          bitmaps=bitmaps))
364dfdc48d5SJohn Snow            # Anonymous bitmap (dynamic, shows new writes)
36532afa5a1SJohn Snow            anonymous = EmulatedBitmap()
36632afa5a1SJohn Snow            anonymous.dirty_group(2)
3675c4343b8SVladimir Sementsov-Ogievskiy            anonymous.compare(vm.get_bitmap(drive0.node, '', recording=True,
3685c4343b8SVladimir Sementsov-Ogievskiy                                            bitmaps=bitmaps))
36932afa5a1SJohn Snow
37032afa5a1SJohn Snow            # Simulate the order in which this will happen:
37132afa5a1SJohn Snow            # group 1 gets cleared first, then group two gets written.
37232afa5a1SJohn Snow            if ((bsync_mode == 'on-success' and not failure) or
37332afa5a1SJohn Snow                (bsync_mode == 'always')):
374bd5ceebfSJohn Snow                ebitmap.clear()
37532afa5a1SJohn Snow            ebitmap.dirty_group(2)
376dfdc48d5SJohn Snow
377dfdc48d5SJohn Snow        vm.run_job(job, auto_dismiss=True, auto_finalize=False,
378dfdc48d5SJohn Snow                   pre_finalize=_callback,
379dfdc48d5SJohn Snow                   cancel=(failure == 'simulated'))
3805c4343b8SVladimir Sementsov-Ogievskiy        bitmaps = vm.query_bitmaps()
3815c4343b8SVladimir Sementsov-Ogievskiy        log({'bitmaps': bitmaps}, indent=2)
382dfdc48d5SJohn Snow        log('')
383dfdc48d5SJohn Snow
384dfdc48d5SJohn Snow        if bsync_mode == 'always' and failure == 'intermediate':
385bd5ceebfSJohn Snow            # TOP treats anything allocated as dirty, expect to see:
386bd5ceebfSJohn Snow            if msync_mode == 'top':
387bd5ceebfSJohn Snow                ebitmap.dirty_group(0)
388bd5ceebfSJohn Snow
389dfdc48d5SJohn Snow            # We manage to copy one sector (one bit) before the error.
39032afa5a1SJohn Snow            ebitmap.clear_bit(ebitmap.first_bit)
391bd5ceebfSJohn Snow
392bd5ceebfSJohn Snow            # Full returns all bits set except what was copied/skipped
393bd5ceebfSJohn Snow            if msync_mode == 'full':
394bd5ceebfSJohn Snow                fail_bit = ebitmap.first_bit
395bd5ceebfSJohn Snow                ebitmap.clear()
396bd5ceebfSJohn Snow                ebitmap.dirty_bits(range(fail_bit, SIZE // GRANULARITY))
397bd5ceebfSJohn Snow
3985c4343b8SVladimir Sementsov-Ogievskiy        ebitmap.compare(vm.get_bitmap(drive0.node, 'bitmap0', bitmaps=bitmaps))
399dfdc48d5SJohn Snow
400dfdc48d5SJohn Snow        # 2 - Writes and Reference Backup
401dfdc48d5SJohn Snow        bitmaps = perform_writes(drive0, 3)
40232afa5a1SJohn Snow        ebitmap.dirty_group(3)
4035c4343b8SVladimir Sementsov-Ogievskiy        ebitmap.compare(vm.get_bitmap(drive0.node, 'bitmap0', bitmaps=bitmaps))
404dfdc48d5SJohn Snow        reference_backup(drive0, 2, fbackup2)
405dfdc48d5SJohn Snow
406dfdc48d5SJohn Snow        # 2 - Bitmap Backup (In failure modes, this is a recovery.)
4070af2a09cSJohn Snow        job = backup(drive0, 2, bsync2, "bitmap",
4080af2a09cSJohn Snow                     bitmap="bitmap0", bitmap_mode=bsync_mode)
409dfdc48d5SJohn Snow        vm.run_job(job, auto_dismiss=True, auto_finalize=False)
4105c4343b8SVladimir Sementsov-Ogievskiy        bitmaps = vm.query_bitmaps()
4115c4343b8SVladimir Sementsov-Ogievskiy        log({'bitmaps': bitmaps}, indent=2)
412dfdc48d5SJohn Snow        log('')
41332afa5a1SJohn Snow        if bsync_mode != 'never':
41432afa5a1SJohn Snow            ebitmap.clear()
4155c4343b8SVladimir Sementsov-Ogievskiy        ebitmap.compare(vm.get_bitmap(drive0.node, 'bitmap0', bitmaps=bitmaps))
416dfdc48d5SJohn Snow
417dfdc48d5SJohn Snow        log('--- Cleanup ---\n')
418dfdc48d5SJohn Snow        vm.qmp_log("block-dirty-bitmap-remove",
419f1648454SVladimir Sementsov-Ogievskiy                   node=drive0.node, name="bitmap0")
4205c4343b8SVladimir Sementsov-Ogievskiy        bitmaps = vm.query_bitmaps()
4215c4343b8SVladimir Sementsov-Ogievskiy        log({'bitmaps': bitmaps}, indent=2)
422dfdc48d5SJohn Snow        vm.shutdown()
423dfdc48d5SJohn Snow        log('')
424dfdc48d5SJohn Snow
425dfdc48d5SJohn Snow        log('--- Verification ---\n')
426dfdc48d5SJohn Snow        # 'simulated' failures will actually all pass here because we canceled
427dfdc48d5SJohn Snow        # while "pending". This is actually undefined behavior,
428dfdc48d5SJohn Snow        # don't rely on this to be true!
429dfdc48d5SJohn Snow        compare_images(bsync1, fbackup1, baseimg=fbackup0,
430dfdc48d5SJohn Snow                       expected_match=failure != 'intermediate')
431dfdc48d5SJohn Snow        if not failure or bsync_mode == 'always':
432dfdc48d5SJohn Snow            # Always keep the last backup on success or when using 'always'
433dfdc48d5SJohn Snow            base = bsync1
434dfdc48d5SJohn Snow        else:
435dfdc48d5SJohn Snow            base = fbackup0
436dfdc48d5SJohn Snow        compare_images(bsync2, fbackup2, baseimg=base)
437dfdc48d5SJohn Snow        compare_images(img_path, fbackup2)
438dfdc48d5SJohn Snow        log('')
439dfdc48d5SJohn Snow
440352092d3SJohn Snowdef test_backup_api():
441352092d3SJohn Snow    """
442352092d3SJohn Snow    Test malformed and prohibited invocations of the backup API.
443352092d3SJohn Snow    """
4443192fad7SNir Soffer    with iotests.FilePath('img', 'bsync1') as (img_path, backup_path), \
445352092d3SJohn Snow         iotests.VM() as vm:
446352092d3SJohn Snow
447352092d3SJohn Snow        log("\n=== API failure tests ===\n")
448352092d3SJohn Snow        log('--- Preparing image & VM ---\n')
449352092d3SJohn Snow        drive0 = Drive(img_path, vm=vm)
450352092d3SJohn Snow        drive0.img_create(iotests.imgfmt, SIZE)
45122329f0dSLaurent Vivier        vm.add_device("{},id=scsi0".format('virtio-scsi'))
452352092d3SJohn Snow        vm.launch()
453352092d3SJohn Snow
454352092d3SJohn Snow        file_config = {
455352092d3SJohn Snow            'driver': 'file',
456352092d3SJohn Snow            'filename': drive0.path
457352092d3SJohn Snow        }
458352092d3SJohn Snow
459f1648454SVladimir Sementsov-Ogievskiy        drive0.node = 'drive0'
460352092d3SJohn Snow        vm.qmp_log('blockdev-add',
461352092d3SJohn Snow                   filters=[iotests.filter_qmp_testfiles],
462f1648454SVladimir Sementsov-Ogievskiy                   node_name=drive0.node,
463352092d3SJohn Snow                   driver=drive0.fmt,
464352092d3SJohn Snow                   file=file_config)
465352092d3SJohn Snow        log('')
466352092d3SJohn Snow
467352092d3SJohn Snow        target0 = Drive(backup_path, vm=vm)
468352092d3SJohn Snow        target0.create_target("backup_target", drive0.fmt, drive0.size)
469352092d3SJohn Snow        log('')
470352092d3SJohn Snow
471f1648454SVladimir Sementsov-Ogievskiy        vm.qmp_log("block-dirty-bitmap-add", node=drive0.node,
472352092d3SJohn Snow                   name="bitmap0", granularity=GRANULARITY)
473352092d3SJohn Snow        log('')
474352092d3SJohn Snow
475352092d3SJohn Snow        log('-- Testing invalid QMP commands --\n')
476352092d3SJohn Snow
477352092d3SJohn Snow        error_cases = {
478352092d3SJohn Snow            'incremental': {
479352092d3SJohn Snow                None:        ['on-success', 'always', 'never', None],
480352092d3SJohn Snow                'bitmap404': ['on-success', 'always', 'never', None],
481352092d3SJohn Snow                'bitmap0':   ['always', 'never']
482352092d3SJohn Snow            },
483352092d3SJohn Snow            'bitmap': {
484352092d3SJohn Snow                None:        ['on-success', 'always', 'never', None],
485352092d3SJohn Snow                'bitmap404': ['on-success', 'always', 'never', None],
486352092d3SJohn Snow                'bitmap0':   [None],
487352092d3SJohn Snow            },
488bd5ceebfSJohn Snow            'full': {
489bd5ceebfSJohn Snow                None:        ['on-success', 'always', 'never'],
490bd5ceebfSJohn Snow                'bitmap404': ['on-success', 'always', 'never', None],
491bd5ceebfSJohn Snow                'bitmap0':   ['never', None],
492bd5ceebfSJohn Snow            },
493bd5ceebfSJohn Snow            'top': {
494bd5ceebfSJohn Snow                None:        ['on-success', 'always', 'never'],
495bd5ceebfSJohn Snow                'bitmap404': ['on-success', 'always', 'never', None],
496bd5ceebfSJohn Snow                'bitmap0':   ['never', None],
497bd5ceebfSJohn Snow            },
498bd5ceebfSJohn Snow            'none': {
499bd5ceebfSJohn Snow                None:        ['on-success', 'always', 'never'],
500bd5ceebfSJohn Snow                'bitmap404': ['on-success', 'always', 'never', None],
501bd5ceebfSJohn Snow                'bitmap0':   ['on-success', 'always', 'never', None],
502bd5ceebfSJohn Snow            }
503352092d3SJohn Snow        }
504352092d3SJohn Snow
505352092d3SJohn Snow        # Dicts, as always, are not stably-ordered prior to 3.7, so use tuples:
506bd5ceebfSJohn Snow        for sync_mode in ('incremental', 'bitmap', 'full', 'top', 'none'):
507352092d3SJohn Snow            log("-- Sync mode {:s} tests --\n".format(sync_mode))
508352092d3SJohn Snow            for bitmap in (None, 'bitmap404', 'bitmap0'):
509352092d3SJohn Snow                for policy in error_cases[sync_mode][bitmap]:
510f1648454SVladimir Sementsov-Ogievskiy                    blockdev_backup(drive0.vm, drive0.node, "backup_target",
511352092d3SJohn Snow                                    sync_mode, job_id='api_job',
512352092d3SJohn Snow                                    bitmap=bitmap, bitmap_mode=policy)
513352092d3SJohn Snow                    log('')
514352092d3SJohn Snow
515352092d3SJohn Snow
516dfdc48d5SJohn Snowdef main():
517dfdc48d5SJohn Snow    for bsync_mode in ("never", "on-success", "always"):
518dfdc48d5SJohn Snow        for failure in ("simulated", "intermediate", None):
5190af2a09cSJohn Snow            test_bitmap_sync(bsync_mode, "bitmap", failure)
520dfdc48d5SJohn Snow
521bd5ceebfSJohn Snow    for sync_mode in ('full', 'top'):
522bd5ceebfSJohn Snow        for bsync_mode in ('on-success', 'always'):
523bd5ceebfSJohn Snow            for failure in ('simulated', 'intermediate', None):
524bd5ceebfSJohn Snow                test_bitmap_sync(bsync_mode, sync_mode, failure)
525bd5ceebfSJohn Snow
526352092d3SJohn Snow    test_backup_api()
527352092d3SJohn Snow
528dfdc48d5SJohn Snowif __name__ == '__main__':
529103cbc77SMax Reitz    iotests.script_main(main, supported_fmts=['qcow2'],
530103cbc77SMax Reitz                        supported_protocols=['file'])
531