xref: /openbmc/qemu/tests/qemu-iotests/300 (revision 30b6852c)
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