qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

300 (23787B)


      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 
     22 import os
     23 import random
     24 import re
     25 from typing import Dict, List, Optional
     26 
     27 import iotests
     28 
     29 
     30 BlockBitmapMapping = List[Dict[str, object]]
     31 
     32 mig_sock = os.path.join(iotests.sock_dir, 'mig_sock')
     33 
     34 
     35 class 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 
    176 class 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 
    223 class TestNodeAliasMigration(TestAliasMigration):
    224     src_node_name = 'node-src'
    225     dst_node_name = 'node-dst'
    226 
    227 
    228 class TestBitmapAliasMigration(TestAliasMigration):
    229     src_bmap_name = 'bmap-src'
    230     dst_bmap_name = 'bmap-dst'
    231 
    232 
    233 class 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 
    240 class 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 
    257 class 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 
    499 class 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 
    599 class 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 
    695 if __name__ == '__main__':
    696     iotests.main(supported_protocols=['file'])