1#!/usr/bin/env python3 2# group: migration 3# 4# Copyright (C) 2020 Red Hat, Inc. 5# 6# Tests for dirty bitmaps migration with node aliases 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 random 24import re 25from typing import Dict, List, Optional, Union 26import iotests 27import qemu 28 29BlockBitmapMapping = List[Dict[str, Union[str, List[Dict[str, str]]]]] 30 31assert iotests.sock_dir is not None 32mig_sock = os.path.join(iotests.sock_dir, 'mig_sock') 33 34 35class TestDirtyBitmapMigration(iotests.QMPTestCase): 36 src_node_name: str = '' 37 dst_node_name: str = '' 38 src_bmap_name: str = '' 39 dst_bmap_name: str = '' 40 41 def setUp(self) -> None: 42 self.vm_a = iotests.VM(path_suffix='-a') 43 self.vm_a.add_blockdev(f'node-name={self.src_node_name},' 44 'driver=null-co') 45 self.vm_a.launch() 46 47 self.vm_b = iotests.VM(path_suffix='-b') 48 self.vm_b.add_blockdev(f'node-name={self.dst_node_name},' 49 'driver=null-co') 50 self.vm_b.add_incoming(f'unix:{mig_sock}') 51 self.vm_b.launch() 52 53 result = self.vm_a.qmp('block-dirty-bitmap-add', 54 node=self.src_node_name, 55 name=self.src_bmap_name) 56 self.assert_qmp(result, 'return', {}) 57 58 # Dirty some random megabytes 59 for _ in range(9): 60 mb_ofs = random.randrange(1024) 61 self.vm_a.hmp_qemu_io(self.src_node_name, f'discard {mb_ofs}M 1M') 62 63 result = self.vm_a.qmp('x-debug-block-dirty-bitmap-sha256', 64 node=self.src_node_name, 65 name=self.src_bmap_name) 66 self.bitmap_hash_reference = result['return']['sha256'] 67 68 caps = [{'capability': name, 'state': True} 69 for name in ('dirty-bitmaps', 'events')] 70 71 for vm in (self.vm_a, self.vm_b): 72 result = vm.qmp('migrate-set-capabilities', capabilities=caps) 73 self.assert_qmp(result, 'return', {}) 74 75 def tearDown(self) -> None: 76 self.vm_a.shutdown() 77 self.vm_b.shutdown() 78 try: 79 os.remove(mig_sock) 80 except OSError: 81 pass 82 83 def check_bitmap(self, bitmap_name_valid: bool) -> None: 84 result = self.vm_b.qmp('x-debug-block-dirty-bitmap-sha256', 85 node=self.dst_node_name, 86 name=self.dst_bmap_name) 87 if bitmap_name_valid: 88 self.assert_qmp(result, 'return/sha256', 89 self.bitmap_hash_reference) 90 else: 91 self.assert_qmp(result, 'error/desc', 92 f"Dirty bitmap '{self.dst_bmap_name}' not found") 93 94 def migrate(self, bitmap_name_valid: bool = True, 95 migration_success: bool = True) -> None: 96 result = self.vm_a.qmp('migrate', uri=f'unix:{mig_sock}') 97 self.assert_qmp(result, 'return', {}) 98 99 with iotests.Timeout(5, 'Timeout waiting for migration to complete'): 100 self.assertEqual(self.vm_a.wait_migration('postmigrate'), 101 migration_success) 102 self.assertEqual(self.vm_b.wait_migration('running'), 103 migration_success) 104 105 if migration_success: 106 self.check_bitmap(bitmap_name_valid) 107 108 def verify_dest_error(self, msg: Optional[str]) -> None: 109 """ 110 Check whether the given error message is present in vm_b's log. 111 (vm_b is shut down to do so.) 112 If @msg is None, check that there has not been any error. 113 """ 114 self.vm_b.shutdown() 115 if msg is None: 116 self.assertNotIn('qemu-system-', self.vm_b.get_log()) 117 else: 118 self.assertIn(msg, self.vm_b.get_log()) 119 120 @staticmethod 121 def mapping(node_name: str, node_alias: str, 122 bitmap_name: str, bitmap_alias: str) -> BlockBitmapMapping: 123 return [{ 124 'node-name': node_name, 125 'alias': node_alias, 126 'bitmaps': [{ 127 'name': bitmap_name, 128 'alias': bitmap_alias 129 }] 130 }] 131 132 def set_mapping(self, vm: iotests.VM, mapping: BlockBitmapMapping, 133 error: Optional[str] = None) -> None: 134 """ 135 Invoke migrate-set-parameters on @vm to set the given @mapping. 136 Check for success if @error is None, or verify the error message 137 if it is not. 138 On success, verify that "info migrate_parameters" on HMP returns 139 our mapping. (Just to check its formatting code.) 140 """ 141 result = vm.qmp('migrate-set-parameters', 142 block_bitmap_mapping=mapping) 143 144 if error is None: 145 self.assert_qmp(result, 'return', {}) 146 147 result = vm.qmp('human-monitor-command', 148 command_line='info migrate_parameters') 149 150 m = re.search(r'^block-bitmap-mapping:\r?(\n .*)*\n', 151 result['return'], flags=re.MULTILINE) 152 hmp_mapping = m.group(0).replace('\r', '') if m else None 153 154 self.assertEqual(hmp_mapping, self.to_hmp_mapping(mapping)) 155 else: 156 self.assert_qmp(result, 'error/desc', error) 157 158 @staticmethod 159 def to_hmp_mapping(mapping: BlockBitmapMapping) -> str: 160 result = 'block-bitmap-mapping:\n' 161 162 for node in mapping: 163 result += f" '{node['node-name']}' -> '{node['alias']}'\n" 164 165 assert isinstance(node['bitmaps'], list) 166 for bitmap in node['bitmaps']: 167 result += f" '{bitmap['name']}' -> '{bitmap['alias']}'\n" 168 169 return result 170 171 172class TestAliasMigration(TestDirtyBitmapMigration): 173 src_node_name = 'node0' 174 dst_node_name = 'node0' 175 src_bmap_name = 'bmap0' 176 dst_bmap_name = 'bmap0' 177 178 def test_migration_without_alias(self) -> None: 179 self.migrate(self.src_node_name == self.dst_node_name and 180 self.src_bmap_name == self.dst_bmap_name) 181 182 # Check for error message on the destination 183 if self.src_node_name != self.dst_node_name: 184 self.verify_dest_error(f"Cannot find " 185 f"device={self.src_node_name} nor " 186 f"node_name={self.src_node_name}") 187 else: 188 self.verify_dest_error(None) 189 190 def test_alias_on_src_migration(self) -> None: 191 mapping = self.mapping(self.src_node_name, self.dst_node_name, 192 self.src_bmap_name, self.dst_bmap_name) 193 194 self.set_mapping(self.vm_a, mapping) 195 self.migrate() 196 self.verify_dest_error(None) 197 198 def test_alias_on_dst_migration(self) -> None: 199 mapping = self.mapping(self.dst_node_name, self.src_node_name, 200 self.dst_bmap_name, self.src_bmap_name) 201 202 self.set_mapping(self.vm_b, mapping) 203 self.migrate() 204 self.verify_dest_error(None) 205 206 def test_alias_on_both_migration(self) -> None: 207 src_map = self.mapping(self.src_node_name, 'node-alias', 208 self.src_bmap_name, 'bmap-alias') 209 210 dst_map = self.mapping(self.dst_node_name, 'node-alias', 211 self.dst_bmap_name, 'bmap-alias') 212 213 self.set_mapping(self.vm_a, src_map) 214 self.set_mapping(self.vm_b, dst_map) 215 self.migrate() 216 self.verify_dest_error(None) 217 218 219class TestNodeAliasMigration(TestAliasMigration): 220 src_node_name = 'node-src' 221 dst_node_name = 'node-dst' 222 223 224class TestBitmapAliasMigration(TestAliasMigration): 225 src_bmap_name = 'bmap-src' 226 dst_bmap_name = 'bmap-dst' 227 228 229class TestFullAliasMigration(TestAliasMigration): 230 src_node_name = 'node-src' 231 dst_node_name = 'node-dst' 232 src_bmap_name = 'bmap-src' 233 dst_bmap_name = 'bmap-dst' 234 235 236class TestLongBitmapNames(TestAliasMigration): 237 # Giving long bitmap names is OK, as long as there is a short alias for 238 # migration 239 src_bmap_name = 'a' * 512 240 dst_bmap_name = 'b' * 512 241 242 # Skip all tests that do not use the intermediate alias 243 def test_migration_without_alias(self) -> None: 244 pass 245 246 def test_alias_on_src_migration(self) -> None: 247 pass 248 249 def test_alias_on_dst_migration(self) -> None: 250 pass 251 252 253class TestBlockBitmapMappingErrors(TestDirtyBitmapMigration): 254 src_node_name = 'node0' 255 dst_node_name = 'node0' 256 src_bmap_name = 'bmap0' 257 dst_bmap_name = 'bmap0' 258 259 """ 260 Note that mapping nodes or bitmaps that do not exist is not an error. 261 """ 262 263 def test_non_injective_node_mapping(self) -> None: 264 mapping: BlockBitmapMapping = [ 265 { 266 'node-name': 'node0', 267 'alias': 'common-alias', 268 'bitmaps': [{ 269 'name': 'bmap0', 270 'alias': 'bmap-alias0' 271 }] 272 }, 273 { 274 'node-name': 'node1', 275 'alias': 'common-alias', 276 'bitmaps': [{ 277 'name': 'bmap1', 278 'alias': 'bmap-alias1' 279 }] 280 } 281 ] 282 283 self.set_mapping(self.vm_a, mapping, 284 "Invalid mapping given for block-bitmap-mapping: " 285 "The node alias 'common-alias' is used twice") 286 287 def test_non_injective_bitmap_mapping(self) -> None: 288 mapping: BlockBitmapMapping = [{ 289 'node-name': 'node0', 290 'alias': 'node-alias0', 291 'bitmaps': [ 292 { 293 'name': 'bmap0', 294 'alias': 'common-alias' 295 }, 296 { 297 'name': 'bmap1', 298 'alias': 'common-alias' 299 } 300 ] 301 }] 302 303 self.set_mapping(self.vm_a, mapping, 304 "Invalid mapping given for block-bitmap-mapping: " 305 "The bitmap alias 'node-alias0'/'common-alias' is " 306 "used twice") 307 308 def test_ambiguous_node_mapping(self) -> None: 309 mapping: BlockBitmapMapping = [ 310 { 311 'node-name': 'node0', 312 'alias': 'node-alias0', 313 'bitmaps': [{ 314 'name': 'bmap0', 315 'alias': 'bmap-alias0' 316 }] 317 }, 318 { 319 'node-name': 'node0', 320 'alias': 'node-alias1', 321 'bitmaps': [{ 322 'name': 'bmap0', 323 'alias': 'bmap-alias0' 324 }] 325 } 326 ] 327 328 self.set_mapping(self.vm_a, mapping, 329 "Invalid mapping given for block-bitmap-mapping: " 330 "The node name 'node0' is mapped twice") 331 332 def test_ambiguous_bitmap_mapping(self) -> None: 333 mapping: BlockBitmapMapping = [{ 334 'node-name': 'node0', 335 'alias': 'node-alias0', 336 'bitmaps': [ 337 { 338 'name': 'bmap0', 339 'alias': 'bmap-alias0' 340 }, 341 { 342 'name': 'bmap0', 343 'alias': 'bmap-alias1' 344 } 345 ] 346 }] 347 348 self.set_mapping(self.vm_a, mapping, 349 "Invalid mapping given for block-bitmap-mapping: " 350 "The bitmap 'node0'/'bmap0' is mapped twice") 351 352 def test_migratee_node_is_not_mapped_on_src(self) -> None: 353 self.set_mapping(self.vm_a, []) 354 # Should just ignore all bitmaps on unmapped nodes 355 self.migrate(False) 356 self.verify_dest_error(None) 357 358 def test_migratee_node_is_not_mapped_on_dst(self) -> None: 359 self.set_mapping(self.vm_b, []) 360 self.migrate(False) 361 self.verify_dest_error(f"Unknown node alias '{self.src_node_name}'") 362 363 def test_migratee_bitmap_is_not_mapped_on_src(self) -> None: 364 mapping: BlockBitmapMapping = [{ 365 'node-name': self.src_node_name, 366 'alias': self.dst_node_name, 367 'bitmaps': [] 368 }] 369 370 self.set_mapping(self.vm_a, mapping) 371 # Should just ignore all unmapped bitmaps 372 self.migrate(False) 373 self.verify_dest_error(None) 374 375 def test_migratee_bitmap_is_not_mapped_on_dst(self) -> None: 376 mapping: BlockBitmapMapping = [{ 377 'node-name': self.dst_node_name, 378 'alias': self.src_node_name, 379 'bitmaps': [] 380 }] 381 382 self.set_mapping(self.vm_b, mapping) 383 self.migrate(False) 384 self.verify_dest_error(f"Unknown bitmap alias " 385 f"'{self.src_bmap_name}' " 386 f"on node '{self.dst_node_name}' " 387 f"(alias '{self.src_node_name}')") 388 389 def test_unused_mapping_on_dst(self) -> None: 390 # Let the source not send any bitmaps 391 self.set_mapping(self.vm_a, []) 392 393 # Establish some mapping on the destination 394 self.set_mapping(self.vm_b, []) 395 396 # The fact that there is a mapping on B without any bitmaps 397 # being received should be fine, not fatal 398 self.migrate(False) 399 self.verify_dest_error(None) 400 401 def test_non_wellformed_node_alias(self) -> None: 402 alias = '123-foo' 403 404 mapping: BlockBitmapMapping = [{ 405 'node-name': self.src_node_name, 406 'alias': alias, 407 'bitmaps': [] 408 }] 409 410 self.set_mapping(self.vm_a, mapping, 411 f"Invalid mapping given for block-bitmap-mapping: " 412 f"The node alias '{alias}' is not well-formed") 413 414 def test_node_alias_too_long(self) -> None: 415 alias = 'a' * 256 416 417 mapping: BlockBitmapMapping = [{ 418 'node-name': self.src_node_name, 419 'alias': alias, 420 'bitmaps': [] 421 }] 422 423 self.set_mapping(self.vm_a, mapping, 424 f"Invalid mapping given for block-bitmap-mapping: " 425 f"The node alias '{alias}' is longer than 255 bytes") 426 427 def test_bitmap_alias_too_long(self) -> None: 428 alias = 'a' * 256 429 430 mapping = self.mapping(self.src_node_name, self.dst_node_name, 431 self.src_bmap_name, alias) 432 433 self.set_mapping(self.vm_a, mapping, 434 f"Invalid mapping given for block-bitmap-mapping: " 435 f"The bitmap alias '{alias}' is longer than 255 " 436 f"bytes") 437 438 def test_bitmap_name_too_long(self) -> None: 439 name = 'a' * 256 440 441 result = self.vm_a.qmp('block-dirty-bitmap-add', 442 node=self.src_node_name, 443 name=name) 444 self.assert_qmp(result, 'return', {}) 445 446 self.migrate(False, False) 447 448 # Check for the error in the source's log 449 self.vm_a.shutdown() 450 self.assertIn(f"Cannot migrate bitmap '{name}' on node " 451 f"'{self.src_node_name}': Name is longer than 255 bytes", 452 self.vm_a.get_log()) 453 454 # Expect abnormal shutdown of the destination VM because of 455 # the failed migration 456 try: 457 self.vm_b.shutdown() 458 except qemu.machine.AbnormalShutdown: 459 pass 460 461 def test_aliased_bitmap_name_too_long(self) -> None: 462 # Longer than the maximum for bitmap names 463 self.dst_bmap_name = 'a' * 1024 464 465 mapping = self.mapping(self.dst_node_name, self.src_node_name, 466 self.dst_bmap_name, self.src_bmap_name) 467 468 # We would have to create this bitmap during migration, and 469 # that would fail, because the name is too long. Better to 470 # catch it early. 471 self.set_mapping(self.vm_b, mapping, 472 f"Invalid mapping given for block-bitmap-mapping: " 473 f"The bitmap name '{self.dst_bmap_name}' is longer " 474 f"than 1023 bytes") 475 476 def test_node_name_too_long(self) -> None: 477 # Longer than the maximum for node names 478 self.dst_node_name = 'a' * 32 479 480 mapping = self.mapping(self.dst_node_name, self.src_node_name, 481 self.dst_bmap_name, self.src_bmap_name) 482 483 # During migration, this would appear simply as a node that 484 # cannot be found. Still better to catch impossible node 485 # names early (similar to test_non_wellformed_node_alias). 486 self.set_mapping(self.vm_b, mapping, 487 f"Invalid mapping given for block-bitmap-mapping: " 488 f"The node name '{self.dst_node_name}' is longer " 489 f"than 31 bytes") 490 491 492class TestCrossAliasMigration(TestDirtyBitmapMigration): 493 """ 494 Swap aliases, both to see that qemu does not get confused, and 495 that we can migrate multiple things at once. 496 497 So we migrate this: 498 node-a.bmap-a -> node-b.bmap-b 499 node-a.bmap-b -> node-b.bmap-a 500 node-b.bmap-a -> node-a.bmap-b 501 node-b.bmap-b -> node-a.bmap-a 502 """ 503 504 src_node_name = 'node-a' 505 dst_node_name = 'node-b' 506 src_bmap_name = 'bmap-a' 507 dst_bmap_name = 'bmap-b' 508 509 def setUp(self) -> None: 510 TestDirtyBitmapMigration.setUp(self) 511 512 # Now create another block device and let both have two bitmaps each 513 result = self.vm_a.qmp('blockdev-add', 514 node_name='node-b', driver='null-co') 515 self.assert_qmp(result, 'return', {}) 516 517 result = self.vm_b.qmp('blockdev-add', 518 node_name='node-a', driver='null-co') 519 self.assert_qmp(result, 'return', {}) 520 521 bmaps_to_add = (('node-a', 'bmap-b'), 522 ('node-b', 'bmap-a'), 523 ('node-b', 'bmap-b')) 524 525 for (node, bmap) in bmaps_to_add: 526 result = self.vm_a.qmp('block-dirty-bitmap-add', 527 node=node, name=bmap) 528 self.assert_qmp(result, 'return', {}) 529 530 @staticmethod 531 def cross_mapping() -> BlockBitmapMapping: 532 return [ 533 { 534 'node-name': 'node-a', 535 'alias': 'node-b', 536 'bitmaps': [ 537 { 538 'name': 'bmap-a', 539 'alias': 'bmap-b' 540 }, 541 { 542 'name': 'bmap-b', 543 'alias': 'bmap-a' 544 } 545 ] 546 }, 547 { 548 'node-name': 'node-b', 549 'alias': 'node-a', 550 'bitmaps': [ 551 { 552 'name': 'bmap-b', 553 'alias': 'bmap-a' 554 }, 555 { 556 'name': 'bmap-a', 557 'alias': 'bmap-b' 558 } 559 ] 560 } 561 ] 562 563 def verify_dest_has_all_bitmaps(self) -> None: 564 bitmaps = self.vm_b.query_bitmaps() 565 566 # Extract and sort bitmap names 567 for node in bitmaps: 568 bitmaps[node] = sorted((bmap['name'] for bmap in bitmaps[node])) 569 570 self.assertEqual(bitmaps, 571 {'node-a': ['bmap-a', 'bmap-b'], 572 'node-b': ['bmap-a', 'bmap-b']}) 573 574 def test_alias_on_src(self) -> None: 575 self.set_mapping(self.vm_a, self.cross_mapping()) 576 577 # Checks that node-a.bmap-a was migrated to node-b.bmap-b, and 578 # that is enough 579 self.migrate() 580 self.verify_dest_has_all_bitmaps() 581 self.verify_dest_error(None) 582 583 def test_alias_on_dst(self) -> None: 584 self.set_mapping(self.vm_b, self.cross_mapping()) 585 586 # Checks that node-a.bmap-a was migrated to node-b.bmap-b, and 587 # that is enough 588 self.migrate() 589 self.verify_dest_has_all_bitmaps() 590 self.verify_dest_error(None) 591 592 593if __name__ == '__main__': 594 iotests.main(supported_protocols=['file']) 595