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 26 27import iotests 28 29 30BlockBitmapMapping = List[Dict[str, object]] 31 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 116 log = self.vm_b.get_log() 117 assert log is not None # Loaded after shutdown 118 119 if msg is None: 120 self.assertNotIn('qemu-system-', log) 121 else: 122 self.assertIn(msg, log) 123 124 @staticmethod 125 def mapping(node_name: str, node_alias: str, 126 bitmap_name: str, bitmap_alias: str) -> BlockBitmapMapping: 127 return [{ 128 'node-name': node_name, 129 'alias': node_alias, 130 'bitmaps': [{ 131 'name': bitmap_name, 132 'alias': bitmap_alias 133 }] 134 }] 135 136 def set_mapping(self, vm: iotests.VM, mapping: BlockBitmapMapping, 137 error: Optional[str] = None) -> None: 138 """ 139 Invoke migrate-set-parameters on @vm to set the given @mapping. 140 Check for success if @error is None, or verify the error message 141 if it is not. 142 On success, verify that "info migrate_parameters" on HMP returns 143 our mapping. (Just to check its formatting code.) 144 """ 145 result = vm.qmp('migrate-set-parameters', 146 block_bitmap_mapping=mapping) 147 148 if error is None: 149 self.assert_qmp(result, 'return', {}) 150 151 result = vm.qmp('human-monitor-command', 152 command_line='info migrate_parameters') 153 154 m = re.search(r'^block-bitmap-mapping:\r?(\n .*)*\n', 155 result['return'], flags=re.MULTILINE) 156 hmp_mapping = m.group(0).replace('\r', '') if m else None 157 158 self.assertEqual(hmp_mapping, self.to_hmp_mapping(mapping)) 159 else: 160 self.assert_qmp(result, 'error/desc', error) 161 162 @staticmethod 163 def to_hmp_mapping(mapping: BlockBitmapMapping) -> str: 164 result = 'block-bitmap-mapping:\n' 165 166 for node in mapping: 167 result += f" '{node['node-name']}' -> '{node['alias']}'\n" 168 169 assert isinstance(node['bitmaps'], list) 170 for bitmap in node['bitmaps']: 171 result += f" '{bitmap['name']}' -> '{bitmap['alias']}'\n" 172 173 return result 174 175 176class TestAliasMigration(TestDirtyBitmapMigration): 177 src_node_name = 'node0' 178 dst_node_name = 'node0' 179 src_bmap_name = 'bmap0' 180 dst_bmap_name = 'bmap0' 181 182 def test_migration_without_alias(self) -> None: 183 self.migrate(self.src_node_name == self.dst_node_name and 184 self.src_bmap_name == self.dst_bmap_name) 185 186 # Check for error message on the destination 187 if self.src_node_name != self.dst_node_name: 188 self.verify_dest_error(f"Cannot find " 189 f"device='{self.src_node_name}' nor " 190 f"node-name='{self.src_node_name}'") 191 else: 192 self.verify_dest_error(None) 193 194 def test_alias_on_src_migration(self) -> None: 195 mapping = self.mapping(self.src_node_name, self.dst_node_name, 196 self.src_bmap_name, self.dst_bmap_name) 197 198 self.set_mapping(self.vm_a, mapping) 199 self.migrate() 200 self.verify_dest_error(None) 201 202 def test_alias_on_dst_migration(self) -> None: 203 mapping = self.mapping(self.dst_node_name, self.src_node_name, 204 self.dst_bmap_name, self.src_bmap_name) 205 206 self.set_mapping(self.vm_b, mapping) 207 self.migrate() 208 self.verify_dest_error(None) 209 210 def test_alias_on_both_migration(self) -> None: 211 src_map = self.mapping(self.src_node_name, 'node-alias', 212 self.src_bmap_name, 'bmap-alias') 213 214 dst_map = self.mapping(self.dst_node_name, 'node-alias', 215 self.dst_bmap_name, 'bmap-alias') 216 217 self.set_mapping(self.vm_a, src_map) 218 self.set_mapping(self.vm_b, dst_map) 219 self.migrate() 220 self.verify_dest_error(None) 221 222 223class TestNodeAliasMigration(TestAliasMigration): 224 src_node_name = 'node-src' 225 dst_node_name = 'node-dst' 226 227 228class TestBitmapAliasMigration(TestAliasMigration): 229 src_bmap_name = 'bmap-src' 230 dst_bmap_name = 'bmap-dst' 231 232 233class TestFullAliasMigration(TestAliasMigration): 234 src_node_name = 'node-src' 235 dst_node_name = 'node-dst' 236 src_bmap_name = 'bmap-src' 237 dst_bmap_name = 'bmap-dst' 238 239 240class TestLongBitmapNames(TestAliasMigration): 241 # Giving long bitmap names is OK, as long as there is a short alias for 242 # migration 243 src_bmap_name = 'a' * 512 244 dst_bmap_name = 'b' * 512 245 246 # Skip all tests that do not use the intermediate alias 247 def test_migration_without_alias(self) -> None: 248 pass 249 250 def test_alias_on_src_migration(self) -> None: 251 pass 252 253 def test_alias_on_dst_migration(self) -> None: 254 pass 255 256 257class TestBlockBitmapMappingErrors(TestDirtyBitmapMigration): 258 src_node_name = 'node0' 259 dst_node_name = 'node0' 260 src_bmap_name = 'bmap0' 261 dst_bmap_name = 'bmap0' 262 263 """ 264 Note that mapping nodes or bitmaps that do not exist is not an error. 265 """ 266 267 def test_non_injective_node_mapping(self) -> None: 268 mapping: BlockBitmapMapping = [ 269 { 270 'node-name': 'node0', 271 'alias': 'common-alias', 272 'bitmaps': [{ 273 'name': 'bmap0', 274 'alias': 'bmap-alias0' 275 }] 276 }, 277 { 278 'node-name': 'node1', 279 'alias': 'common-alias', 280 'bitmaps': [{ 281 'name': 'bmap1', 282 'alias': 'bmap-alias1' 283 }] 284 } 285 ] 286 287 self.set_mapping(self.vm_a, mapping, 288 "Invalid mapping given for block-bitmap-mapping: " 289 "The node alias 'common-alias' is used twice") 290 291 def test_non_injective_bitmap_mapping(self) -> None: 292 mapping: BlockBitmapMapping = [{ 293 'node-name': 'node0', 294 'alias': 'node-alias0', 295 'bitmaps': [ 296 { 297 'name': 'bmap0', 298 'alias': 'common-alias' 299 }, 300 { 301 'name': 'bmap1', 302 'alias': 'common-alias' 303 } 304 ] 305 }] 306 307 self.set_mapping(self.vm_a, mapping, 308 "Invalid mapping given for block-bitmap-mapping: " 309 "The bitmap alias 'node-alias0'/'common-alias' is " 310 "used twice") 311 312 def test_ambiguous_node_mapping(self) -> None: 313 mapping: BlockBitmapMapping = [ 314 { 315 'node-name': 'node0', 316 'alias': 'node-alias0', 317 'bitmaps': [{ 318 'name': 'bmap0', 319 'alias': 'bmap-alias0' 320 }] 321 }, 322 { 323 'node-name': 'node0', 324 'alias': 'node-alias1', 325 'bitmaps': [{ 326 'name': 'bmap0', 327 'alias': 'bmap-alias0' 328 }] 329 } 330 ] 331 332 self.set_mapping(self.vm_a, mapping, 333 "Invalid mapping given for block-bitmap-mapping: " 334 "The node name 'node0' is mapped twice") 335 336 def test_ambiguous_bitmap_mapping(self) -> None: 337 mapping: BlockBitmapMapping = [{ 338 'node-name': 'node0', 339 'alias': 'node-alias0', 340 'bitmaps': [ 341 { 342 'name': 'bmap0', 343 'alias': 'bmap-alias0' 344 }, 345 { 346 'name': 'bmap0', 347 'alias': 'bmap-alias1' 348 } 349 ] 350 }] 351 352 self.set_mapping(self.vm_a, mapping, 353 "Invalid mapping given for block-bitmap-mapping: " 354 "The bitmap 'node0'/'bmap0' is mapped twice") 355 356 def test_migratee_node_is_not_mapped_on_src(self) -> None: 357 self.set_mapping(self.vm_a, []) 358 # Should just ignore all bitmaps on unmapped nodes 359 self.migrate(False) 360 self.verify_dest_error(None) 361 362 def test_migratee_node_is_not_mapped_on_dst(self) -> None: 363 self.set_mapping(self.vm_b, []) 364 self.migrate(False) 365 self.verify_dest_error(f"Unknown node alias '{self.src_node_name}'") 366 367 def test_migratee_bitmap_is_not_mapped_on_src(self) -> None: 368 mapping: BlockBitmapMapping = [{ 369 'node-name': self.src_node_name, 370 'alias': self.dst_node_name, 371 'bitmaps': [] 372 }] 373 374 self.set_mapping(self.vm_a, mapping) 375 # Should just ignore all unmapped bitmaps 376 self.migrate(False) 377 self.verify_dest_error(None) 378 379 def test_migratee_bitmap_is_not_mapped_on_dst(self) -> None: 380 mapping: BlockBitmapMapping = [{ 381 'node-name': self.dst_node_name, 382 'alias': self.src_node_name, 383 'bitmaps': [] 384 }] 385 386 self.set_mapping(self.vm_b, mapping) 387 self.migrate(False) 388 self.verify_dest_error(f"Unknown bitmap alias " 389 f"'{self.src_bmap_name}' " 390 f"on node '{self.dst_node_name}' " 391 f"(alias '{self.src_node_name}')") 392 393 def test_unused_mapping_on_dst(self) -> None: 394 # Let the source not send any bitmaps 395 self.set_mapping(self.vm_a, []) 396 397 # Establish some mapping on the destination 398 self.set_mapping(self.vm_b, []) 399 400 # The fact that there is a mapping on B without any bitmaps 401 # being received should be fine, not fatal 402 self.migrate(False) 403 self.verify_dest_error(None) 404 405 def test_non_wellformed_node_alias(self) -> None: 406 alias = '123-foo' 407 408 mapping: BlockBitmapMapping = [{ 409 'node-name': self.src_node_name, 410 'alias': alias, 411 'bitmaps': [] 412 }] 413 414 self.set_mapping(self.vm_a, mapping, 415 f"Invalid mapping given for block-bitmap-mapping: " 416 f"The node alias '{alias}' is not well-formed") 417 418 def test_node_alias_too_long(self) -> None: 419 alias = 'a' * 256 420 421 mapping: BlockBitmapMapping = [{ 422 'node-name': self.src_node_name, 423 'alias': alias, 424 'bitmaps': [] 425 }] 426 427 self.set_mapping(self.vm_a, mapping, 428 f"Invalid mapping given for block-bitmap-mapping: " 429 f"The node alias '{alias}' is longer than 255 bytes") 430 431 def test_bitmap_alias_too_long(self) -> None: 432 alias = 'a' * 256 433 434 mapping = self.mapping(self.src_node_name, self.dst_node_name, 435 self.src_bmap_name, alias) 436 437 self.set_mapping(self.vm_a, mapping, 438 f"Invalid mapping given for block-bitmap-mapping: " 439 f"The bitmap alias '{alias}' is longer than 255 " 440 f"bytes") 441 442 def test_bitmap_name_too_long(self) -> None: 443 name = 'a' * 256 444 445 result = self.vm_a.qmp('block-dirty-bitmap-add', 446 node=self.src_node_name, 447 name=name) 448 self.assert_qmp(result, 'return', {}) 449 450 self.migrate(False, False) 451 452 # Check for the error in the source's log 453 self.vm_a.shutdown() 454 455 log = self.vm_a.get_log() 456 assert log is not None # Loaded after shutdown 457 458 self.assertIn(f"Cannot migrate bitmap '{name}' on node " 459 f"'{self.src_node_name}': Name is longer than 255 bytes", 460 log) 461 462 # Destination VM will terminate w/ error of its own accord 463 # due to the failed migration. 464 self.vm_b.wait() 465 rc = self.vm_b.exitcode() 466 assert rc is not None and rc > 0 467 468 def test_aliased_bitmap_name_too_long(self) -> None: 469 # Longer than the maximum for bitmap names 470 self.dst_bmap_name = 'a' * 1024 471 472 mapping = self.mapping(self.dst_node_name, self.src_node_name, 473 self.dst_bmap_name, self.src_bmap_name) 474 475 # We would have to create this bitmap during migration, and 476 # that would fail, because the name is too long. Better to 477 # catch it early. 478 self.set_mapping(self.vm_b, mapping, 479 f"Invalid mapping given for block-bitmap-mapping: " 480 f"The bitmap name '{self.dst_bmap_name}' is longer " 481 f"than 1023 bytes") 482 483 def test_node_name_too_long(self) -> None: 484 # Longer than the maximum for node names 485 self.dst_node_name = 'a' * 32 486 487 mapping = self.mapping(self.dst_node_name, self.src_node_name, 488 self.dst_bmap_name, self.src_bmap_name) 489 490 # During migration, this would appear simply as a node that 491 # cannot be found. Still better to catch impossible node 492 # names early (similar to test_non_wellformed_node_alias). 493 self.set_mapping(self.vm_b, mapping, 494 f"Invalid mapping given for block-bitmap-mapping: " 495 f"The node name '{self.dst_node_name}' is longer " 496 f"than 31 bytes") 497 498 499class TestCrossAliasMigration(TestDirtyBitmapMigration): 500 """ 501 Swap aliases, both to see that qemu does not get confused, and 502 that we can migrate multiple things at once. 503 504 So we migrate this: 505 node-a.bmap-a -> node-b.bmap-b 506 node-a.bmap-b -> node-b.bmap-a 507 node-b.bmap-a -> node-a.bmap-b 508 node-b.bmap-b -> node-a.bmap-a 509 """ 510 511 src_node_name = 'node-a' 512 dst_node_name = 'node-b' 513 src_bmap_name = 'bmap-a' 514 dst_bmap_name = 'bmap-b' 515 516 def setUp(self) -> None: 517 TestDirtyBitmapMigration.setUp(self) 518 519 # Now create another block device and let both have two bitmaps each 520 result = self.vm_a.qmp('blockdev-add', 521 node_name='node-b', driver='null-co') 522 self.assert_qmp(result, 'return', {}) 523 524 result = self.vm_b.qmp('blockdev-add', 525 node_name='node-a', driver='null-co') 526 self.assert_qmp(result, 'return', {}) 527 528 bmaps_to_add = (('node-a', 'bmap-b'), 529 ('node-b', 'bmap-a'), 530 ('node-b', 'bmap-b')) 531 532 for (node, bmap) in bmaps_to_add: 533 result = self.vm_a.qmp('block-dirty-bitmap-add', 534 node=node, name=bmap) 535 self.assert_qmp(result, 'return', {}) 536 537 @staticmethod 538 def cross_mapping() -> BlockBitmapMapping: 539 return [ 540 { 541 'node-name': 'node-a', 542 'alias': 'node-b', 543 'bitmaps': [ 544 { 545 'name': 'bmap-a', 546 'alias': 'bmap-b' 547 }, 548 { 549 'name': 'bmap-b', 550 'alias': 'bmap-a' 551 } 552 ] 553 }, 554 { 555 'node-name': 'node-b', 556 'alias': 'node-a', 557 'bitmaps': [ 558 { 559 'name': 'bmap-b', 560 'alias': 'bmap-a' 561 }, 562 { 563 'name': 'bmap-a', 564 'alias': 'bmap-b' 565 } 566 ] 567 } 568 ] 569 570 def verify_dest_has_all_bitmaps(self) -> None: 571 bitmaps = self.vm_b.query_bitmaps() 572 573 # Extract and sort bitmap names 574 for node in bitmaps: 575 bitmaps[node] = sorted((bmap['name'] for bmap in bitmaps[node])) 576 577 self.assertEqual(bitmaps, 578 {'node-a': ['bmap-a', 'bmap-b'], 579 'node-b': ['bmap-a', 'bmap-b']}) 580 581 def test_alias_on_src(self) -> None: 582 self.set_mapping(self.vm_a, self.cross_mapping()) 583 584 # Checks that node-a.bmap-a was migrated to node-b.bmap-b, and 585 # that is enough 586 self.migrate() 587 self.verify_dest_has_all_bitmaps() 588 self.verify_dest_error(None) 589 590 def test_alias_on_dst(self) -> None: 591 self.set_mapping(self.vm_b, self.cross_mapping()) 592 593 # Checks that node-a.bmap-a was migrated to node-b.bmap-b, and 594 # that is enough 595 self.migrate() 596 self.verify_dest_has_all_bitmaps() 597 self.verify_dest_error(None) 598 599class TestAliasTransformMigration(TestDirtyBitmapMigration): 600 """ 601 Tests the 'transform' option which modifies bitmap persistence on 602 migration. 603 """ 604 605 src_node_name = 'node-a' 606 dst_node_name = 'node-b' 607 src_bmap_name = 'bmap-a' 608 dst_bmap_name = 'bmap-b' 609 610 def setUp(self) -> None: 611 TestDirtyBitmapMigration.setUp(self) 612 613 # Now create another block device and let both have two bitmaps each 614 result = self.vm_a.qmp('blockdev-add', 615 node_name='node-b', driver='null-co', 616 read_zeroes=False) 617 self.assert_qmp(result, 'return', {}) 618 619 result = self.vm_b.qmp('blockdev-add', 620 node_name='node-a', driver='null-co', 621 read_zeroes=False) 622 self.assert_qmp(result, 'return', {}) 623 624 bmaps_to_add = (('node-a', 'bmap-b'), 625 ('node-b', 'bmap-a'), 626 ('node-b', 'bmap-b')) 627 628 for (node, bmap) in bmaps_to_add: 629 result = self.vm_a.qmp('block-dirty-bitmap-add', 630 node=node, name=bmap) 631 self.assert_qmp(result, 'return', {}) 632 633 @staticmethod 634 def transform_mapping() -> BlockBitmapMapping: 635 return [ 636 { 637 'node-name': 'node-a', 638 'alias': 'node-a', 639 'bitmaps': [ 640 { 641 'name': 'bmap-a', 642 'alias': 'bmap-a', 643 'transform': 644 { 645 'persistent': True 646 } 647 }, 648 { 649 'name': 'bmap-b', 650 'alias': 'bmap-b' 651 } 652 ] 653 }, 654 { 655 'node-name': 'node-b', 656 'alias': 'node-b', 657 'bitmaps': [ 658 { 659 'name': 'bmap-a', 660 'alias': 'bmap-a' 661 }, 662 { 663 'name': 'bmap-b', 664 'alias': 'bmap-b' 665 } 666 ] 667 } 668 ] 669 670 def verify_dest_bitmap_state(self) -> None: 671 bitmaps = self.vm_b.query_bitmaps() 672 673 for node in bitmaps: 674 bitmaps[node] = sorted(((bmap['name'], bmap['persistent']) 675 for bmap in bitmaps[node])) 676 677 self.assertEqual(bitmaps, 678 {'node-a': [('bmap-a', True), ('bmap-b', False)], 679 'node-b': [('bmap-a', False), ('bmap-b', False)]}) 680 681 def test_transform_on_src(self) -> None: 682 self.set_mapping(self.vm_a, self.transform_mapping()) 683 684 self.migrate() 685 self.verify_dest_bitmap_state() 686 self.verify_dest_error(None) 687 688 def test_transform_on_dst(self) -> None: 689 self.set_mapping(self.vm_b, self.transform_mapping()) 690 691 self.migrate() 692 self.verify_dest_bitmap_state() 693 self.verify_dest_error(None) 694 695if __name__ == '__main__': 696 iotests.main(supported_protocols=['file']) 697