1#!/usr/bin/env python3 2# group: rw migration 3# 4# Tests for dirty bitmaps migration. 5# 6# Copyright (c) 2016-2017 Virtuozzo International GmbH. All rights reserved. 7# 8# This program is free software; you can redistribute it and/or modify 9# it under the terms of the GNU General Public License as published by 10# the Free Software Foundation; either version 2 of the License, or 11# (at your option) any later version. 12# 13# This program is distributed in the hope that it will be useful, 14# but WITHOUT ANY WARRANTY; without even the implied warranty of 15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16# GNU General Public License for more details. 17# 18# You should have received a copy of the GNU General Public License 19# along with this program. If not, see <http://www.gnu.org/licenses/>. 20# 21 22import os 23import iotests 24import time 25import itertools 26import operator 27import re 28from iotests import qemu_img, qemu_img_create, Timeout 29 30 31disk_a = os.path.join(iotests.test_dir, 'disk_a') 32disk_b = os.path.join(iotests.test_dir, 'disk_b') 33base_a = os.path.join(iotests.test_dir, 'base_a') 34size = '1M' 35mig_file = os.path.join(iotests.test_dir, 'mig_file') 36mig_cmd = 'exec: cat > ' + mig_file 37incoming_cmd = 'exec: cat ' + mig_file 38 39 40class TestDirtyBitmapMigration(iotests.QMPTestCase): 41 def tearDown(self): 42 self.vm_a.shutdown() 43 self.vm_b.shutdown() 44 os.remove(disk_a) 45 os.remove(disk_b) 46 os.remove(mig_file) 47 48 def setUp(self): 49 qemu_img('create', '-f', iotests.imgfmt, disk_a, size) 50 qemu_img('create', '-f', iotests.imgfmt, disk_b, size) 51 52 self.vm_a = iotests.VM(path_suffix='a').add_drive(disk_a) 53 self.vm_a.launch() 54 55 self.vm_b = iotests.VM(path_suffix='b') 56 57 def add_bitmap(self, vm, granularity, persistent): 58 params = {'node': 'drive0', 59 'name': 'bitmap0', 60 'granularity': granularity} 61 if persistent: 62 params['persistent'] = True 63 64 result = vm.qmp('block-dirty-bitmap-add', **params) 65 self.assert_qmp(result, 'return', {}); 66 67 def get_bitmap_hash(self, vm): 68 result = vm.qmp('x-debug-block-dirty-bitmap-sha256', 69 node='drive0', name='bitmap0') 70 return result['return']['sha256'] 71 72 def check_bitmap(self, vm, sha256): 73 result = vm.qmp('x-debug-block-dirty-bitmap-sha256', 74 node='drive0', name='bitmap0') 75 if sha256: 76 self.assert_qmp(result, 'return/sha256', sha256); 77 else: 78 self.assert_qmp(result, 'error/desc', 79 "Dirty bitmap 'bitmap0' not found"); 80 81 def do_test_migration_resume_source(self, persistent, migrate_bitmaps): 82 granularity = 512 83 84 # regions = ((start, count), ...) 85 regions = ((0, 0x10000), 86 (0xf0000, 0x10000), 87 (0xa0201, 0x1000)) 88 89 mig_caps = [{'capability': 'events', 'state': True}] 90 if migrate_bitmaps: 91 mig_caps.append({'capability': 'dirty-bitmaps', 'state': True}) 92 93 result = self.vm_a.qmp('migrate-set-capabilities', 94 capabilities=mig_caps) 95 self.assert_qmp(result, 'return', {}) 96 97 self.add_bitmap(self.vm_a, granularity, persistent) 98 for r in regions: 99 self.vm_a.hmp_qemu_io('drive0', 'write %d %d' % r) 100 sha256 = self.get_bitmap_hash(self.vm_a) 101 102 result = self.vm_a.qmp('migrate', uri=mig_cmd) 103 while True: 104 event = self.vm_a.event_wait('MIGRATION') 105 if event['data']['status'] == 'completed': 106 break 107 while True: 108 result = self.vm_a.qmp('query-status') 109 if (result['return']['status'] == 'postmigrate'): 110 break 111 112 # test that bitmap is still here 113 removed = (not migrate_bitmaps) and persistent 114 self.check_bitmap(self.vm_a, False if removed else sha256) 115 116 result = self.vm_a.qmp('cont') 117 self.assert_qmp(result, 'return', {}) 118 119 # test that bitmap is still here after invalidation 120 self.check_bitmap(self.vm_a, sha256) 121 122 # shutdown and check that invalidation didn't fail 123 self.vm_a.shutdown() 124 125 # catch 'Could not reopen qcow2 layer: Bitmap already exists' 126 # possible error 127 log = self.vm_a.get_log() 128 log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log) 129 log = re.sub(r'^(wrote .* bytes at offset .*\n.*KiB.*ops.*sec.*\n){3}', 130 '', log) 131 log = re.sub(r'\[I \+\d+\.\d+\] CLOSED\n?$', '', log) 132 self.assertEqual(log, '') 133 134 # test that bitmap is still persistent 135 self.vm_a.launch() 136 self.check_bitmap(self.vm_a, sha256 if persistent else False) 137 138 def do_test_migration(self, persistent, migrate_bitmaps, online, 139 shared_storage, pre_shutdown): 140 granularity = 512 141 142 # regions = ((start, count), ...) 143 regions = ((0, 0x10000), 144 (0xf0000, 0x10000), 145 (0xa0201, 0x1000)) 146 147 should_migrate = \ 148 (migrate_bitmaps and (persistent or not pre_shutdown)) or \ 149 (persistent and shared_storage) 150 mig_caps = [{'capability': 'events', 'state': True}] 151 if migrate_bitmaps: 152 mig_caps.append({'capability': 'dirty-bitmaps', 'state': True}) 153 154 self.vm_b.add_incoming(incoming_cmd if online else "defer") 155 self.vm_b.add_drive(disk_a if shared_storage else disk_b) 156 157 if online: 158 os.mkfifo(mig_file) 159 self.vm_b.launch() 160 result = self.vm_b.qmp('migrate-set-capabilities', 161 capabilities=mig_caps) 162 self.assert_qmp(result, 'return', {}) 163 164 self.add_bitmap(self.vm_a, granularity, persistent) 165 for r in regions: 166 self.vm_a.hmp_qemu_io('drive0', 'write %d %d' % r) 167 sha256 = self.get_bitmap_hash(self.vm_a) 168 169 if pre_shutdown: 170 self.vm_a.shutdown() 171 self.vm_a.launch() 172 173 result = self.vm_a.qmp('migrate-set-capabilities', 174 capabilities=mig_caps) 175 self.assert_qmp(result, 'return', {}) 176 177 result = self.vm_a.qmp('migrate', uri=mig_cmd) 178 while True: 179 event = self.vm_a.event_wait('MIGRATION') 180 if event['data']['status'] == 'completed': 181 break 182 183 if not online: 184 self.vm_a.shutdown() 185 self.vm_b.launch() 186 result = self.vm_b.qmp('migrate-set-capabilities', 187 capabilities=mig_caps) 188 self.assert_qmp(result, 'return', {}) 189 result = self.vm_b.qmp('migrate-incoming', uri=incoming_cmd) 190 self.assert_qmp(result, 'return', {}) 191 192 while True: 193 event = self.vm_b.event_wait('MIGRATION') 194 if event['data']['status'] == 'completed': 195 break 196 197 self.check_bitmap(self.vm_b, sha256 if should_migrate else False) 198 199 if should_migrate: 200 self.vm_b.shutdown() 201 202 # catch 'Could not reopen qcow2 layer: Bitmap already exists' 203 # possible error 204 log = self.vm_b.get_log() 205 log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log) 206 log = re.sub(r'\[I \+\d+\.\d+\] CLOSED\n?$', '', log) 207 self.assertEqual(log, '') 208 209 # recreate vm_b, as we don't want -incoming option (this will lead 210 # to "cat" process left alive after test finish) 211 self.vm_b = iotests.VM(path_suffix='b') 212 self.vm_b.add_drive(disk_a if shared_storage else disk_b) 213 self.vm_b.launch() 214 self.check_bitmap(self.vm_b, sha256 if persistent else False) 215 216 217def inject_test_case(klass, name, method, *args, **kwargs): 218 mc = operator.methodcaller(method, *args, **kwargs) 219 setattr(klass, 'test_' + method + name, lambda self: mc(self)) 220 221for cmb in list(itertools.product((True, False), repeat=5)): 222 name = ('_' if cmb[0] else '_not_') + 'persistent_' 223 name += ('_' if cmb[1] else '_not_') + 'migbitmap_' 224 name += '_online' if cmb[2] else '_offline' 225 name += '_shared' if cmb[3] else '_nonshared' 226 if (cmb[4]): 227 name += '__pre_shutdown' 228 229 inject_test_case(TestDirtyBitmapMigration, name, 'do_test_migration', 230 *list(cmb)) 231 232for cmb in list(itertools.product((True, False), repeat=2)): 233 name = ('_' if cmb[0] else '_not_') + 'persistent_' 234 name += ('_' if cmb[1] else '_not_') + 'migbitmap' 235 236 inject_test_case(TestDirtyBitmapMigration, name, 237 'do_test_migration_resume_source', *list(cmb)) 238 239 240class TestDirtyBitmapBackingMigration(iotests.QMPTestCase): 241 def setUp(self): 242 qemu_img_create('-f', iotests.imgfmt, base_a, size) 243 qemu_img_create('-f', iotests.imgfmt, '-F', iotests.imgfmt, 244 '-b', base_a, disk_a, size) 245 246 for f in (disk_a, base_a): 247 qemu_img('bitmap', '--add', f, 'bmap0') 248 249 blockdev = { 250 'node-name': 'node0', 251 'driver': iotests.imgfmt, 252 'file': { 253 'driver': 'file', 254 'filename': disk_a 255 }, 256 'backing': { 257 'node-name': 'node0-base', 258 'driver': iotests.imgfmt, 259 'file': { 260 'driver': 'file', 261 'filename': base_a 262 } 263 } 264 } 265 266 self.vm = iotests.VM() 267 self.vm.launch() 268 269 result = self.vm.qmp('blockdev-add', **blockdev) 270 self.assert_qmp(result, 'return', {}) 271 272 # Check that the bitmaps are there 273 for node in self.vm.qmp('query-named-block-nodes', flat=True)['return']: 274 if 'node0' in node['node-name']: 275 self.assert_qmp(node, 'dirty-bitmaps[0]/name', 'bmap0') 276 277 caps = [{'capability': 'events', 'state': True}] 278 result = self.vm.qmp('migrate-set-capabilities', capabilities=caps) 279 self.assert_qmp(result, 'return', {}) 280 281 def tearDown(self): 282 self.vm.shutdown() 283 for f in (disk_a, base_a): 284 os.remove(f) 285 286 def test_cont_on_source(self): 287 """ 288 Continue the source after migration. 289 """ 290 result = self.vm.qmp('migrate', uri=f'exec: cat > /dev/null') 291 self.assert_qmp(result, 'return', {}) 292 293 with Timeout(10, 'Migration timeout'): 294 self.vm.wait_migration('postmigrate') 295 296 result = self.vm.qmp('cont') 297 self.assert_qmp(result, 'return', {}) 298 299 300if __name__ == '__main__': 301 iotests.main(supported_fmts=['qcow2'], 302 supported_protocols=['file']) 303