| #!/usr/bin/env python3 |
| # |
| # Tests for dirty bitmaps postcopy migration. |
| # |
| # Copyright (c) 2016-2017 Virtuozzo International GmbH. All rights reserved. |
| # |
| # This program is free software; you can redistribute it and/or modify |
| # it under the terms of the GNU General Public License as published by |
| # the Free Software Foundation; either version 2 of the License, or |
| # (at your option) any later version. |
| # |
| # This program is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| # GNU General Public License for more details. |
| # |
| # You should have received a copy of the GNU General Public License |
| # along with this program. If not, see <http://www.gnu.org/licenses/>. |
| # |
| |
| import os |
| import iotests |
| from iotests import qemu_img |
| |
| debug = False |
| |
| disk_a = os.path.join(iotests.test_dir, 'disk_a') |
| disk_b = os.path.join(iotests.test_dir, 'disk_b') |
| size = '256G' |
| fifo = os.path.join(iotests.test_dir, 'mig_fifo') |
| |
| granularity = 512 |
| nb_bitmaps = 15 |
| |
| GiB = 1024 * 1024 * 1024 |
| |
| discards1 = ( |
| (0, GiB), |
| (2 * GiB + 512 * 5, 512), |
| (3 * GiB + 512 * 5, 512), |
| (100 * GiB, GiB) |
| ) |
| |
| discards2 = ( |
| (3 * GiB + 512 * 8, 512), |
| (4 * GiB + 512 * 8, 512), |
| (50 * GiB, GiB), |
| (100 * GiB + GiB // 2, GiB) |
| ) |
| |
| |
| def apply_discards(vm, discards): |
| for d in discards: |
| vm.hmp_qemu_io('drive0', 'discard {} {}'.format(*d)) |
| |
| |
| def event_seconds(event): |
| return event['timestamp']['seconds'] + \ |
| event['timestamp']['microseconds'] / 1000000.0 |
| |
| |
| def event_dist(e1, e2): |
| return event_seconds(e2) - event_seconds(e1) |
| |
| |
| def check_bitmaps(vm, count): |
| result = vm.qmp('query-block') |
| |
| if count == 0: |
| assert 'dirty-bitmaps' not in result['return'][0] |
| else: |
| assert len(result['return'][0]['dirty-bitmaps']) == count |
| |
| |
| class TestDirtyBitmapPostcopyMigration(iotests.QMPTestCase): |
| def tearDown(self): |
| if debug: |
| self.vm_a_events += self.vm_a.get_qmp_events() |
| self.vm_b_events += self.vm_b.get_qmp_events() |
| for e in self.vm_a_events: |
| e['vm'] = 'SRC' |
| for e in self.vm_b_events: |
| e['vm'] = 'DST' |
| events = (self.vm_a_events + self.vm_b_events) |
| events = [(e['timestamp']['seconds'], |
| e['timestamp']['microseconds'], |
| e['vm'], |
| e['event'], |
| e.get('data', '')) for e in events] |
| for e in sorted(events): |
| print('{}.{:06} {} {} {}'.format(*e)) |
| |
| self.vm_a.shutdown() |
| self.vm_b.shutdown() |
| os.remove(disk_a) |
| os.remove(disk_b) |
| os.remove(fifo) |
| |
| def setUp(self): |
| os.mkfifo(fifo) |
| qemu_img('create', '-f', iotests.imgfmt, disk_a, size) |
| qemu_img('create', '-f', iotests.imgfmt, disk_b, size) |
| self.vm_a = iotests.VM(path_suffix='a').add_drive(disk_a, |
| 'discard=unmap') |
| self.vm_b = iotests.VM(path_suffix='b').add_drive(disk_b, |
| 'discard=unmap') |
| self.vm_b.add_incoming("exec: cat '" + fifo + "'") |
| self.vm_a.launch() |
| self.vm_b.launch() |
| |
| # collect received events for debug |
| self.vm_a_events = [] |
| self.vm_b_events = [] |
| |
| def start_postcopy(self): |
| """ Run migration until RESUME event on target. Return this event. """ |
| for i in range(nb_bitmaps): |
| result = self.vm_a.qmp('block-dirty-bitmap-add', node='drive0', |
| name='bitmap{}'.format(i), |
| granularity=granularity, |
| persistent=True) |
| self.assert_qmp(result, 'return', {}) |
| |
| result = self.vm_a.qmp('x-debug-block-dirty-bitmap-sha256', |
| node='drive0', name='bitmap0') |
| empty_sha256 = result['return']['sha256'] |
| |
| apply_discards(self.vm_a, discards1) |
| |
| result = self.vm_a.qmp('x-debug-block-dirty-bitmap-sha256', |
| node='drive0', name='bitmap0') |
| self.discards1_sha256 = result['return']['sha256'] |
| |
| # Check, that updating the bitmap by discards works |
| assert self.discards1_sha256 != empty_sha256 |
| |
| # We want to calculate resulting sha256. Do it in bitmap0, so, disable |
| # other bitmaps |
| for i in range(1, nb_bitmaps): |
| result = self.vm_a.qmp('block-dirty-bitmap-disable', node='drive0', |
| name='bitmap{}'.format(i)) |
| self.assert_qmp(result, 'return', {}) |
| |
| apply_discards(self.vm_a, discards2) |
| |
| result = self.vm_a.qmp('x-debug-block-dirty-bitmap-sha256', |
| node='drive0', name='bitmap0') |
| self.all_discards_sha256 = result['return']['sha256'] |
| |
| # Now, enable some bitmaps, to be updated during migration |
| for i in range(2, nb_bitmaps, 2): |
| result = self.vm_a.qmp('block-dirty-bitmap-enable', node='drive0', |
| name='bitmap{}'.format(i)) |
| self.assert_qmp(result, 'return', {}) |
| |
| caps = [{'capability': 'dirty-bitmaps', 'state': True}, |
| {'capability': 'events', 'state': True}] |
| |
| result = self.vm_a.qmp('migrate-set-capabilities', capabilities=caps) |
| self.assert_qmp(result, 'return', {}) |
| |
| result = self.vm_b.qmp('migrate-set-capabilities', capabilities=caps) |
| self.assert_qmp(result, 'return', {}) |
| |
| result = self.vm_a.qmp('migrate', uri='exec:cat>' + fifo) |
| self.assert_qmp(result, 'return', {}) |
| |
| result = self.vm_a.qmp('migrate-start-postcopy') |
| self.assert_qmp(result, 'return', {}) |
| |
| event_resume = self.vm_b.event_wait('RESUME') |
| self.vm_b_events.append(event_resume) |
| return event_resume |
| |
| def test_postcopy_success(self): |
| event_resume = self.start_postcopy() |
| |
| # enabled bitmaps should be updated |
| apply_discards(self.vm_b, discards2) |
| |
| match = {'data': {'status': 'completed'}} |
| event_complete = self.vm_b.event_wait('MIGRATION', match=match) |
| self.vm_b_events.append(event_complete) |
| |
| # take queued event, should already been happened |
| event_stop = self.vm_a.event_wait('STOP') |
| self.vm_a_events.append(event_stop) |
| |
| downtime = event_dist(event_stop, event_resume) |
| postcopy_time = event_dist(event_resume, event_complete) |
| |
| assert downtime * 10 < postcopy_time |
| if debug: |
| print('downtime:', downtime) |
| print('postcopy_time:', postcopy_time) |
| |
| # check that there are no bitmaps stored on source |
| self.vm_a_events += self.vm_a.get_qmp_events() |
| self.vm_a.shutdown() |
| self.vm_a.launch() |
| check_bitmaps(self.vm_a, 0) |
| |
| # check that bitmaps are migrated and persistence works |
| check_bitmaps(self.vm_b, nb_bitmaps) |
| self.vm_b.shutdown() |
| # recreate vm_b, so there is no incoming option, which prevents |
| # loading bitmaps from disk |
| self.vm_b = iotests.VM(path_suffix='b').add_drive(disk_b) |
| self.vm_b.launch() |
| check_bitmaps(self.vm_b, nb_bitmaps) |
| |
| # Check content of migrated bitmaps. Still, don't waste time checking |
| # every bitmap |
| for i in range(0, nb_bitmaps, 5): |
| result = self.vm_b.qmp('x-debug-block-dirty-bitmap-sha256', |
| node='drive0', name='bitmap{}'.format(i)) |
| sha = self.discards1_sha256 if i % 2 else self.all_discards_sha256 |
| self.assert_qmp(result, 'return/sha256', sha) |
| |
| def test_early_shutdown_destination(self): |
| self.start_postcopy() |
| |
| self.vm_b_events += self.vm_b.get_qmp_events() |
| self.vm_b.shutdown() |
| # recreate vm_b, so there is no incoming option, which prevents |
| # loading bitmaps from disk |
| self.vm_b = iotests.VM(path_suffix='b').add_drive(disk_b) |
| self.vm_b.launch() |
| check_bitmaps(self.vm_b, 0) |
| |
| # Bitmaps will be lost if we just shutdown the vm, as they are marked |
| # to skip storing to disk when prepared for migration. And that's |
| # correct, as actual data may be modified in target vm, so we play |
| # safe. |
| # Still, this mark would be taken away if we do 'cont', and bitmaps |
| # become persistent again. (see iotest 169 for such behavior case) |
| result = self.vm_a.qmp('query-status') |
| assert not result['return']['running'] |
| self.vm_a_events += self.vm_a.get_qmp_events() |
| self.vm_a.shutdown() |
| self.vm_a.launch() |
| check_bitmaps(self.vm_a, 0) |
| |
| def test_early_kill_source(self): |
| self.start_postcopy() |
| |
| self.vm_a_events = self.vm_a.get_qmp_events() |
| self.vm_a.kill() |
| |
| self.vm_a.launch() |
| |
| match = {'data': {'status': 'completed'}} |
| e_complete = self.vm_b.event_wait('MIGRATION', match=match) |
| self.vm_b_events.append(e_complete) |
| |
| check_bitmaps(self.vm_a, 0) |
| check_bitmaps(self.vm_b, 0) |
| |
| |
| if __name__ == '__main__': |
| iotests.main(supported_fmts=['qcow2']) |