4 # Tests for dirty bitmaps postcopy migration.
6 # Copyright (c) 2016-2017 Virtuozzo International GmbH. All rights reserved.
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.
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.
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/>.
24 from iotests import qemu_img
28 disk_a = os.path.join(iotests.test_dir, 'disk_a')
29 disk_b = os.path.join(iotests.test_dir, 'disk_b')
31 fifo = os.path.join(iotests.test_dir, 'mig_fifo')
36 GiB = 1024 * 1024 * 1024
40 (2 * GiB + 512 * 5, 512),
41 (3 * GiB + 512 * 5, 512),
46 (3 * GiB + 512 * 8, 512),
47 (4 * GiB + 512 * 8, 512),
49 (100 * GiB + GiB // 2, GiB)
53 def apply_discards(vm, discards):
55 vm.hmp_qemu_io('drive0', 'discard {} {}'.format(*d))
58 def event_seconds(event):
59 return event['timestamp']['seconds'] + \
60 event['timestamp']['microseconds'] / 1000000.0
63 def event_dist(e1, e2):
64 return event_seconds(e2) - event_seconds(e1)
67 def check_bitmaps(vm, count):
68 result = vm.qmp('query-block')
71 assert 'dirty-bitmaps' not in result['return'][0]
73 assert len(result['return'][0]['dirty-bitmaps']) == count
76 class TestDirtyBitmapPostcopyMigration(iotests.QMPTestCase):
79 self.vm_a_events += self.vm_a.get_qmp_events()
80 self.vm_b_events += self.vm_b.get_qmp_events()
81 for e in self.vm_a_events:
83 for e in self.vm_b_events:
85 events = (self.vm_a_events + self.vm_b_events)
86 events = [(e['timestamp']['seconds'],
87 e['timestamp']['microseconds'],
90 e.get('data', '')) for e in events]
91 for e in sorted(events):
92 print('{}.{:06} {} {} {}'.format(*e))
102 qemu_img('create', '-f', iotests.imgfmt, disk_a, size)
103 qemu_img('create', '-f', iotests.imgfmt, disk_b, size)
104 self.vm_a = iotests.VM(path_suffix='a').add_drive(disk_a,
106 self.vm_b = iotests.VM(path_suffix='b').add_drive(disk_b,
108 self.vm_b.add_incoming("exec: cat '" + fifo + "'")
112 # collect received events for debug
113 self.vm_a_events = []
114 self.vm_b_events = []
116 def start_postcopy(self):
117 """ Run migration until RESUME event on target. Return this event. """
118 for i in range(nb_bitmaps):
119 result = self.vm_a.qmp('block-dirty-bitmap-add', node='drive0',
120 name='bitmap{}'.format(i),
121 granularity=granularity,
123 self.assert_qmp(result, 'return', {})
125 result = self.vm_a.qmp('x-debug-block-dirty-bitmap-sha256',
126 node='drive0', name='bitmap0')
127 empty_sha256 = result['return']['sha256']
129 apply_discards(self.vm_a, discards1)
131 result = self.vm_a.qmp('x-debug-block-dirty-bitmap-sha256',
132 node='drive0', name='bitmap0')
133 self.discards1_sha256 = result['return']['sha256']
135 # Check, that updating the bitmap by discards works
136 assert self.discards1_sha256 != empty_sha256
138 # We want to calculate resulting sha256. Do it in bitmap0, so, disable
140 for i in range(1, nb_bitmaps):
141 result = self.vm_a.qmp('block-dirty-bitmap-disable', node='drive0',
142 name='bitmap{}'.format(i))
143 self.assert_qmp(result, 'return', {})
145 apply_discards(self.vm_a, discards2)
147 result = self.vm_a.qmp('x-debug-block-dirty-bitmap-sha256',
148 node='drive0', name='bitmap0')
149 self.all_discards_sha256 = result['return']['sha256']
151 # Now, enable some bitmaps, to be updated during migration
152 for i in range(2, nb_bitmaps, 2):
153 result = self.vm_a.qmp('block-dirty-bitmap-enable', node='drive0',
154 name='bitmap{}'.format(i))
155 self.assert_qmp(result, 'return', {})
157 caps = [{'capability': 'dirty-bitmaps', 'state': True},
158 {'capability': 'events', 'state': True}]
160 result = self.vm_a.qmp('migrate-set-capabilities', capabilities=caps)
161 self.assert_qmp(result, 'return', {})
163 result = self.vm_b.qmp('migrate-set-capabilities', capabilities=caps)
164 self.assert_qmp(result, 'return', {})
166 result = self.vm_a.qmp('migrate', uri='exec:cat>' + fifo)
167 self.assert_qmp(result, 'return', {})
169 result = self.vm_a.qmp('migrate-start-postcopy')
170 self.assert_qmp(result, 'return', {})
172 event_resume = self.vm_b.event_wait('RESUME')
173 self.vm_b_events.append(event_resume)
176 def test_postcopy_success(self):
177 event_resume = self.start_postcopy()
179 # enabled bitmaps should be updated
180 apply_discards(self.vm_b, discards2)
182 match = {'data': {'status': 'completed'}}
183 event_complete = self.vm_b.event_wait('MIGRATION', match=match)
184 self.vm_b_events.append(event_complete)
186 # take queued event, should already been happened
187 event_stop = self.vm_a.event_wait('STOP')
188 self.vm_a_events.append(event_stop)
190 downtime = event_dist(event_stop, event_resume)
191 postcopy_time = event_dist(event_resume, event_complete)
193 assert downtime * 10 < postcopy_time
195 print('downtime:', downtime)
196 print('postcopy_time:', postcopy_time)
198 # check that there are no bitmaps stored on source
199 self.vm_a_events += self.vm_a.get_qmp_events()
202 check_bitmaps(self.vm_a, 0)
204 # check that bitmaps are migrated and persistence works
205 check_bitmaps(self.vm_b, nb_bitmaps)
207 # recreate vm_b, so there is no incoming option, which prevents
208 # loading bitmaps from disk
209 self.vm_b = iotests.VM(path_suffix='b').add_drive(disk_b)
211 check_bitmaps(self.vm_b, nb_bitmaps)
213 # Check content of migrated bitmaps. Still, don't waste time checking
215 for i in range(0, nb_bitmaps, 5):
216 result = self.vm_b.qmp('x-debug-block-dirty-bitmap-sha256',
217 node='drive0', name='bitmap{}'.format(i))
218 sha = self.discards1_sha256 if i % 2 else self.all_discards_sha256
219 self.assert_qmp(result, 'return/sha256', sha)
221 def test_early_shutdown_destination(self):
222 self.start_postcopy()
224 self.vm_b_events += self.vm_b.get_qmp_events()
226 # recreate vm_b, so there is no incoming option, which prevents
227 # loading bitmaps from disk
228 self.vm_b = iotests.VM(path_suffix='b').add_drive(disk_b)
230 check_bitmaps(self.vm_b, 0)
232 # Bitmaps will be lost if we just shutdown the vm, as they are marked
233 # to skip storing to disk when prepared for migration. And that's
234 # correct, as actual data may be modified in target vm, so we play
236 # Still, this mark would be taken away if we do 'cont', and bitmaps
237 # become persistent again. (see iotest 169 for such behavior case)
238 result = self.vm_a.qmp('query-status')
239 assert not result['return']['running']
240 self.vm_a_events += self.vm_a.get_qmp_events()
243 check_bitmaps(self.vm_a, 0)
245 def test_early_kill_source(self):
246 self.start_postcopy()
248 self.vm_a_events = self.vm_a.get_qmp_events()
253 match = {'data': {'status': 'completed'}}
254 e_complete = self.vm_b.event_wait('MIGRATION', match=match)
255 self.vm_b_events.append(e_complete)
257 check_bitmaps(self.vm_a, 0)
258 check_bitmaps(self.vm_b, 0)
261 if __name__ == '__main__':
262 iotests.main(supported_fmts=['qcow2'])