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