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