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 self.wait_until_completed() 54 55 self.assert_no_active_block_jobs() 56 self.vm.shutdown() 57 58 self.assertEqual(qemu_io('-c', 'map', backing_img), 59 qemu_io('-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('-c', 'map', backing_img), 90 qemu_io('-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('-c', 'map', mid_img), 105 qemu_io('-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 self.assert_qmp(event, 'data/device', 'drive0') 249 self.assert_qmp(event, 'data/operation', 'read') 250 251 result = self.vm.qmp('query-block-jobs') 252 self.assert_qmp(result, 'return[0]/paused', True) 253 self.assert_qmp(result, 'return[0]/offset', self.STREAM_BUFFER_SIZE) 254 self.assert_qmp(result, 'return[0]/io-status', 'failed') 255 256 result = self.vm.qmp('block-job-resume', device='drive0') 257 self.assert_qmp(result, 'return', {}) 258 259 result = self.vm.qmp('query-block-jobs') 260 self.assert_qmp(result, 'return[0]/paused', False) 261 self.assert_qmp(result, 'return[0]/io-status', 'ok') 262 error = True 263 elif event['event'] == 'BLOCK_JOB_COMPLETED': 264 self.assertTrue(error, 'job completed unexpectedly') 265 self.assert_qmp(event, 'data/type', 'stream') 266 self.assert_qmp(event, 'data/device', 'drive0') 267 self.assert_qmp_absent(event, 'data/error') 268 self.assert_qmp(event, 'data/offset', self.image_len) 269 self.assert_qmp(event, 'data/len', self.image_len) 270 completed = True 271 272 self.assert_no_active_block_jobs() 273 self.vm.shutdown() 274 275 def test_enospc(self): 276 self.assert_no_active_block_jobs() 277 278 result = self.vm.qmp('block-stream', device='drive0', on_error='enospc') 279 self.assert_qmp(result, 'return', {}) 280 281 completed = False 282 error = False 283 while not completed: 284 for event in self.vm.get_qmp_events(wait=True): 285 if event['event'] == 'BLOCK_JOB_ERROR': 286 self.assert_qmp(event, 'data/device', 'drive0') 287 self.assert_qmp(event, 'data/operation', 'read') 288 error = True 289 elif event['event'] == 'BLOCK_JOB_COMPLETED': 290 self.assertTrue(error, 'job completed unexpectedly') 291 self.assert_qmp(event, 'data/type', 'stream') 292 self.assert_qmp(event, 'data/device', 'drive0') 293 self.assert_qmp(event, 'data/error', 'Input/output error') 294 self.assert_qmp(event, 'data/offset', self.STREAM_BUFFER_SIZE) 295 self.assert_qmp(event, 'data/len', self.image_len) 296 completed = True 297 298 self.assert_no_active_block_jobs() 299 self.vm.shutdown() 300 301class TestENOSPC(TestErrors): 302 def setUp(self): 303 self.blkdebug_file = backing_img + ".blkdebug" 304 iotests.create_image(backing_img, TestErrors.image_len) 305 self.create_blkdebug_file(self.blkdebug_file, "read_aio", 28) 306 qemu_img('create', '-f', iotests.imgfmt, 307 '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw' 308 % (self.blkdebug_file, backing_img), 309 test_img) 310 self.vm = iotests.VM().add_drive(test_img) 311 self.vm.launch() 312 313 def tearDown(self): 314 self.vm.shutdown() 315 os.remove(test_img) 316 os.remove(backing_img) 317 os.remove(self.blkdebug_file) 318 319 def test_enospc(self): 320 self.assert_no_active_block_jobs() 321 322 result = self.vm.qmp('block-stream', device='drive0', on_error='enospc') 323 self.assert_qmp(result, 'return', {}) 324 325 error = False 326 completed = False 327 while not completed: 328 for event in self.vm.get_qmp_events(wait=True): 329 if event['event'] == 'BLOCK_JOB_ERROR': 330 self.assert_qmp(event, 'data/device', 'drive0') 331 self.assert_qmp(event, 'data/operation', 'read') 332 333 result = self.vm.qmp('query-block-jobs') 334 self.assert_qmp(result, 'return[0]/paused', True) 335 self.assert_qmp(result, 'return[0]/offset', self.STREAM_BUFFER_SIZE) 336 self.assert_qmp(result, 'return[0]/io-status', 'nospace') 337 338 result = self.vm.qmp('block-job-resume', device='drive0') 339 self.assert_qmp(result, 'return', {}) 340 341 result = self.vm.qmp('query-block-jobs') 342 self.assert_qmp(result, 'return[0]/paused', False) 343 self.assert_qmp(result, 'return[0]/io-status', 'ok') 344 error = True 345 elif event['event'] == 'BLOCK_JOB_COMPLETED': 346 self.assertTrue(error, 'job completed unexpectedly') 347 self.assert_qmp(event, 'data/type', 'stream') 348 self.assert_qmp(event, 'data/device', 'drive0') 349 self.assert_qmp_absent(event, 'data/error') 350 self.assert_qmp(event, 'data/offset', self.image_len) 351 self.assert_qmp(event, 'data/len', self.image_len) 352 completed = True 353 354 self.assert_no_active_block_jobs() 355 self.vm.shutdown() 356 357class TestStreamStop(iotests.QMPTestCase): 358 image_len = 8 * 1024 * 1024 * 1024 # GB 359 360 def setUp(self): 361 qemu_img('create', backing_img, str(TestStreamStop.image_len)) 362 qemu_io('-c', 'write -P 0x1 0 32M', backing_img) 363 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img) 364 qemu_io('-c', 'write -P 0x1 32M 32M', test_img) 365 self.vm = iotests.VM().add_drive("blkdebug::" + test_img) 366 self.vm.launch() 367 368 def tearDown(self): 369 self.vm.shutdown() 370 os.remove(test_img) 371 os.remove(backing_img) 372 373 def test_stream_stop(self): 374 self.assert_no_active_block_jobs() 375 376 self.vm.pause_drive('drive0') 377 result = self.vm.qmp('block-stream', device='drive0') 378 self.assert_qmp(result, 'return', {}) 379 380 time.sleep(0.1) 381 events = self.vm.get_qmp_events(wait=False) 382 self.assertEqual(events, [], 'unexpected QMP event: %s' % events) 383 384 self.cancel_and_wait(resume=True) 385 386class TestSetSpeed(iotests.QMPTestCase): 387 image_len = 80 * 1024 * 1024 # MB 388 389 def setUp(self): 390 qemu_img('create', backing_img, str(TestSetSpeed.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('blkdebug::' + 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 # This is a short performance test which is not run by default. 403 # Invoke "IMGFMT=qed ./030 TestSetSpeed.perf_test_throughput" 404 def perf_test_throughput(self): 405 self.assert_no_active_block_jobs() 406 407 result = self.vm.qmp('block-stream', device='drive0') 408 self.assert_qmp(result, 'return', {}) 409 410 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024) 411 self.assert_qmp(result, 'return', {}) 412 413 self.wait_until_completed() 414 415 self.assert_no_active_block_jobs() 416 417 def test_set_speed(self): 418 self.assert_no_active_block_jobs() 419 420 self.vm.pause_drive('drive0') 421 result = self.vm.qmp('block-stream', device='drive0') 422 self.assert_qmp(result, 'return', {}) 423 424 # Default speed is 0 425 result = self.vm.qmp('query-block-jobs') 426 self.assert_qmp(result, 'return[0]/device', 'drive0') 427 self.assert_qmp(result, 'return[0]/speed', 0) 428 429 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024) 430 self.assert_qmp(result, 'return', {}) 431 432 # Ensure the speed we set was accepted 433 result = self.vm.qmp('query-block-jobs') 434 self.assert_qmp(result, 'return[0]/device', 'drive0') 435 self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024) 436 437 self.cancel_and_wait(resume=True) 438 self.vm.pause_drive('drive0') 439 440 # Check setting speed in block-stream works 441 result = self.vm.qmp('block-stream', device='drive0', speed=4 * 1024 * 1024) 442 self.assert_qmp(result, 'return', {}) 443 444 result = self.vm.qmp('query-block-jobs') 445 self.assert_qmp(result, 'return[0]/device', 'drive0') 446 self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024) 447 448 self.cancel_and_wait(resume=True) 449 450 def test_set_speed_invalid(self): 451 self.assert_no_active_block_jobs() 452 453 result = self.vm.qmp('block-stream', device='drive0', speed=-1) 454 self.assert_qmp(result, 'error/class', 'GenericError') 455 456 self.assert_no_active_block_jobs() 457 458 result = self.vm.qmp('block-stream', device='drive0') 459 self.assert_qmp(result, 'return', {}) 460 461 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1) 462 self.assert_qmp(result, 'error/class', 'GenericError') 463 464 self.cancel_and_wait() 465 466if __name__ == '__main__': 467 iotests.main(supported_fmts=['qcow2', 'qed']) 468