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