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_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img) 392 self.vm = iotests.VM().add_drive(test_img) 393 self.vm.launch() 394 395 def tearDown(self): 396 self.vm.shutdown() 397 os.remove(test_img) 398 os.remove(backing_img) 399 400 def test_stream_stop(self): 401 self.assert_no_active_block_jobs() 402 403 result = self.vm.qmp('block-stream', device='drive0') 404 self.assert_qmp(result, 'return', {}) 405 406 time.sleep(0.1) 407 events = self.vm.get_qmp_events(wait=False) 408 self.assertEqual(events, [], 'unexpected QMP event: %s' % events) 409 410 self.cancel_and_wait() 411 412class TestSetSpeed(iotests.QMPTestCase): 413 image_len = 80 * 1024 * 1024 # MB 414 415 def setUp(self): 416 qemu_img('create', backing_img, str(TestSetSpeed.image_len)) 417 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img) 418 self.vm = iotests.VM().add_drive(test_img) 419 self.vm.launch() 420 421 def tearDown(self): 422 self.vm.shutdown() 423 os.remove(test_img) 424 os.remove(backing_img) 425 426 # This is a short performance test which is not run by default. 427 # Invoke "IMGFMT=qed ./030 TestSetSpeed.perf_test_throughput" 428 def perf_test_throughput(self): 429 self.assert_no_active_block_jobs() 430 431 result = self.vm.qmp('block-stream', device='drive0') 432 self.assert_qmp(result, 'return', {}) 433 434 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024) 435 self.assert_qmp(result, 'return', {}) 436 437 completed = False 438 while not completed: 439 for event in self.vm.get_qmp_events(wait=True): 440 if event['event'] == 'BLOCK_JOB_COMPLETED': 441 self.assert_qmp(event, 'data/type', 'stream') 442 self.assert_qmp(event, 'data/device', 'drive0') 443 self.assert_qmp(event, 'data/offset', self.image_len) 444 self.assert_qmp(event, 'data/len', self.image_len) 445 completed = True 446 447 self.assert_no_active_block_jobs() 448 449 def test_set_speed(self): 450 self.assert_no_active_block_jobs() 451 452 result = self.vm.qmp('block-stream', device='drive0') 453 self.assert_qmp(result, 'return', {}) 454 455 # Default speed is 0 456 result = self.vm.qmp('query-block-jobs') 457 self.assert_qmp(result, 'return[0]/device', 'drive0') 458 self.assert_qmp(result, 'return[0]/speed', 0) 459 460 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024) 461 self.assert_qmp(result, 'return', {}) 462 463 # Ensure the speed we set was accepted 464 result = self.vm.qmp('query-block-jobs') 465 self.assert_qmp(result, 'return[0]/device', 'drive0') 466 self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024) 467 468 self.cancel_and_wait() 469 470 # Check setting speed in block-stream works 471 result = self.vm.qmp('block-stream', device='drive0', speed=4 * 1024 * 1024) 472 self.assert_qmp(result, 'return', {}) 473 474 result = self.vm.qmp('query-block-jobs') 475 self.assert_qmp(result, 'return[0]/device', 'drive0') 476 self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024) 477 478 self.cancel_and_wait() 479 480 def test_set_speed_invalid(self): 481 self.assert_no_active_block_jobs() 482 483 result = self.vm.qmp('block-stream', device='drive0', speed=-1) 484 self.assert_qmp(result, 'error/class', 'GenericError') 485 486 self.assert_no_active_block_jobs() 487 488 result = self.vm.qmp('block-stream', device='drive0') 489 self.assert_qmp(result, 'return', {}) 490 491 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1) 492 self.assert_qmp(result, 'error/class', 'GenericError') 493 494 self.cancel_and_wait() 495 496if __name__ == '__main__': 497 iotests.main(supported_fmts=['qcow2', 'qed']) 498