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 39a3d71595SJohn Snowclass Bitmap: 40a3d71595SJohn Snow def __init__(self, name, drive): 41a3d71595SJohn Snow self.name = name 42a3d71595SJohn Snow self.drive = drive 43a3d71595SJohn Snow self.num = 0 44a3d71595SJohn Snow self.backups = list() 45a3d71595SJohn Snow 46a3d71595SJohn Snow def base_target(self): 47a3d71595SJohn Snow return (self.drive['backup'], None) 48a3d71595SJohn Snow 49a3d71595SJohn Snow def new_target(self, num=None): 50a3d71595SJohn Snow if num is None: 51a3d71595SJohn Snow num = self.num 52a3d71595SJohn Snow self.num = num + 1 53a3d71595SJohn Snow base = os.path.join(iotests.test_dir, 54a3d71595SJohn Snow "%s.%s." % (self.drive['id'], self.name)) 55a3d71595SJohn Snow suff = "%i.%s" % (num, self.drive['fmt']) 56a3d71595SJohn Snow target = base + "inc" + suff 57a3d71595SJohn Snow reference = base + "ref" + suff 58a3d71595SJohn Snow self.backups.append((target, reference)) 59a3d71595SJohn Snow return (target, reference) 60a3d71595SJohn Snow 61a3d71595SJohn Snow def last_target(self): 62a3d71595SJohn Snow if self.backups: 63a3d71595SJohn Snow return self.backups[-1] 64a3d71595SJohn Snow return self.base_target() 65a3d71595SJohn Snow 66a3d71595SJohn Snow def del_target(self): 67a3d71595SJohn Snow for image in self.backups.pop(): 68a3d71595SJohn Snow try_remove(image) 69a3d71595SJohn Snow self.num -= 1 70a3d71595SJohn Snow 71a3d71595SJohn Snow def cleanup(self): 72a3d71595SJohn Snow for backup in self.backups: 73a3d71595SJohn Snow for image in backup: 74a3d71595SJohn Snow try_remove(image) 75a3d71595SJohn Snow 76a3d71595SJohn Snow 779f7264f5SJohn Snowclass TestIncrementalBackup(iotests.QMPTestCase): 789f7264f5SJohn Snow def setUp(self): 799f7264f5SJohn Snow self.bitmaps = list() 809f7264f5SJohn Snow self.files = list() 819f7264f5SJohn Snow self.drives = list() 829f7264f5SJohn Snow self.vm = iotests.VM() 839f7264f5SJohn Snow self.err_img = os.path.join(iotests.test_dir, 'err.%s' % iotests.imgfmt) 849f7264f5SJohn Snow 859f7264f5SJohn Snow # Create a base image with a distinctive patterning 869f7264f5SJohn Snow drive0 = self.add_node('drive0') 879f7264f5SJohn Snow self.img_create(drive0['file'], drive0['fmt']) 889f7264f5SJohn Snow self.vm.add_drive(drive0['file']) 899f7264f5SJohn Snow io_write_patterns(drive0['file'], (('0x41', 0, 512), 909f7264f5SJohn Snow ('0xd5', '1M', '32k'), 919f7264f5SJohn Snow ('0xdc', '32M', '124k'))) 929f7264f5SJohn Snow self.vm.launch() 939f7264f5SJohn Snow 949f7264f5SJohn Snow 959f7264f5SJohn Snow def add_node(self, node_id, fmt=iotests.imgfmt, path=None, backup=None): 969f7264f5SJohn Snow if path is None: 979f7264f5SJohn Snow path = os.path.join(iotests.test_dir, '%s.%s' % (node_id, fmt)) 989f7264f5SJohn Snow if backup is None: 999f7264f5SJohn Snow backup = os.path.join(iotests.test_dir, 1009f7264f5SJohn Snow '%s.full.backup.%s' % (node_id, fmt)) 1019f7264f5SJohn Snow 1029f7264f5SJohn Snow self.drives.append({ 1039f7264f5SJohn Snow 'id': node_id, 1049f7264f5SJohn Snow 'file': path, 1059f7264f5SJohn Snow 'backup': backup, 1069f7264f5SJohn Snow 'fmt': fmt }) 1079f7264f5SJohn Snow return self.drives[-1] 1089f7264f5SJohn Snow 1099f7264f5SJohn Snow 1109f7264f5SJohn Snow def img_create(self, img, fmt=iotests.imgfmt, size='64M', 1119f7264f5SJohn Snow parent=None, parentFormat=None): 1129f7264f5SJohn Snow if parent: 1139f7264f5SJohn Snow if parentFormat is None: 1149f7264f5SJohn Snow parentFormat = fmt 1159f7264f5SJohn Snow iotests.qemu_img('create', '-f', fmt, img, size, 1169f7264f5SJohn Snow '-b', parent, '-F', parentFormat) 1179f7264f5SJohn Snow else: 1189f7264f5SJohn Snow iotests.qemu_img('create', '-f', fmt, img, size) 1199f7264f5SJohn Snow self.files.append(img) 1209f7264f5SJohn Snow 121a3d71595SJohn Snow 122a3d71595SJohn Snow def do_qmp_backup(self, error='Input/output error', **kwargs): 123a3d71595SJohn Snow res = self.vm.qmp('drive-backup', **kwargs) 124a3d71595SJohn Snow self.assert_qmp(res, 'return', {}) 125a3d71595SJohn Snow 126a3d71595SJohn Snow event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED", 127a3d71595SJohn Snow match={'data': {'device': kwargs['device']}}) 128a3d71595SJohn Snow self.assertIsNotNone(event) 129a3d71595SJohn Snow 130a3d71595SJohn Snow try: 131a3d71595SJohn Snow failure = self.dictpath(event, 'data/error') 132a3d71595SJohn Snow except AssertionError: 133a3d71595SJohn Snow # Backup succeeded. 134a3d71595SJohn Snow self.assert_qmp(event, 'data/offset', event['data']['len']) 135a3d71595SJohn Snow return True 136a3d71595SJohn Snow else: 137a3d71595SJohn Snow # Backup failed. 138a3d71595SJohn Snow self.assert_qmp(event, 'data/error', error) 139a3d71595SJohn Snow return False 140a3d71595SJohn Snow 141a3d71595SJohn Snow 142a3d71595SJohn Snow def create_anchor_backup(self, drive=None): 143a3d71595SJohn Snow if drive is None: 144a3d71595SJohn Snow drive = self.drives[-1] 145a3d71595SJohn Snow res = self.do_qmp_backup(device=drive['id'], sync='full', 146a3d71595SJohn Snow format=drive['fmt'], target=drive['backup']) 147a3d71595SJohn Snow self.assertTrue(res) 148a3d71595SJohn Snow self.files.append(drive['backup']) 149a3d71595SJohn Snow return drive['backup'] 150a3d71595SJohn Snow 151a3d71595SJohn Snow 152a3d71595SJohn Snow def make_reference_backup(self, bitmap=None): 153a3d71595SJohn Snow if bitmap is None: 154a3d71595SJohn Snow bitmap = self.bitmaps[-1] 155a3d71595SJohn Snow _, reference = bitmap.last_target() 156a3d71595SJohn Snow res = self.do_qmp_backup(device=bitmap.drive['id'], sync='full', 157a3d71595SJohn Snow format=bitmap.drive['fmt'], target=reference) 158a3d71595SJohn Snow self.assertTrue(res) 159a3d71595SJohn Snow 160a3d71595SJohn Snow 161a3d71595SJohn Snow def add_bitmap(self, name, drive): 162a3d71595SJohn Snow bitmap = Bitmap(name, drive) 163a3d71595SJohn Snow self.bitmaps.append(bitmap) 164a3d71595SJohn Snow result = self.vm.qmp('block-dirty-bitmap-add', node=drive['id'], 165a3d71595SJohn Snow name=bitmap.name) 166a3d71595SJohn Snow self.assert_qmp(result, 'return', {}) 167a3d71595SJohn Snow return bitmap 168a3d71595SJohn Snow 169a3d71595SJohn Snow 170a3d71595SJohn Snow def prepare_backup(self, bitmap=None, parent=None): 171a3d71595SJohn Snow if bitmap is None: 172a3d71595SJohn Snow bitmap = self.bitmaps[-1] 173a3d71595SJohn Snow if parent is None: 174a3d71595SJohn Snow parent, _ = bitmap.last_target() 175a3d71595SJohn Snow 176a3d71595SJohn Snow target, _ = bitmap.new_target() 177a3d71595SJohn Snow self.img_create(target, bitmap.drive['fmt'], parent=parent) 178a3d71595SJohn Snow return target 179a3d71595SJohn Snow 180a3d71595SJohn Snow 181a3d71595SJohn Snow def create_incremental(self, bitmap=None, parent=None, 182a3d71595SJohn Snow parentFormat=None, validate=True): 183a3d71595SJohn Snow if bitmap is None: 184a3d71595SJohn Snow bitmap = self.bitmaps[-1] 185a3d71595SJohn Snow if parent is None: 186a3d71595SJohn Snow parent, _ = bitmap.last_target() 187a3d71595SJohn Snow 188a3d71595SJohn Snow target = self.prepare_backup(bitmap, parent) 189a3d71595SJohn Snow res = self.do_qmp_backup(device=bitmap.drive['id'], 190a3d71595SJohn Snow sync='dirty-bitmap', bitmap=bitmap.name, 191a3d71595SJohn Snow format=bitmap.drive['fmt'], target=target, 192a3d71595SJohn Snow mode='existing') 193a3d71595SJohn Snow if not res: 194a3d71595SJohn Snow bitmap.del_target(); 195a3d71595SJohn Snow self.assertFalse(validate) 196a3d71595SJohn Snow else: 197a3d71595SJohn Snow self.make_reference_backup(bitmap) 198a3d71595SJohn Snow return res 199a3d71595SJohn Snow 200a3d71595SJohn Snow 201a3d71595SJohn Snow def check_backups(self): 202a3d71595SJohn Snow for bitmap in self.bitmaps: 203a3d71595SJohn Snow for incremental, reference in bitmap.backups: 204a3d71595SJohn Snow self.assertTrue(iotests.compare_images(incremental, reference)) 205a3d71595SJohn Snow last = bitmap.last_target()[0] 206a3d71595SJohn Snow self.assertTrue(iotests.compare_images(last, bitmap.drive['file'])) 207a3d71595SJohn Snow 208a3d71595SJohn Snow 209a3d71595SJohn Snow def hmp_io_writes(self, drive, patterns): 210a3d71595SJohn Snow for pattern in patterns: 211a3d71595SJohn Snow self.vm.hmp_qemu_io(drive, 'write -P%s %s %s' % pattern) 212a3d71595SJohn Snow self.vm.hmp_qemu_io(drive, 'flush') 213a3d71595SJohn Snow 214a3d71595SJohn Snow 215a3d71595SJohn Snow def test_incremental_simple(self): 216a3d71595SJohn Snow ''' 217a3d71595SJohn Snow Test: Create and verify three incremental backups. 218a3d71595SJohn Snow 219a3d71595SJohn Snow Create a bitmap and a full backup before VM execution begins, 220a3d71595SJohn Snow then create a series of three incremental backups "during execution," 221a3d71595SJohn Snow i.e.; after IO requests begin modifying the drive. 222a3d71595SJohn Snow ''' 223a3d71595SJohn Snow self.create_anchor_backup() 224a3d71595SJohn Snow self.add_bitmap('bitmap0', self.drives[0]) 225a3d71595SJohn Snow 226a3d71595SJohn Snow # Sanity: Create a "hollow" incremental backup 227a3d71595SJohn Snow self.create_incremental() 228a3d71595SJohn Snow # Three writes: One complete overwrite, one new segment, 229a3d71595SJohn Snow # and one partial overlap. 230a3d71595SJohn Snow self.hmp_io_writes(self.drives[0]['id'], (('0xab', 0, 512), 231a3d71595SJohn Snow ('0xfe', '16M', '256k'), 232a3d71595SJohn Snow ('0x64', '32736k', '64k'))) 233a3d71595SJohn Snow self.create_incremental() 234a3d71595SJohn Snow # Three more writes, one of each kind, like above 235a3d71595SJohn Snow self.hmp_io_writes(self.drives[0]['id'], (('0x9a', 0, 512), 236a3d71595SJohn Snow ('0x55', '8M', '352k'), 237a3d71595SJohn Snow ('0x78', '15872k', '1M'))) 238a3d71595SJohn Snow self.create_incremental() 239a3d71595SJohn Snow self.vm.shutdown() 240a3d71595SJohn Snow self.check_backups() 241a3d71595SJohn Snow 242a3d71595SJohn Snow 243*24618f53SJohn Snow def test_incremental_failure(self): 244*24618f53SJohn Snow '''Test: Verify backups made after a failure are correct. 245*24618f53SJohn Snow 246*24618f53SJohn Snow Simulate a failure during an incremental backup block job, 247*24618f53SJohn Snow emulate additional writes, then create another incremental backup 248*24618f53SJohn Snow afterwards and verify that the backup created is correct. 249*24618f53SJohn Snow ''' 250*24618f53SJohn Snow 251*24618f53SJohn Snow # Create a blkdebug interface to this img as 'drive1', 252*24618f53SJohn Snow # but don't actually create a new image. 253*24618f53SJohn Snow drive1 = self.add_node('drive1', self.drives[0]['fmt'], 254*24618f53SJohn Snow path=self.drives[0]['file'], 255*24618f53SJohn Snow backup=self.drives[0]['backup']) 256*24618f53SJohn Snow result = self.vm.qmp('blockdev-add', options={ 257*24618f53SJohn Snow 'id': drive1['id'], 258*24618f53SJohn Snow 'driver': drive1['fmt'], 259*24618f53SJohn Snow 'file': { 260*24618f53SJohn Snow 'driver': 'blkdebug', 261*24618f53SJohn Snow 'image': { 262*24618f53SJohn Snow 'driver': 'file', 263*24618f53SJohn Snow 'filename': drive1['file'] 264*24618f53SJohn Snow }, 265*24618f53SJohn Snow 'set-state': [{ 266*24618f53SJohn Snow 'event': 'flush_to_disk', 267*24618f53SJohn Snow 'state': 1, 268*24618f53SJohn Snow 'new_state': 2 269*24618f53SJohn Snow }], 270*24618f53SJohn Snow 'inject-error': [{ 271*24618f53SJohn Snow 'event': 'read_aio', 272*24618f53SJohn Snow 'errno': 5, 273*24618f53SJohn Snow 'state': 2, 274*24618f53SJohn Snow 'immediately': False, 275*24618f53SJohn Snow 'once': True 276*24618f53SJohn Snow }], 277*24618f53SJohn Snow } 278*24618f53SJohn Snow }) 279*24618f53SJohn Snow self.assert_qmp(result, 'return', {}) 280*24618f53SJohn Snow 281*24618f53SJohn Snow self.create_anchor_backup(self.drives[0]) 282*24618f53SJohn Snow self.add_bitmap('bitmap0', drive1) 283*24618f53SJohn Snow # Note: at this point, during a normal execution, 284*24618f53SJohn Snow # Assume that the VM resumes and begins issuing IO requests here. 285*24618f53SJohn Snow 286*24618f53SJohn Snow self.hmp_io_writes(drive1['id'], (('0xab', 0, 512), 287*24618f53SJohn Snow ('0xfe', '16M', '256k'), 288*24618f53SJohn Snow ('0x64', '32736k', '64k'))) 289*24618f53SJohn Snow 290*24618f53SJohn Snow result = self.create_incremental(validate=False) 291*24618f53SJohn Snow self.assertFalse(result) 292*24618f53SJohn Snow self.hmp_io_writes(drive1['id'], (('0x9a', 0, 512), 293*24618f53SJohn Snow ('0x55', '8M', '352k'), 294*24618f53SJohn Snow ('0x78', '15872k', '1M'))) 295*24618f53SJohn Snow self.create_incremental() 296*24618f53SJohn Snow self.vm.shutdown() 297*24618f53SJohn Snow self.check_backups() 298*24618f53SJohn Snow 299*24618f53SJohn Snow 3009f7264f5SJohn Snow def test_sync_dirty_bitmap_missing(self): 3019f7264f5SJohn Snow self.assert_no_active_block_jobs() 3029f7264f5SJohn Snow self.files.append(self.err_img) 3039f7264f5SJohn Snow result = self.vm.qmp('drive-backup', device=self.drives[0]['id'], 3049f7264f5SJohn Snow sync='dirty-bitmap', format=self.drives[0]['fmt'], 3059f7264f5SJohn Snow target=self.err_img) 3069f7264f5SJohn Snow self.assert_qmp(result, 'error/class', 'GenericError') 3079f7264f5SJohn Snow 3089f7264f5SJohn Snow 3099f7264f5SJohn Snow def test_sync_dirty_bitmap_not_found(self): 3109f7264f5SJohn Snow self.assert_no_active_block_jobs() 3119f7264f5SJohn Snow self.files.append(self.err_img) 3129f7264f5SJohn Snow result = self.vm.qmp('drive-backup', device=self.drives[0]['id'], 3139f7264f5SJohn Snow sync='dirty-bitmap', bitmap='unknown', 3149f7264f5SJohn Snow format=self.drives[0]['fmt'], target=self.err_img) 3159f7264f5SJohn Snow self.assert_qmp(result, 'error/class', 'GenericError') 3169f7264f5SJohn Snow 3179f7264f5SJohn Snow 3189f7264f5SJohn Snow def tearDown(self): 3199f7264f5SJohn Snow self.vm.shutdown() 320a3d71595SJohn Snow for bitmap in self.bitmaps: 321a3d71595SJohn Snow bitmap.cleanup() 3229f7264f5SJohn Snow for filename in self.files: 323a3d71595SJohn Snow try_remove(filename) 3249f7264f5SJohn Snow 3259f7264f5SJohn Snow 3269f7264f5SJohn Snowif __name__ == '__main__': 3279f7264f5SJohn Snow iotests.main(supported_fmts=['qcow2']) 328