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