1903cb1bfSPhilippe Mathieu-Daudé#!/usr/bin/env python3 29dd003a9SVladimir Sementsov-Ogievskiy# group: rw backing 39f7264f5SJohn Snow# 49f7264f5SJohn Snow# Tests for incremental drive-backup 59f7264f5SJohn Snow# 69f7264f5SJohn Snow# Copyright (C) 2015 John Snow for Red Hat, Inc. 79f7264f5SJohn Snow# 89f7264f5SJohn Snow# Based on 056. 99f7264f5SJohn Snow# 109f7264f5SJohn Snow# This program is free software; you can redistribute it and/or modify 119f7264f5SJohn Snow# it under the terms of the GNU General Public License as published by 129f7264f5SJohn Snow# the Free Software Foundation; either version 2 of the License, or 139f7264f5SJohn Snow# (at your option) any later version. 149f7264f5SJohn Snow# 159f7264f5SJohn Snow# This program is distributed in the hope that it will be useful, 169f7264f5SJohn Snow# but WITHOUT ANY WARRANTY; without even the implied warranty of 179f7264f5SJohn Snow# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 189f7264f5SJohn Snow# GNU General Public License for more details. 199f7264f5SJohn Snow# 209f7264f5SJohn Snow# You should have received a copy of the GNU General Public License 219f7264f5SJohn Snow# along with this program. If not, see <http://www.gnu.org/licenses/>. 229f7264f5SJohn Snow# 239f7264f5SJohn Snow 249f7264f5SJohn Snowimport os 259f7264f5SJohn Snowimport iotests 26c5ff5a3cSMax Reitzfrom iotests import try_remove 27b6aed193SVladimir Sementsov-Ogievskiyfrom qemu.qmp.qmp_client import ExecuteError 289f7264f5SJohn Snow 299f7264f5SJohn Snow 309f7264f5SJohn Snowdef io_write_patterns(img, patterns): 319f7264f5SJohn Snow for pattern in patterns: 329f7264f5SJohn Snow iotests.qemu_io('-c', 'write -P%s %s %s' % pattern, img) 339f7264f5SJohn Snow 349f7264f5SJohn Snow 35749ad5e8SJohn Snowdef transaction_action(action, **kwargs): 36749ad5e8SJohn Snow return { 37749ad5e8SJohn Snow 'type': action, 3868474776SMax Reitz 'data': dict((k.replace('_', '-'), v) for k, v in kwargs.items()) 39749ad5e8SJohn Snow } 40749ad5e8SJohn Snow 41749ad5e8SJohn Snow 42749ad5e8SJohn Snowdef transaction_bitmap_clear(node, name, **kwargs): 43749ad5e8SJohn Snow return transaction_action('block-dirty-bitmap-clear', 44749ad5e8SJohn Snow node=node, name=name, **kwargs) 45749ad5e8SJohn Snow 46749ad5e8SJohn Snow 47749ad5e8SJohn Snowdef transaction_drive_backup(device, target, **kwargs): 48eed87583SKevin Wolf return transaction_action('drive-backup', job_id=device, device=device, 49eed87583SKevin Wolf target=target, **kwargs) 50749ad5e8SJohn Snow 51749ad5e8SJohn Snow 52a3d71595SJohn Snowclass Bitmap: 53a3d71595SJohn Snow def __init__(self, name, drive): 54a3d71595SJohn Snow self.name = name 55a3d71595SJohn Snow self.drive = drive 56a3d71595SJohn Snow self.num = 0 57a3d71595SJohn Snow self.backups = list() 58a3d71595SJohn Snow 59a3d71595SJohn Snow def base_target(self): 60a3d71595SJohn Snow return (self.drive['backup'], None) 61a3d71595SJohn Snow 62a3d71595SJohn Snow def new_target(self, num=None): 63a3d71595SJohn Snow if num is None: 64a3d71595SJohn Snow num = self.num 65a3d71595SJohn Snow self.num = num + 1 66a3d71595SJohn Snow base = os.path.join(iotests.test_dir, 67a3d71595SJohn Snow "%s.%s." % (self.drive['id'], self.name)) 68a3d71595SJohn Snow suff = "%i.%s" % (num, self.drive['fmt']) 69a3d71595SJohn Snow target = base + "inc" + suff 70a3d71595SJohn Snow reference = base + "ref" + suff 71a3d71595SJohn Snow self.backups.append((target, reference)) 72a3d71595SJohn Snow return (target, reference) 73a3d71595SJohn Snow 74a3d71595SJohn Snow def last_target(self): 75a3d71595SJohn Snow if self.backups: 76a3d71595SJohn Snow return self.backups[-1] 77a3d71595SJohn Snow return self.base_target() 78a3d71595SJohn Snow 79a3d71595SJohn Snow def del_target(self): 80a3d71595SJohn Snow for image in self.backups.pop(): 81a3d71595SJohn Snow try_remove(image) 82a3d71595SJohn Snow self.num -= 1 83a3d71595SJohn Snow 84a3d71595SJohn Snow def cleanup(self): 85a3d71595SJohn Snow for backup in self.backups: 86a3d71595SJohn Snow for image in backup: 87a3d71595SJohn Snow try_remove(image) 88a3d71595SJohn Snow 89a3d71595SJohn Snow 901b19bb9dSJohn Snowclass TestIncrementalBackupBase(iotests.QMPTestCase): 911b19bb9dSJohn Snow def __init__(self, *args): 921b19bb9dSJohn Snow super(TestIncrementalBackupBase, self).__init__(*args) 939f7264f5SJohn Snow self.bitmaps = list() 949f7264f5SJohn Snow self.files = list() 959f7264f5SJohn Snow self.drives = list() 969f7264f5SJohn Snow self.vm = iotests.VM() 979f7264f5SJohn Snow self.err_img = os.path.join(iotests.test_dir, 'err.%s' % iotests.imgfmt) 989f7264f5SJohn Snow 991b19bb9dSJohn Snow 1001b19bb9dSJohn Snow def setUp(self): 1019f7264f5SJohn Snow # Create a base image with a distinctive patterning 1029f7264f5SJohn Snow drive0 = self.add_node('drive0') 1039f7264f5SJohn Snow self.img_create(drive0['file'], drive0['fmt']) 1045c4343b8SVladimir Sementsov-Ogievskiy self.vm.add_drive(drive0['file'], opts='node-name=node0') 1051b19bb9dSJohn Snow self.write_default_pattern(drive0['file']) 1061b19bb9dSJohn Snow self.vm.launch() 1071b19bb9dSJohn Snow 1081b19bb9dSJohn Snow 1091b19bb9dSJohn Snow def write_default_pattern(self, target): 1101b19bb9dSJohn Snow io_write_patterns(target, (('0x41', 0, 512), 1119f7264f5SJohn Snow ('0xd5', '1M', '32k'), 1129f7264f5SJohn Snow ('0xdc', '32M', '124k'))) 1139f7264f5SJohn Snow 1149f7264f5SJohn Snow 1159f7264f5SJohn Snow def add_node(self, node_id, fmt=iotests.imgfmt, path=None, backup=None): 1169f7264f5SJohn Snow if path is None: 1179f7264f5SJohn Snow path = os.path.join(iotests.test_dir, '%s.%s' % (node_id, fmt)) 1189f7264f5SJohn Snow if backup is None: 1199f7264f5SJohn Snow backup = os.path.join(iotests.test_dir, 1209f7264f5SJohn Snow '%s.full.backup.%s' % (node_id, fmt)) 1219f7264f5SJohn Snow 1229f7264f5SJohn Snow self.drives.append({ 1239f7264f5SJohn Snow 'id': node_id, 1249f7264f5SJohn Snow 'file': path, 1259f7264f5SJohn Snow 'backup': backup, 1269f7264f5SJohn Snow 'fmt': fmt }) 1279f7264f5SJohn Snow return self.drives[-1] 1289f7264f5SJohn Snow 1299f7264f5SJohn Snow 1309f7264f5SJohn Snow def img_create(self, img, fmt=iotests.imgfmt, size='64M', 131cc199b16SJohn Snow parent=None, parentFormat=None, **kwargs): 132cc199b16SJohn Snow optargs = [] 13368474776SMax Reitz for k,v in kwargs.items(): 134cc199b16SJohn Snow optargs = optargs + ['-o', '%s=%s' % (k,v)] 135cc199b16SJohn Snow args = ['create', '-f', fmt] + optargs + [img, size] 1369f7264f5SJohn Snow if parent: 1379f7264f5SJohn Snow if parentFormat is None: 1389f7264f5SJohn Snow parentFormat = fmt 139cc199b16SJohn Snow args = args + ['-b', parent, '-F', parentFormat] 140cc199b16SJohn Snow iotests.qemu_img(*args) 1419f7264f5SJohn Snow self.files.append(img) 1429f7264f5SJohn Snow 143a3d71595SJohn Snow 144a3d71595SJohn Snow def do_qmp_backup(self, error='Input/output error', **kwargs): 145b6aed193SVladimir Sementsov-Ogievskiy self.vm.cmd('drive-backup', **kwargs) 146fc6c796fSJohn Snow return self.wait_qmp_backup(kwargs['device'], error) 147a3d71595SJohn Snow 148fc6c796fSJohn Snow 1491dac83f1SKevin Wolf def ignore_job_status_change_events(self): 1501dac83f1SKevin Wolf while True: 1511dac83f1SKevin Wolf e = self.vm.event_wait(name="JOB_STATUS_CHANGE") 1521dac83f1SKevin Wolf if e['data']['status'] == 'null': 1531dac83f1SKevin Wolf break 1541dac83f1SKevin Wolf 155fc6c796fSJohn Snow def wait_qmp_backup(self, device, error='Input/output error'): 156a3d71595SJohn Snow event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED", 157fc6c796fSJohn Snow match={'data': {'device': device}}) 158ff793890SJohn Snow self.assertNotEqual(event, None) 1591dac83f1SKevin Wolf self.ignore_job_status_change_events() 160a3d71595SJohn Snow 161a3d71595SJohn Snow try: 162a3d71595SJohn Snow failure = self.dictpath(event, 'data/error') 163a3d71595SJohn Snow except AssertionError: 164a3d71595SJohn Snow # Backup succeeded. 165a3d71595SJohn Snow self.assert_qmp(event, 'data/offset', event['data']['len']) 166a3d71595SJohn Snow return True 167a3d71595SJohn Snow else: 168a3d71595SJohn Snow # Backup failed. 169a3d71595SJohn Snow self.assert_qmp(event, 'data/error', error) 170a3d71595SJohn Snow return False 171a3d71595SJohn Snow 172a3d71595SJohn Snow 173fc6c796fSJohn Snow def wait_qmp_backup_cancelled(self, device): 174fc6c796fSJohn Snow event = self.vm.event_wait(name='BLOCK_JOB_CANCELLED', 175fc6c796fSJohn Snow match={'data': {'device': device}}) 176fc6c796fSJohn Snow self.assertNotEqual(event, None) 1771dac83f1SKevin Wolf self.ignore_job_status_change_events() 178fc6c796fSJohn Snow 179fc6c796fSJohn Snow 180a3d71595SJohn Snow def create_anchor_backup(self, drive=None): 181a3d71595SJohn Snow if drive is None: 182a3d71595SJohn Snow drive = self.drives[-1] 183eed87583SKevin Wolf res = self.do_qmp_backup(job_id=drive['id'], 184eed87583SKevin Wolf device=drive['id'], sync='full', 185a3d71595SJohn Snow format=drive['fmt'], target=drive['backup']) 186a3d71595SJohn Snow self.assertTrue(res) 187a3d71595SJohn Snow self.files.append(drive['backup']) 188a3d71595SJohn Snow return drive['backup'] 189a3d71595SJohn Snow 190a3d71595SJohn Snow 191a3d71595SJohn Snow def make_reference_backup(self, bitmap=None): 192a3d71595SJohn Snow if bitmap is None: 193a3d71595SJohn Snow bitmap = self.bitmaps[-1] 194a3d71595SJohn Snow _, reference = bitmap.last_target() 195eed87583SKevin Wolf res = self.do_qmp_backup(job_id=bitmap.drive['id'], 196eed87583SKevin Wolf device=bitmap.drive['id'], sync='full', 197a3d71595SJohn Snow format=bitmap.drive['fmt'], target=reference) 198a3d71595SJohn Snow self.assertTrue(res) 199a3d71595SJohn Snow 200a3d71595SJohn Snow 20159fc5d84SJohn Snow def add_bitmap(self, name, drive, **kwargs): 202a3d71595SJohn Snow bitmap = Bitmap(name, drive) 203a3d71595SJohn Snow self.bitmaps.append(bitmap) 204b6aed193SVladimir Sementsov-Ogievskiy self.vm.cmd('block-dirty-bitmap-add', node=drive['id'], 20559fc5d84SJohn Snow name=bitmap.name, **kwargs) 206a3d71595SJohn Snow return bitmap 207a3d71595SJohn Snow 208a3d71595SJohn Snow 2098a9cb864SMax Reitz def prepare_backup(self, bitmap=None, parent=None, **kwargs): 210a3d71595SJohn Snow if bitmap is None: 211a3d71595SJohn Snow bitmap = self.bitmaps[-1] 212a3d71595SJohn Snow if parent is None: 213a3d71595SJohn Snow parent, _ = bitmap.last_target() 214a3d71595SJohn Snow 215a3d71595SJohn Snow target, _ = bitmap.new_target() 2168a9cb864SMax Reitz self.img_create(target, bitmap.drive['fmt'], parent=parent, 2178a9cb864SMax Reitz **kwargs) 218a3d71595SJohn Snow return target 219a3d71595SJohn Snow 220a3d71595SJohn Snow 221a3d71595SJohn Snow def create_incremental(self, bitmap=None, parent=None, 2228a9cb864SMax Reitz parentFormat=None, validate=True, 2238a9cb864SMax Reitz target=None): 224a3d71595SJohn Snow if bitmap is None: 225a3d71595SJohn Snow bitmap = self.bitmaps[-1] 226a3d71595SJohn Snow if parent is None: 227a3d71595SJohn Snow parent, _ = bitmap.last_target() 228a3d71595SJohn Snow 2298a9cb864SMax Reitz if target is None: 230a3d71595SJohn Snow target = self.prepare_backup(bitmap, parent) 231eed87583SKevin Wolf res = self.do_qmp_backup(job_id=bitmap.drive['id'], 232eed87583SKevin Wolf device=bitmap.drive['id'], 2334b80ab2bSJohn Snow sync='incremental', bitmap=bitmap.name, 234a3d71595SJohn Snow format=bitmap.drive['fmt'], target=target, 235a3d71595SJohn Snow mode='existing') 236a3d71595SJohn Snow if not res: 237a3d71595SJohn Snow bitmap.del_target(); 238a3d71595SJohn Snow self.assertFalse(validate) 239a3d71595SJohn Snow else: 240a3d71595SJohn Snow self.make_reference_backup(bitmap) 241a3d71595SJohn Snow return res 242a3d71595SJohn Snow 243a3d71595SJohn Snow 244a3d71595SJohn Snow def check_backups(self): 245a3d71595SJohn Snow for bitmap in self.bitmaps: 246a3d71595SJohn Snow for incremental, reference in bitmap.backups: 247a3d71595SJohn Snow self.assertTrue(iotests.compare_images(incremental, reference)) 248a3d71595SJohn Snow last = bitmap.last_target()[0] 249a3d71595SJohn Snow self.assertTrue(iotests.compare_images(last, bitmap.drive['file'])) 250a3d71595SJohn Snow 251a3d71595SJohn Snow 252a3d71595SJohn Snow def hmp_io_writes(self, drive, patterns): 253a3d71595SJohn Snow for pattern in patterns: 254a3d71595SJohn Snow self.vm.hmp_qemu_io(drive, 'write -P%s %s %s' % pattern) 255a3d71595SJohn Snow self.vm.hmp_qemu_io(drive, 'flush') 256a3d71595SJohn Snow 257a3d71595SJohn Snow 25859fc5d84SJohn Snow def do_incremental_simple(self, **kwargs): 259a3d71595SJohn Snow self.create_anchor_backup() 26059fc5d84SJohn Snow self.add_bitmap('bitmap0', self.drives[0], **kwargs) 261a3d71595SJohn Snow 262a3d71595SJohn Snow # Sanity: Create a "hollow" incremental backup 263a3d71595SJohn Snow self.create_incremental() 264a3d71595SJohn Snow # Three writes: One complete overwrite, one new segment, 265a3d71595SJohn Snow # and one partial overlap. 266a3d71595SJohn Snow self.hmp_io_writes(self.drives[0]['id'], (('0xab', 0, 512), 267a3d71595SJohn Snow ('0xfe', '16M', '256k'), 268a3d71595SJohn Snow ('0x64', '32736k', '64k'))) 269a3d71595SJohn Snow self.create_incremental() 270a3d71595SJohn Snow # Three more writes, one of each kind, like above 271a3d71595SJohn Snow self.hmp_io_writes(self.drives[0]['id'], (('0x9a', 0, 512), 272a3d71595SJohn Snow ('0x55', '8M', '352k'), 273a3d71595SJohn Snow ('0x78', '15872k', '1M'))) 274a3d71595SJohn Snow self.create_incremental() 275a3d71595SJohn Snow self.vm.shutdown() 276a3d71595SJohn Snow self.check_backups() 277a3d71595SJohn Snow 278a3d71595SJohn Snow 2791b19bb9dSJohn Snow def tearDown(self): 2801b19bb9dSJohn Snow self.vm.shutdown() 2811b19bb9dSJohn Snow for bitmap in self.bitmaps: 2821b19bb9dSJohn Snow bitmap.cleanup() 2831b19bb9dSJohn Snow for filename in self.files: 2841b19bb9dSJohn Snow try_remove(filename) 2851b19bb9dSJohn Snow 2861b19bb9dSJohn Snow 2871b19bb9dSJohn Snow 2881b19bb9dSJohn Snowclass TestIncrementalBackup(TestIncrementalBackupBase): 28959fc5d84SJohn Snow def test_incremental_simple(self): 29059fc5d84SJohn Snow ''' 29159fc5d84SJohn Snow Test: Create and verify three incremental backups. 29259fc5d84SJohn Snow 29359fc5d84SJohn Snow Create a bitmap and a full backup before VM execution begins, 29459fc5d84SJohn Snow then create a series of three incremental backups "during execution," 29559fc5d84SJohn Snow i.e.; after IO requests begin modifying the drive. 29659fc5d84SJohn Snow ''' 29759fc5d84SJohn Snow return self.do_incremental_simple() 29859fc5d84SJohn Snow 29959fc5d84SJohn Snow 30059fc5d84SJohn Snow def test_small_granularity(self): 30159fc5d84SJohn Snow ''' 30259fc5d84SJohn Snow Test: Create and verify backups made with a small granularity bitmap. 30359fc5d84SJohn Snow 30459fc5d84SJohn Snow Perform the same test as test_incremental_simple, but with a granularity 30559fc5d84SJohn Snow of only 32KiB instead of the present default of 64KiB. 30659fc5d84SJohn Snow ''' 30759fc5d84SJohn Snow return self.do_incremental_simple(granularity=32768) 30859fc5d84SJohn Snow 30959fc5d84SJohn Snow 31059fc5d84SJohn Snow def test_large_granularity(self): 31159fc5d84SJohn Snow ''' 31259fc5d84SJohn Snow Test: Create and verify backups made with a large granularity bitmap. 31359fc5d84SJohn Snow 31459fc5d84SJohn Snow Perform the same test as test_incremental_simple, but with a granularity 31559fc5d84SJohn Snow of 128KiB instead of the present default of 64KiB. 31659fc5d84SJohn Snow ''' 31759fc5d84SJohn Snow return self.do_incremental_simple(granularity=131072) 31859fc5d84SJohn Snow 31959fc5d84SJohn Snow 320cc199b16SJohn Snow def test_larger_cluster_target(self): 321cc199b16SJohn Snow ''' 322cc199b16SJohn Snow Test: Create and verify backups made to a larger cluster size target. 323cc199b16SJohn Snow 324cc199b16SJohn Snow With a default granularity of 64KiB, verify that backups made to a 325cc199b16SJohn Snow larger cluster size target of 128KiB without a backing file works. 326cc199b16SJohn Snow ''' 327cc199b16SJohn Snow drive0 = self.drives[0] 328cc199b16SJohn Snow 329cc199b16SJohn Snow # Create a cluster_size=128k full backup / "anchor" backup 330cc199b16SJohn Snow self.img_create(drive0['backup'], cluster_size='128k') 331cc199b16SJohn Snow self.assertTrue(self.do_qmp_backup(device=drive0['id'], sync='full', 332cc199b16SJohn Snow format=drive0['fmt'], 333cc199b16SJohn Snow target=drive0['backup'], 334cc199b16SJohn Snow mode='existing')) 335cc199b16SJohn Snow 336cc199b16SJohn Snow # Create bitmap and dirty it with some new writes. 337cc199b16SJohn Snow # overwrite [32736, 32799] which will dirty bitmap clusters at 338cc199b16SJohn Snow # 32M-64K and 32M. 32M+64K will be left undirtied. 339cc199b16SJohn Snow bitmap0 = self.add_bitmap('bitmap0', drive0) 340cc199b16SJohn Snow self.hmp_io_writes(drive0['id'], 341cc199b16SJohn Snow (('0xab', 0, 512), 342cc199b16SJohn Snow ('0xfe', '16M', '256k'), 343cc199b16SJohn Snow ('0x64', '32736k', '64k'))) 3441e2b1f64SEric Blake # Check the dirty bitmap stats 3455c4343b8SVladimir Sementsov-Ogievskiy self.assertTrue(self.vm.check_bitmap_status( 3465c4343b8SVladimir Sementsov-Ogievskiy 'node0', bitmap0.name, { 3475c4343b8SVladimir Sementsov-Ogievskiy 'name': 'bitmap0', 3485c4343b8SVladimir Sementsov-Ogievskiy 'count': 458752, 3495c4343b8SVladimir Sementsov-Ogievskiy 'granularity': 65536, 3505c4343b8SVladimir Sementsov-Ogievskiy 'persistent': False 3515c4343b8SVladimir Sementsov-Ogievskiy })) 352cc199b16SJohn Snow 353cc199b16SJohn Snow # Prepare a cluster_size=128k backup target without a backing file. 354cc199b16SJohn Snow (target, _) = bitmap0.new_target() 355cc199b16SJohn Snow self.img_create(target, bitmap0.drive['fmt'], cluster_size='128k') 356cc199b16SJohn Snow 357cc199b16SJohn Snow # Perform Incremental Backup 358cc199b16SJohn Snow self.assertTrue(self.do_qmp_backup(device=bitmap0.drive['id'], 359cc199b16SJohn Snow sync='incremental', 360cc199b16SJohn Snow bitmap=bitmap0.name, 361cc199b16SJohn Snow format=bitmap0.drive['fmt'], 362cc199b16SJohn Snow target=target, 363cc199b16SJohn Snow mode='existing')) 364cc199b16SJohn Snow self.make_reference_backup(bitmap0) 365cc199b16SJohn Snow 366cc199b16SJohn Snow # Add the backing file, then compare and exit. 367cc199b16SJohn Snow iotests.qemu_img('rebase', '-f', drive0['fmt'], '-u', '-b', 368cc199b16SJohn Snow drive0['backup'], '-F', drive0['fmt'], target) 369cc199b16SJohn Snow self.vm.shutdown() 370cc199b16SJohn Snow self.check_backups() 371cc199b16SJohn Snow 372cc199b16SJohn Snow 373749ad5e8SJohn Snow def test_incremental_transaction(self): 374749ad5e8SJohn Snow '''Test: Verify backups made from transactionally created bitmaps. 375749ad5e8SJohn Snow 376749ad5e8SJohn Snow Create a bitmap "before" VM execution begins, then create a second 377749ad5e8SJohn Snow bitmap AFTER writes have already occurred. Use transactions to create 378749ad5e8SJohn Snow a full backup and synchronize both bitmaps to this backup. 379749ad5e8SJohn Snow Create an incremental backup through both bitmaps and verify that 380749ad5e8SJohn Snow both backups match the current drive0 image. 381749ad5e8SJohn Snow ''' 382749ad5e8SJohn Snow 383749ad5e8SJohn Snow drive0 = self.drives[0] 384749ad5e8SJohn Snow bitmap0 = self.add_bitmap('bitmap0', drive0) 385749ad5e8SJohn Snow self.hmp_io_writes(drive0['id'], (('0xab', 0, 512), 386749ad5e8SJohn Snow ('0xfe', '16M', '256k'), 387749ad5e8SJohn Snow ('0x64', '32736k', '64k'))) 388749ad5e8SJohn Snow bitmap1 = self.add_bitmap('bitmap1', drive0) 389749ad5e8SJohn Snow 390b6aed193SVladimir Sementsov-Ogievskiy self.vm.cmd('transaction', actions=[ 391749ad5e8SJohn Snow transaction_bitmap_clear(bitmap0.drive['id'], bitmap0.name), 392749ad5e8SJohn Snow transaction_bitmap_clear(bitmap1.drive['id'], bitmap1.name), 393749ad5e8SJohn Snow transaction_drive_backup(drive0['id'], drive0['backup'], 394749ad5e8SJohn Snow sync='full', format=drive0['fmt']) 395749ad5e8SJohn Snow ]) 396749ad5e8SJohn Snow self.wait_until_completed(drive0['id']) 397749ad5e8SJohn Snow self.files.append(drive0['backup']) 398749ad5e8SJohn Snow 399749ad5e8SJohn Snow self.hmp_io_writes(drive0['id'], (('0x9a', 0, 512), 400749ad5e8SJohn Snow ('0x55', '8M', '352k'), 401749ad5e8SJohn Snow ('0x78', '15872k', '1M'))) 402749ad5e8SJohn Snow # Both bitmaps should be correctly in sync. 403749ad5e8SJohn Snow self.create_incremental(bitmap0) 404749ad5e8SJohn Snow self.create_incremental(bitmap1) 405749ad5e8SJohn Snow self.vm.shutdown() 406749ad5e8SJohn Snow self.check_backups() 407749ad5e8SJohn Snow 408749ad5e8SJohn Snow 4090aef09b9SJohn Snow def do_transaction_failure_test(self, race=False): 410fc6c796fSJohn Snow # Create a second drive, with pattern: 411fc6c796fSJohn Snow drive1 = self.add_node('drive1') 412fc6c796fSJohn Snow self.img_create(drive1['file'], drive1['fmt']) 413fc6c796fSJohn Snow io_write_patterns(drive1['file'], (('0x14', 0, 512), 414fc6c796fSJohn Snow ('0x5d', '1M', '32k'), 415fc6c796fSJohn Snow ('0xcd', '32M', '124k'))) 416fc6c796fSJohn Snow 417fc6c796fSJohn Snow # Create a blkdebug interface to this img as 'drive1' 418b6aed193SVladimir Sementsov-Ogievskiy self.vm.cmd('blockdev-add', 4190153d2f5SKevin Wolf node_name=drive1['id'], 4200153d2f5SKevin Wolf driver=drive1['fmt'], 4210153d2f5SKevin Wolf file={ 422fc6c796fSJohn Snow 'driver': 'blkdebug', 423fc6c796fSJohn Snow 'image': { 424fc6c796fSJohn Snow 'driver': 'file', 425fc6c796fSJohn Snow 'filename': drive1['file'] 426fc6c796fSJohn Snow }, 427fc6c796fSJohn Snow 'set-state': [{ 428fc6c796fSJohn Snow 'event': 'flush_to_disk', 429fc6c796fSJohn Snow 'state': 1, 430fc6c796fSJohn Snow 'new_state': 2 431fc6c796fSJohn Snow }], 432fc6c796fSJohn Snow 'inject-error': [{ 433fc6c796fSJohn Snow 'event': 'read_aio', 434fc6c796fSJohn Snow 'errno': 5, 435fc6c796fSJohn Snow 'state': 2, 436fc6c796fSJohn Snow 'immediately': False, 437fc6c796fSJohn Snow 'once': True 438fc6c796fSJohn Snow }], 439fc6c796fSJohn Snow } 4400153d2f5SKevin Wolf ) 441fc6c796fSJohn Snow 442fc6c796fSJohn Snow # Create bitmaps and full backups for both drives 443fc6c796fSJohn Snow drive0 = self.drives[0] 444fc6c796fSJohn Snow dr0bm0 = self.add_bitmap('bitmap0', drive0) 445fc6c796fSJohn Snow dr1bm0 = self.add_bitmap('bitmap0', drive1) 446fc6c796fSJohn Snow self.create_anchor_backup(drive0) 447fc6c796fSJohn Snow self.create_anchor_backup(drive1) 448fc6c796fSJohn Snow self.assert_no_active_block_jobs() 449fc6c796fSJohn Snow self.assertFalse(self.vm.get_qmp_events(wait=False)) 450fc6c796fSJohn Snow 451fc6c796fSJohn Snow # Emulate some writes 4520aef09b9SJohn Snow if not race: 453fc6c796fSJohn Snow self.hmp_io_writes(drive0['id'], (('0xab', 0, 512), 454fc6c796fSJohn Snow ('0xfe', '16M', '256k'), 455fc6c796fSJohn Snow ('0x64', '32736k', '64k'))) 456fc6c796fSJohn Snow self.hmp_io_writes(drive1['id'], (('0xba', 0, 512), 457fc6c796fSJohn Snow ('0xef', '16M', '256k'), 458fc6c796fSJohn Snow ('0x46', '32736k', '64k'))) 459fc6c796fSJohn Snow 460fc6c796fSJohn Snow # Create incremental backup targets 461fc6c796fSJohn Snow target0 = self.prepare_backup(dr0bm0) 462fc6c796fSJohn Snow target1 = self.prepare_backup(dr1bm0) 463fc6c796fSJohn Snow 464fc6c796fSJohn Snow # Ask for a new incremental backup per-each drive, 4650aef09b9SJohn Snow # expecting drive1's backup to fail. In the 'race' test, 4660aef09b9SJohn Snow # we expect drive1 to attempt to cancel the empty drive0 job. 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 ] 475b6aed193SVladimir Sementsov-Ogievskiy self.vm.cmd('transaction', actions=transaction, 476fc6c796fSJohn Snow properties={'completion-mode': 'grouped'} ) 477fc6c796fSJohn Snow 478fc6c796fSJohn Snow # Observe that drive0's backup is cancelled and drive1 completes with 479fc6c796fSJohn Snow # an error. 480fc6c796fSJohn Snow self.wait_qmp_backup_cancelled(drive0['id']) 481fc6c796fSJohn Snow self.assertFalse(self.wait_qmp_backup(drive1['id'])) 482fc6c796fSJohn Snow error = self.vm.event_wait('BLOCK_JOB_ERROR') 483fc6c796fSJohn Snow self.assert_qmp(error, 'data', {'device': drive1['id'], 484fc6c796fSJohn Snow 'action': 'report', 485fc6c796fSJohn Snow 'operation': 'read'}) 486fc6c796fSJohn Snow self.assertFalse(self.vm.get_qmp_events(wait=False)) 487fc6c796fSJohn Snow self.assert_no_active_block_jobs() 488fc6c796fSJohn Snow 489fc6c796fSJohn Snow # Delete drive0's successful target and eliminate our record of the 4900aef09b9SJohn Snow # unsuccessful drive1 target. 491fc6c796fSJohn Snow dr0bm0.del_target() 492fc6c796fSJohn Snow dr1bm0.del_target() 4930aef09b9SJohn Snow if race: 4940aef09b9SJohn Snow # Don't re-run the transaction, we only wanted to test the race. 4950aef09b9SJohn Snow self.vm.shutdown() 4960aef09b9SJohn Snow return 4970aef09b9SJohn Snow 4980aef09b9SJohn Snow # Re-run the same transaction: 499fc6c796fSJohn Snow target0 = self.prepare_backup(dr0bm0) 500fc6c796fSJohn Snow target1 = self.prepare_backup(dr1bm0) 501fc6c796fSJohn Snow 502fc6c796fSJohn Snow # Re-run the exact same transaction. 503b6aed193SVladimir Sementsov-Ogievskiy self.vm.cmd('transaction', actions=transaction, 504fc6c796fSJohn Snow properties={'completion-mode':'grouped'}) 505fc6c796fSJohn Snow 506fc6c796fSJohn Snow # Both should complete successfully this time. 507fc6c796fSJohn Snow self.assertTrue(self.wait_qmp_backup(drive0['id'])) 508fc6c796fSJohn Snow self.assertTrue(self.wait_qmp_backup(drive1['id'])) 509fc6c796fSJohn Snow self.make_reference_backup(dr0bm0) 510fc6c796fSJohn Snow self.make_reference_backup(dr1bm0) 511fc6c796fSJohn Snow self.assertFalse(self.vm.get_qmp_events(wait=False)) 512fc6c796fSJohn Snow self.assert_no_active_block_jobs() 513fc6c796fSJohn Snow 514fc6c796fSJohn Snow # And the images should of course validate. 515fc6c796fSJohn Snow self.vm.shutdown() 516fc6c796fSJohn Snow self.check_backups() 517fc6c796fSJohn Snow 5180aef09b9SJohn Snow def test_transaction_failure(self): 5190aef09b9SJohn Snow '''Test: Verify backups made from a transaction that partially fails. 5200aef09b9SJohn Snow 5210aef09b9SJohn Snow Add a second drive with its own unique pattern, and add a bitmap to each 5220aef09b9SJohn Snow drive. Use blkdebug to interfere with the backup on just one drive and 5230aef09b9SJohn Snow attempt to create a coherent incremental backup across both drives. 5240aef09b9SJohn Snow 5250aef09b9SJohn Snow verify a failure in one but not both, then delete the failed stubs and 5260aef09b9SJohn Snow re-run the same transaction. 5270aef09b9SJohn Snow 5280aef09b9SJohn Snow verify that both incrementals are created successfully. 5290aef09b9SJohn Snow ''' 5300aef09b9SJohn Snow self.do_transaction_failure_test() 5310aef09b9SJohn Snow 5320aef09b9SJohn Snow def test_transaction_failure_race(self): 5330aef09b9SJohn Snow '''Test: Verify that transactions with jobs that have no data to 5340aef09b9SJohn Snow transfer do not cause race conditions in the cancellation of the entire 5350aef09b9SJohn Snow transaction job group. 5360aef09b9SJohn Snow ''' 5370aef09b9SJohn Snow self.do_transaction_failure_test(race=True) 5380aef09b9SJohn Snow 539fc6c796fSJohn Snow 5409f7264f5SJohn Snow def test_sync_dirty_bitmap_missing(self): 5419f7264f5SJohn Snow self.assert_no_active_block_jobs() 5429f7264f5SJohn Snow self.files.append(self.err_img) 5439f7264f5SJohn Snow result = self.vm.qmp('drive-backup', device=self.drives[0]['id'], 5444b80ab2bSJohn Snow sync='incremental', format=self.drives[0]['fmt'], 5459f7264f5SJohn Snow target=self.err_img) 5469f7264f5SJohn Snow self.assert_qmp(result, 'error/class', 'GenericError') 5479f7264f5SJohn Snow 5489f7264f5SJohn Snow 5499f7264f5SJohn Snow def test_sync_dirty_bitmap_not_found(self): 5509f7264f5SJohn Snow self.assert_no_active_block_jobs() 5519f7264f5SJohn Snow self.files.append(self.err_img) 5529f7264f5SJohn Snow result = self.vm.qmp('drive-backup', device=self.drives[0]['id'], 5534b80ab2bSJohn Snow sync='incremental', bitmap='unknown', 5549f7264f5SJohn Snow format=self.drives[0]['fmt'], target=self.err_img) 5559f7264f5SJohn Snow self.assert_qmp(result, 'error/class', 'GenericError') 5569f7264f5SJohn Snow 5579f7264f5SJohn Snow 55859fc5d84SJohn Snow def test_sync_dirty_bitmap_bad_granularity(self): 55959fc5d84SJohn Snow ''' 56059fc5d84SJohn Snow Test: Test what happens if we provide an improper granularity. 56159fc5d84SJohn Snow 56259fc5d84SJohn Snow The granularity must always be a power of 2. 56359fc5d84SJohn Snow ''' 56459fc5d84SJohn Snow self.assert_no_active_block_jobs() 565b6aed193SVladimir Sementsov-Ogievskiy self.assertRaises(ExecuteError, self.add_bitmap, 56659fc5d84SJohn Snow 'bitmap0', self.drives[0], 56759fc5d84SJohn Snow granularity=64000) 56859fc5d84SJohn Snow 5698a9cb864SMax Reitz def test_growing_before_backup(self): 5708a9cb864SMax Reitz ''' 5718a9cb864SMax Reitz Test: Add a bitmap, truncate the image, write past the old 5728a9cb864SMax Reitz end, do a backup. 5738a9cb864SMax Reitz 5748a9cb864SMax Reitz Incremental backup should not ignore dirty bits past the old 5758a9cb864SMax Reitz image end. 5768a9cb864SMax Reitz ''' 5778a9cb864SMax Reitz self.assert_no_active_block_jobs() 5788a9cb864SMax Reitz 5798a9cb864SMax Reitz self.create_anchor_backup() 5808a9cb864SMax Reitz 5818a9cb864SMax Reitz self.add_bitmap('bitmap0', self.drives[0]) 5828a9cb864SMax Reitz 583b6aed193SVladimir Sementsov-Ogievskiy self.vm.cmd('block_resize', device=self.drives[0]['id'], 5848a9cb864SMax Reitz size=(65 * 1048576)) 5858a9cb864SMax Reitz 5868a9cb864SMax Reitz # Dirty the image past the old end 5878a9cb864SMax Reitz self.vm.hmp_qemu_io(self.drives[0]['id'], 'write 64M 64k') 5888a9cb864SMax Reitz 5898a9cb864SMax Reitz target = self.prepare_backup(size='65M') 5908a9cb864SMax Reitz self.create_incremental(target=target) 5918a9cb864SMax Reitz 5928a9cb864SMax Reitz self.vm.shutdown() 5938a9cb864SMax Reitz self.check_backups() 5948a9cb864SMax Reitz 59559fc5d84SJohn Snow 596ce2cbc49SJohn Snowclass TestIncrementalBackupBlkdebug(TestIncrementalBackupBase): 597ce2cbc49SJohn Snow '''Incremental backup tests that utilize a BlkDebug filter on drive0.''' 598ce2cbc49SJohn Snow 59935cea223SJohn Snow def setUp(self): 60035cea223SJohn Snow drive0 = self.add_node('drive0') 60135cea223SJohn Snow self.img_create(drive0['file'], drive0['fmt']) 60235cea223SJohn Snow self.write_default_pattern(drive0['file']) 60335cea223SJohn Snow self.vm.launch() 60435cea223SJohn Snow 605ce2cbc49SJohn Snow def test_incremental_failure(self): 606ce2cbc49SJohn Snow '''Test: Verify backups made after a failure are correct. 607ce2cbc49SJohn Snow 608ce2cbc49SJohn Snow Simulate a failure during an incremental backup block job, 609ce2cbc49SJohn Snow emulate additional writes, then create another incremental backup 610ce2cbc49SJohn Snow afterwards and verify that the backup created is correct. 611ce2cbc49SJohn Snow ''' 612ce2cbc49SJohn Snow 61335cea223SJohn Snow drive0 = self.drives[0] 614b6aed193SVladimir Sementsov-Ogievskiy self.vm.cmd('blockdev-add', 6150153d2f5SKevin Wolf node_name=drive0['id'], 6160153d2f5SKevin Wolf driver=drive0['fmt'], 6170153d2f5SKevin Wolf file={ 618ce2cbc49SJohn Snow 'driver': 'blkdebug', 619ce2cbc49SJohn Snow 'image': { 620ce2cbc49SJohn Snow 'driver': 'file', 62135cea223SJohn Snow 'filename': drive0['file'] 622ce2cbc49SJohn Snow }, 623ce2cbc49SJohn Snow 'set-state': [{ 624ce2cbc49SJohn Snow 'event': 'flush_to_disk', 625ce2cbc49SJohn Snow 'state': 1, 626ce2cbc49SJohn Snow 'new_state': 2 627ce2cbc49SJohn Snow }], 628ce2cbc49SJohn Snow 'inject-error': [{ 629ce2cbc49SJohn Snow 'event': 'read_aio', 630ce2cbc49SJohn Snow 'errno': 5, 631ce2cbc49SJohn Snow 'state': 2, 632ce2cbc49SJohn Snow 'immediately': False, 633ce2cbc49SJohn Snow 'once': True 634ce2cbc49SJohn Snow }], 635ce2cbc49SJohn Snow } 6360153d2f5SKevin Wolf ) 637ce2cbc49SJohn Snow 63835cea223SJohn Snow self.create_anchor_backup(drive0) 63935cea223SJohn Snow self.add_bitmap('bitmap0', drive0) 640ce2cbc49SJohn Snow # Note: at this point, during a normal execution, 641ce2cbc49SJohn Snow # Assume that the VM resumes and begins issuing IO requests here. 642ce2cbc49SJohn Snow 64335cea223SJohn Snow self.hmp_io_writes(drive0['id'], (('0xab', 0, 512), 644ce2cbc49SJohn Snow ('0xfe', '16M', '256k'), 645ce2cbc49SJohn Snow ('0x64', '32736k', '64k'))) 646ce2cbc49SJohn Snow 647ce2cbc49SJohn Snow result = self.create_incremental(validate=False) 648ce2cbc49SJohn Snow self.assertFalse(result) 64935cea223SJohn Snow self.hmp_io_writes(drive0['id'], (('0x9a', 0, 512), 650ce2cbc49SJohn Snow ('0x55', '8M', '352k'), 651ce2cbc49SJohn Snow ('0x78', '15872k', '1M'))) 652ce2cbc49SJohn Snow self.create_incremental() 653ce2cbc49SJohn Snow self.vm.shutdown() 654ce2cbc49SJohn Snow self.check_backups() 655ce2cbc49SJohn Snow 656c61b198bSJohn Snow def test_incremental_pause(self): 657c61b198bSJohn Snow """ 658c61b198bSJohn Snow Test an incremental backup that errors into a pause and is resumed. 659c61b198bSJohn Snow """ 660c61b198bSJohn Snow 661c61b198bSJohn Snow drive0 = self.drives[0] 6625c4343b8SVladimir Sementsov-Ogievskiy # NB: The blkdebug script here looks for a "flush, read" pattern. 6635c4343b8SVladimir Sementsov-Ogievskiy # The flush occurs in hmp_io_writes, and the read during the block job. 664b6aed193SVladimir Sementsov-Ogievskiy self.vm.cmd('blockdev-add', 665c61b198bSJohn Snow node_name=drive0['id'], 666c61b198bSJohn Snow driver=drive0['fmt'], 667c61b198bSJohn Snow file={ 668c61b198bSJohn Snow 'driver': 'blkdebug', 669c61b198bSJohn Snow 'image': { 670c61b198bSJohn Snow 'driver': 'file', 671c61b198bSJohn Snow 'filename': drive0['file'] 672c61b198bSJohn Snow }, 673c61b198bSJohn Snow 'set-state': [{ 674c61b198bSJohn Snow 'event': 'flush_to_disk', 675c61b198bSJohn Snow 'state': 1, 676c61b198bSJohn Snow 'new_state': 2 677c61b198bSJohn Snow }], 678c61b198bSJohn Snow 'inject-error': [{ 679c61b198bSJohn Snow 'event': 'read_aio', 680c61b198bSJohn Snow 'errno': 5, 6815c4343b8SVladimir Sementsov-Ogievskiy 'state': 2, 682c61b198bSJohn Snow 'immediately': False, 683c61b198bSJohn Snow 'once': True 684c61b198bSJohn Snow }], 685c61b198bSJohn Snow }) 686c61b198bSJohn Snow self.create_anchor_backup(drive0) 687c61b198bSJohn Snow bitmap = self.add_bitmap('bitmap0', drive0) 688c61b198bSJohn Snow 689c61b198bSJohn Snow # Emulate guest activity 690c61b198bSJohn Snow self.hmp_io_writes(drive0['id'], (('0xab', 0, 512), 691c61b198bSJohn Snow ('0xfe', '16M', '256k'), 692c61b198bSJohn Snow ('0x64', '32736k', '64k'))) 693c61b198bSJohn Snow 694c61b198bSJohn Snow # Bitmap Status Check 6955c4343b8SVladimir Sementsov-Ogievskiy self.assertTrue(self.vm.check_bitmap_status( 6965c4343b8SVladimir Sementsov-Ogievskiy drive0['id'], bitmap.name, { 6975c4343b8SVladimir Sementsov-Ogievskiy 'count': 458752, 6985c4343b8SVladimir Sementsov-Ogievskiy 'granularity': 65536, 6995c4343b8SVladimir Sementsov-Ogievskiy 'busy': False, 7005c4343b8SVladimir Sementsov-Ogievskiy 'recording': True 7015c4343b8SVladimir Sementsov-Ogievskiy })) 702c61b198bSJohn Snow 703c61b198bSJohn Snow # Start backup 704c61b198bSJohn Snow parent, _ = bitmap.last_target() 705c61b198bSJohn Snow target = self.prepare_backup(bitmap, parent) 706b6aed193SVladimir Sementsov-Ogievskiy self.vm.cmd('drive-backup', 707c61b198bSJohn Snow job_id=bitmap.drive['id'], 708c61b198bSJohn Snow device=bitmap.drive['id'], 709c61b198bSJohn Snow sync='incremental', 710c61b198bSJohn Snow bitmap=bitmap.name, 711c61b198bSJohn Snow format=bitmap.drive['fmt'], 712c61b198bSJohn Snow target=target, 713c61b198bSJohn Snow mode='existing', 714c61b198bSJohn Snow on_source_error='stop') 715c61b198bSJohn Snow 716c61b198bSJohn Snow # Wait for the error 717c61b198bSJohn Snow event = self.vm.event_wait(name="BLOCK_JOB_ERROR", 718c61b198bSJohn Snow match={"data":{"device":bitmap.drive['id']}}) 719c61b198bSJohn Snow self.assert_qmp(event, 'data', {'device': bitmap.drive['id'], 720c61b198bSJohn Snow 'action': 'stop', 721c61b198bSJohn Snow 'operation': 'read'}) 722c61b198bSJohn Snow 723c61b198bSJohn Snow # Bitmap Status Check 7245c4343b8SVladimir Sementsov-Ogievskiy self.assertTrue(self.vm.check_bitmap_status( 7255c4343b8SVladimir Sementsov-Ogievskiy drive0['id'], bitmap.name, { 7265c4343b8SVladimir Sementsov-Ogievskiy 'count': 458752, 7275c4343b8SVladimir Sementsov-Ogievskiy 'granularity': 65536, 7285c4343b8SVladimir Sementsov-Ogievskiy 'busy': True, 7295c4343b8SVladimir Sementsov-Ogievskiy 'recording': True 7305c4343b8SVladimir Sementsov-Ogievskiy })) 731c61b198bSJohn Snow 732c61b198bSJohn Snow # Resume and check incremental backup for consistency 733b6aed193SVladimir Sementsov-Ogievskiy self.vm.cmd('block-job-resume', device=bitmap.drive['id']) 734c61b198bSJohn Snow self.wait_qmp_backup(bitmap.drive['id']) 735c61b198bSJohn Snow 736c61b198bSJohn Snow # Bitmap Status Check 7375c4343b8SVladimir Sementsov-Ogievskiy self.assertTrue(self.vm.check_bitmap_status( 7385c4343b8SVladimir Sementsov-Ogievskiy drive0['id'], bitmap.name, { 7395c4343b8SVladimir Sementsov-Ogievskiy 'count': 0, 7405c4343b8SVladimir Sementsov-Ogievskiy 'granularity': 65536, 7415c4343b8SVladimir Sementsov-Ogievskiy 'busy': False, 7425c4343b8SVladimir Sementsov-Ogievskiy 'recording': True 7435c4343b8SVladimir Sementsov-Ogievskiy })) 744c61b198bSJohn Snow 745c61b198bSJohn Snow # Finalize / Cleanup 746c61b198bSJohn Snow self.make_reference_backup(bitmap) 747c61b198bSJohn Snow self.vm.shutdown() 748c61b198bSJohn Snow self.check_backups() 749c61b198bSJohn Snow 750ce2cbc49SJohn Snow 7519f7264f5SJohn Snowif __name__ == '__main__': 752103cbc77SMax Reitz iotests.main(supported_fmts=['qcow2'], 753103cbc77SMax Reitz supported_protocols=['file']) 754