cachepc-qemu

Fork of AMDESE/qemu with changes for cachepc side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-qemu
Log | Files | Refs | Submodules | LICENSE | sfeed.txt

300 (23818B)


      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'])