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