]> git.proxmox.com Git - mirror_qemu.git/blob - tests/qemu-iotests/300
63036f6a6e1395729fdaaa604573a7d19d310ea6
[mirror_qemu.git] / tests / qemu-iotests / 300
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, Union
26
27 import iotests
28
29 # Import qemu after iotests.py has amended sys.path
30 # pylint: disable=wrong-import-order
31 import qemu
32
33 BlockBitmapMapping = List[Dict[str, Union[str, List[Dict[str, str]]]]]
34
35 mig_sock = os.path.join(iotests.sock_dir, 'mig_sock')
36
37
38 class 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
179 class 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
226 class TestNodeAliasMigration(TestAliasMigration):
227 src_node_name = 'node-src'
228 dst_node_name = 'node-dst'
229
230
231 class TestBitmapAliasMigration(TestAliasMigration):
232 src_bmap_name = 'bmap-src'
233 dst_bmap_name = 'bmap-dst'
234
235
236 class 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
243 class 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
260 class 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
503 class 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
603 class TestAliasTransformMigration(TestDirtyBitmapMigration):
604 """
605 Tests the 'transform' option which modifies bitmap persistence on 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']) for bmap in bitmaps[node]))
678
679 self.assertEqual(bitmaps,
680 {'node-a': [('bmap-a', True), ('bmap-b', False)],
681 'node-b': [('bmap-a', False), ('bmap-b', False)]})
682
683 def test_transform_on_src(self) -> None:
684 self.set_mapping(self.vm_a, self.transform_mapping())
685
686 self.migrate()
687 self.verify_dest_bitmap_state()
688 self.verify_dest_error(None)
689
690 def test_transform_on_dst(self) -> None:
691 self.set_mapping(self.vm_b, self.transform_mapping())
692
693 self.migrate()
694 self.verify_dest_bitmap_state()
695 self.verify_dest_error(None)
696
697 if __name__ == '__main__':
698 iotests.main(supported_protocols=['file'])