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