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 32*a3d71595SJohn Snowdef try_remove(img): 33*a3d71595SJohn Snow try: 34*a3d71595SJohn Snow os.remove(img) 35*a3d71595SJohn Snow except OSError: 36*a3d71595SJohn Snow pass 37*a3d71595SJohn Snow 38*a3d71595SJohn Snow 39*a3d71595SJohn Snowclass Bitmap: 40*a3d71595SJohn Snow def __init__(self, name, drive): 41*a3d71595SJohn Snow self.name = name 42*a3d71595SJohn Snow self.drive = drive 43*a3d71595SJohn Snow self.num = 0 44*a3d71595SJohn Snow self.backups = list() 45*a3d71595SJohn Snow 46*a3d71595SJohn Snow def base_target(self): 47*a3d71595SJohn Snow return (self.drive['backup'], None) 48*a3d71595SJohn Snow 49*a3d71595SJohn Snow def new_target(self, num=None): 50*a3d71595SJohn Snow if num is None: 51*a3d71595SJohn Snow num = self.num 52*a3d71595SJohn Snow self.num = num + 1 53*a3d71595SJohn Snow base = os.path.join(iotests.test_dir, 54*a3d71595SJohn Snow "%s.%s." % (self.drive['id'], self.name)) 55*a3d71595SJohn Snow suff = "%i.%s" % (num, self.drive['fmt']) 56*a3d71595SJohn Snow target = base + "inc" + suff 57*a3d71595SJohn Snow reference = base + "ref" + suff 58*a3d71595SJohn Snow self.backups.append((target, reference)) 59*a3d71595SJohn Snow return (target, reference) 60*a3d71595SJohn Snow 61*a3d71595SJohn Snow def last_target(self): 62*a3d71595SJohn Snow if self.backups: 63*a3d71595SJohn Snow return self.backups[-1] 64*a3d71595SJohn Snow return self.base_target() 65*a3d71595SJohn Snow 66*a3d71595SJohn Snow def del_target(self): 67*a3d71595SJohn Snow for image in self.backups.pop(): 68*a3d71595SJohn Snow try_remove(image) 69*a3d71595SJohn Snow self.num -= 1 70*a3d71595SJohn Snow 71*a3d71595SJohn Snow def cleanup(self): 72*a3d71595SJohn Snow for backup in self.backups: 73*a3d71595SJohn Snow for image in backup: 74*a3d71595SJohn Snow try_remove(image) 75*a3d71595SJohn Snow 76*a3d71595SJohn 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 121*a3d71595SJohn Snow 122*a3d71595SJohn Snow def do_qmp_backup(self, error='Input/output error', **kwargs): 123*a3d71595SJohn Snow res = self.vm.qmp('drive-backup', **kwargs) 124*a3d71595SJohn Snow self.assert_qmp(res, 'return', {}) 125*a3d71595SJohn Snow 126*a3d71595SJohn Snow event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED", 127*a3d71595SJohn Snow match={'data': {'device': kwargs['device']}}) 128*a3d71595SJohn Snow self.assertIsNotNone(event) 129*a3d71595SJohn Snow 130*a3d71595SJohn Snow try: 131*a3d71595SJohn Snow failure = self.dictpath(event, 'data/error') 132*a3d71595SJohn Snow except AssertionError: 133*a3d71595SJohn Snow # Backup succeeded. 134*a3d71595SJohn Snow self.assert_qmp(event, 'data/offset', event['data']['len']) 135*a3d71595SJohn Snow return True 136*a3d71595SJohn Snow else: 137*a3d71595SJohn Snow # Backup failed. 138*a3d71595SJohn Snow self.assert_qmp(event, 'data/error', error) 139*a3d71595SJohn Snow return False 140*a3d71595SJohn Snow 141*a3d71595SJohn Snow 142*a3d71595SJohn Snow def create_anchor_backup(self, drive=None): 143*a3d71595SJohn Snow if drive is None: 144*a3d71595SJohn Snow drive = self.drives[-1] 145*a3d71595SJohn Snow res = self.do_qmp_backup(device=drive['id'], sync='full', 146*a3d71595SJohn Snow format=drive['fmt'], target=drive['backup']) 147*a3d71595SJohn Snow self.assertTrue(res) 148*a3d71595SJohn Snow self.files.append(drive['backup']) 149*a3d71595SJohn Snow return drive['backup'] 150*a3d71595SJohn Snow 151*a3d71595SJohn Snow 152*a3d71595SJohn Snow def make_reference_backup(self, bitmap=None): 153*a3d71595SJohn Snow if bitmap is None: 154*a3d71595SJohn Snow bitmap = self.bitmaps[-1] 155*a3d71595SJohn Snow _, reference = bitmap.last_target() 156*a3d71595SJohn Snow res = self.do_qmp_backup(device=bitmap.drive['id'], sync='full', 157*a3d71595SJohn Snow format=bitmap.drive['fmt'], target=reference) 158*a3d71595SJohn Snow self.assertTrue(res) 159*a3d71595SJohn Snow 160*a3d71595SJohn Snow 161*a3d71595SJohn Snow def add_bitmap(self, name, drive): 162*a3d71595SJohn Snow bitmap = Bitmap(name, drive) 163*a3d71595SJohn Snow self.bitmaps.append(bitmap) 164*a3d71595SJohn Snow result = self.vm.qmp('block-dirty-bitmap-add', node=drive['id'], 165*a3d71595SJohn Snow name=bitmap.name) 166*a3d71595SJohn Snow self.assert_qmp(result, 'return', {}) 167*a3d71595SJohn Snow return bitmap 168*a3d71595SJohn Snow 169*a3d71595SJohn Snow 170*a3d71595SJohn Snow def prepare_backup(self, bitmap=None, parent=None): 171*a3d71595SJohn Snow if bitmap is None: 172*a3d71595SJohn Snow bitmap = self.bitmaps[-1] 173*a3d71595SJohn Snow if parent is None: 174*a3d71595SJohn Snow parent, _ = bitmap.last_target() 175*a3d71595SJohn Snow 176*a3d71595SJohn Snow target, _ = bitmap.new_target() 177*a3d71595SJohn Snow self.img_create(target, bitmap.drive['fmt'], parent=parent) 178*a3d71595SJohn Snow return target 179*a3d71595SJohn Snow 180*a3d71595SJohn Snow 181*a3d71595SJohn Snow def create_incremental(self, bitmap=None, parent=None, 182*a3d71595SJohn Snow parentFormat=None, validate=True): 183*a3d71595SJohn Snow if bitmap is None: 184*a3d71595SJohn Snow bitmap = self.bitmaps[-1] 185*a3d71595SJohn Snow if parent is None: 186*a3d71595SJohn Snow parent, _ = bitmap.last_target() 187*a3d71595SJohn Snow 188*a3d71595SJohn Snow target = self.prepare_backup(bitmap, parent) 189*a3d71595SJohn Snow res = self.do_qmp_backup(device=bitmap.drive['id'], 190*a3d71595SJohn Snow sync='dirty-bitmap', bitmap=bitmap.name, 191*a3d71595SJohn Snow format=bitmap.drive['fmt'], target=target, 192*a3d71595SJohn Snow mode='existing') 193*a3d71595SJohn Snow if not res: 194*a3d71595SJohn Snow bitmap.del_target(); 195*a3d71595SJohn Snow self.assertFalse(validate) 196*a3d71595SJohn Snow else: 197*a3d71595SJohn Snow self.make_reference_backup(bitmap) 198*a3d71595SJohn Snow return res 199*a3d71595SJohn Snow 200*a3d71595SJohn Snow 201*a3d71595SJohn Snow def check_backups(self): 202*a3d71595SJohn Snow for bitmap in self.bitmaps: 203*a3d71595SJohn Snow for incremental, reference in bitmap.backups: 204*a3d71595SJohn Snow self.assertTrue(iotests.compare_images(incremental, reference)) 205*a3d71595SJohn Snow last = bitmap.last_target()[0] 206*a3d71595SJohn Snow self.assertTrue(iotests.compare_images(last, bitmap.drive['file'])) 207*a3d71595SJohn Snow 208*a3d71595SJohn Snow 209*a3d71595SJohn Snow def hmp_io_writes(self, drive, patterns): 210*a3d71595SJohn Snow for pattern in patterns: 211*a3d71595SJohn Snow self.vm.hmp_qemu_io(drive, 'write -P%s %s %s' % pattern) 212*a3d71595SJohn Snow self.vm.hmp_qemu_io(drive, 'flush') 213*a3d71595SJohn Snow 214*a3d71595SJohn Snow 215*a3d71595SJohn Snow def test_incremental_simple(self): 216*a3d71595SJohn Snow ''' 217*a3d71595SJohn Snow Test: Create and verify three incremental backups. 218*a3d71595SJohn Snow 219*a3d71595SJohn Snow Create a bitmap and a full backup before VM execution begins, 220*a3d71595SJohn Snow then create a series of three incremental backups "during execution," 221*a3d71595SJohn Snow i.e.; after IO requests begin modifying the drive. 222*a3d71595SJohn Snow ''' 223*a3d71595SJohn Snow self.create_anchor_backup() 224*a3d71595SJohn Snow self.add_bitmap('bitmap0', self.drives[0]) 225*a3d71595SJohn Snow 226*a3d71595SJohn Snow # Sanity: Create a "hollow" incremental backup 227*a3d71595SJohn Snow self.create_incremental() 228*a3d71595SJohn Snow # Three writes: One complete overwrite, one new segment, 229*a3d71595SJohn Snow # and one partial overlap. 230*a3d71595SJohn Snow self.hmp_io_writes(self.drives[0]['id'], (('0xab', 0, 512), 231*a3d71595SJohn Snow ('0xfe', '16M', '256k'), 232*a3d71595SJohn Snow ('0x64', '32736k', '64k'))) 233*a3d71595SJohn Snow self.create_incremental() 234*a3d71595SJohn Snow # Three more writes, one of each kind, like above 235*a3d71595SJohn Snow self.hmp_io_writes(self.drives[0]['id'], (('0x9a', 0, 512), 236*a3d71595SJohn Snow ('0x55', '8M', '352k'), 237*a3d71595SJohn Snow ('0x78', '15872k', '1M'))) 238*a3d71595SJohn Snow self.create_incremental() 239*a3d71595SJohn Snow self.vm.shutdown() 240*a3d71595SJohn Snow self.check_backups() 241*a3d71595SJohn Snow 242*a3d71595SJohn Snow 2439f7264f5SJohn Snow def test_sync_dirty_bitmap_missing(self): 2449f7264f5SJohn Snow self.assert_no_active_block_jobs() 2459f7264f5SJohn Snow self.files.append(self.err_img) 2469f7264f5SJohn Snow result = self.vm.qmp('drive-backup', device=self.drives[0]['id'], 2479f7264f5SJohn Snow sync='dirty-bitmap', format=self.drives[0]['fmt'], 2489f7264f5SJohn Snow target=self.err_img) 2499f7264f5SJohn Snow self.assert_qmp(result, 'error/class', 'GenericError') 2509f7264f5SJohn Snow 2519f7264f5SJohn Snow 2529f7264f5SJohn Snow def test_sync_dirty_bitmap_not_found(self): 2539f7264f5SJohn Snow self.assert_no_active_block_jobs() 2549f7264f5SJohn Snow self.files.append(self.err_img) 2559f7264f5SJohn Snow result = self.vm.qmp('drive-backup', device=self.drives[0]['id'], 2569f7264f5SJohn Snow sync='dirty-bitmap', bitmap='unknown', 2579f7264f5SJohn Snow format=self.drives[0]['fmt'], target=self.err_img) 2589f7264f5SJohn Snow self.assert_qmp(result, 'error/class', 'GenericError') 2599f7264f5SJohn Snow 2609f7264f5SJohn Snow 2619f7264f5SJohn Snow def tearDown(self): 2629f7264f5SJohn Snow self.vm.shutdown() 263*a3d71595SJohn Snow for bitmap in self.bitmaps: 264*a3d71595SJohn Snow bitmap.cleanup() 2659f7264f5SJohn Snow for filename in self.files: 266*a3d71595SJohn Snow try_remove(filename) 2679f7264f5SJohn Snow 2689f7264f5SJohn Snow 2699f7264f5SJohn Snowif __name__ == '__main__': 2709f7264f5SJohn Snow iotests.main(supported_fmts=['qcow2']) 271