From a44be0334beae3a9affb4a3a92cc6852993d7a84 Mon Sep 17 00:00:00 2001 From: Vladimir Sementsov-Ogievskiy Date: Mon, 25 Jan 2021 21:50:56 +0300 Subject: iotests: rename and move 169 and 199 tests Rename bitmaps migration tests and move them to tests subdirectory to demonstrate new human-friendly test naming. Signed-off-by: Vladimir Sementsov-Ogievskiy Message-Id: <20210125185056.129513-7-vsementsov@virtuozzo.com> Signed-off-by: Kevin Wolf --- tests/qemu-iotests/169 | 302 --------------------- tests/qemu-iotests/169.out | 5 - tests/qemu-iotests/199 | 262 ------------------ tests/qemu-iotests/199.out | 5 - .../tests/migrate-bitmaps-postcopy-test | 262 ++++++++++++++++++ .../tests/migrate-bitmaps-postcopy-test.out | 5 + tests/qemu-iotests/tests/migrate-bitmaps-test | 302 +++++++++++++++++++++ tests/qemu-iotests/tests/migrate-bitmaps-test.out | 5 + 8 files changed, 574 insertions(+), 574 deletions(-) delete mode 100755 tests/qemu-iotests/169 delete mode 100644 tests/qemu-iotests/169.out delete mode 100755 tests/qemu-iotests/199 delete mode 100644 tests/qemu-iotests/199.out create mode 100755 tests/qemu-iotests/tests/migrate-bitmaps-postcopy-test create mode 100644 tests/qemu-iotests/tests/migrate-bitmaps-postcopy-test.out create mode 100755 tests/qemu-iotests/tests/migrate-bitmaps-test create mode 100644 tests/qemu-iotests/tests/migrate-bitmaps-test.out (limited to 'tests/qemu-iotests') diff --git a/tests/qemu-iotests/169 b/tests/qemu-iotests/169 deleted file mode 100755 index a5c7bc83e0..0000000000 --- a/tests/qemu-iotests/169 +++ /dev/null @@ -1,302 +0,0 @@ -#!/usr/bin/env python3 -# group: rw migration -# -# Tests for dirty bitmaps 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 . -# - -import os -import iotests -import time -import itertools -import operator -import re -from iotests import qemu_img, qemu_img_create, Timeout - - -disk_a = os.path.join(iotests.test_dir, 'disk_a') -disk_b = os.path.join(iotests.test_dir, 'disk_b') -base_a = os.path.join(iotests.test_dir, 'base_a') -size = '1M' -mig_file = os.path.join(iotests.test_dir, 'mig_file') -mig_cmd = 'exec: cat > ' + mig_file -incoming_cmd = 'exec: cat ' + mig_file - - -class TestDirtyBitmapMigration(iotests.QMPTestCase): - def tearDown(self): - self.vm_a.shutdown() - self.vm_b.shutdown() - os.remove(disk_a) - os.remove(disk_b) - os.remove(mig_file) - - def setUp(self): - 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) - self.vm_a.launch() - - self.vm_b = iotests.VM(path_suffix='b') - - def add_bitmap(self, vm, granularity, persistent): - params = {'node': 'drive0', - 'name': 'bitmap0', - 'granularity': granularity} - if persistent: - params['persistent'] = True - - result = vm.qmp('block-dirty-bitmap-add', **params) - self.assert_qmp(result, 'return', {}); - - def get_bitmap_hash(self, vm): - result = vm.qmp('x-debug-block-dirty-bitmap-sha256', - node='drive0', name='bitmap0') - return result['return']['sha256'] - - def check_bitmap(self, vm, sha256): - result = vm.qmp('x-debug-block-dirty-bitmap-sha256', - node='drive0', name='bitmap0') - if sha256: - self.assert_qmp(result, 'return/sha256', sha256); - else: - self.assert_qmp(result, 'error/desc', - "Dirty bitmap 'bitmap0' not found"); - - def do_test_migration_resume_source(self, persistent, migrate_bitmaps): - granularity = 512 - - # regions = ((start, count), ...) - regions = ((0, 0x10000), - (0xf0000, 0x10000), - (0xa0201, 0x1000)) - - mig_caps = [{'capability': 'events', 'state': True}] - if migrate_bitmaps: - mig_caps.append({'capability': 'dirty-bitmaps', 'state': True}) - - result = self.vm_a.qmp('migrate-set-capabilities', - capabilities=mig_caps) - self.assert_qmp(result, 'return', {}) - - self.add_bitmap(self.vm_a, granularity, persistent) - for r in regions: - self.vm_a.hmp_qemu_io('drive0', 'write %d %d' % r) - sha256 = self.get_bitmap_hash(self.vm_a) - - result = self.vm_a.qmp('migrate', uri=mig_cmd) - while True: - event = self.vm_a.event_wait('MIGRATION') - if event['data']['status'] == 'completed': - break - while True: - result = self.vm_a.qmp('query-status') - if (result['return']['status'] == 'postmigrate'): - break - - # test that bitmap is still here - removed = (not migrate_bitmaps) and persistent - self.check_bitmap(self.vm_a, False if removed else sha256) - - result = self.vm_a.qmp('cont') - self.assert_qmp(result, 'return', {}) - - # test that bitmap is still here after invalidation - self.check_bitmap(self.vm_a, sha256) - - # shutdown and check that invalidation didn't fail - self.vm_a.shutdown() - - # catch 'Could not reopen qcow2 layer: Bitmap already exists' - # possible error - log = self.vm_a.get_log() - log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log) - log = re.sub(r'^(wrote .* bytes at offset .*\n.*KiB.*ops.*sec.*\n){3}', - '', log) - log = re.sub(r'\[I \+\d+\.\d+\] CLOSED\n?$', '', log) - self.assertEqual(log, '') - - # test that bitmap is still persistent - self.vm_a.launch() - self.check_bitmap(self.vm_a, sha256 if persistent else False) - - def do_test_migration(self, persistent, migrate_bitmaps, online, - shared_storage, pre_shutdown): - granularity = 512 - - # regions = ((start, count), ...) - regions = ((0, 0x10000), - (0xf0000, 0x10000), - (0xa0201, 0x1000)) - - should_migrate = \ - (migrate_bitmaps and (persistent or not pre_shutdown)) or \ - (persistent and shared_storage) - mig_caps = [{'capability': 'events', 'state': True}] - if migrate_bitmaps: - mig_caps.append({'capability': 'dirty-bitmaps', 'state': True}) - - self.vm_b.add_incoming(incoming_cmd if online else "defer") - self.vm_b.add_drive(disk_a if shared_storage else disk_b) - - if online: - os.mkfifo(mig_file) - self.vm_b.launch() - result = self.vm_b.qmp('migrate-set-capabilities', - capabilities=mig_caps) - self.assert_qmp(result, 'return', {}) - - self.add_bitmap(self.vm_a, granularity, persistent) - for r in regions: - self.vm_a.hmp_qemu_io('drive0', 'write %d %d' % r) - sha256 = self.get_bitmap_hash(self.vm_a) - - if pre_shutdown: - self.vm_a.shutdown() - self.vm_a.launch() - - result = self.vm_a.qmp('migrate-set-capabilities', - capabilities=mig_caps) - self.assert_qmp(result, 'return', {}) - - result = self.vm_a.qmp('migrate', uri=mig_cmd) - while True: - event = self.vm_a.event_wait('MIGRATION') - if event['data']['status'] == 'completed': - break - - if not online: - self.vm_a.shutdown() - self.vm_b.launch() - result = self.vm_b.qmp('migrate-set-capabilities', - capabilities=mig_caps) - self.assert_qmp(result, 'return', {}) - result = self.vm_b.qmp('migrate-incoming', uri=incoming_cmd) - self.assert_qmp(result, 'return', {}) - - while True: - event = self.vm_b.event_wait('MIGRATION') - if event['data']['status'] == 'completed': - break - - self.check_bitmap(self.vm_b, sha256 if should_migrate else False) - - if should_migrate: - self.vm_b.shutdown() - - # catch 'Could not reopen qcow2 layer: Bitmap already exists' - # possible error - log = self.vm_b.get_log() - log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log) - log = re.sub(r'\[I \+\d+\.\d+\] CLOSED\n?$', '', log) - self.assertEqual(log, '') - - # recreate vm_b, as we don't want -incoming option (this will lead - # to "cat" process left alive after test finish) - self.vm_b = iotests.VM(path_suffix='b') - self.vm_b.add_drive(disk_a if shared_storage else disk_b) - self.vm_b.launch() - self.check_bitmap(self.vm_b, sha256 if persistent else False) - - -def inject_test_case(klass, name, method, *args, **kwargs): - mc = operator.methodcaller(method, *args, **kwargs) - setattr(klass, 'test_' + method + name, lambda self: mc(self)) - -for cmb in list(itertools.product((True, False), repeat=5)): - name = ('_' if cmb[0] else '_not_') + 'persistent_' - name += ('_' if cmb[1] else '_not_') + 'migbitmap_' - name += '_online' if cmb[2] else '_offline' - name += '_shared' if cmb[3] else '_nonshared' - if (cmb[4]): - name += '__pre_shutdown' - - inject_test_case(TestDirtyBitmapMigration, name, 'do_test_migration', - *list(cmb)) - -for cmb in list(itertools.product((True, False), repeat=2)): - name = ('_' if cmb[0] else '_not_') + 'persistent_' - name += ('_' if cmb[1] else '_not_') + 'migbitmap' - - inject_test_case(TestDirtyBitmapMigration, name, - 'do_test_migration_resume_source', *list(cmb)) - - -class TestDirtyBitmapBackingMigration(iotests.QMPTestCase): - def setUp(self): - qemu_img_create('-f', iotests.imgfmt, base_a, size) - qemu_img_create('-f', iotests.imgfmt, '-F', iotests.imgfmt, - '-b', base_a, disk_a, size) - - for f in (disk_a, base_a): - qemu_img('bitmap', '--add', f, 'bmap0') - - blockdev = { - 'node-name': 'node0', - 'driver': iotests.imgfmt, - 'file': { - 'driver': 'file', - 'filename': disk_a - }, - 'backing': { - 'node-name': 'node0-base', - 'driver': iotests.imgfmt, - 'file': { - 'driver': 'file', - 'filename': base_a - } - } - } - - self.vm = iotests.VM() - self.vm.launch() - - result = self.vm.qmp('blockdev-add', **blockdev) - self.assert_qmp(result, 'return', {}) - - # Check that the bitmaps are there - for node in self.vm.qmp('query-named-block-nodes', flat=True)['return']: - if 'node0' in node['node-name']: - self.assert_qmp(node, 'dirty-bitmaps[0]/name', 'bmap0') - - caps = [{'capability': 'events', 'state': True}] - result = self.vm.qmp('migrate-set-capabilities', capabilities=caps) - self.assert_qmp(result, 'return', {}) - - def tearDown(self): - self.vm.shutdown() - for f in (disk_a, base_a): - os.remove(f) - - def test_cont_on_source(self): - """ - Continue the source after migration. - """ - result = self.vm.qmp('migrate', uri=f'exec: cat > /dev/null') - self.assert_qmp(result, 'return', {}) - - with Timeout(10, 'Migration timeout'): - self.vm.wait_migration('postmigrate') - - result = self.vm.qmp('cont') - self.assert_qmp(result, 'return', {}) - - -if __name__ == '__main__': - iotests.main(supported_fmts=['qcow2'], - supported_protocols=['file']) diff --git a/tests/qemu-iotests/169.out b/tests/qemu-iotests/169.out deleted file mode 100644 index cafb8161f7..0000000000 --- a/tests/qemu-iotests/169.out +++ /dev/null @@ -1,5 +0,0 @@ -..................................... ----------------------------------------------------------------------- -Ran 37 tests - -OK diff --git a/tests/qemu-iotests/199 b/tests/qemu-iotests/199 deleted file mode 100755 index dbf10e58d3..0000000000 --- a/tests/qemu-iotests/199 +++ /dev/null @@ -1,262 +0,0 @@ -#!/usr/bin/env python3 -# group: rw migration -# -# 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 . -# - -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']) diff --git a/tests/qemu-iotests/199.out b/tests/qemu-iotests/199.out deleted file mode 100644 index 8d7e996700..0000000000 --- a/tests/qemu-iotests/199.out +++ /dev/null @@ -1,5 +0,0 @@ -... ----------------------------------------------------------------------- -Ran 3 tests - -OK diff --git a/tests/qemu-iotests/tests/migrate-bitmaps-postcopy-test b/tests/qemu-iotests/tests/migrate-bitmaps-postcopy-test new file mode 100755 index 0000000000..dbf10e58d3 --- /dev/null +++ b/tests/qemu-iotests/tests/migrate-bitmaps-postcopy-test @@ -0,0 +1,262 @@ +#!/usr/bin/env python3 +# group: rw migration +# +# 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 . +# + +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']) diff --git a/tests/qemu-iotests/tests/migrate-bitmaps-postcopy-test.out b/tests/qemu-iotests/tests/migrate-bitmaps-postcopy-test.out new file mode 100644 index 0000000000..8d7e996700 --- /dev/null +++ b/tests/qemu-iotests/tests/migrate-bitmaps-postcopy-test.out @@ -0,0 +1,5 @@ +... +---------------------------------------------------------------------- +Ran 3 tests + +OK diff --git a/tests/qemu-iotests/tests/migrate-bitmaps-test b/tests/qemu-iotests/tests/migrate-bitmaps-test new file mode 100755 index 0000000000..a5c7bc83e0 --- /dev/null +++ b/tests/qemu-iotests/tests/migrate-bitmaps-test @@ -0,0 +1,302 @@ +#!/usr/bin/env python3 +# group: rw migration +# +# Tests for dirty bitmaps 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 . +# + +import os +import iotests +import time +import itertools +import operator +import re +from iotests import qemu_img, qemu_img_create, Timeout + + +disk_a = os.path.join(iotests.test_dir, 'disk_a') +disk_b = os.path.join(iotests.test_dir, 'disk_b') +base_a = os.path.join(iotests.test_dir, 'base_a') +size = '1M' +mig_file = os.path.join(iotests.test_dir, 'mig_file') +mig_cmd = 'exec: cat > ' + mig_file +incoming_cmd = 'exec: cat ' + mig_file + + +class TestDirtyBitmapMigration(iotests.QMPTestCase): + def tearDown(self): + self.vm_a.shutdown() + self.vm_b.shutdown() + os.remove(disk_a) + os.remove(disk_b) + os.remove(mig_file) + + def setUp(self): + 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) + self.vm_a.launch() + + self.vm_b = iotests.VM(path_suffix='b') + + def add_bitmap(self, vm, granularity, persistent): + params = {'node': 'drive0', + 'name': 'bitmap0', + 'granularity': granularity} + if persistent: + params['persistent'] = True + + result = vm.qmp('block-dirty-bitmap-add', **params) + self.assert_qmp(result, 'return', {}); + + def get_bitmap_hash(self, vm): + result = vm.qmp('x-debug-block-dirty-bitmap-sha256', + node='drive0', name='bitmap0') + return result['return']['sha256'] + + def check_bitmap(self, vm, sha256): + result = vm.qmp('x-debug-block-dirty-bitmap-sha256', + node='drive0', name='bitmap0') + if sha256: + self.assert_qmp(result, 'return/sha256', sha256); + else: + self.assert_qmp(result, 'error/desc', + "Dirty bitmap 'bitmap0' not found"); + + def do_test_migration_resume_source(self, persistent, migrate_bitmaps): + granularity = 512 + + # regions = ((start, count), ...) + regions = ((0, 0x10000), + (0xf0000, 0x10000), + (0xa0201, 0x1000)) + + mig_caps = [{'capability': 'events', 'state': True}] + if migrate_bitmaps: + mig_caps.append({'capability': 'dirty-bitmaps', 'state': True}) + + result = self.vm_a.qmp('migrate-set-capabilities', + capabilities=mig_caps) + self.assert_qmp(result, 'return', {}) + + self.add_bitmap(self.vm_a, granularity, persistent) + for r in regions: + self.vm_a.hmp_qemu_io('drive0', 'write %d %d' % r) + sha256 = self.get_bitmap_hash(self.vm_a) + + result = self.vm_a.qmp('migrate', uri=mig_cmd) + while True: + event = self.vm_a.event_wait('MIGRATION') + if event['data']['status'] == 'completed': + break + while True: + result = self.vm_a.qmp('query-status') + if (result['return']['status'] == 'postmigrate'): + break + + # test that bitmap is still here + removed = (not migrate_bitmaps) and persistent + self.check_bitmap(self.vm_a, False if removed else sha256) + + result = self.vm_a.qmp('cont') + self.assert_qmp(result, 'return', {}) + + # test that bitmap is still here after invalidation + self.check_bitmap(self.vm_a, sha256) + + # shutdown and check that invalidation didn't fail + self.vm_a.shutdown() + + # catch 'Could not reopen qcow2 layer: Bitmap already exists' + # possible error + log = self.vm_a.get_log() + log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log) + log = re.sub(r'^(wrote .* bytes at offset .*\n.*KiB.*ops.*sec.*\n){3}', + '', log) + log = re.sub(r'\[I \+\d+\.\d+\] CLOSED\n?$', '', log) + self.assertEqual(log, '') + + # test that bitmap is still persistent + self.vm_a.launch() + self.check_bitmap(self.vm_a, sha256 if persistent else False) + + def do_test_migration(self, persistent, migrate_bitmaps, online, + shared_storage, pre_shutdown): + granularity = 512 + + # regions = ((start, count), ...) + regions = ((0, 0x10000), + (0xf0000, 0x10000), + (0xa0201, 0x1000)) + + should_migrate = \ + (migrate_bitmaps and (persistent or not pre_shutdown)) or \ + (persistent and shared_storage) + mig_caps = [{'capability': 'events', 'state': True}] + if migrate_bitmaps: + mig_caps.append({'capability': 'dirty-bitmaps', 'state': True}) + + self.vm_b.add_incoming(incoming_cmd if online else "defer") + self.vm_b.add_drive(disk_a if shared_storage else disk_b) + + if online: + os.mkfifo(mig_file) + self.vm_b.launch() + result = self.vm_b.qmp('migrate-set-capabilities', + capabilities=mig_caps) + self.assert_qmp(result, 'return', {}) + + self.add_bitmap(self.vm_a, granularity, persistent) + for r in regions: + self.vm_a.hmp_qemu_io('drive0', 'write %d %d' % r) + sha256 = self.get_bitmap_hash(self.vm_a) + + if pre_shutdown: + self.vm_a.shutdown() + self.vm_a.launch() + + result = self.vm_a.qmp('migrate-set-capabilities', + capabilities=mig_caps) + self.assert_qmp(result, 'return', {}) + + result = self.vm_a.qmp('migrate', uri=mig_cmd) + while True: + event = self.vm_a.event_wait('MIGRATION') + if event['data']['status'] == 'completed': + break + + if not online: + self.vm_a.shutdown() + self.vm_b.launch() + result = self.vm_b.qmp('migrate-set-capabilities', + capabilities=mig_caps) + self.assert_qmp(result, 'return', {}) + result = self.vm_b.qmp('migrate-incoming', uri=incoming_cmd) + self.assert_qmp(result, 'return', {}) + + while True: + event = self.vm_b.event_wait('MIGRATION') + if event['data']['status'] == 'completed': + break + + self.check_bitmap(self.vm_b, sha256 if should_migrate else False) + + if should_migrate: + self.vm_b.shutdown() + + # catch 'Could not reopen qcow2 layer: Bitmap already exists' + # possible error + log = self.vm_b.get_log() + log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log) + log = re.sub(r'\[I \+\d+\.\d+\] CLOSED\n?$', '', log) + self.assertEqual(log, '') + + # recreate vm_b, as we don't want -incoming option (this will lead + # to "cat" process left alive after test finish) + self.vm_b = iotests.VM(path_suffix='b') + self.vm_b.add_drive(disk_a if shared_storage else disk_b) + self.vm_b.launch() + self.check_bitmap(self.vm_b, sha256 if persistent else False) + + +def inject_test_case(klass, name, method, *args, **kwargs): + mc = operator.methodcaller(method, *args, **kwargs) + setattr(klass, 'test_' + method + name, lambda self: mc(self)) + +for cmb in list(itertools.product((True, False), repeat=5)): + name = ('_' if cmb[0] else '_not_') + 'persistent_' + name += ('_' if cmb[1] else '_not_') + 'migbitmap_' + name += '_online' if cmb[2] else '_offline' + name += '_shared' if cmb[3] else '_nonshared' + if (cmb[4]): + name += '__pre_shutdown' + + inject_test_case(TestDirtyBitmapMigration, name, 'do_test_migration', + *list(cmb)) + +for cmb in list(itertools.product((True, False), repeat=2)): + name = ('_' if cmb[0] else '_not_') + 'persistent_' + name += ('_' if cmb[1] else '_not_') + 'migbitmap' + + inject_test_case(TestDirtyBitmapMigration, name, + 'do_test_migration_resume_source', *list(cmb)) + + +class TestDirtyBitmapBackingMigration(iotests.QMPTestCase): + def setUp(self): + qemu_img_create('-f', iotests.imgfmt, base_a, size) + qemu_img_create('-f', iotests.imgfmt, '-F', iotests.imgfmt, + '-b', base_a, disk_a, size) + + for f in (disk_a, base_a): + qemu_img('bitmap', '--add', f, 'bmap0') + + blockdev = { + 'node-name': 'node0', + 'driver': iotests.imgfmt, + 'file': { + 'driver': 'file', + 'filename': disk_a + }, + 'backing': { + 'node-name': 'node0-base', + 'driver': iotests.imgfmt, + 'file': { + 'driver': 'file', + 'filename': base_a + } + } + } + + self.vm = iotests.VM() + self.vm.launch() + + result = self.vm.qmp('blockdev-add', **blockdev) + self.assert_qmp(result, 'return', {}) + + # Check that the bitmaps are there + for node in self.vm.qmp('query-named-block-nodes', flat=True)['return']: + if 'node0' in node['node-name']: + self.assert_qmp(node, 'dirty-bitmaps[0]/name', 'bmap0') + + caps = [{'capability': 'events', 'state': True}] + result = self.vm.qmp('migrate-set-capabilities', capabilities=caps) + self.assert_qmp(result, 'return', {}) + + def tearDown(self): + self.vm.shutdown() + for f in (disk_a, base_a): + os.remove(f) + + def test_cont_on_source(self): + """ + Continue the source after migration. + """ + result = self.vm.qmp('migrate', uri=f'exec: cat > /dev/null') + self.assert_qmp(result, 'return', {}) + + with Timeout(10, 'Migration timeout'): + self.vm.wait_migration('postmigrate') + + result = self.vm.qmp('cont') + self.assert_qmp(result, 'return', {}) + + +if __name__ == '__main__': + iotests.main(supported_fmts=['qcow2'], + supported_protocols=['file']) diff --git a/tests/qemu-iotests/tests/migrate-bitmaps-test.out b/tests/qemu-iotests/tests/migrate-bitmaps-test.out new file mode 100644 index 0000000000..cafb8161f7 --- /dev/null +++ b/tests/qemu-iotests/tests/migrate-bitmaps-test.out @@ -0,0 +1,5 @@ +..................................... +---------------------------------------------------------------------- +Ran 37 tests + +OK -- cgit v1.2.3-55-g7522