]>
Commit | Line | Data |
---|---|---|
33dac6f3 VSO |
1 | #!/usr/bin/env python |
2 | # | |
3 | # Tests for dirty bitmaps migration. | |
4 | # | |
5 | # Copyright (c) 2016-2017 Virtuozzo International GmbH. All rights reserved. | |
6 | # | |
7 | # This program is free software; you can redistribute it and/or modify | |
8 | # it under the terms of the GNU General Public License as published by | |
9 | # the Free Software Foundation; either version 2 of the License, or | |
10 | # (at your option) any later version. | |
11 | # | |
12 | # This program is distributed in the hope that it will be useful, | |
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | # GNU General Public License for more details. | |
16 | # | |
17 | # You should have received a copy of the GNU General Public License | |
18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 | # | |
20 | ||
21 | import os | |
22 | import iotests | |
23 | import time | |
24 | import itertools | |
25 | import operator | |
b9247fc1 | 26 | import re |
33dac6f3 VSO |
27 | from iotests import qemu_img |
28 | ||
29 | ||
30 | disk_a = os.path.join(iotests.test_dir, 'disk_a') | |
31 | disk_b = os.path.join(iotests.test_dir, 'disk_b') | |
32 | size = '1M' | |
33 | mig_file = os.path.join(iotests.test_dir, 'mig_file') | |
25bf2426 VSO |
34 | mig_cmd = 'exec: cat > ' + mig_file |
35 | incoming_cmd = 'exec: cat ' + mig_file | |
33dac6f3 VSO |
36 | |
37 | ||
38 | class TestDirtyBitmapMigration(iotests.QMPTestCase): | |
39 | def tearDown(self): | |
40 | self.vm_a.shutdown() | |
41 | self.vm_b.shutdown() | |
42 | os.remove(disk_a) | |
43 | os.remove(disk_b) | |
44 | os.remove(mig_file) | |
45 | ||
46 | def setUp(self): | |
47 | qemu_img('create', '-f', iotests.imgfmt, disk_a, size) | |
48 | qemu_img('create', '-f', iotests.imgfmt, disk_b, size) | |
49 | ||
50 | self.vm_a = iotests.VM(path_suffix='a').add_drive(disk_a) | |
51 | self.vm_a.launch() | |
52 | ||
53 | self.vm_b = iotests.VM(path_suffix='b') | |
33dac6f3 VSO |
54 | |
55 | def add_bitmap(self, vm, granularity, persistent): | |
56 | params = {'node': 'drive0', | |
57 | 'name': 'bitmap0', | |
58 | 'granularity': granularity} | |
59 | if persistent: | |
60 | params['persistent'] = True | |
33dac6f3 VSO |
61 | |
62 | result = vm.qmp('block-dirty-bitmap-add', **params) | |
63 | self.assert_qmp(result, 'return', {}); | |
64 | ||
65 | def get_bitmap_hash(self, vm): | |
66 | result = vm.qmp('x-debug-block-dirty-bitmap-sha256', | |
67 | node='drive0', name='bitmap0') | |
68 | return result['return']['sha256'] | |
69 | ||
70 | def check_bitmap(self, vm, sha256): | |
71 | result = vm.qmp('x-debug-block-dirty-bitmap-sha256', | |
72 | node='drive0', name='bitmap0') | |
73 | if sha256: | |
74 | self.assert_qmp(result, 'return/sha256', sha256); | |
75 | else: | |
76 | self.assert_qmp(result, 'error/desc', | |
77 | "Dirty bitmap 'bitmap0' not found"); | |
78 | ||
3e6d88f2 VSO |
79 | def do_test_migration_resume_source(self, persistent, migrate_bitmaps): |
80 | granularity = 512 | |
81 | ||
82 | # regions = ((start, count), ...) | |
83 | regions = ((0, 0x10000), | |
84 | (0xf0000, 0x10000), | |
85 | (0xa0201, 0x1000)) | |
86 | ||
87 | mig_caps = [{'capability': 'events', 'state': True}] | |
88 | if migrate_bitmaps: | |
89 | mig_caps.append({'capability': 'dirty-bitmaps', 'state': True}) | |
90 | ||
91 | result = self.vm_a.qmp('migrate-set-capabilities', | |
92 | capabilities=mig_caps) | |
93 | self.assert_qmp(result, 'return', {}) | |
94 | ||
95 | self.add_bitmap(self.vm_a, granularity, persistent) | |
96 | for r in regions: | |
97 | self.vm_a.hmp_qemu_io('drive0', 'write %d %d' % r) | |
98 | sha256 = self.get_bitmap_hash(self.vm_a) | |
99 | ||
100 | result = self.vm_a.qmp('migrate', uri=mig_cmd) | |
101 | while True: | |
102 | event = self.vm_a.event_wait('MIGRATION') | |
103 | if event['data']['status'] == 'completed': | |
104 | break | |
105 | ||
106 | # test that bitmap is still here | |
107 | removed = (not migrate_bitmaps) and persistent | |
108 | self.check_bitmap(self.vm_a, False if removed else sha256) | |
109 | ||
110 | self.vm_a.qmp('cont') | |
111 | ||
112 | # test that bitmap is still here after invalidation | |
113 | self.check_bitmap(self.vm_a, sha256) | |
114 | ||
115 | # shutdown and check that invalidation didn't fail | |
116 | self.vm_a.shutdown() | |
117 | ||
118 | # catch 'Could not reopen qcow2 layer: Bitmap already exists' | |
119 | # possible error | |
120 | log = self.vm_a.get_log() | |
121 | log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log) | |
122 | log = re.sub(r'^(wrote .* bytes at offset .*\n.*KiB.*ops.*sec.*\n){3}', | |
123 | '', log) | |
124 | log = re.sub(r'\[I \+\d+\.\d+\] CLOSED\n?$', '', log) | |
125 | self.assertEqual(log, '') | |
126 | ||
127 | # test that bitmap is still persistent | |
128 | self.vm_a.launch() | |
129 | self.check_bitmap(self.vm_a, sha256 if persistent else False) | |
130 | ||
33dac6f3 VSO |
131 | def do_test_migration(self, persistent, migrate_bitmaps, online, |
132 | shared_storage): | |
133 | granularity = 512 | |
134 | ||
135 | # regions = ((start, count), ...) | |
136 | regions = ((0, 0x10000), | |
137 | (0xf0000, 0x10000), | |
138 | (0xa0201, 0x1000)) | |
139 | ||
140 | should_migrate = migrate_bitmaps or persistent and shared_storage | |
25bf2426 VSO |
141 | mig_caps = [{'capability': 'events', 'state': True}] |
142 | if migrate_bitmaps: | |
143 | mig_caps.append({'capability': 'dirty-bitmaps', 'state': True}) | |
33dac6f3 | 144 | |
25bf2426 VSO |
145 | result = self.vm_a.qmp('migrate-set-capabilities', |
146 | capabilities=mig_caps) | |
147 | self.assert_qmp(result, 'return', {}) | |
148 | ||
149 | self.vm_b.add_incoming(incoming_cmd if online else "defer") | |
33dac6f3 VSO |
150 | self.vm_b.add_drive(disk_a if shared_storage else disk_b) |
151 | ||
152 | if online: | |
153 | os.mkfifo(mig_file) | |
154 | self.vm_b.launch() | |
25bf2426 VSO |
155 | result = self.vm_b.qmp('migrate-set-capabilities', |
156 | capabilities=mig_caps) | |
157 | self.assert_qmp(result, 'return', {}) | |
33dac6f3 VSO |
158 | |
159 | self.add_bitmap(self.vm_a, granularity, persistent) | |
160 | for r in regions: | |
161 | self.vm_a.hmp_qemu_io('drive0', 'write %d %d' % r) | |
162 | sha256 = self.get_bitmap_hash(self.vm_a) | |
163 | ||
25bf2426 | 164 | result = self.vm_a.qmp('migrate', uri=mig_cmd) |
33dac6f3 VSO |
165 | while True: |
166 | event = self.vm_a.event_wait('MIGRATION') | |
167 | if event['data']['status'] == 'completed': | |
168 | break | |
169 | ||
170 | if not online: | |
171 | self.vm_a.shutdown() | |
172 | self.vm_b.launch() | |
25bf2426 VSO |
173 | result = self.vm_b.qmp('migrate-set-capabilities', |
174 | capabilities=mig_caps) | |
175 | self.assert_qmp(result, 'return', {}) | |
176 | result = self.vm_b.qmp('migrate-incoming', uri=incoming_cmd) | |
177 | self.assert_qmp(result, 'return', {}) | |
33dac6f3 | 178 | |
25bf2426 VSO |
179 | while True: |
180 | event = self.vm_b.event_wait('MIGRATION') | |
181 | if event['data']['status'] == 'completed': | |
182 | break | |
33dac6f3 VSO |
183 | |
184 | self.check_bitmap(self.vm_b, sha256 if should_migrate else False) | |
185 | ||
186 | if should_migrate: | |
187 | self.vm_b.shutdown() | |
b9247fc1 VSO |
188 | |
189 | # catch 'Could not reopen qcow2 layer: Bitmap already exists' | |
190 | # possible error | |
191 | log = self.vm_b.get_log() | |
192 | log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log) | |
193 | log = re.sub(r'\[I \+\d+\.\d+\] CLOSED\n?$', '', log) | |
194 | self.assertEqual(log, '') | |
195 | ||
25bf2426 VSO |
196 | # recreate vm_b, as we don't want -incoming option (this will lead |
197 | # to "cat" process left alive after test finish) | |
198 | self.vm_b = iotests.VM(path_suffix='b') | |
199 | self.vm_b.add_drive(disk_a if shared_storage else disk_b) | |
33dac6f3 VSO |
200 | self.vm_b.launch() |
201 | self.check_bitmap(self.vm_b, sha256 if persistent else False) | |
202 | ||
203 | ||
204 | def inject_test_case(klass, name, method, *args, **kwargs): | |
205 | mc = operator.methodcaller(method, *args, **kwargs) | |
c1a65cba | 206 | setattr(klass, 'test_' + method + name, lambda self: mc(self)) |
33dac6f3 | 207 | |
f7640f0d | 208 | for cmb in list(itertools.product((True, False), repeat=4)): |
33dac6f3 VSO |
209 | name = ('_' if cmb[0] else '_not_') + 'persistent_' |
210 | name += ('_' if cmb[1] else '_not_') + 'migbitmap_' | |
211 | name += '_online' if cmb[2] else '_offline' | |
f7640f0d | 212 | name += '_shared' if cmb[3] else '_nonshared' |
33dac6f3 VSO |
213 | |
214 | inject_test_case(TestDirtyBitmapMigration, name, 'do_test_migration', | |
f7640f0d | 215 | *list(cmb)) |
33dac6f3 | 216 | |
3e6d88f2 VSO |
217 | for cmb in list(itertools.product((True, False), repeat=2)): |
218 | name = ('_' if cmb[0] else '_not_') + 'persistent_' | |
219 | name += ('_' if cmb[1] else '_not_') + 'migbitmap' | |
220 | ||
221 | inject_test_case(TestDirtyBitmapMigration, name, | |
222 | 'do_test_migration_resume_source', *list(cmb)) | |
33dac6f3 VSO |
223 | |
224 | if __name__ == '__main__': | |
225 | iotests.main(supported_fmts=['qcow2']) |