blob: 10f9f2a8da68fe4f2d6d023c44e7b79abadbdc80 [file] [log] [blame]
Max Reitzcb5c6cd2020-08-20 17:07:25 +02001#!/usr/bin/env python3
Vladimir Sementsov-Ogievskiy9dd003a2021-01-16 16:44:19 +03002# group: migration
Max Reitzcb5c6cd2020-08-20 17:07:25 +02003#
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
Eric Blake4aa6fc62021-02-15 16:05:18 -060025from typing import Dict, List, Optional
Max Reitz59c94662021-01-18 11:57:20 +010026
John Snowaf6d4c52021-09-23 14:07:10 -040027from qemu.machine import machine
28
Max Reitzcb5c6cd2020-08-20 17:07:25 +020029import iotests
Max Reitz59c94662021-01-18 11:57:20 +010030
Max Reitzcb5c6cd2020-08-20 17:07:25 +020031
Eric Blake4aa6fc62021-02-15 16:05:18 -060032BlockBitmapMapping = List[Dict[str, object]]
Max Reitzcb5c6cd2020-08-20 17:07:25 +020033
Max Reitzcb5c6cd2020-08-20 17:07:25 +020034mig_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()
Max Reitz59c94662021-01-18 11:57:20 +0100117
118 log = self.vm_b.get_log()
119 assert log is not None # Loaded after shutdown
120
Max Reitzcb5c6cd2020-08-20 17:07:25 +0200121 if msg is None:
Max Reitz59c94662021-01-18 11:57:20 +0100122 self.assertNotIn('qemu-system-', log)
Max Reitzcb5c6cd2020-08-20 17:07:25 +0200123 else:
Max Reitz59c94662021-01-18 11:57:20 +0100124 self.assertIn(msg, log)
Max Reitzcb5c6cd2020-08-20 17:07:25 +0200125
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 "
Connor Kuehl785ec4b2021-03-05 09:19:28 -0600191 f"device='{self.src_node_name}' nor "
192 f"node-name='{self.src_node_name}'")
Max Reitzcb5c6cd2020-08-20 17:07:25 +0200193 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()
Max Reitz59c94662021-01-18 11:57:20 +0100456
457 log = self.vm_a.get_log()
458 assert log is not None # Loaded after shutdown
459
Max Reitzcb5c6cd2020-08-20 17:07:25 +0200460 self.assertIn(f"Cannot migrate bitmap '{name}' on node "
461 f"'{self.src_node_name}': Name is longer than 255 bytes",
Max Reitz59c94662021-01-18 11:57:20 +0100462 log)
Max Reitzcb5c6cd2020-08-20 17:07:25 +0200463
464 # Expect abnormal shutdown of the destination VM because of
465 # the failed migration
466 try:
467 self.vm_b.shutdown()
John Snowbeb6b572021-05-27 17:16:53 -0400468 except machine.AbnormalShutdown:
Max Reitzcb5c6cd2020-08-20 17:07:25 +0200469 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
Peter Krempaca4bfec2021-02-12 18:34:25 +0100602class TestAliasTransformMigration(TestDirtyBitmapMigration):
603 """
Eric Blake4aa6fc62021-02-15 16:05:18 -0600604 Tests the 'transform' option which modifies bitmap persistence on
605 migration.
Peter Krempaca4bfec2021-02-12 18:34:25 +0100606 """
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:
Eric Blake4aa6fc62021-02-15 16:05:18 -0600677 bitmaps[node] = sorted(((bmap['name'], bmap['persistent'])
678 for bmap in bitmaps[node]))
Peter Krempaca4bfec2021-02-12 18:34:25 +0100679
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)
Max Reitzcb5c6cd2020-08-20 17:07:25 +0200697
698if __name__ == '__main__':
699 iotests.main(supported_protocols=['file'])