1#!/usr/bin/env python 2# 3# Tests for image streaming. 4# 5# Copyright (C) 2012 IBM Corp. 6# 7# This program is free software; you can redistribute it and/or modify 8# it under the terms of the GNU General Public License as published by 9# the Free Software Foundation; either version 2 of the License, or 10# (at your option) any later version. 11# 12# This program is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU General Public License for more details. 16# 17# You should have received a copy of the GNU General Public License 18# along with this program. If not, see <http://www.gnu.org/licenses/>. 19# 20 21import os 22import iotests 23from iotests import qemu_img, qemu_io 24import struct 25 26backing_img = os.path.join(iotests.test_dir, 'backing.img') 27mid_img = os.path.join(iotests.test_dir, 'mid.img') 28test_img = os.path.join(iotests.test_dir, 'test.img') 29 30class ImageStreamingTestCase(iotests.QMPTestCase): 31 '''Abstract base class for image streaming test cases''' 32 33 def assert_no_active_streams(self): 34 result = self.vm.qmp('query-block-jobs') 35 self.assert_qmp(result, 'return', []) 36 37 def cancel_and_wait(self, drive='drive0'): 38 '''Cancel a block job and wait for it to finish''' 39 result = self.vm.qmp('block-job-cancel', device=drive) 40 self.assert_qmp(result, 'return', {}) 41 42 cancelled = False 43 while not cancelled: 44 for event in self.vm.get_qmp_events(wait=True): 45 if event['event'] == 'BLOCK_JOB_CANCELLED': 46 self.assert_qmp(event, 'data/type', 'stream') 47 self.assert_qmp(event, 'data/device', drive) 48 cancelled = True 49 50 self.assert_no_active_streams() 51 52 def create_image(self, name, size): 53 file = open(name, 'w') 54 i = 0 55 while i < size: 56 sector = struct.pack('>l504xl', i / 512, i / 512) 57 file.write(sector) 58 i = i + 512 59 file.close() 60 61 62class TestSingleDrive(ImageStreamingTestCase): 63 image_len = 1 * 1024 * 1024 # MB 64 65 def setUp(self): 66 self.create_image(backing_img, TestSingleDrive.image_len) 67 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, mid_img) 68 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % mid_img, test_img) 69 self.vm = iotests.VM().add_drive(test_img) 70 self.vm.launch() 71 72 def tearDown(self): 73 self.vm.shutdown() 74 os.remove(test_img) 75 os.remove(mid_img) 76 os.remove(backing_img) 77 78 def test_stream(self): 79 self.assert_no_active_streams() 80 81 result = self.vm.qmp('block-stream', device='drive0') 82 self.assert_qmp(result, 'return', {}) 83 84 completed = False 85 while not completed: 86 for event in self.vm.get_qmp_events(wait=True): 87 if event['event'] == 'BLOCK_JOB_COMPLETED': 88 self.assert_qmp(event, 'data/type', 'stream') 89 self.assert_qmp(event, 'data/device', 'drive0') 90 self.assert_qmp(event, 'data/offset', self.image_len) 91 self.assert_qmp(event, 'data/len', self.image_len) 92 completed = True 93 94 self.assert_no_active_streams() 95 self.vm.shutdown() 96 97 self.assertEqual(qemu_io('-c', 'map', backing_img), 98 qemu_io('-c', 'map', test_img), 99 'image file map does not match backing file after streaming') 100 101 def test_stream_partial(self): 102 self.assert_no_active_streams() 103 104 result = self.vm.qmp('block-stream', device='drive0', base=mid_img) 105 self.assert_qmp(result, 'return', {}) 106 107 completed = False 108 while not completed: 109 for event in self.vm.get_qmp_events(wait=True): 110 if event['event'] == 'BLOCK_JOB_COMPLETED': 111 self.assert_qmp(event, 'data/type', 'stream') 112 self.assert_qmp(event, 'data/device', 'drive0') 113 self.assert_qmp(event, 'data/offset', self.image_len) 114 self.assert_qmp(event, 'data/len', self.image_len) 115 completed = True 116 117 self.assert_no_active_streams() 118 self.vm.shutdown() 119 120 self.assertEqual(qemu_io('-c', 'map', mid_img), 121 qemu_io('-c', 'map', test_img), 122 'image file map does not match backing file after streaming') 123 124 def test_device_not_found(self): 125 result = self.vm.qmp('block-stream', device='nonexistent') 126 self.assert_qmp(result, 'error/class', 'DeviceNotFound') 127 128class TestStreamStop(ImageStreamingTestCase): 129 image_len = 8 * 1024 * 1024 * 1024 # GB 130 131 def setUp(self): 132 qemu_img('create', backing_img, str(TestStreamStop.image_len)) 133 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img) 134 self.vm = iotests.VM().add_drive(test_img) 135 self.vm.launch() 136 137 def tearDown(self): 138 self.vm.shutdown() 139 os.remove(test_img) 140 os.remove(backing_img) 141 142 def test_stream_stop(self): 143 import time 144 145 self.assert_no_active_streams() 146 147 result = self.vm.qmp('block-stream', device='drive0') 148 self.assert_qmp(result, 'return', {}) 149 150 time.sleep(0.1) 151 events = self.vm.get_qmp_events(wait=False) 152 self.assertEqual(events, [], 'unexpected QMP event: %s' % events) 153 154 self.cancel_and_wait() 155 156class TestSetSpeed(ImageStreamingTestCase): 157 image_len = 80 * 1024 * 1024 # MB 158 159 def setUp(self): 160 qemu_img('create', backing_img, str(TestSetSpeed.image_len)) 161 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img) 162 self.vm = iotests.VM().add_drive(test_img) 163 self.vm.launch() 164 165 def tearDown(self): 166 self.vm.shutdown() 167 os.remove(test_img) 168 os.remove(backing_img) 169 170 # This is a short performance test which is not run by default. 171 # Invoke "IMGFMT=qed ./030 TestSetSpeed.perf_test_throughput" 172 def perf_test_throughput(self): 173 self.assert_no_active_streams() 174 175 result = self.vm.qmp('block-stream', device='drive0') 176 self.assert_qmp(result, 'return', {}) 177 178 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024) 179 self.assert_qmp(result, 'return', {}) 180 181 completed = False 182 while not completed: 183 for event in self.vm.get_qmp_events(wait=True): 184 if event['event'] == 'BLOCK_JOB_COMPLETED': 185 self.assert_qmp(event, 'data/type', 'stream') 186 self.assert_qmp(event, 'data/device', 'drive0') 187 self.assert_qmp(event, 'data/offset', self.image_len) 188 self.assert_qmp(event, 'data/len', self.image_len) 189 completed = True 190 191 self.assert_no_active_streams() 192 193 def test_set_speed(self): 194 self.assert_no_active_streams() 195 196 result = self.vm.qmp('block-stream', device='drive0') 197 self.assert_qmp(result, 'return', {}) 198 199 # Default speed is 0 200 result = self.vm.qmp('query-block-jobs') 201 self.assert_qmp(result, 'return[0]/device', 'drive0') 202 self.assert_qmp(result, 'return[0]/speed', 0) 203 204 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024) 205 self.assert_qmp(result, 'return', {}) 206 207 # Ensure the speed we set was accepted 208 result = self.vm.qmp('query-block-jobs') 209 self.assert_qmp(result, 'return[0]/device', 'drive0') 210 self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024) 211 212 self.cancel_and_wait() 213 214 # Check setting speed in block-stream works 215 result = self.vm.qmp('block-stream', device='drive0', speed=4 * 1024 * 1024) 216 self.assert_qmp(result, 'return', {}) 217 218 result = self.vm.qmp('query-block-jobs') 219 self.assert_qmp(result, 'return[0]/device', 'drive0') 220 self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024) 221 222 self.cancel_and_wait() 223 224 def test_set_speed_invalid(self): 225 self.assert_no_active_streams() 226 227 result = self.vm.qmp('block-stream', device='drive0', speed=-1) 228 self.assert_qmp(result, 'error/class', 'GenericError') 229 230 self.assert_no_active_streams() 231 232 result = self.vm.qmp('block-stream', device='drive0') 233 self.assert_qmp(result, 'return', {}) 234 235 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1) 236 self.assert_qmp(result, 'error/class', 'GenericError') 237 238 self.cancel_and_wait() 239 240if __name__ == '__main__': 241 iotests.main(supported_fmts=['qcow2', 'qed']) 242