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']) 108*5c4343b8SVladimir Sementsov-Ogievskiy self.vm.add_drive(drive0['file'], opts='node-name=node0') 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 351*5c4343b8SVladimir Sementsov-Ogievskiy self.assertTrue(self.vm.check_bitmap_status( 352*5c4343b8SVladimir Sementsov-Ogievskiy 'node0', bitmap0.name, { 353*5c4343b8SVladimir Sementsov-Ogievskiy 'name': 'bitmap0', 354*5c4343b8SVladimir Sementsov-Ogievskiy 'count': 458752, 355*5c4343b8SVladimir Sementsov-Ogievskiy 'granularity': 65536, 356*5c4343b8SVladimir Sementsov-Ogievskiy 'status': 'active', 357*5c4343b8SVladimir Sementsov-Ogievskiy 'persistent': False 358*5c4343b8SVladimir Sementsov-Ogievskiy })) 359cc199b16SJohn Snow 360cc199b16SJohn Snow # Prepare a cluster_size=128k backup target without a backing file. 361cc199b16SJohn Snow (target, _) = bitmap0.new_target() 362cc199b16SJohn Snow self.img_create(target, bitmap0.drive['fmt'], cluster_size='128k') 363cc199b16SJohn Snow 364cc199b16SJohn Snow # Perform Incremental Backup 365cc199b16SJohn Snow self.assertTrue(self.do_qmp_backup(device=bitmap0.drive['id'], 366cc199b16SJohn Snow sync='incremental', 367cc199b16SJohn Snow bitmap=bitmap0.name, 368cc199b16SJohn Snow format=bitmap0.drive['fmt'], 369cc199b16SJohn Snow target=target, 370cc199b16SJohn Snow mode='existing')) 371cc199b16SJohn Snow self.make_reference_backup(bitmap0) 372cc199b16SJohn Snow 373cc199b16SJohn Snow # Add the backing file, then compare and exit. 374cc199b16SJohn Snow iotests.qemu_img('rebase', '-f', drive0['fmt'], '-u', '-b', 375cc199b16SJohn Snow drive0['backup'], '-F', drive0['fmt'], target) 376cc199b16SJohn Snow self.vm.shutdown() 377cc199b16SJohn Snow self.check_backups() 378cc199b16SJohn Snow 379cc199b16SJohn Snow 380749ad5e8SJohn Snow def test_incremental_transaction(self): 381749ad5e8SJohn Snow '''Test: Verify backups made from transactionally created bitmaps. 382749ad5e8SJohn Snow 383749ad5e8SJohn Snow Create a bitmap "before" VM execution begins, then create a second 384749ad5e8SJohn Snow bitmap AFTER writes have already occurred. Use transactions to create 385749ad5e8SJohn Snow a full backup and synchronize both bitmaps to this backup. 386749ad5e8SJohn Snow Create an incremental backup through both bitmaps and verify that 387749ad5e8SJohn Snow both backups match the current drive0 image. 388749ad5e8SJohn Snow ''' 389749ad5e8SJohn Snow 390749ad5e8SJohn Snow drive0 = self.drives[0] 391749ad5e8SJohn Snow bitmap0 = self.add_bitmap('bitmap0', drive0) 392749ad5e8SJohn Snow self.hmp_io_writes(drive0['id'], (('0xab', 0, 512), 393749ad5e8SJohn Snow ('0xfe', '16M', '256k'), 394749ad5e8SJohn Snow ('0x64', '32736k', '64k'))) 395749ad5e8SJohn Snow bitmap1 = self.add_bitmap('bitmap1', drive0) 396749ad5e8SJohn Snow 397749ad5e8SJohn Snow result = self.vm.qmp('transaction', actions=[ 398749ad5e8SJohn Snow transaction_bitmap_clear(bitmap0.drive['id'], bitmap0.name), 399749ad5e8SJohn Snow transaction_bitmap_clear(bitmap1.drive['id'], bitmap1.name), 400749ad5e8SJohn Snow transaction_drive_backup(drive0['id'], drive0['backup'], 401749ad5e8SJohn Snow sync='full', format=drive0['fmt']) 402749ad5e8SJohn Snow ]) 403749ad5e8SJohn Snow self.assert_qmp(result, 'return', {}) 404749ad5e8SJohn Snow self.wait_until_completed(drive0['id']) 405749ad5e8SJohn Snow self.files.append(drive0['backup']) 406749ad5e8SJohn Snow 407749ad5e8SJohn Snow self.hmp_io_writes(drive0['id'], (('0x9a', 0, 512), 408749ad5e8SJohn Snow ('0x55', '8M', '352k'), 409749ad5e8SJohn Snow ('0x78', '15872k', '1M'))) 410749ad5e8SJohn Snow # Both bitmaps should be correctly in sync. 411749ad5e8SJohn Snow self.create_incremental(bitmap0) 412749ad5e8SJohn Snow self.create_incremental(bitmap1) 413749ad5e8SJohn Snow self.vm.shutdown() 414749ad5e8SJohn Snow self.check_backups() 415749ad5e8SJohn Snow 416749ad5e8SJohn Snow 4170aef09b9SJohn Snow def do_transaction_failure_test(self, race=False): 418fc6c796fSJohn Snow # Create a second drive, with pattern: 419fc6c796fSJohn Snow drive1 = self.add_node('drive1') 420fc6c796fSJohn Snow self.img_create(drive1['file'], drive1['fmt']) 421fc6c796fSJohn Snow io_write_patterns(drive1['file'], (('0x14', 0, 512), 422fc6c796fSJohn Snow ('0x5d', '1M', '32k'), 423fc6c796fSJohn Snow ('0xcd', '32M', '124k'))) 424fc6c796fSJohn Snow 425fc6c796fSJohn Snow # Create a blkdebug interface to this img as 'drive1' 4260153d2f5SKevin Wolf result = self.vm.qmp('blockdev-add', 4270153d2f5SKevin Wolf node_name=drive1['id'], 4280153d2f5SKevin Wolf driver=drive1['fmt'], 4290153d2f5SKevin Wolf file={ 430fc6c796fSJohn Snow 'driver': 'blkdebug', 431fc6c796fSJohn Snow 'image': { 432fc6c796fSJohn Snow 'driver': 'file', 433fc6c796fSJohn Snow 'filename': drive1['file'] 434fc6c796fSJohn Snow }, 435fc6c796fSJohn Snow 'set-state': [{ 436fc6c796fSJohn Snow 'event': 'flush_to_disk', 437fc6c796fSJohn Snow 'state': 1, 438fc6c796fSJohn Snow 'new_state': 2 439fc6c796fSJohn Snow }], 440fc6c796fSJohn Snow 'inject-error': [{ 441fc6c796fSJohn Snow 'event': 'read_aio', 442fc6c796fSJohn Snow 'errno': 5, 443fc6c796fSJohn Snow 'state': 2, 444fc6c796fSJohn Snow 'immediately': False, 445fc6c796fSJohn Snow 'once': True 446fc6c796fSJohn Snow }], 447fc6c796fSJohn Snow } 4480153d2f5SKevin Wolf ) 449fc6c796fSJohn Snow self.assert_qmp(result, 'return', {}) 450fc6c796fSJohn Snow 451fc6c796fSJohn Snow # Create bitmaps and full backups for both drives 452fc6c796fSJohn Snow drive0 = self.drives[0] 453fc6c796fSJohn Snow dr0bm0 = self.add_bitmap('bitmap0', drive0) 454fc6c796fSJohn Snow dr1bm0 = self.add_bitmap('bitmap0', drive1) 455fc6c796fSJohn Snow self.create_anchor_backup(drive0) 456fc6c796fSJohn Snow self.create_anchor_backup(drive1) 457fc6c796fSJohn Snow self.assert_no_active_block_jobs() 458fc6c796fSJohn Snow self.assertFalse(self.vm.get_qmp_events(wait=False)) 459fc6c796fSJohn Snow 460fc6c796fSJohn Snow # Emulate some writes 4610aef09b9SJohn Snow if not race: 462fc6c796fSJohn Snow self.hmp_io_writes(drive0['id'], (('0xab', 0, 512), 463fc6c796fSJohn Snow ('0xfe', '16M', '256k'), 464fc6c796fSJohn Snow ('0x64', '32736k', '64k'))) 465fc6c796fSJohn Snow self.hmp_io_writes(drive1['id'], (('0xba', 0, 512), 466fc6c796fSJohn Snow ('0xef', '16M', '256k'), 467fc6c796fSJohn Snow ('0x46', '32736k', '64k'))) 468fc6c796fSJohn Snow 469fc6c796fSJohn Snow # Create incremental backup targets 470fc6c796fSJohn Snow target0 = self.prepare_backup(dr0bm0) 471fc6c796fSJohn Snow target1 = self.prepare_backup(dr1bm0) 472fc6c796fSJohn Snow 473fc6c796fSJohn Snow # Ask for a new incremental backup per-each drive, 4740aef09b9SJohn Snow # expecting drive1's backup to fail. In the 'race' test, 4750aef09b9SJohn Snow # we expect drive1 to attempt to cancel the empty drive0 job. 476fc6c796fSJohn Snow transaction = [ 477fc6c796fSJohn Snow transaction_drive_backup(drive0['id'], target0, sync='incremental', 478fc6c796fSJohn Snow format=drive0['fmt'], mode='existing', 479fc6c796fSJohn Snow bitmap=dr0bm0.name), 480fc6c796fSJohn Snow transaction_drive_backup(drive1['id'], target1, sync='incremental', 481fc6c796fSJohn Snow format=drive1['fmt'], mode='existing', 482fc6c796fSJohn Snow bitmap=dr1bm0.name) 483fc6c796fSJohn Snow ] 484fc6c796fSJohn Snow result = self.vm.qmp('transaction', actions=transaction, 485fc6c796fSJohn Snow properties={'completion-mode': 'grouped'} ) 486fc6c796fSJohn Snow self.assert_qmp(result, 'return', {}) 487fc6c796fSJohn Snow 488fc6c796fSJohn Snow # Observe that drive0's backup is cancelled and drive1 completes with 489fc6c796fSJohn Snow # an error. 490fc6c796fSJohn Snow self.wait_qmp_backup_cancelled(drive0['id']) 491fc6c796fSJohn Snow self.assertFalse(self.wait_qmp_backup(drive1['id'])) 492fc6c796fSJohn Snow error = self.vm.event_wait('BLOCK_JOB_ERROR') 493fc6c796fSJohn Snow self.assert_qmp(error, 'data', {'device': drive1['id'], 494fc6c796fSJohn Snow 'action': 'report', 495fc6c796fSJohn Snow 'operation': 'read'}) 496fc6c796fSJohn Snow self.assertFalse(self.vm.get_qmp_events(wait=False)) 497fc6c796fSJohn Snow self.assert_no_active_block_jobs() 498fc6c796fSJohn Snow 499fc6c796fSJohn Snow # Delete drive0's successful target and eliminate our record of the 5000aef09b9SJohn Snow # unsuccessful drive1 target. 501fc6c796fSJohn Snow dr0bm0.del_target() 502fc6c796fSJohn Snow dr1bm0.del_target() 5030aef09b9SJohn Snow if race: 5040aef09b9SJohn Snow # Don't re-run the transaction, we only wanted to test the race. 5050aef09b9SJohn Snow self.vm.shutdown() 5060aef09b9SJohn Snow return 5070aef09b9SJohn Snow 5080aef09b9SJohn Snow # Re-run the same transaction: 509fc6c796fSJohn Snow target0 = self.prepare_backup(dr0bm0) 510fc6c796fSJohn Snow target1 = self.prepare_backup(dr1bm0) 511fc6c796fSJohn Snow 512fc6c796fSJohn Snow # Re-run the exact same transaction. 513fc6c796fSJohn Snow result = self.vm.qmp('transaction', actions=transaction, 514fc6c796fSJohn Snow properties={'completion-mode':'grouped'}) 515fc6c796fSJohn Snow self.assert_qmp(result, 'return', {}) 516fc6c796fSJohn Snow 517fc6c796fSJohn Snow # Both should complete successfully this time. 518fc6c796fSJohn Snow self.assertTrue(self.wait_qmp_backup(drive0['id'])) 519fc6c796fSJohn Snow self.assertTrue(self.wait_qmp_backup(drive1['id'])) 520fc6c796fSJohn Snow self.make_reference_backup(dr0bm0) 521fc6c796fSJohn Snow self.make_reference_backup(dr1bm0) 522fc6c796fSJohn Snow self.assertFalse(self.vm.get_qmp_events(wait=False)) 523fc6c796fSJohn Snow self.assert_no_active_block_jobs() 524fc6c796fSJohn Snow 525fc6c796fSJohn Snow # And the images should of course validate. 526fc6c796fSJohn Snow self.vm.shutdown() 527fc6c796fSJohn Snow self.check_backups() 528fc6c796fSJohn Snow 5290aef09b9SJohn Snow def test_transaction_failure(self): 5300aef09b9SJohn Snow '''Test: Verify backups made from a transaction that partially fails. 5310aef09b9SJohn Snow 5320aef09b9SJohn Snow Add a second drive with its own unique pattern, and add a bitmap to each 5330aef09b9SJohn Snow drive. Use blkdebug to interfere with the backup on just one drive and 5340aef09b9SJohn Snow attempt to create a coherent incremental backup across both drives. 5350aef09b9SJohn Snow 5360aef09b9SJohn Snow verify a failure in one but not both, then delete the failed stubs and 5370aef09b9SJohn Snow re-run the same transaction. 5380aef09b9SJohn Snow 5390aef09b9SJohn Snow verify that both incrementals are created successfully. 5400aef09b9SJohn Snow ''' 5410aef09b9SJohn Snow self.do_transaction_failure_test() 5420aef09b9SJohn Snow 5430aef09b9SJohn Snow def test_transaction_failure_race(self): 5440aef09b9SJohn Snow '''Test: Verify that transactions with jobs that have no data to 5450aef09b9SJohn Snow transfer do not cause race conditions in the cancellation of the entire 5460aef09b9SJohn Snow transaction job group. 5470aef09b9SJohn Snow ''' 5480aef09b9SJohn Snow self.do_transaction_failure_test(race=True) 5490aef09b9SJohn Snow 550fc6c796fSJohn Snow 5519f7264f5SJohn Snow def test_sync_dirty_bitmap_missing(self): 5529f7264f5SJohn Snow self.assert_no_active_block_jobs() 5539f7264f5SJohn Snow self.files.append(self.err_img) 5549f7264f5SJohn Snow result = self.vm.qmp('drive-backup', device=self.drives[0]['id'], 5554b80ab2bSJohn Snow sync='incremental', format=self.drives[0]['fmt'], 5569f7264f5SJohn Snow target=self.err_img) 5579f7264f5SJohn Snow self.assert_qmp(result, 'error/class', 'GenericError') 5589f7264f5SJohn Snow 5599f7264f5SJohn Snow 5609f7264f5SJohn Snow def test_sync_dirty_bitmap_not_found(self): 5619f7264f5SJohn Snow self.assert_no_active_block_jobs() 5629f7264f5SJohn Snow self.files.append(self.err_img) 5639f7264f5SJohn Snow result = self.vm.qmp('drive-backup', device=self.drives[0]['id'], 5644b80ab2bSJohn Snow sync='incremental', bitmap='unknown', 5659f7264f5SJohn Snow format=self.drives[0]['fmt'], target=self.err_img) 5669f7264f5SJohn Snow self.assert_qmp(result, 'error/class', 'GenericError') 5679f7264f5SJohn Snow 5689f7264f5SJohn Snow 56959fc5d84SJohn Snow def test_sync_dirty_bitmap_bad_granularity(self): 57059fc5d84SJohn Snow ''' 57159fc5d84SJohn Snow Test: Test what happens if we provide an improper granularity. 57259fc5d84SJohn Snow 57359fc5d84SJohn Snow The granularity must always be a power of 2. 57459fc5d84SJohn Snow ''' 57559fc5d84SJohn Snow self.assert_no_active_block_jobs() 57659fc5d84SJohn Snow self.assertRaises(AssertionError, self.add_bitmap, 57759fc5d84SJohn Snow 'bitmap0', self.drives[0], 57859fc5d84SJohn Snow granularity=64000) 57959fc5d84SJohn Snow 5808a9cb864SMax Reitz def test_growing_before_backup(self): 5818a9cb864SMax Reitz ''' 5828a9cb864SMax Reitz Test: Add a bitmap, truncate the image, write past the old 5838a9cb864SMax Reitz end, do a backup. 5848a9cb864SMax Reitz 5858a9cb864SMax Reitz Incremental backup should not ignore dirty bits past the old 5868a9cb864SMax Reitz image end. 5878a9cb864SMax Reitz ''' 5888a9cb864SMax Reitz self.assert_no_active_block_jobs() 5898a9cb864SMax Reitz 5908a9cb864SMax Reitz self.create_anchor_backup() 5918a9cb864SMax Reitz 5928a9cb864SMax Reitz self.add_bitmap('bitmap0', self.drives[0]) 5938a9cb864SMax Reitz 5948a9cb864SMax Reitz res = self.vm.qmp('block_resize', device=self.drives[0]['id'], 5958a9cb864SMax Reitz size=(65 * 1048576)) 5968a9cb864SMax Reitz self.assert_qmp(res, 'return', {}) 5978a9cb864SMax Reitz 5988a9cb864SMax Reitz # Dirty the image past the old end 5998a9cb864SMax Reitz self.vm.hmp_qemu_io(self.drives[0]['id'], 'write 64M 64k') 6008a9cb864SMax Reitz 6018a9cb864SMax Reitz target = self.prepare_backup(size='65M') 6028a9cb864SMax Reitz self.create_incremental(target=target) 6038a9cb864SMax Reitz 6048a9cb864SMax Reitz self.vm.shutdown() 6058a9cb864SMax Reitz self.check_backups() 6068a9cb864SMax Reitz 60759fc5d84SJohn Snow 608ce2cbc49SJohn Snowclass TestIncrementalBackupBlkdebug(TestIncrementalBackupBase): 609ce2cbc49SJohn Snow '''Incremental backup tests that utilize a BlkDebug filter on drive0.''' 610ce2cbc49SJohn Snow 61135cea223SJohn Snow def setUp(self): 61235cea223SJohn Snow drive0 = self.add_node('drive0') 61335cea223SJohn Snow self.img_create(drive0['file'], drive0['fmt']) 61435cea223SJohn Snow self.write_default_pattern(drive0['file']) 61535cea223SJohn Snow self.vm.launch() 61635cea223SJohn Snow 617ce2cbc49SJohn Snow def test_incremental_failure(self): 618ce2cbc49SJohn Snow '''Test: Verify backups made after a failure are correct. 619ce2cbc49SJohn Snow 620ce2cbc49SJohn Snow Simulate a failure during an incremental backup block job, 621ce2cbc49SJohn Snow emulate additional writes, then create another incremental backup 622ce2cbc49SJohn Snow afterwards and verify that the backup created is correct. 623ce2cbc49SJohn Snow ''' 624ce2cbc49SJohn Snow 62535cea223SJohn Snow drive0 = self.drives[0] 6260153d2f5SKevin Wolf result = self.vm.qmp('blockdev-add', 6270153d2f5SKevin Wolf node_name=drive0['id'], 6280153d2f5SKevin Wolf driver=drive0['fmt'], 6290153d2f5SKevin Wolf file={ 630ce2cbc49SJohn Snow 'driver': 'blkdebug', 631ce2cbc49SJohn Snow 'image': { 632ce2cbc49SJohn Snow 'driver': 'file', 63335cea223SJohn Snow 'filename': drive0['file'] 634ce2cbc49SJohn Snow }, 635ce2cbc49SJohn Snow 'set-state': [{ 636ce2cbc49SJohn Snow 'event': 'flush_to_disk', 637ce2cbc49SJohn Snow 'state': 1, 638ce2cbc49SJohn Snow 'new_state': 2 639ce2cbc49SJohn Snow }], 640ce2cbc49SJohn Snow 'inject-error': [{ 641ce2cbc49SJohn Snow 'event': 'read_aio', 642ce2cbc49SJohn Snow 'errno': 5, 643ce2cbc49SJohn Snow 'state': 2, 644ce2cbc49SJohn Snow 'immediately': False, 645ce2cbc49SJohn Snow 'once': True 646ce2cbc49SJohn Snow }], 647ce2cbc49SJohn Snow } 6480153d2f5SKevin Wolf ) 649ce2cbc49SJohn Snow self.assert_qmp(result, 'return', {}) 650ce2cbc49SJohn Snow 65135cea223SJohn Snow self.create_anchor_backup(drive0) 65235cea223SJohn Snow self.add_bitmap('bitmap0', drive0) 653ce2cbc49SJohn Snow # Note: at this point, during a normal execution, 654ce2cbc49SJohn Snow # Assume that the VM resumes and begins issuing IO requests here. 655ce2cbc49SJohn Snow 65635cea223SJohn Snow self.hmp_io_writes(drive0['id'], (('0xab', 0, 512), 657ce2cbc49SJohn Snow ('0xfe', '16M', '256k'), 658ce2cbc49SJohn Snow ('0x64', '32736k', '64k'))) 659ce2cbc49SJohn Snow 660ce2cbc49SJohn Snow result = self.create_incremental(validate=False) 661ce2cbc49SJohn Snow self.assertFalse(result) 66235cea223SJohn Snow self.hmp_io_writes(drive0['id'], (('0x9a', 0, 512), 663ce2cbc49SJohn Snow ('0x55', '8M', '352k'), 664ce2cbc49SJohn Snow ('0x78', '15872k', '1M'))) 665ce2cbc49SJohn Snow self.create_incremental() 666ce2cbc49SJohn Snow self.vm.shutdown() 667ce2cbc49SJohn Snow self.check_backups() 668ce2cbc49SJohn Snow 669c61b198bSJohn Snow def test_incremental_pause(self): 670c61b198bSJohn Snow """ 671c61b198bSJohn Snow Test an incremental backup that errors into a pause and is resumed. 672c61b198bSJohn Snow """ 673c61b198bSJohn Snow 674c61b198bSJohn Snow drive0 = self.drives[0] 675*5c4343b8SVladimir Sementsov-Ogievskiy # NB: The blkdebug script here looks for a "flush, read" pattern. 676*5c4343b8SVladimir Sementsov-Ogievskiy # The flush occurs in hmp_io_writes, and the read during the block job. 677c61b198bSJohn Snow result = self.vm.qmp('blockdev-add', 678c61b198bSJohn Snow node_name=drive0['id'], 679c61b198bSJohn Snow driver=drive0['fmt'], 680c61b198bSJohn Snow file={ 681c61b198bSJohn Snow 'driver': 'blkdebug', 682c61b198bSJohn Snow 'image': { 683c61b198bSJohn Snow 'driver': 'file', 684c61b198bSJohn Snow 'filename': drive0['file'] 685c61b198bSJohn Snow }, 686c61b198bSJohn Snow 'set-state': [{ 687c61b198bSJohn Snow 'event': 'flush_to_disk', 688c61b198bSJohn Snow 'state': 1, 689c61b198bSJohn Snow 'new_state': 2 690c61b198bSJohn Snow }], 691c61b198bSJohn Snow 'inject-error': [{ 692c61b198bSJohn Snow 'event': 'read_aio', 693c61b198bSJohn Snow 'errno': 5, 694*5c4343b8SVladimir Sementsov-Ogievskiy 'state': 2, 695c61b198bSJohn Snow 'immediately': False, 696c61b198bSJohn Snow 'once': True 697c61b198bSJohn Snow }], 698c61b198bSJohn Snow }) 699c61b198bSJohn Snow self.assert_qmp(result, 'return', {}) 700c61b198bSJohn Snow self.create_anchor_backup(drive0) 701c61b198bSJohn Snow bitmap = self.add_bitmap('bitmap0', drive0) 702c61b198bSJohn Snow 703c61b198bSJohn Snow # Emulate guest activity 704c61b198bSJohn Snow self.hmp_io_writes(drive0['id'], (('0xab', 0, 512), 705c61b198bSJohn Snow ('0xfe', '16M', '256k'), 706c61b198bSJohn Snow ('0x64', '32736k', '64k'))) 707c61b198bSJohn Snow 708c61b198bSJohn Snow # Bitmap Status Check 709*5c4343b8SVladimir Sementsov-Ogievskiy self.assertTrue(self.vm.check_bitmap_status( 710*5c4343b8SVladimir Sementsov-Ogievskiy drive0['id'], bitmap.name, { 711*5c4343b8SVladimir Sementsov-Ogievskiy 'count': 458752, 712*5c4343b8SVladimir Sementsov-Ogievskiy 'granularity': 65536, 713*5c4343b8SVladimir Sementsov-Ogievskiy 'status': 'active', 714*5c4343b8SVladimir Sementsov-Ogievskiy 'busy': False, 715*5c4343b8SVladimir Sementsov-Ogievskiy 'recording': True 716*5c4343b8SVladimir Sementsov-Ogievskiy })) 717c61b198bSJohn Snow 718c61b198bSJohn Snow # Start backup 719c61b198bSJohn Snow parent, _ = bitmap.last_target() 720c61b198bSJohn Snow target = self.prepare_backup(bitmap, parent) 721c61b198bSJohn Snow res = self.vm.qmp('drive-backup', 722c61b198bSJohn Snow job_id=bitmap.drive['id'], 723c61b198bSJohn Snow device=bitmap.drive['id'], 724c61b198bSJohn Snow sync='incremental', 725c61b198bSJohn Snow bitmap=bitmap.name, 726c61b198bSJohn Snow format=bitmap.drive['fmt'], 727c61b198bSJohn Snow target=target, 728c61b198bSJohn Snow mode='existing', 729c61b198bSJohn Snow on_source_error='stop') 730c61b198bSJohn Snow self.assert_qmp(res, 'return', {}) 731c61b198bSJohn Snow 732c61b198bSJohn Snow # Wait for the error 733c61b198bSJohn Snow event = self.vm.event_wait(name="BLOCK_JOB_ERROR", 734c61b198bSJohn Snow match={"data":{"device":bitmap.drive['id']}}) 735c61b198bSJohn Snow self.assert_qmp(event, 'data', {'device': bitmap.drive['id'], 736c61b198bSJohn Snow 'action': 'stop', 737c61b198bSJohn Snow 'operation': 'read'}) 738c61b198bSJohn Snow 739c61b198bSJohn Snow # Bitmap Status Check 740*5c4343b8SVladimir Sementsov-Ogievskiy self.assertTrue(self.vm.check_bitmap_status( 741*5c4343b8SVladimir Sementsov-Ogievskiy drive0['id'], bitmap.name, { 742*5c4343b8SVladimir Sementsov-Ogievskiy 'count': 458752, 743*5c4343b8SVladimir Sementsov-Ogievskiy 'granularity': 65536, 744*5c4343b8SVladimir Sementsov-Ogievskiy 'status': 'frozen', 745*5c4343b8SVladimir Sementsov-Ogievskiy 'busy': True, 746*5c4343b8SVladimir Sementsov-Ogievskiy 'recording': True 747*5c4343b8SVladimir Sementsov-Ogievskiy })) 748c61b198bSJohn Snow 749c61b198bSJohn Snow # Resume and check incremental backup for consistency 750c61b198bSJohn Snow res = self.vm.qmp('block-job-resume', device=bitmap.drive['id']) 751c61b198bSJohn Snow self.assert_qmp(res, 'return', {}) 752c61b198bSJohn Snow self.wait_qmp_backup(bitmap.drive['id']) 753c61b198bSJohn Snow 754c61b198bSJohn Snow # Bitmap Status Check 755*5c4343b8SVladimir Sementsov-Ogievskiy self.assertTrue(self.vm.check_bitmap_status( 756*5c4343b8SVladimir Sementsov-Ogievskiy drive0['id'], bitmap.name, { 757*5c4343b8SVladimir Sementsov-Ogievskiy 'count': 0, 758*5c4343b8SVladimir Sementsov-Ogievskiy 'granularity': 65536, 759*5c4343b8SVladimir Sementsov-Ogievskiy 'status': 'active', 760*5c4343b8SVladimir Sementsov-Ogievskiy 'busy': False, 761*5c4343b8SVladimir Sementsov-Ogievskiy 'recording': True 762*5c4343b8SVladimir Sementsov-Ogievskiy })) 763c61b198bSJohn Snow 764c61b198bSJohn Snow # Finalize / Cleanup 765c61b198bSJohn Snow self.make_reference_backup(bitmap) 766c61b198bSJohn Snow self.vm.shutdown() 767c61b198bSJohn Snow self.check_backups() 768c61b198bSJohn Snow 769ce2cbc49SJohn Snow 7709f7264f5SJohn Snowif __name__ == '__main__': 771103cbc77SMax Reitz iotests.main(supported_fmts=['qcow2'], 772103cbc77SMax Reitz supported_protocols=['file']) 773