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