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 time 22import os 23import iotests 24from iotests import qemu_img, qemu_io 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 TestSingleDrive(iotests.QMPTestCase): 31 image_len = 1 * 1024 * 1024 # MB 32 33 def setUp(self): 34 iotests.create_image(backing_img, TestSingleDrive.image_len) 35 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, mid_img) 36 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % mid_img, test_img) 37 qemu_io('-f', 'raw', '-c', 'write -P 0x1 0 512', backing_img) 38 self.vm = iotests.VM().add_drive("blkdebug::" + test_img) 39 self.vm.launch() 40 41 def tearDown(self): 42 self.vm.shutdown() 43 os.remove(test_img) 44 os.remove(mid_img) 45 os.remove(backing_img) 46 47 def test_stream(self): 48 self.assert_no_active_block_jobs() 49 50 result = self.vm.qmp('block-stream', device='drive0') 51 self.assert_qmp(result, 'return', {}) 52 53 self.wait_until_completed() 54 55 self.assert_no_active_block_jobs() 56 self.vm.shutdown() 57 58 self.assertEqual(qemu_io('-f', 'raw', '-c', 'map', backing_img), 59 qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img), 60 'image file map does not match backing file after streaming') 61 62 def test_stream_pause(self): 63 self.assert_no_active_block_jobs() 64 65 self.vm.pause_drive('drive0') 66 result = self.vm.qmp('block-stream', device='drive0') 67 self.assert_qmp(result, 'return', {}) 68 69 result = self.vm.qmp('block-job-pause', device='drive0') 70 self.assert_qmp(result, 'return', {}) 71 72 time.sleep(1) 73 result = self.vm.qmp('query-block-jobs') 74 offset = self.dictpath(result, 'return[0]/offset') 75 76 time.sleep(1) 77 result = self.vm.qmp('query-block-jobs') 78 self.assert_qmp(result, 'return[0]/offset', offset) 79 80 result = self.vm.qmp('block-job-resume', device='drive0') 81 self.assert_qmp(result, 'return', {}) 82 83 self.vm.resume_drive('drive0') 84 self.wait_until_completed() 85 86 self.assert_no_active_block_jobs() 87 self.vm.shutdown() 88 89 self.assertEqual(qemu_io('-f', 'raw', '-c', 'map', backing_img), 90 qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img), 91 'image file map does not match backing file after streaming') 92 93 def test_stream_partial(self): 94 self.assert_no_active_block_jobs() 95 96 result = self.vm.qmp('block-stream', device='drive0', base=mid_img) 97 self.assert_qmp(result, 'return', {}) 98 99 self.wait_until_completed() 100 101 self.assert_no_active_block_jobs() 102 self.vm.shutdown() 103 104 self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', mid_img), 105 qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img), 106 'image file map does not match backing file after streaming') 107 108 def test_device_not_found(self): 109 result = self.vm.qmp('block-stream', device='nonexistent') 110 self.assert_qmp(result, 'error/class', 'DeviceNotFound') 111 112 113class TestSmallerBackingFile(iotests.QMPTestCase): 114 backing_len = 1 * 1024 * 1024 # MB 115 image_len = 2 * backing_len 116 117 def setUp(self): 118 iotests.create_image(backing_img, self.backing_len) 119 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img, str(self.image_len)) 120 self.vm = iotests.VM().add_drive(test_img) 121 self.vm.launch() 122 123 # If this hangs, then you are missing a fix to complete streaming when the 124 # end of the backing file is reached. 125 def test_stream(self): 126 self.assert_no_active_block_jobs() 127 128 result = self.vm.qmp('block-stream', device='drive0') 129 self.assert_qmp(result, 'return', {}) 130 131 self.wait_until_completed() 132 133 self.assert_no_active_block_jobs() 134 self.vm.shutdown() 135 136class TestErrors(iotests.QMPTestCase): 137 image_len = 2 * 1024 * 1024 # MB 138 139 # this should match STREAM_BUFFER_SIZE/512 in block/stream.c 140 STREAM_BUFFER_SIZE = 512 * 1024 141 142 def create_blkdebug_file(self, name, event, errno): 143 file = open(name, 'w') 144 file.write(''' 145[inject-error] 146state = "1" 147event = "%s" 148errno = "%d" 149immediately = "off" 150once = "on" 151sector = "%d" 152 153[set-state] 154state = "1" 155event = "%s" 156new_state = "2" 157 158[set-state] 159state = "2" 160event = "%s" 161new_state = "1" 162''' % (event, errno, self.STREAM_BUFFER_SIZE / 512, event, event)) 163 file.close() 164 165class TestEIO(TestErrors): 166 def setUp(self): 167 self.blkdebug_file = backing_img + ".blkdebug" 168 iotests.create_image(backing_img, TestErrors.image_len) 169 self.create_blkdebug_file(self.blkdebug_file, "read_aio", 5) 170 qemu_img('create', '-f', iotests.imgfmt, 171 '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw' 172 % (self.blkdebug_file, backing_img), 173 test_img) 174 self.vm = iotests.VM().add_drive(test_img) 175 self.vm.launch() 176 177 def tearDown(self): 178 self.vm.shutdown() 179 os.remove(test_img) 180 os.remove(backing_img) 181 os.remove(self.blkdebug_file) 182 183 def test_report(self): 184 self.assert_no_active_block_jobs() 185 186 result = self.vm.qmp('block-stream', device='drive0') 187 self.assert_qmp(result, 'return', {}) 188 189 completed = False 190 error = False 191 while not completed: 192 for event in self.vm.get_qmp_events(wait=True): 193 if event['event'] == 'BLOCK_JOB_ERROR': 194 self.assert_qmp(event, 'data/device', 'drive0') 195 self.assert_qmp(event, 'data/operation', 'read') 196 error = True 197 elif event['event'] == 'BLOCK_JOB_COMPLETED': 198 self.assertTrue(error, 'job completed unexpectedly') 199 self.assert_qmp(event, 'data/type', 'stream') 200 self.assert_qmp(event, 'data/device', 'drive0') 201 self.assert_qmp(event, 'data/error', 'Input/output error') 202 self.assert_qmp(event, 'data/offset', self.STREAM_BUFFER_SIZE) 203 self.assert_qmp(event, 'data/len', self.image_len) 204 completed = True 205 206 self.assert_no_active_block_jobs() 207 self.vm.shutdown() 208 209 def test_ignore(self): 210 self.assert_no_active_block_jobs() 211 212 result = self.vm.qmp('block-stream', device='drive0', on_error='ignore') 213 self.assert_qmp(result, 'return', {}) 214 215 error = False 216 completed = False 217 while not completed: 218 for event in self.vm.get_qmp_events(wait=True): 219 if event['event'] == 'BLOCK_JOB_ERROR': 220 self.assert_qmp(event, 'data/device', 'drive0') 221 self.assert_qmp(event, 'data/operation', 'read') 222 result = self.vm.qmp('query-block-jobs') 223 self.assert_qmp(result, 'return[0]/paused', False) 224 error = True 225 elif event['event'] == 'BLOCK_JOB_COMPLETED': 226 self.assertTrue(error, 'job completed unexpectedly') 227 self.assert_qmp(event, 'data/type', 'stream') 228 self.assert_qmp(event, 'data/device', 'drive0') 229 self.assert_qmp(event, 'data/error', 'Input/output error') 230 self.assert_qmp(event, 'data/offset', self.image_len) 231 self.assert_qmp(event, 'data/len', self.image_len) 232 completed = True 233 234 self.assert_no_active_block_jobs() 235 self.vm.shutdown() 236 237 def test_stop(self): 238 self.assert_no_active_block_jobs() 239 240 result = self.vm.qmp('block-stream', device='drive0', on_error='stop') 241 self.assert_qmp(result, 'return', {}) 242 243 error = False 244 completed = False 245 while not completed: 246 for event in self.vm.get_qmp_events(wait=True): 247 if event['event'] == 'BLOCK_JOB_ERROR': 248 error = True 249 self.assert_qmp(event, 'data/device', 'drive0') 250 self.assert_qmp(event, 'data/operation', 'read') 251 252 result = self.vm.qmp('query-block-jobs') 253 self.assert_qmp(result, 'return[0]/paused', True) 254 self.assert_qmp(result, 'return[0]/offset', self.STREAM_BUFFER_SIZE) 255 self.assert_qmp(result, 'return[0]/io-status', 'failed') 256 257 result = self.vm.qmp('block-job-resume', device='drive0') 258 self.assert_qmp(result, 'return', {}) 259 260 result = self.vm.qmp('query-block-jobs') 261 if result == {'return': []}: 262 # Race; likely already finished. Check. 263 continue 264 self.assert_qmp(result, 'return[0]/paused', False) 265 self.assert_qmp(result, 'return[0]/io-status', 'ok') 266 elif event['event'] == 'BLOCK_JOB_COMPLETED': 267 self.assertTrue(error, 'job completed unexpectedly') 268 self.assert_qmp(event, 'data/type', 'stream') 269 self.assert_qmp(event, 'data/device', 'drive0') 270 self.assert_qmp_absent(event, 'data/error') 271 self.assert_qmp(event, 'data/offset', self.image_len) 272 self.assert_qmp(event, 'data/len', self.image_len) 273 completed = True 274 275 self.assert_no_active_block_jobs() 276 self.vm.shutdown() 277 278 def test_enospc(self): 279 self.assert_no_active_block_jobs() 280 281 result = self.vm.qmp('block-stream', device='drive0', on_error='enospc') 282 self.assert_qmp(result, 'return', {}) 283 284 completed = False 285 error = False 286 while not completed: 287 for event in self.vm.get_qmp_events(wait=True): 288 if event['event'] == 'BLOCK_JOB_ERROR': 289 self.assert_qmp(event, 'data/device', 'drive0') 290 self.assert_qmp(event, 'data/operation', 'read') 291 error = True 292 elif event['event'] == 'BLOCK_JOB_COMPLETED': 293 self.assertTrue(error, 'job completed unexpectedly') 294 self.assert_qmp(event, 'data/type', 'stream') 295 self.assert_qmp(event, 'data/device', 'drive0') 296 self.assert_qmp(event, 'data/error', 'Input/output error') 297 self.assert_qmp(event, 'data/offset', self.STREAM_BUFFER_SIZE) 298 self.assert_qmp(event, 'data/len', self.image_len) 299 completed = True 300 301 self.assert_no_active_block_jobs() 302 self.vm.shutdown() 303 304class TestENOSPC(TestErrors): 305 def setUp(self): 306 self.blkdebug_file = backing_img + ".blkdebug" 307 iotests.create_image(backing_img, TestErrors.image_len) 308 self.create_blkdebug_file(self.blkdebug_file, "read_aio", 28) 309 qemu_img('create', '-f', iotests.imgfmt, 310 '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw' 311 % (self.blkdebug_file, backing_img), 312 test_img) 313 self.vm = iotests.VM().add_drive(test_img) 314 self.vm.launch() 315 316 def tearDown(self): 317 self.vm.shutdown() 318 os.remove(test_img) 319 os.remove(backing_img) 320 os.remove(self.blkdebug_file) 321 322 def test_enospc(self): 323 self.assert_no_active_block_jobs() 324 325 result = self.vm.qmp('block-stream', device='drive0', on_error='enospc') 326 self.assert_qmp(result, 'return', {}) 327 328 error = False 329 completed = False 330 while not completed: 331 for event in self.vm.get_qmp_events(wait=True): 332 if event['event'] == 'BLOCK_JOB_ERROR': 333 self.assert_qmp(event, 'data/device', 'drive0') 334 self.assert_qmp(event, 'data/operation', 'read') 335 336 result = self.vm.qmp('query-block-jobs') 337 self.assert_qmp(result, 'return[0]/paused', True) 338 self.assert_qmp(result, 'return[0]/offset', self.STREAM_BUFFER_SIZE) 339 self.assert_qmp(result, 'return[0]/io-status', 'nospace') 340 341 result = self.vm.qmp('block-job-resume', device='drive0') 342 self.assert_qmp(result, 'return', {}) 343 344 result = self.vm.qmp('query-block-jobs') 345 self.assert_qmp(result, 'return[0]/paused', False) 346 self.assert_qmp(result, 'return[0]/io-status', 'ok') 347 error = True 348 elif event['event'] == 'BLOCK_JOB_COMPLETED': 349 self.assertTrue(error, 'job completed unexpectedly') 350 self.assert_qmp(event, 'data/type', 'stream') 351 self.assert_qmp(event, 'data/device', 'drive0') 352 self.assert_qmp_absent(event, 'data/error') 353 self.assert_qmp(event, 'data/offset', self.image_len) 354 self.assert_qmp(event, 'data/len', self.image_len) 355 completed = True 356 357 self.assert_no_active_block_jobs() 358 self.vm.shutdown() 359 360class TestStreamStop(iotests.QMPTestCase): 361 image_len = 8 * 1024 * 1024 * 1024 # GB 362 363 def setUp(self): 364 qemu_img('create', backing_img, str(TestStreamStop.image_len)) 365 qemu_io('-f', 'raw', '-c', 'write -P 0x1 0 32M', backing_img) 366 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img) 367 qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x1 32M 32M', test_img) 368 self.vm = iotests.VM().add_drive("blkdebug::" + test_img) 369 self.vm.launch() 370 371 def tearDown(self): 372 self.vm.shutdown() 373 os.remove(test_img) 374 os.remove(backing_img) 375 376 def test_stream_stop(self): 377 self.assert_no_active_block_jobs() 378 379 self.vm.pause_drive('drive0') 380 result = self.vm.qmp('block-stream', device='drive0') 381 self.assert_qmp(result, 'return', {}) 382 383 time.sleep(0.1) 384 events = self.vm.get_qmp_events(wait=False) 385 self.assertEqual(events, [], 'unexpected QMP event: %s' % events) 386 387 self.cancel_and_wait(resume=True) 388 389class TestSetSpeed(iotests.QMPTestCase): 390 image_len = 80 * 1024 * 1024 # MB 391 392 def setUp(self): 393 qemu_img('create', backing_img, str(TestSetSpeed.image_len)) 394 qemu_io('-f', 'raw', '-c', 'write -P 0x1 0 32M', backing_img) 395 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img) 396 qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x1 32M 32M', test_img) 397 self.vm = iotests.VM().add_drive('blkdebug::' + test_img) 398 self.vm.launch() 399 400 def tearDown(self): 401 self.vm.shutdown() 402 os.remove(test_img) 403 os.remove(backing_img) 404 405 # This is a short performance test which is not run by default. 406 # Invoke "IMGFMT=qed ./030 TestSetSpeed.perf_test_throughput" 407 def perf_test_throughput(self): 408 self.assert_no_active_block_jobs() 409 410 result = self.vm.qmp('block-stream', device='drive0') 411 self.assert_qmp(result, 'return', {}) 412 413 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024) 414 self.assert_qmp(result, 'return', {}) 415 416 self.wait_until_completed() 417 418 self.assert_no_active_block_jobs() 419 420 def test_set_speed(self): 421 self.assert_no_active_block_jobs() 422 423 self.vm.pause_drive('drive0') 424 result = self.vm.qmp('block-stream', device='drive0') 425 self.assert_qmp(result, 'return', {}) 426 427 # Default speed is 0 428 result = self.vm.qmp('query-block-jobs') 429 self.assert_qmp(result, 'return[0]/device', 'drive0') 430 self.assert_qmp(result, 'return[0]/speed', 0) 431 432 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024) 433 self.assert_qmp(result, 'return', {}) 434 435 # Ensure the speed we set was accepted 436 result = self.vm.qmp('query-block-jobs') 437 self.assert_qmp(result, 'return[0]/device', 'drive0') 438 self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024) 439 440 self.cancel_and_wait(resume=True) 441 self.vm.pause_drive('drive0') 442 443 # Check setting speed in block-stream works 444 result = self.vm.qmp('block-stream', device='drive0', speed=4 * 1024 * 1024) 445 self.assert_qmp(result, 'return', {}) 446 447 result = self.vm.qmp('query-block-jobs') 448 self.assert_qmp(result, 'return[0]/device', 'drive0') 449 self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024) 450 451 self.cancel_and_wait(resume=True) 452 453 def test_set_speed_invalid(self): 454 self.assert_no_active_block_jobs() 455 456 result = self.vm.qmp('block-stream', device='drive0', speed=-1) 457 self.assert_qmp(result, 'error/class', 'GenericError') 458 459 self.assert_no_active_block_jobs() 460 461 result = self.vm.qmp('block-stream', device='drive0') 462 self.assert_qmp(result, 'return', {}) 463 464 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1) 465 self.assert_qmp(result, 'error/class', 'GenericError') 466 467 self.cancel_and_wait() 468 469if __name__ == '__main__': 470 iotests.main(supported_fmts=['qcow2', 'qed']) 471