19f7264f5SJohn Snow#!/usr/bin/env python 29f7264f5SJohn Snow# 39f7264f5SJohn Snow# Tests for incremental drive-backup 49f7264f5SJohn Snow# 59f7264f5SJohn Snow# Copyright (C) 2015 John Snow for Red Hat, Inc. 69f7264f5SJohn Snow# 79f7264f5SJohn Snow# Based on 056. 89f7264f5SJohn Snow# 99f7264f5SJohn Snow# This program is free software; you can redistribute it and/or modify 109f7264f5SJohn Snow# it under the terms of the GNU General Public License as published by 119f7264f5SJohn Snow# the Free Software Foundation; either version 2 of the License, or 129f7264f5SJohn Snow# (at your option) any later version. 139f7264f5SJohn Snow# 149f7264f5SJohn Snow# This program is distributed in the hope that it will be useful, 159f7264f5SJohn Snow# but WITHOUT ANY WARRANTY; without even the implied warranty of 169f7264f5SJohn Snow# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 179f7264f5SJohn Snow# GNU General Public License for more details. 189f7264f5SJohn Snow# 199f7264f5SJohn Snow# You should have received a copy of the GNU General Public License 209f7264f5SJohn Snow# along with this program. If not, see <http://www.gnu.org/licenses/>. 219f7264f5SJohn Snow# 229f7264f5SJohn Snow 239f7264f5SJohn Snowimport os 249f7264f5SJohn Snowimport iotests 259f7264f5SJohn Snow 269f7264f5SJohn Snow 279f7264f5SJohn Snowdef io_write_patterns(img, patterns): 289f7264f5SJohn Snow for pattern in patterns: 299f7264f5SJohn Snow iotests.qemu_io('-c', 'write -P%s %s %s' % pattern, img) 309f7264f5SJohn Snow 319f7264f5SJohn Snow 32a3d71595SJohn Snowdef try_remove(img): 33a3d71595SJohn Snow try: 34a3d71595SJohn Snow os.remove(img) 35a3d71595SJohn Snow except OSError: 36a3d71595SJohn Snow pass 37a3d71595SJohn Snow 38a3d71595SJohn Snow 39749ad5e8SJohn Snowdef transaction_action(action, **kwargs): 40749ad5e8SJohn Snow return { 41749ad5e8SJohn Snow 'type': action, 42fc6c796fSJohn Snow 'data': dict((k.replace('_', '-'), v) for k, v in kwargs.iteritems()) 43749ad5e8SJohn Snow } 44749ad5e8SJohn Snow 45749ad5e8SJohn Snow 46749ad5e8SJohn Snowdef transaction_bitmap_clear(node, name, **kwargs): 47749ad5e8SJohn Snow return transaction_action('block-dirty-bitmap-clear', 48749ad5e8SJohn Snow node=node, name=name, **kwargs) 49749ad5e8SJohn Snow 50749ad5e8SJohn Snow 51749ad5e8SJohn Snowdef transaction_drive_backup(device, target, **kwargs): 52*eed87583SKevin Wolf return transaction_action('drive-backup', job_id=device, device=device, 53*eed87583SKevin Wolf target=target, **kwargs) 54749ad5e8SJohn Snow 55749ad5e8SJohn Snow 56a3d71595SJohn Snowclass Bitmap: 57a3d71595SJohn Snow def __init__(self, name, drive): 58a3d71595SJohn Snow self.name = name 59a3d71595SJohn Snow self.drive = drive 60a3d71595SJohn Snow self.num = 0 61a3d71595SJohn Snow self.backups = list() 62a3d71595SJohn Snow 63a3d71595SJohn Snow def base_target(self): 64a3d71595SJohn Snow return (self.drive['backup'], None) 65a3d71595SJohn Snow 66a3d71595SJohn Snow def new_target(self, num=None): 67a3d71595SJohn Snow if num is None: 68a3d71595SJohn Snow num = self.num 69a3d71595SJohn Snow self.num = num + 1 70a3d71595SJohn Snow base = os.path.join(iotests.test_dir, 71a3d71595SJohn Snow "%s.%s." % (self.drive['id'], self.name)) 72a3d71595SJohn Snow suff = "%i.%s" % (num, self.drive['fmt']) 73a3d71595SJohn Snow target = base + "inc" + suff 74a3d71595SJohn Snow reference = base + "ref" + suff 75a3d71595SJohn Snow self.backups.append((target, reference)) 76a3d71595SJohn Snow return (target, reference) 77a3d71595SJohn Snow 78a3d71595SJohn Snow def last_target(self): 79a3d71595SJohn Snow if self.backups: 80a3d71595SJohn Snow return self.backups[-1] 81a3d71595SJohn Snow return self.base_target() 82a3d71595SJohn Snow 83a3d71595SJohn Snow def del_target(self): 84a3d71595SJohn Snow for image in self.backups.pop(): 85a3d71595SJohn Snow try_remove(image) 86a3d71595SJohn Snow self.num -= 1 87a3d71595SJohn Snow 88a3d71595SJohn Snow def cleanup(self): 89a3d71595SJohn Snow for backup in self.backups: 90a3d71595SJohn Snow for image in backup: 91a3d71595SJohn Snow try_remove(image) 92a3d71595SJohn Snow 93a3d71595SJohn Snow 941b19bb9dSJohn Snowclass TestIncrementalBackupBase(iotests.QMPTestCase): 951b19bb9dSJohn Snow def __init__(self, *args): 961b19bb9dSJohn Snow super(TestIncrementalBackupBase, self).__init__(*args) 979f7264f5SJohn Snow self.bitmaps = list() 989f7264f5SJohn Snow self.files = list() 999f7264f5SJohn Snow self.drives = list() 1009f7264f5SJohn Snow self.vm = iotests.VM() 1019f7264f5SJohn Snow self.err_img = os.path.join(iotests.test_dir, 'err.%s' % iotests.imgfmt) 1029f7264f5SJohn Snow 1031b19bb9dSJohn Snow 1041b19bb9dSJohn Snow def setUp(self): 1059f7264f5SJohn Snow # Create a base image with a distinctive patterning 1069f7264f5SJohn Snow drive0 = self.add_node('drive0') 1079f7264f5SJohn Snow self.img_create(drive0['file'], drive0['fmt']) 1089f7264f5SJohn Snow self.vm.add_drive(drive0['file']) 1091b19bb9dSJohn Snow self.write_default_pattern(drive0['file']) 1101b19bb9dSJohn Snow self.vm.launch() 1111b19bb9dSJohn Snow 1121b19bb9dSJohn Snow 1131b19bb9dSJohn Snow def write_default_pattern(self, target): 1141b19bb9dSJohn Snow io_write_patterns(target, (('0x41', 0, 512), 1159f7264f5SJohn Snow ('0xd5', '1M', '32k'), 1169f7264f5SJohn Snow ('0xdc', '32M', '124k'))) 1179f7264f5SJohn Snow 1189f7264f5SJohn Snow 1199f7264f5SJohn Snow def add_node(self, node_id, fmt=iotests.imgfmt, path=None, backup=None): 1209f7264f5SJohn Snow if path is None: 1219f7264f5SJohn Snow path = os.path.join(iotests.test_dir, '%s.%s' % (node_id, fmt)) 1229f7264f5SJohn Snow if backup is None: 1239f7264f5SJohn Snow backup = os.path.join(iotests.test_dir, 1249f7264f5SJohn Snow '%s.full.backup.%s' % (node_id, fmt)) 1259f7264f5SJohn Snow 1269f7264f5SJohn Snow self.drives.append({ 1279f7264f5SJohn Snow 'id': node_id, 1289f7264f5SJohn Snow 'file': path, 1299f7264f5SJohn Snow 'backup': backup, 1309f7264f5SJohn Snow 'fmt': fmt }) 1319f7264f5SJohn Snow return self.drives[-1] 1329f7264f5SJohn Snow 1339f7264f5SJohn Snow 1349f7264f5SJohn Snow def img_create(self, img, fmt=iotests.imgfmt, size='64M', 135cc199b16SJohn Snow parent=None, parentFormat=None, **kwargs): 136cc199b16SJohn Snow optargs = [] 137cc199b16SJohn Snow for k,v in kwargs.iteritems(): 138cc199b16SJohn Snow optargs = optargs + ['-o', '%s=%s' % (k,v)] 139cc199b16SJohn Snow args = ['create', '-f', fmt] + optargs + [img, size] 1409f7264f5SJohn Snow if parent: 1419f7264f5SJohn Snow if parentFormat is None: 1429f7264f5SJohn Snow parentFormat = fmt 143cc199b16SJohn Snow args = args + ['-b', parent, '-F', parentFormat] 144cc199b16SJohn Snow iotests.qemu_img(*args) 1459f7264f5SJohn Snow self.files.append(img) 1469f7264f5SJohn Snow 147a3d71595SJohn Snow 148a3d71595SJohn Snow def do_qmp_backup(self, error='Input/output error', **kwargs): 149a3d71595SJohn Snow res = self.vm.qmp('drive-backup', **kwargs) 150a3d71595SJohn Snow self.assert_qmp(res, 'return', {}) 151fc6c796fSJohn Snow return self.wait_qmp_backup(kwargs['device'], error) 152a3d71595SJohn Snow 153fc6c796fSJohn Snow 154fc6c796fSJohn Snow def wait_qmp_backup(self, device, error='Input/output error'): 155a3d71595SJohn Snow event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED", 156fc6c796fSJohn Snow match={'data': {'device': device}}) 157ff793890SJohn Snow self.assertNotEqual(event, None) 158a3d71595SJohn Snow 159a3d71595SJohn Snow try: 160a3d71595SJohn Snow failure = self.dictpath(event, 'data/error') 161a3d71595SJohn Snow except AssertionError: 162a3d71595SJohn Snow # Backup succeeded. 163a3d71595SJohn Snow self.assert_qmp(event, 'data/offset', event['data']['len']) 164a3d71595SJohn Snow return True 165a3d71595SJohn Snow else: 166a3d71595SJohn Snow # Backup failed. 167a3d71595SJohn Snow self.assert_qmp(event, 'data/error', error) 168a3d71595SJohn Snow return False 169a3d71595SJohn Snow 170a3d71595SJohn Snow 171fc6c796fSJohn Snow def wait_qmp_backup_cancelled(self, device): 172fc6c796fSJohn Snow event = self.vm.event_wait(name='BLOCK_JOB_CANCELLED', 173fc6c796fSJohn Snow match={'data': {'device': device}}) 174fc6c796fSJohn Snow self.assertNotEqual(event, None) 175fc6c796fSJohn Snow 176fc6c796fSJohn Snow 177a3d71595SJohn Snow def create_anchor_backup(self, drive=None): 178a3d71595SJohn Snow if drive is None: 179a3d71595SJohn Snow drive = self.drives[-1] 180*eed87583SKevin Wolf res = self.do_qmp_backup(job_id=drive['id'], 181*eed87583SKevin Wolf device=drive['id'], sync='full', 182a3d71595SJohn Snow format=drive['fmt'], target=drive['backup']) 183a3d71595SJohn Snow self.assertTrue(res) 184a3d71595SJohn Snow self.files.append(drive['backup']) 185a3d71595SJohn Snow return drive['backup'] 186a3d71595SJohn Snow 187a3d71595SJohn Snow 188a3d71595SJohn Snow def make_reference_backup(self, bitmap=None): 189a3d71595SJohn Snow if bitmap is None: 190a3d71595SJohn Snow bitmap = self.bitmaps[-1] 191a3d71595SJohn Snow _, reference = bitmap.last_target() 192*eed87583SKevin Wolf res = self.do_qmp_backup(job_id=bitmap.drive['id'], 193*eed87583SKevin Wolf device=bitmap.drive['id'], sync='full', 194a3d71595SJohn Snow format=bitmap.drive['fmt'], target=reference) 195a3d71595SJohn Snow self.assertTrue(res) 196a3d71595SJohn Snow 197a3d71595SJohn Snow 19859fc5d84SJohn Snow def add_bitmap(self, name, drive, **kwargs): 199a3d71595SJohn Snow bitmap = Bitmap(name, drive) 200a3d71595SJohn Snow self.bitmaps.append(bitmap) 201a3d71595SJohn Snow result = self.vm.qmp('block-dirty-bitmap-add', node=drive['id'], 20259fc5d84SJohn Snow name=bitmap.name, **kwargs) 203a3d71595SJohn Snow self.assert_qmp(result, 'return', {}) 204a3d71595SJohn Snow return bitmap 205a3d71595SJohn Snow 206a3d71595SJohn Snow 207a3d71595SJohn Snow def prepare_backup(self, bitmap=None, parent=None): 208a3d71595SJohn Snow if bitmap is None: 209a3d71595SJohn Snow bitmap = self.bitmaps[-1] 210a3d71595SJohn Snow if parent is None: 211a3d71595SJohn Snow parent, _ = bitmap.last_target() 212a3d71595SJohn Snow 213a3d71595SJohn Snow target, _ = bitmap.new_target() 214a3d71595SJohn Snow self.img_create(target, bitmap.drive['fmt'], parent=parent) 215a3d71595SJohn Snow return target 216a3d71595SJohn Snow 217a3d71595SJohn Snow 218a3d71595SJohn Snow def create_incremental(self, bitmap=None, parent=None, 219a3d71595SJohn Snow parentFormat=None, validate=True): 220a3d71595SJohn Snow if bitmap is None: 221a3d71595SJohn Snow bitmap = self.bitmaps[-1] 222a3d71595SJohn Snow if parent is None: 223a3d71595SJohn Snow parent, _ = bitmap.last_target() 224a3d71595SJohn Snow 225a3d71595SJohn Snow target = self.prepare_backup(bitmap, parent) 226*eed87583SKevin Wolf res = self.do_qmp_backup(job_id=bitmap.drive['id'], 227*eed87583SKevin Wolf device=bitmap.drive['id'], 2284b80ab2bSJohn Snow sync='incremental', bitmap=bitmap.name, 229a3d71595SJohn Snow format=bitmap.drive['fmt'], target=target, 230a3d71595SJohn Snow mode='existing') 231a3d71595SJohn Snow if not res: 232a3d71595SJohn Snow bitmap.del_target(); 233a3d71595SJohn Snow self.assertFalse(validate) 234a3d71595SJohn Snow else: 235a3d71595SJohn Snow self.make_reference_backup(bitmap) 236a3d71595SJohn Snow return res 237a3d71595SJohn Snow 238a3d71595SJohn Snow 239a3d71595SJohn Snow def check_backups(self): 240a3d71595SJohn Snow for bitmap in self.bitmaps: 241a3d71595SJohn Snow for incremental, reference in bitmap.backups: 242a3d71595SJohn Snow self.assertTrue(iotests.compare_images(incremental, reference)) 243a3d71595SJohn Snow last = bitmap.last_target()[0] 244a3d71595SJohn Snow self.assertTrue(iotests.compare_images(last, bitmap.drive['file'])) 245a3d71595SJohn Snow 246a3d71595SJohn Snow 247a3d71595SJohn Snow def hmp_io_writes(self, drive, patterns): 248a3d71595SJohn Snow for pattern in patterns: 249a3d71595SJohn Snow self.vm.hmp_qemu_io(drive, 'write -P%s %s %s' % pattern) 250a3d71595SJohn Snow self.vm.hmp_qemu_io(drive, 'flush') 251a3d71595SJohn Snow 252a3d71595SJohn Snow 25359fc5d84SJohn Snow def do_incremental_simple(self, **kwargs): 254a3d71595SJohn Snow self.create_anchor_backup() 25559fc5d84SJohn Snow self.add_bitmap('bitmap0', self.drives[0], **kwargs) 256a3d71595SJohn Snow 257a3d71595SJohn Snow # Sanity: Create a "hollow" incremental backup 258a3d71595SJohn Snow self.create_incremental() 259a3d71595SJohn Snow # Three writes: One complete overwrite, one new segment, 260a3d71595SJohn Snow # and one partial overlap. 261a3d71595SJohn Snow self.hmp_io_writes(self.drives[0]['id'], (('0xab', 0, 512), 262a3d71595SJohn Snow ('0xfe', '16M', '256k'), 263a3d71595SJohn Snow ('0x64', '32736k', '64k'))) 264a3d71595SJohn Snow self.create_incremental() 265a3d71595SJohn Snow # Three more writes, one of each kind, like above 266a3d71595SJohn Snow self.hmp_io_writes(self.drives[0]['id'], (('0x9a', 0, 512), 267a3d71595SJohn Snow ('0x55', '8M', '352k'), 268a3d71595SJohn Snow ('0x78', '15872k', '1M'))) 269a3d71595SJohn Snow self.create_incremental() 270a3d71595SJohn Snow self.vm.shutdown() 271a3d71595SJohn Snow self.check_backups() 272a3d71595SJohn Snow 273a3d71595SJohn Snow 2741b19bb9dSJohn Snow def tearDown(self): 2751b19bb9dSJohn Snow self.vm.shutdown() 2761b19bb9dSJohn Snow for bitmap in self.bitmaps: 2771b19bb9dSJohn Snow bitmap.cleanup() 2781b19bb9dSJohn Snow for filename in self.files: 2791b19bb9dSJohn Snow try_remove(filename) 2801b19bb9dSJohn Snow 2811b19bb9dSJohn Snow 2821b19bb9dSJohn Snow 2831b19bb9dSJohn Snowclass TestIncrementalBackup(TestIncrementalBackupBase): 28459fc5d84SJohn Snow def test_incremental_simple(self): 28559fc5d84SJohn Snow ''' 28659fc5d84SJohn Snow Test: Create and verify three incremental backups. 28759fc5d84SJohn Snow 28859fc5d84SJohn Snow Create a bitmap and a full backup before VM execution begins, 28959fc5d84SJohn Snow then create a series of three incremental backups "during execution," 29059fc5d84SJohn Snow i.e.; after IO requests begin modifying the drive. 29159fc5d84SJohn Snow ''' 29259fc5d84SJohn Snow return self.do_incremental_simple() 29359fc5d84SJohn Snow 29459fc5d84SJohn Snow 29559fc5d84SJohn Snow def test_small_granularity(self): 29659fc5d84SJohn Snow ''' 29759fc5d84SJohn Snow Test: Create and verify backups made with a small granularity bitmap. 29859fc5d84SJohn Snow 29959fc5d84SJohn Snow Perform the same test as test_incremental_simple, but with a granularity 30059fc5d84SJohn Snow of only 32KiB instead of the present default of 64KiB. 30159fc5d84SJohn Snow ''' 30259fc5d84SJohn Snow return self.do_incremental_simple(granularity=32768) 30359fc5d84SJohn Snow 30459fc5d84SJohn Snow 30559fc5d84SJohn Snow def test_large_granularity(self): 30659fc5d84SJohn Snow ''' 30759fc5d84SJohn Snow Test: Create and verify backups made with a large granularity bitmap. 30859fc5d84SJohn Snow 30959fc5d84SJohn Snow Perform the same test as test_incremental_simple, but with a granularity 31059fc5d84SJohn Snow of 128KiB instead of the present default of 64KiB. 31159fc5d84SJohn Snow ''' 31259fc5d84SJohn Snow return self.do_incremental_simple(granularity=131072) 31359fc5d84SJohn Snow 31459fc5d84SJohn Snow 315cc199b16SJohn Snow def test_larger_cluster_target(self): 316cc199b16SJohn Snow ''' 317cc199b16SJohn Snow Test: Create and verify backups made to a larger cluster size target. 318cc199b16SJohn Snow 319cc199b16SJohn Snow With a default granularity of 64KiB, verify that backups made to a 320cc199b16SJohn Snow larger cluster size target of 128KiB without a backing file works. 321cc199b16SJohn Snow ''' 322cc199b16SJohn Snow drive0 = self.drives[0] 323cc199b16SJohn Snow 324cc199b16SJohn Snow # Create a cluster_size=128k full backup / "anchor" backup 325cc199b16SJohn Snow self.img_create(drive0['backup'], cluster_size='128k') 326cc199b16SJohn Snow self.assertTrue(self.do_qmp_backup(device=drive0['id'], sync='full', 327cc199b16SJohn Snow format=drive0['fmt'], 328cc199b16SJohn Snow target=drive0['backup'], 329cc199b16SJohn Snow mode='existing')) 330cc199b16SJohn Snow 331cc199b16SJohn Snow # Create bitmap and dirty it with some new writes. 332cc199b16SJohn Snow # overwrite [32736, 32799] which will dirty bitmap clusters at 333cc199b16SJohn Snow # 32M-64K and 32M. 32M+64K will be left undirtied. 334cc199b16SJohn Snow bitmap0 = self.add_bitmap('bitmap0', drive0) 335cc199b16SJohn Snow self.hmp_io_writes(drive0['id'], 336cc199b16SJohn Snow (('0xab', 0, 512), 337cc199b16SJohn Snow ('0xfe', '16M', '256k'), 338cc199b16SJohn Snow ('0x64', '32736k', '64k'))) 339cc199b16SJohn Snow 340cc199b16SJohn Snow 341cc199b16SJohn Snow # Prepare a cluster_size=128k backup target without a backing file. 342cc199b16SJohn Snow (target, _) = bitmap0.new_target() 343cc199b16SJohn Snow self.img_create(target, bitmap0.drive['fmt'], cluster_size='128k') 344cc199b16SJohn Snow 345cc199b16SJohn Snow # Perform Incremental Backup 346cc199b16SJohn Snow self.assertTrue(self.do_qmp_backup(device=bitmap0.drive['id'], 347cc199b16SJohn Snow sync='incremental', 348cc199b16SJohn Snow bitmap=bitmap0.name, 349cc199b16SJohn Snow format=bitmap0.drive['fmt'], 350cc199b16SJohn Snow target=target, 351cc199b16SJohn Snow mode='existing')) 352cc199b16SJohn Snow self.make_reference_backup(bitmap0) 353cc199b16SJohn Snow 354cc199b16SJohn Snow # Add the backing file, then compare and exit. 355cc199b16SJohn Snow iotests.qemu_img('rebase', '-f', drive0['fmt'], '-u', '-b', 356cc199b16SJohn Snow drive0['backup'], '-F', drive0['fmt'], target) 357cc199b16SJohn Snow self.vm.shutdown() 358cc199b16SJohn Snow self.check_backups() 359cc199b16SJohn Snow 360cc199b16SJohn Snow 361749ad5e8SJohn Snow def test_incremental_transaction(self): 362749ad5e8SJohn Snow '''Test: Verify backups made from transactionally created bitmaps. 363749ad5e8SJohn Snow 364749ad5e8SJohn Snow Create a bitmap "before" VM execution begins, then create a second 365749ad5e8SJohn Snow bitmap AFTER writes have already occurred. Use transactions to create 366749ad5e8SJohn Snow a full backup and synchronize both bitmaps to this backup. 367749ad5e8SJohn Snow Create an incremental backup through both bitmaps and verify that 368749ad5e8SJohn Snow both backups match the current drive0 image. 369749ad5e8SJohn Snow ''' 370749ad5e8SJohn Snow 371749ad5e8SJohn Snow drive0 = self.drives[0] 372749ad5e8SJohn Snow bitmap0 = self.add_bitmap('bitmap0', drive0) 373749ad5e8SJohn Snow self.hmp_io_writes(drive0['id'], (('0xab', 0, 512), 374749ad5e8SJohn Snow ('0xfe', '16M', '256k'), 375749ad5e8SJohn Snow ('0x64', '32736k', '64k'))) 376749ad5e8SJohn Snow bitmap1 = self.add_bitmap('bitmap1', drive0) 377749ad5e8SJohn Snow 378749ad5e8SJohn Snow result = self.vm.qmp('transaction', actions=[ 379749ad5e8SJohn Snow transaction_bitmap_clear(bitmap0.drive['id'], bitmap0.name), 380749ad5e8SJohn Snow transaction_bitmap_clear(bitmap1.drive['id'], bitmap1.name), 381749ad5e8SJohn Snow transaction_drive_backup(drive0['id'], drive0['backup'], 382749ad5e8SJohn Snow sync='full', format=drive0['fmt']) 383749ad5e8SJohn Snow ]) 384749ad5e8SJohn Snow self.assert_qmp(result, 'return', {}) 385749ad5e8SJohn Snow self.wait_until_completed(drive0['id']) 386749ad5e8SJohn Snow self.files.append(drive0['backup']) 387749ad5e8SJohn Snow 388749ad5e8SJohn Snow self.hmp_io_writes(drive0['id'], (('0x9a', 0, 512), 389749ad5e8SJohn Snow ('0x55', '8M', '352k'), 390749ad5e8SJohn Snow ('0x78', '15872k', '1M'))) 391749ad5e8SJohn Snow # Both bitmaps should be correctly in sync. 392749ad5e8SJohn Snow self.create_incremental(bitmap0) 393749ad5e8SJohn Snow self.create_incremental(bitmap1) 394749ad5e8SJohn Snow self.vm.shutdown() 395749ad5e8SJohn Snow self.check_backups() 396749ad5e8SJohn Snow 397749ad5e8SJohn Snow 398fc6c796fSJohn Snow def test_transaction_failure(self): 399fc6c796fSJohn Snow '''Test: Verify backups made from a transaction that partially fails. 400fc6c796fSJohn Snow 401fc6c796fSJohn Snow Add a second drive with its own unique pattern, and add a bitmap to each 402fc6c796fSJohn Snow drive. Use blkdebug to interfere with the backup on just one drive and 403fc6c796fSJohn Snow attempt to create a coherent incremental backup across both drives. 404fc6c796fSJohn Snow 405fc6c796fSJohn Snow verify a failure in one but not both, then delete the failed stubs and 406fc6c796fSJohn Snow re-run the same transaction. 407fc6c796fSJohn Snow 408fc6c796fSJohn Snow verify that both incrementals are created successfully. 409fc6c796fSJohn Snow ''' 410fc6c796fSJohn Snow 411fc6c796fSJohn Snow # Create a second drive, with pattern: 412fc6c796fSJohn Snow drive1 = self.add_node('drive1') 413fc6c796fSJohn Snow self.img_create(drive1['file'], drive1['fmt']) 414fc6c796fSJohn Snow io_write_patterns(drive1['file'], (('0x14', 0, 512), 415fc6c796fSJohn Snow ('0x5d', '1M', '32k'), 416fc6c796fSJohn Snow ('0xcd', '32M', '124k'))) 417fc6c796fSJohn Snow 418fc6c796fSJohn Snow # Create a blkdebug interface to this img as 'drive1' 419fc6c796fSJohn Snow result = self.vm.qmp('blockdev-add', options={ 420*eed87583SKevin Wolf 'node-name': drive1['id'], 421fc6c796fSJohn Snow 'driver': drive1['fmt'], 422fc6c796fSJohn Snow 'file': { 423fc6c796fSJohn Snow 'driver': 'blkdebug', 424fc6c796fSJohn Snow 'image': { 425fc6c796fSJohn Snow 'driver': 'file', 426fc6c796fSJohn Snow 'filename': drive1['file'] 427fc6c796fSJohn Snow }, 428fc6c796fSJohn Snow 'set-state': [{ 429fc6c796fSJohn Snow 'event': 'flush_to_disk', 430fc6c796fSJohn Snow 'state': 1, 431fc6c796fSJohn Snow 'new_state': 2 432fc6c796fSJohn Snow }], 433fc6c796fSJohn Snow 'inject-error': [{ 434fc6c796fSJohn Snow 'event': 'read_aio', 435fc6c796fSJohn Snow 'errno': 5, 436fc6c796fSJohn Snow 'state': 2, 437fc6c796fSJohn Snow 'immediately': False, 438fc6c796fSJohn Snow 'once': True 439fc6c796fSJohn Snow }], 440fc6c796fSJohn Snow } 441fc6c796fSJohn Snow }) 442fc6c796fSJohn Snow self.assert_qmp(result, 'return', {}) 443fc6c796fSJohn Snow 444fc6c796fSJohn Snow # Create bitmaps and full backups for both drives 445fc6c796fSJohn Snow drive0 = self.drives[0] 446fc6c796fSJohn Snow dr0bm0 = self.add_bitmap('bitmap0', drive0) 447fc6c796fSJohn Snow dr1bm0 = self.add_bitmap('bitmap0', drive1) 448fc6c796fSJohn Snow self.create_anchor_backup(drive0) 449fc6c796fSJohn Snow self.create_anchor_backup(drive1) 450fc6c796fSJohn Snow self.assert_no_active_block_jobs() 451fc6c796fSJohn Snow self.assertFalse(self.vm.get_qmp_events(wait=False)) 452fc6c796fSJohn Snow 453fc6c796fSJohn Snow # Emulate some writes 454fc6c796fSJohn Snow self.hmp_io_writes(drive0['id'], (('0xab', 0, 512), 455fc6c796fSJohn Snow ('0xfe', '16M', '256k'), 456fc6c796fSJohn Snow ('0x64', '32736k', '64k'))) 457fc6c796fSJohn Snow self.hmp_io_writes(drive1['id'], (('0xba', 0, 512), 458fc6c796fSJohn Snow ('0xef', '16M', '256k'), 459fc6c796fSJohn Snow ('0x46', '32736k', '64k'))) 460fc6c796fSJohn Snow 461fc6c796fSJohn Snow # Create incremental backup targets 462fc6c796fSJohn Snow target0 = self.prepare_backup(dr0bm0) 463fc6c796fSJohn Snow target1 = self.prepare_backup(dr1bm0) 464fc6c796fSJohn Snow 465fc6c796fSJohn Snow # Ask for a new incremental backup per-each drive, 466fc6c796fSJohn Snow # expecting drive1's backup to fail: 467fc6c796fSJohn Snow transaction = [ 468fc6c796fSJohn Snow transaction_drive_backup(drive0['id'], target0, sync='incremental', 469fc6c796fSJohn Snow format=drive0['fmt'], mode='existing', 470fc6c796fSJohn Snow bitmap=dr0bm0.name), 471fc6c796fSJohn Snow transaction_drive_backup(drive1['id'], target1, sync='incremental', 472fc6c796fSJohn Snow format=drive1['fmt'], mode='existing', 473fc6c796fSJohn Snow bitmap=dr1bm0.name) 474fc6c796fSJohn Snow ] 475fc6c796fSJohn Snow result = self.vm.qmp('transaction', actions=transaction, 476fc6c796fSJohn Snow properties={'completion-mode': 'grouped'} ) 477fc6c796fSJohn Snow self.assert_qmp(result, 'return', {}) 478fc6c796fSJohn Snow 479fc6c796fSJohn Snow # Observe that drive0's backup is cancelled and drive1 completes with 480fc6c796fSJohn Snow # an error. 481fc6c796fSJohn Snow self.wait_qmp_backup_cancelled(drive0['id']) 482fc6c796fSJohn Snow self.assertFalse(self.wait_qmp_backup(drive1['id'])) 483fc6c796fSJohn Snow error = self.vm.event_wait('BLOCK_JOB_ERROR') 484fc6c796fSJohn Snow self.assert_qmp(error, 'data', {'device': drive1['id'], 485fc6c796fSJohn Snow 'action': 'report', 486fc6c796fSJohn Snow 'operation': 'read'}) 487fc6c796fSJohn Snow self.assertFalse(self.vm.get_qmp_events(wait=False)) 488fc6c796fSJohn Snow self.assert_no_active_block_jobs() 489fc6c796fSJohn Snow 490fc6c796fSJohn Snow # Delete drive0's successful target and eliminate our record of the 491fc6c796fSJohn Snow # unsuccessful drive1 target. Then re-run the same transaction. 492fc6c796fSJohn Snow dr0bm0.del_target() 493fc6c796fSJohn Snow dr1bm0.del_target() 494fc6c796fSJohn Snow target0 = self.prepare_backup(dr0bm0) 495fc6c796fSJohn Snow target1 = self.prepare_backup(dr1bm0) 496fc6c796fSJohn Snow 497fc6c796fSJohn Snow # Re-run the exact same transaction. 498fc6c796fSJohn Snow result = self.vm.qmp('transaction', actions=transaction, 499fc6c796fSJohn Snow properties={'completion-mode':'grouped'}) 500fc6c796fSJohn Snow self.assert_qmp(result, 'return', {}) 501fc6c796fSJohn Snow 502fc6c796fSJohn Snow # Both should complete successfully this time. 503fc6c796fSJohn Snow self.assertTrue(self.wait_qmp_backup(drive0['id'])) 504fc6c796fSJohn Snow self.assertTrue(self.wait_qmp_backup(drive1['id'])) 505fc6c796fSJohn Snow self.make_reference_backup(dr0bm0) 506fc6c796fSJohn Snow self.make_reference_backup(dr1bm0) 507fc6c796fSJohn Snow self.assertFalse(self.vm.get_qmp_events(wait=False)) 508fc6c796fSJohn Snow self.assert_no_active_block_jobs() 509fc6c796fSJohn Snow 510fc6c796fSJohn Snow # And the images should of course validate. 511fc6c796fSJohn Snow self.vm.shutdown() 512fc6c796fSJohn Snow self.check_backups() 513fc6c796fSJohn Snow 514fc6c796fSJohn Snow 5159f7264f5SJohn Snow def test_sync_dirty_bitmap_missing(self): 5169f7264f5SJohn Snow self.assert_no_active_block_jobs() 5179f7264f5SJohn Snow self.files.append(self.err_img) 5189f7264f5SJohn Snow result = self.vm.qmp('drive-backup', device=self.drives[0]['id'], 5194b80ab2bSJohn Snow sync='incremental', format=self.drives[0]['fmt'], 5209f7264f5SJohn Snow target=self.err_img) 5219f7264f5SJohn Snow self.assert_qmp(result, 'error/class', 'GenericError') 5229f7264f5SJohn Snow 5239f7264f5SJohn Snow 5249f7264f5SJohn Snow def test_sync_dirty_bitmap_not_found(self): 5259f7264f5SJohn Snow self.assert_no_active_block_jobs() 5269f7264f5SJohn Snow self.files.append(self.err_img) 5279f7264f5SJohn Snow result = self.vm.qmp('drive-backup', device=self.drives[0]['id'], 5284b80ab2bSJohn Snow sync='incremental', bitmap='unknown', 5299f7264f5SJohn Snow format=self.drives[0]['fmt'], target=self.err_img) 5309f7264f5SJohn Snow self.assert_qmp(result, 'error/class', 'GenericError') 5319f7264f5SJohn Snow 5329f7264f5SJohn Snow 53359fc5d84SJohn Snow def test_sync_dirty_bitmap_bad_granularity(self): 53459fc5d84SJohn Snow ''' 53559fc5d84SJohn Snow Test: Test what happens if we provide an improper granularity. 53659fc5d84SJohn Snow 53759fc5d84SJohn Snow The granularity must always be a power of 2. 53859fc5d84SJohn Snow ''' 53959fc5d84SJohn Snow self.assert_no_active_block_jobs() 54059fc5d84SJohn Snow self.assertRaises(AssertionError, self.add_bitmap, 54159fc5d84SJohn Snow 'bitmap0', self.drives[0], 54259fc5d84SJohn Snow granularity=64000) 54359fc5d84SJohn Snow 54459fc5d84SJohn Snow 545ce2cbc49SJohn Snowclass TestIncrementalBackupBlkdebug(TestIncrementalBackupBase): 546ce2cbc49SJohn Snow '''Incremental backup tests that utilize a BlkDebug filter on drive0.''' 547ce2cbc49SJohn Snow 54835cea223SJohn Snow def setUp(self): 54935cea223SJohn Snow drive0 = self.add_node('drive0') 55035cea223SJohn Snow self.img_create(drive0['file'], drive0['fmt']) 55135cea223SJohn Snow self.write_default_pattern(drive0['file']) 55235cea223SJohn Snow self.vm.launch() 55335cea223SJohn Snow 554ce2cbc49SJohn Snow def test_incremental_failure(self): 555ce2cbc49SJohn Snow '''Test: Verify backups made after a failure are correct. 556ce2cbc49SJohn Snow 557ce2cbc49SJohn Snow Simulate a failure during an incremental backup block job, 558ce2cbc49SJohn Snow emulate additional writes, then create another incremental backup 559ce2cbc49SJohn Snow afterwards and verify that the backup created is correct. 560ce2cbc49SJohn Snow ''' 561ce2cbc49SJohn Snow 56235cea223SJohn Snow drive0 = self.drives[0] 563ce2cbc49SJohn Snow result = self.vm.qmp('blockdev-add', options={ 564*eed87583SKevin Wolf 'node-name': drive0['id'], 56535cea223SJohn Snow 'driver': drive0['fmt'], 566ce2cbc49SJohn Snow 'file': { 567ce2cbc49SJohn Snow 'driver': 'blkdebug', 568ce2cbc49SJohn Snow 'image': { 569ce2cbc49SJohn Snow 'driver': 'file', 57035cea223SJohn Snow 'filename': drive0['file'] 571ce2cbc49SJohn Snow }, 572ce2cbc49SJohn Snow 'set-state': [{ 573ce2cbc49SJohn Snow 'event': 'flush_to_disk', 574ce2cbc49SJohn Snow 'state': 1, 575ce2cbc49SJohn Snow 'new_state': 2 576ce2cbc49SJohn Snow }], 577ce2cbc49SJohn Snow 'inject-error': [{ 578ce2cbc49SJohn Snow 'event': 'read_aio', 579ce2cbc49SJohn Snow 'errno': 5, 580ce2cbc49SJohn Snow 'state': 2, 581ce2cbc49SJohn Snow 'immediately': False, 582ce2cbc49SJohn Snow 'once': True 583ce2cbc49SJohn Snow }], 584ce2cbc49SJohn Snow } 585ce2cbc49SJohn Snow }) 586ce2cbc49SJohn Snow self.assert_qmp(result, 'return', {}) 587ce2cbc49SJohn Snow 58835cea223SJohn Snow self.create_anchor_backup(drive0) 58935cea223SJohn Snow self.add_bitmap('bitmap0', drive0) 590ce2cbc49SJohn Snow # Note: at this point, during a normal execution, 591ce2cbc49SJohn Snow # Assume that the VM resumes and begins issuing IO requests here. 592ce2cbc49SJohn Snow 59335cea223SJohn Snow self.hmp_io_writes(drive0['id'], (('0xab', 0, 512), 594ce2cbc49SJohn Snow ('0xfe', '16M', '256k'), 595ce2cbc49SJohn Snow ('0x64', '32736k', '64k'))) 596ce2cbc49SJohn Snow 597ce2cbc49SJohn Snow result = self.create_incremental(validate=False) 598ce2cbc49SJohn Snow self.assertFalse(result) 59935cea223SJohn Snow self.hmp_io_writes(drive0['id'], (('0x9a', 0, 512), 600ce2cbc49SJohn Snow ('0x55', '8M', '352k'), 601ce2cbc49SJohn Snow ('0x78', '15872k', '1M'))) 602ce2cbc49SJohn Snow self.create_incremental() 603ce2cbc49SJohn Snow self.vm.shutdown() 604ce2cbc49SJohn Snow self.check_backups() 605ce2cbc49SJohn Snow 606ce2cbc49SJohn Snow 6079f7264f5SJohn Snowif __name__ == '__main__': 6089f7264f5SJohn Snow iotests.main(supported_fmts=['qcow2']) 609