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