iotests: rename and move 169 and 199 tests
authorVladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
Mon, 25 Jan 2021 18:50:56 +0000 (21:50 +0300)
committerKevin Wolf <kwolf@redhat.com>
Wed, 27 Jan 2021 19:53:14 +0000 (20:53 +0100)
Rename bitmaps migration tests and move them to tests subdirectory to
demonstrate new human-friendly test naming.

Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
Message-Id: <20210125185056.129513-7-vsementsov@virtuozzo.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
tests/qemu-iotests/169 [deleted file]
tests/qemu-iotests/169.out [deleted file]
tests/qemu-iotests/199 [deleted file]
tests/qemu-iotests/199.out [deleted file]
tests/qemu-iotests/tests/migrate-bitmaps-postcopy-test [new file with mode: 0755]
tests/qemu-iotests/tests/migrate-bitmaps-postcopy-test.out [new file with mode: 0644]
tests/qemu-iotests/tests/migrate-bitmaps-test [new file with mode: 0755]
tests/qemu-iotests/tests/migrate-bitmaps-test.out [new file with mode: 0644]

diff --git a/tests/qemu-iotests/169 b/tests/qemu-iotests/169
deleted file mode 100755 (executable)
index a5c7bc8..0000000
+++ /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 <http://www.gnu.org/licenses/>.
-#
-
-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 (file)
index cafb816..0000000
+++ /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 (executable)
index dbf10e5..0000000
+++ /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 <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'])
diff --git a/tests/qemu-iotests/199.out b/tests/qemu-iotests/199.out
deleted file mode 100644 (file)
index 8d7e996..0000000
+++ /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 (executable)
index 0000000..dbf10e5
--- /dev/null
@@ -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 <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'])
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 (file)
index 0000000..8d7e996
--- /dev/null
@@ -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 (executable)
index 0000000..a5c7bc8
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+#
+
+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 (file)
index 0000000..cafb816
--- /dev/null
@@ -0,0 +1,5 @@
+.....................................
+----------------------------------------------------------------------
+Ran 37 tests
+
+OK