xref: /openbmc/qemu/tests/qemu-iotests/056 (revision 9dd003a99842d1d82c336e45c5cce656149de382)
1903cb1bfSPhilippe Mathieu-Daudé#!/usr/bin/env python3
2*9dd003a9SVladimir Sementsov-Ogievskiy# group: rw backing
3e3409362SIan Main#
4e3409362SIan Main# Tests for drive-backup
5e3409362SIan Main#
6e3409362SIan Main# Copyright (C) 2013 Red Hat, Inc.
7e3409362SIan Main#
8e3409362SIan Main# Based on 041.
9e3409362SIan Main#
10e3409362SIan Main# This program is free software; you can redistribute it and/or modify
11e3409362SIan Main# it under the terms of the GNU General Public License as published by
12e3409362SIan Main# the Free Software Foundation; either version 2 of the License, or
13e3409362SIan Main# (at your option) any later version.
14e3409362SIan Main#
15e3409362SIan Main# This program is distributed in the hope that it will be useful,
16e3409362SIan Main# but WITHOUT ANY WARRANTY; without even the implied warranty of
17e3409362SIan Main# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18e3409362SIan Main# GNU General Public License for more details.
19e3409362SIan Main#
20e3409362SIan Main# You should have received a copy of the GNU General Public License
21e3409362SIan Main# along with this program.  If not, see <http://www.gnu.org/licenses/>.
22e3409362SIan Main#
23e3409362SIan Main
24e3409362SIan Mainimport time
25e3409362SIan Mainimport os
26e3409362SIan Mainimport iotests
27e3409362SIan Mainfrom iotests import qemu_img, qemu_io, create_image
28e3409362SIan Main
29e3409362SIan Mainbacking_img = os.path.join(iotests.test_dir, 'backing.img')
30e3409362SIan Maintest_img = os.path.join(iotests.test_dir, 'test.img')
31e3409362SIan Maintarget_img = os.path.join(iotests.test_dir, 'target.img')
32e3409362SIan Main
336d8be967SJohn Snowdef img_create(img, fmt=iotests.imgfmt, size='64M', **kwargs):
346d8be967SJohn Snow    fullname = os.path.join(iotests.test_dir, '%s.%s' % (img, fmt))
356d8be967SJohn Snow    optargs = []
3668474776SMax Reitz    for k,v in kwargs.items():
376d8be967SJohn Snow        optargs = optargs + ['-o', '%s=%s' % (k,v)]
386d8be967SJohn Snow    args = ['create', '-f', fmt] + optargs + [fullname, size]
396d8be967SJohn Snow    iotests.qemu_img(*args)
406d8be967SJohn Snow    return fullname
416d8be967SJohn Snow
426d8be967SJohn Snowdef try_remove(img):
436d8be967SJohn Snow    try:
446d8be967SJohn Snow        os.remove(img)
456d8be967SJohn Snow    except OSError:
466d8be967SJohn Snow        pass
476d8be967SJohn Snow
486d8be967SJohn Snowdef io_write_patterns(img, patterns):
496d8be967SJohn Snow    for pattern in patterns:
506d8be967SJohn Snow        iotests.qemu_io('-c', 'write -P%s %s %s' % pattern, img)
516d8be967SJohn Snow
526d8be967SJohn Snow
53e3409362SIan Mainclass TestSyncModesNoneAndTop(iotests.QMPTestCase):
54e3409362SIan Main    image_len = 64 * 1024 * 1024 # MB
55e3409362SIan Main
56e3409362SIan Main    def setUp(self):
57e3409362SIan Main        create_image(backing_img, TestSyncModesNoneAndTop.image_len)
58b66ff2c2SEric Blake        qemu_img('create', '-f', iotests.imgfmt,
59b66ff2c2SEric Blake                 '-o', 'backing_file=%s' % backing_img, '-F', 'raw', test_img)
60e3409362SIan Main        qemu_io('-c', 'write -P0x41 0 512', test_img)
61e3409362SIan Main        qemu_io('-c', 'write -P0xd5 1M 32k', test_img)
62e3409362SIan Main        qemu_io('-c', 'write -P0xdc 32M 124k', test_img)
63e3409362SIan Main        qemu_io('-c', 'write -P0xdc 67043328 64k', test_img)
64e3409362SIan Main        self.vm = iotests.VM().add_drive(test_img)
65e3409362SIan Main        self.vm.launch()
66e3409362SIan Main
67e3409362SIan Main    def tearDown(self):
68e3409362SIan Main        self.vm.shutdown()
69e3409362SIan Main        os.remove(test_img)
70e3409362SIan Main        os.remove(backing_img)
71e3409362SIan Main        try:
72e3409362SIan Main            os.remove(target_img)
73e3409362SIan Main        except OSError:
74e3409362SIan Main            pass
75e3409362SIan Main
76e3409362SIan Main    def test_complete_top(self):
77e3409362SIan Main        self.assert_no_active_block_jobs()
78e3409362SIan Main        result = self.vm.qmp('drive-backup', device='drive0', sync='top',
79e3409362SIan Main                             format=iotests.imgfmt, target=target_img)
80e3409362SIan Main        self.assert_qmp(result, 'return', {})
81e3409362SIan Main
829974ad40SFam Zheng        self.wait_until_completed(check_offset=False)
83e3409362SIan Main
84e3409362SIan Main        self.assert_no_active_block_jobs()
85e3409362SIan Main        self.vm.shutdown()
86e3409362SIan Main        self.assertTrue(iotests.compare_images(test_img, target_img),
87e3409362SIan Main                        'target image does not match source after backup')
88e3409362SIan Main
89e3409362SIan Main    def test_cancel_sync_none(self):
90e3409362SIan Main        self.assert_no_active_block_jobs()
91e3409362SIan Main
92e3409362SIan Main        result = self.vm.qmp('drive-backup', device='drive0',
93e3409362SIan Main                             sync='none', target=target_img)
94e3409362SIan Main        self.assert_qmp(result, 'return', {})
95e3409362SIan Main        time.sleep(1)
96e3409362SIan Main        self.vm.hmp_qemu_io('drive0', 'write -P0x5e 0 512')
97e3409362SIan Main        self.vm.hmp_qemu_io('drive0', 'aio_flush')
98e3409362SIan Main        # Verify that the original contents exist in the target image.
99e3409362SIan Main
100e3409362SIan Main        event = self.cancel_and_wait()
101e3409362SIan Main        self.assert_qmp(event, 'data/type', 'backup')
102e3409362SIan Main
103e3409362SIan Main        self.vm.shutdown()
104e3409362SIan Main        time.sleep(1)
105e3409362SIan Main        self.assertEqual(-1, qemu_io('-c', 'read -P0x41 0 512', target_img).find("verification failed"))
106e3409362SIan Main
1079cc0f19dSFam Zhengclass TestBeforeWriteNotifier(iotests.QMPTestCase):
1089cc0f19dSFam Zheng    def setUp(self):
1099cc0f19dSFam Zheng        self.vm = iotests.VM().add_drive_raw("file=blkdebug::null-co://,id=drive0,align=65536,driver=blkdebug")
1109cc0f19dSFam Zheng        self.vm.launch()
1119cc0f19dSFam Zheng
1129cc0f19dSFam Zheng    def tearDown(self):
1139cc0f19dSFam Zheng        self.vm.shutdown()
1149cc0f19dSFam Zheng        os.remove(target_img)
1159cc0f19dSFam Zheng
1169cc0f19dSFam Zheng    def test_before_write_notifier(self):
1179cc0f19dSFam Zheng        self.vm.pause_drive("drive0")
1189cc0f19dSFam Zheng        result = self.vm.qmp('drive-backup', device='drive0',
1199cc0f19dSFam Zheng                             sync='full', target=target_img,
1209cc0f19dSFam Zheng                             format="file", speed=1)
1219cc0f19dSFam Zheng        self.assert_qmp(result, 'return', {})
1229cc0f19dSFam Zheng        result = self.vm.qmp('block-job-pause', device="drive0")
1239cc0f19dSFam Zheng        self.assert_qmp(result, 'return', {})
1249cc0f19dSFam Zheng        # Speed is low enough that this must be an uncopied range, which will
1259cc0f19dSFam Zheng        # trigger the before write notifier
1269cc0f19dSFam Zheng        self.vm.hmp_qemu_io('drive0', 'aio_write -P 1 512512 512')
1279cc0f19dSFam Zheng        self.vm.resume_drive("drive0")
1289cc0f19dSFam Zheng        result = self.vm.qmp('block-job-resume', device="drive0")
1299cc0f19dSFam Zheng        self.assert_qmp(result, 'return', {})
1309cc0f19dSFam Zheng        event = self.cancel_and_wait()
1319cc0f19dSFam Zheng        self.assert_qmp(event, 'data/type', 'backup')
132e3409362SIan Main
1336d8be967SJohn Snowclass BackupTest(iotests.QMPTestCase):
1346d8be967SJohn Snow    def setUp(self):
1356d8be967SJohn Snow        self.vm = iotests.VM()
1366d8be967SJohn Snow        self.test_img = img_create('test')
1376d8be967SJohn Snow        self.dest_img = img_create('dest')
13800e30f05SVladimir Sementsov-Ogievskiy        self.dest_img2 = img_create('dest2')
1395f594a2eSMax Reitz        self.ref_img = img_create('ref')
1406d8be967SJohn Snow        self.vm.add_drive(self.test_img)
1416d8be967SJohn Snow        self.vm.launch()
1426d8be967SJohn Snow
1436d8be967SJohn Snow    def tearDown(self):
1446d8be967SJohn Snow        self.vm.shutdown()
1456d8be967SJohn Snow        try_remove(self.test_img)
1466d8be967SJohn Snow        try_remove(self.dest_img)
14700e30f05SVladimir Sementsov-Ogievskiy        try_remove(self.dest_img2)
1485f594a2eSMax Reitz        try_remove(self.ref_img)
1496d8be967SJohn Snow
1506d8be967SJohn Snow    def hmp_io_writes(self, drive, patterns):
1516d8be967SJohn Snow        for pattern in patterns:
1526d8be967SJohn Snow            self.vm.hmp_qemu_io(drive, 'write -P%s %s %s' % pattern)
1536d8be967SJohn Snow        self.vm.hmp_qemu_io(drive, 'flush')
1546d8be967SJohn Snow
1556d8be967SJohn Snow    def qmp_backup_and_wait(self, cmd='drive-backup', serror=None,
1566d8be967SJohn Snow                            aerror=None, **kwargs):
1576d8be967SJohn Snow        if not self.qmp_backup(cmd, serror, **kwargs):
1586d8be967SJohn Snow            return False
1596d8be967SJohn Snow        return self.qmp_backup_wait(kwargs['device'], aerror)
1606d8be967SJohn Snow
1616d8be967SJohn Snow    def qmp_backup(self, cmd='drive-backup',
1626d8be967SJohn Snow                   error=None, **kwargs):
1636d8be967SJohn Snow        self.assertTrue('device' in kwargs)
1646d8be967SJohn Snow        res = self.vm.qmp(cmd, **kwargs)
1656d8be967SJohn Snow        if error:
1666d8be967SJohn Snow            self.assert_qmp(res, 'error/desc', error)
1676d8be967SJohn Snow            return False
1686d8be967SJohn Snow        self.assert_qmp(res, 'return', {})
1696d8be967SJohn Snow        return True
1706d8be967SJohn Snow
1716d8be967SJohn Snow    def qmp_backup_wait(self, device, error=None):
1726d8be967SJohn Snow        event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED",
1736d8be967SJohn Snow                                   match={'data': {'device': device}})
1746d8be967SJohn Snow        self.assertNotEqual(event, None)
1756d8be967SJohn Snow        try:
1766d8be967SJohn Snow            failure = self.dictpath(event, 'data/error')
1776d8be967SJohn Snow        except AssertionError:
1786d8be967SJohn Snow            # Backup succeeded.
1796d8be967SJohn Snow            self.assert_qmp(event, 'data/offset', event['data']['len'])
1806d8be967SJohn Snow            return True
1816d8be967SJohn Snow        else:
1826d8be967SJohn Snow            # Failure.
1836d8be967SJohn Snow            self.assert_qmp(event, 'data/error', qerror)
1846d8be967SJohn Snow            return False
1856d8be967SJohn Snow
1865f594a2eSMax Reitz    def test_overlapping_writes(self):
1875f594a2eSMax Reitz        # Write something to back up
1885f594a2eSMax Reitz        self.hmp_io_writes('drive0', [('42', '0M', '2M')])
1895f594a2eSMax Reitz
1905f594a2eSMax Reitz        # Create a reference backup
1915f594a2eSMax Reitz        self.qmp_backup_and_wait(device='drive0', format=iotests.imgfmt,
1925f594a2eSMax Reitz                                 sync='full', target=self.ref_img,
1935f594a2eSMax Reitz                                 auto_dismiss=False)
1945f594a2eSMax Reitz        res = self.vm.qmp('block-job-dismiss', id='drive0')
1955f594a2eSMax Reitz        self.assert_qmp(res, 'return', {})
1965f594a2eSMax Reitz
1975f594a2eSMax Reitz        # Now to the test backup: We simulate the following guest
1985f594a2eSMax Reitz        # writes:
1995f594a2eSMax Reitz        # (1) [1M + 64k, 1M + 128k): Afterwards, everything in that
2005f594a2eSMax Reitz        #     area should be in the target image, and we must not copy
2015f594a2eSMax Reitz        #     it again (because the source image has changed now)
2025f594a2eSMax Reitz        #     (64k is the job's cluster size)
2035f594a2eSMax Reitz        # (2) [1M, 2M): The backup job must not get overeager.  It
2045f594a2eSMax Reitz        #     must copy [1M, 1M + 64k) and [1M + 128k, 2M) separately,
2055f594a2eSMax Reitz        #     but not the area in between.
2065f594a2eSMax Reitz
2075f594a2eSMax Reitz        self.qmp_backup(device='drive0', format=iotests.imgfmt, sync='full',
2085f594a2eSMax Reitz                        target=self.dest_img, speed=1, auto_dismiss=False)
2095f594a2eSMax Reitz
2105f594a2eSMax Reitz        self.hmp_io_writes('drive0', [('23', '%ik' % (1024 + 64), '64k'),
2115f594a2eSMax Reitz                                      ('66', '1M', '1M')])
2125f594a2eSMax Reitz
2135f594a2eSMax Reitz        # Let the job complete
2145f594a2eSMax Reitz        res = self.vm.qmp('block-job-set-speed', device='drive0', speed=0)
2155f594a2eSMax Reitz        self.assert_qmp(res, 'return', {})
2165f594a2eSMax Reitz        self.qmp_backup_wait('drive0')
2175f594a2eSMax Reitz        res = self.vm.qmp('block-job-dismiss', id='drive0')
2185f594a2eSMax Reitz        self.assert_qmp(res, 'return', {})
2195f594a2eSMax Reitz
2205f594a2eSMax Reitz        self.assertTrue(iotests.compare_images(self.ref_img, self.dest_img),
2215f594a2eSMax Reitz                        'target image does not match reference image')
2225f594a2eSMax Reitz
2236d8be967SJohn Snow    def test_dismiss_false(self):
2246d8be967SJohn Snow        res = self.vm.qmp('query-block-jobs')
2256d8be967SJohn Snow        self.assert_qmp(res, 'return', [])
2266d8be967SJohn Snow        self.qmp_backup_and_wait(device='drive0', format=iotests.imgfmt,
2276d8be967SJohn Snow                                 sync='full', target=self.dest_img,
2286d8be967SJohn Snow                                 auto_dismiss=True)
2296d8be967SJohn Snow        res = self.vm.qmp('query-block-jobs')
2306d8be967SJohn Snow        self.assert_qmp(res, 'return', [])
2316d8be967SJohn Snow
2326d8be967SJohn Snow    def test_dismiss_true(self):
2336d8be967SJohn Snow        res = self.vm.qmp('query-block-jobs')
2346d8be967SJohn Snow        self.assert_qmp(res, 'return', [])
2356d8be967SJohn Snow        self.qmp_backup_and_wait(device='drive0', format=iotests.imgfmt,
2366d8be967SJohn Snow                                 sync='full', target=self.dest_img,
2376d8be967SJohn Snow                                 auto_dismiss=False)
2386d8be967SJohn Snow        res = self.vm.qmp('query-block-jobs')
2396d8be967SJohn Snow        self.assert_qmp(res, 'return[0]/status', 'concluded')
2406d8be967SJohn Snow        res = self.vm.qmp('block-job-dismiss', id='drive0')
2416d8be967SJohn Snow        self.assert_qmp(res, 'return', {})
2426d8be967SJohn Snow        res = self.vm.qmp('query-block-jobs')
2436d8be967SJohn Snow        self.assert_qmp(res, 'return', [])
2446d8be967SJohn Snow
2456d8be967SJohn Snow    def test_dismiss_bad_id(self):
2466d8be967SJohn Snow        res = self.vm.qmp('query-block-jobs')
2476d8be967SJohn Snow        self.assert_qmp(res, 'return', [])
2486d8be967SJohn Snow        res = self.vm.qmp('block-job-dismiss', id='foobar')
2496d8be967SJohn Snow        self.assert_qmp(res, 'error/class', 'DeviceNotActive')
2506d8be967SJohn Snow
2516d8be967SJohn Snow    def test_dismiss_collision(self):
2526d8be967SJohn Snow        res = self.vm.qmp('query-block-jobs')
2536d8be967SJohn Snow        self.assert_qmp(res, 'return', [])
2546d8be967SJohn Snow        self.qmp_backup_and_wait(device='drive0', format=iotests.imgfmt,
2556d8be967SJohn Snow                                 sync='full', target=self.dest_img,
2566d8be967SJohn Snow                                 auto_dismiss=False)
2576d8be967SJohn Snow        res = self.vm.qmp('query-block-jobs')
2586d8be967SJohn Snow        self.assert_qmp(res, 'return[0]/status', 'concluded')
2596d8be967SJohn Snow        # Leave zombie job un-dismissed, observe a failure:
26000e30f05SVladimir Sementsov-Ogievskiy        res = self.qmp_backup_and_wait(serror="Job ID 'drive0' already in use",
2616d8be967SJohn Snow                                       device='drive0', format=iotests.imgfmt,
26200e30f05SVladimir Sementsov-Ogievskiy                                       sync='full', target=self.dest_img2,
2636d8be967SJohn Snow                                       auto_dismiss=False)
2646d8be967SJohn Snow        self.assertEqual(res, False)
2656d8be967SJohn Snow        # OK, dismiss the zombie.
2666d8be967SJohn Snow        res = self.vm.qmp('block-job-dismiss', id='drive0')
2676d8be967SJohn Snow        self.assert_qmp(res, 'return', {})
2686d8be967SJohn Snow        res = self.vm.qmp('query-block-jobs')
2696d8be967SJohn Snow        self.assert_qmp(res, 'return', [])
2706d8be967SJohn Snow        # Ensure it's really gone.
2716d8be967SJohn Snow        self.qmp_backup_and_wait(device='drive0', format=iotests.imgfmt,
27200e30f05SVladimir Sementsov-Ogievskiy                                 sync='full', target=self.dest_img2,
2736d8be967SJohn Snow                                 auto_dismiss=False)
2746d8be967SJohn Snow
2756d8be967SJohn Snow    def dismissal_failure(self, dismissal_opt):
2766d8be967SJohn Snow        res = self.vm.qmp('query-block-jobs')
2776d8be967SJohn Snow        self.assert_qmp(res, 'return', [])
2786d8be967SJohn Snow        # Give blkdebug something to chew on
2796d8be967SJohn Snow        self.hmp_io_writes('drive0',
2806d8be967SJohn Snow                           (('0x9a', 0, 512),
2816d8be967SJohn Snow                           ('0x55', '8M', '352k'),
2826d8be967SJohn Snow                           ('0x78', '15872k', '1M')))
2836d8be967SJohn Snow        # Add destination node via blkdebug
2846d8be967SJohn Snow        res = self.vm.qmp('blockdev-add',
2856d8be967SJohn Snow                          node_name='target0',
2866d8be967SJohn Snow                          driver=iotests.imgfmt,
2876d8be967SJohn Snow                          file={
2886d8be967SJohn Snow                              'driver': 'blkdebug',
2896d8be967SJohn Snow                              'image': {
2906d8be967SJohn Snow                                  'driver': 'file',
2916d8be967SJohn Snow                                  'filename': self.dest_img
2926d8be967SJohn Snow                              },
2936d8be967SJohn Snow                              'inject-error': [{
2946d8be967SJohn Snow                                  'event': 'write_aio',
2956d8be967SJohn Snow                                  'errno': 5,
2966d8be967SJohn Snow                                  'immediately': False,
2976d8be967SJohn Snow                                  'once': True
2986d8be967SJohn Snow                              }],
2996d8be967SJohn Snow                          })
3006d8be967SJohn Snow        self.assert_qmp(res, 'return', {})
3016d8be967SJohn Snow
3026d8be967SJohn Snow        res = self.qmp_backup(cmd='blockdev-backup',
3036d8be967SJohn Snow                              device='drive0', target='target0',
3046d8be967SJohn Snow                              on_target_error='stop',
3056d8be967SJohn Snow                              sync='full',
3066d8be967SJohn Snow                              auto_dismiss=dismissal_opt)
3076d8be967SJohn Snow        self.assertTrue(res)
3086d8be967SJohn Snow        event = self.vm.event_wait(name="BLOCK_JOB_ERROR",
3096d8be967SJohn Snow                                   match={'data': {'device': 'drive0'}})
3106d8be967SJohn Snow        self.assertNotEqual(event, None)
3116d8be967SJohn Snow        # OK, job should be wedged
3126d8be967SJohn Snow        res = self.vm.qmp('query-block-jobs')
3136d8be967SJohn Snow        self.assert_qmp(res, 'return[0]/status', 'paused')
3146d8be967SJohn Snow        res = self.vm.qmp('block-job-dismiss', id='drive0')
3156d8be967SJohn Snow        self.assert_qmp(res, 'error/desc',
3166d8be967SJohn Snow                        "Job 'drive0' in state 'paused' cannot accept"
3176d8be967SJohn Snow                        " command verb 'dismiss'")
3186d8be967SJohn Snow        res = self.vm.qmp('query-block-jobs')
3196d8be967SJohn Snow        self.assert_qmp(res, 'return[0]/status', 'paused')
3206d8be967SJohn Snow        # OK, unstick job and move forward.
3216d8be967SJohn Snow        res = self.vm.qmp('block-job-resume', device='drive0')
3226d8be967SJohn Snow        self.assert_qmp(res, 'return', {})
3236d8be967SJohn Snow        # And now we need to wait for it to conclude;
3246d8be967SJohn Snow        res = self.qmp_backup_wait(device='drive0')
3256d8be967SJohn Snow        self.assertTrue(res)
3266d8be967SJohn Snow        if not dismissal_opt:
3276d8be967SJohn Snow            # Job should now be languishing:
3286d8be967SJohn Snow            res = self.vm.qmp('query-block-jobs')
3296d8be967SJohn Snow            self.assert_qmp(res, 'return[0]/status', 'concluded')
3306d8be967SJohn Snow            res = self.vm.qmp('block-job-dismiss', id='drive0')
3316d8be967SJohn Snow            self.assert_qmp(res, 'return', {})
3326d8be967SJohn Snow            res = self.vm.qmp('query-block-jobs')
3336d8be967SJohn Snow            self.assert_qmp(res, 'return', [])
3346d8be967SJohn Snow
3356d8be967SJohn Snow    def test_dismiss_premature(self):
3366d8be967SJohn Snow        self.dismissal_failure(False)
3376d8be967SJohn Snow
3386d8be967SJohn Snow    def test_dismiss_erroneous(self):
3396d8be967SJohn Snow        self.dismissal_failure(True)
3406d8be967SJohn Snow
341e3409362SIan Mainif __name__ == '__main__':
342103cbc77SMax Reitz    iotests.main(supported_fmts=['qcow2', 'qed'],
343103cbc77SMax Reitz                 supported_protocols=['file'])
344