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 128 129class TestSmallerBackingFile(ImageStreamingTestCase): 130 backing_len = 1 * 1024 * 1024 # MB 131 image_len = 2 * backing_len 132 133 def setUp(self): 134 self.create_image(backing_img, self.backing_len) 135 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img, str(self.image_len)) 136 self.vm = iotests.VM().add_drive(test_img) 137 self.vm.launch() 138 139 # If this hangs, then you are missing a fix to complete streaming when the 140 # end of the backing file is reached. 141 def test_stream(self): 142 self.assert_no_active_streams() 143 144 result = self.vm.qmp('block-stream', device='drive0') 145 self.assert_qmp(result, 'return', {}) 146 147 completed = False 148 while not completed: 149 for event in self.vm.get_qmp_events(wait=True): 150 if event['event'] == 'BLOCK_JOB_COMPLETED': 151 self.assert_qmp(event, 'data/type', 'stream') 152 self.assert_qmp(event, 'data/device', 'drive0') 153 self.assert_qmp(event, 'data/offset', self.image_len) 154 self.assert_qmp(event, 'data/len', self.image_len) 155 completed = True 156 157 self.assert_no_active_streams() 158 self.vm.shutdown() 159 160 161class TestStreamStop(ImageStreamingTestCase): 162 image_len = 8 * 1024 * 1024 * 1024 # GB 163 164 def setUp(self): 165 qemu_img('create', backing_img, str(TestStreamStop.image_len)) 166 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img) 167 self.vm = iotests.VM().add_drive(test_img) 168 self.vm.launch() 169 170 def tearDown(self): 171 self.vm.shutdown() 172 os.remove(test_img) 173 os.remove(backing_img) 174 175 def test_stream_stop(self): 176 import time 177 178 self.assert_no_active_streams() 179 180 result = self.vm.qmp('block-stream', device='drive0') 181 self.assert_qmp(result, 'return', {}) 182 183 time.sleep(0.1) 184 events = self.vm.get_qmp_events(wait=False) 185 self.assertEqual(events, [], 'unexpected QMP event: %s' % events) 186 187 self.cancel_and_wait() 188 189class TestSetSpeed(ImageStreamingTestCase): 190 image_len = 80 * 1024 * 1024 # MB 191 192 def setUp(self): 193 qemu_img('create', backing_img, str(TestSetSpeed.image_len)) 194 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img) 195 self.vm = iotests.VM().add_drive(test_img) 196 self.vm.launch() 197 198 def tearDown(self): 199 self.vm.shutdown() 200 os.remove(test_img) 201 os.remove(backing_img) 202 203 # This is a short performance test which is not run by default. 204 # Invoke "IMGFMT=qed ./030 TestSetSpeed.perf_test_throughput" 205 def perf_test_throughput(self): 206 self.assert_no_active_streams() 207 208 result = self.vm.qmp('block-stream', device='drive0') 209 self.assert_qmp(result, 'return', {}) 210 211 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024) 212 self.assert_qmp(result, 'return', {}) 213 214 completed = False 215 while not completed: 216 for event in self.vm.get_qmp_events(wait=True): 217 if event['event'] == 'BLOCK_JOB_COMPLETED': 218 self.assert_qmp(event, 'data/type', 'stream') 219 self.assert_qmp(event, 'data/device', 'drive0') 220 self.assert_qmp(event, 'data/offset', self.image_len) 221 self.assert_qmp(event, 'data/len', self.image_len) 222 completed = True 223 224 self.assert_no_active_streams() 225 226 def test_set_speed(self): 227 self.assert_no_active_streams() 228 229 result = self.vm.qmp('block-stream', device='drive0') 230 self.assert_qmp(result, 'return', {}) 231 232 # Default speed is 0 233 result = self.vm.qmp('query-block-jobs') 234 self.assert_qmp(result, 'return[0]/device', 'drive0') 235 self.assert_qmp(result, 'return[0]/speed', 0) 236 237 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024) 238 self.assert_qmp(result, 'return', {}) 239 240 # Ensure the speed we set was accepted 241 result = self.vm.qmp('query-block-jobs') 242 self.assert_qmp(result, 'return[0]/device', 'drive0') 243 self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024) 244 245 self.cancel_and_wait() 246 247 # Check setting speed in block-stream works 248 result = self.vm.qmp('block-stream', device='drive0', speed=4 * 1024 * 1024) 249 self.assert_qmp(result, 'return', {}) 250 251 result = self.vm.qmp('query-block-jobs') 252 self.assert_qmp(result, 'return[0]/device', 'drive0') 253 self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024) 254 255 self.cancel_and_wait() 256 257 def test_set_speed_invalid(self): 258 self.assert_no_active_streams() 259 260 result = self.vm.qmp('block-stream', device='drive0', speed=-1) 261 self.assert_qmp(result, 'error/class', 'GenericError') 262 263 self.assert_no_active_streams() 264 265 result = self.vm.qmp('block-stream', device='drive0') 266 self.assert_qmp(result, 'return', {}) 267 268 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1) 269 self.assert_qmp(result, 'error/class', 'GenericError') 270 271 self.cancel_and_wait() 272 273if __name__ == '__main__': 274 iotests.main(supported_fmts=['qcow2', 'qed']) 275