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, "backing.node-name=mid") 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_intermediate(self): 64 self.assert_no_active_block_jobs() 65 66 self.assertNotEqual(qemu_io('-f', 'raw', '-c', 'map', backing_img), 67 qemu_io('-f', iotests.imgfmt, '-c', 'map', mid_img), 68 'image file map matches backing file before streaming') 69 70 result = self.vm.qmp('block-stream', device='mid', job_id='stream-mid') 71 self.assert_qmp(result, 'return', {}) 72 73 self.wait_until_completed(drive='stream-mid') 74 75 self.assert_no_active_block_jobs() 76 self.vm.shutdown() 77 78 self.assertEqual(qemu_io('-f', 'raw', '-c', 'map', backing_img), 79 qemu_io('-f', iotests.imgfmt, '-c', 'map', mid_img), 80 'image file map does not match backing file after streaming') 81 82 def test_stream_pause(self): 83 self.assert_no_active_block_jobs() 84 85 self.vm.pause_drive('drive0') 86 result = self.vm.qmp('block-stream', device='drive0') 87 self.assert_qmp(result, 'return', {}) 88 89 result = self.vm.qmp('block-job-pause', device='drive0') 90 self.assert_qmp(result, 'return', {}) 91 92 time.sleep(1) 93 result = self.vm.qmp('query-block-jobs') 94 offset = self.dictpath(result, 'return[0]/offset') 95 96 time.sleep(1) 97 result = self.vm.qmp('query-block-jobs') 98 self.assert_qmp(result, 'return[0]/offset', offset) 99 100 result = self.vm.qmp('block-job-resume', device='drive0') 101 self.assert_qmp(result, 'return', {}) 102 103 self.vm.resume_drive('drive0') 104 self.wait_until_completed() 105 106 self.assert_no_active_block_jobs() 107 self.vm.shutdown() 108 109 self.assertEqual(qemu_io('-f', 'raw', '-c', 'map', backing_img), 110 qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img), 111 'image file map does not match backing file after streaming') 112 113 def test_stream_no_op(self): 114 self.assert_no_active_block_jobs() 115 116 # The image map is empty before the operation 117 empty_map = qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img) 118 119 # This is a no-op: no data should ever be copied from the base image 120 result = self.vm.qmp('block-stream', device='drive0', base=mid_img) 121 self.assert_qmp(result, 'return', {}) 122 123 self.wait_until_completed() 124 125 self.assert_no_active_block_jobs() 126 self.vm.shutdown() 127 128 self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img), 129 empty_map, 'image file map changed after a no-op') 130 131 def test_stream_partial(self): 132 self.assert_no_active_block_jobs() 133 134 result = self.vm.qmp('block-stream', device='drive0', base=backing_img) 135 self.assert_qmp(result, 'return', {}) 136 137 self.wait_until_completed() 138 139 self.assert_no_active_block_jobs() 140 self.vm.shutdown() 141 142 self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', mid_img), 143 qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img), 144 'image file map does not match backing file after streaming') 145 146 def test_device_not_found(self): 147 result = self.vm.qmp('block-stream', device='nonexistent') 148 self.assert_qmp(result, 'error/class', 'GenericError') 149 150 151class TestParallelOps(iotests.QMPTestCase): 152 num_ops = 4 # Number of parallel block-stream operations 153 num_imgs = num_ops * 2 + 1 154 image_len = num_ops * 1024 * 1024 155 imgs = [] 156 157 def setUp(self): 158 opts = [] 159 self.imgs = [] 160 161 # Initialize file names and command-line options 162 for i in range(self.num_imgs): 163 img_depth = self.num_imgs - i - 1 164 opts.append("backing." * img_depth + "node-name=node%d" % i) 165 self.imgs.append(os.path.join(iotests.test_dir, 'img-%d.img' % i)) 166 167 # Create all images 168 iotests.create_image(self.imgs[0], self.image_len) 169 for i in range(1, self.num_imgs): 170 qemu_img('create', '-f', iotests.imgfmt, 171 '-o', 'backing_file=%s' % self.imgs[i-1], self.imgs[i]) 172 173 # Put data into the images we are copying data from 174 for i in range(self.num_imgs / 2): 175 img_index = i * 2 + 1 176 # Alternate between 512k and 1M. 177 # This way jobs will not finish in the same order they were created 178 num_kb = 512 + 512 * (i % 2) 179 qemu_io('-f', iotests.imgfmt, 180 '-c', 'write -P %d %d %d' % (i, i*1024*1024, num_kb * 1024), 181 self.imgs[img_index]) 182 183 # Attach the drive to the VM 184 self.vm = iotests.VM() 185 self.vm.add_drive(self.imgs[-1], ','.join(opts)) 186 self.vm.launch() 187 188 def tearDown(self): 189 self.vm.shutdown() 190 for img in self.imgs: 191 os.remove(img) 192 193 # Test that it's possible to run several block-stream operations 194 # in parallel in the same snapshot chain 195 def test_stream_parallel(self): 196 self.assert_no_active_block_jobs() 197 198 # Check that the maps don't match before the streaming operations 199 for i in range(2, self.num_imgs, 2): 200 self.assertNotEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[i]), 201 qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[i-1]), 202 'image file map matches backing file before streaming') 203 204 # Create all streaming jobs 205 pending_jobs = [] 206 for i in range(2, self.num_imgs, 2): 207 node_name = 'node%d' % i 208 job_id = 'stream-%s' % node_name 209 pending_jobs.append(job_id) 210 result = self.vm.qmp('block-stream', device=node_name, job_id=job_id, base=self.imgs[i-2], speed=512*1024) 211 self.assert_qmp(result, 'return', {}) 212 213 # Wait for all jobs to be finished. 214 while len(pending_jobs) > 0: 215 for event in self.vm.get_qmp_events(wait=True): 216 if event['event'] == 'BLOCK_JOB_COMPLETED': 217 job_id = self.dictpath(event, 'data/device') 218 self.assertTrue(job_id in pending_jobs) 219 self.assert_qmp_absent(event, 'data/error') 220 pending_jobs.remove(job_id) 221 222 self.assert_no_active_block_jobs() 223 self.vm.shutdown() 224 225 # Check that all maps match now 226 for i in range(2, self.num_imgs, 2): 227 self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[i]), 228 qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[i-1]), 229 'image file map does not match backing file after streaming') 230 231 # Test that it's not possible to perform two block-stream 232 # operations if there are nodes involved in both. 233 def test_overlapping_1(self): 234 self.assert_no_active_block_jobs() 235 236 # Set a speed limit to make sure that this job blocks the rest 237 result = self.vm.qmp('block-stream', device='node4', job_id='stream-node4', base=self.imgs[1], speed=1024*1024) 238 self.assert_qmp(result, 'return', {}) 239 240 result = self.vm.qmp('block-stream', device='node5', job_id='stream-node5', base=self.imgs[2]) 241 self.assert_qmp(result, 'error/class', 'GenericError') 242 243 result = self.vm.qmp('block-stream', device='node3', job_id='stream-node3', base=self.imgs[2]) 244 self.assert_qmp(result, 'error/class', 'GenericError') 245 246 result = self.vm.qmp('block-stream', device='node4', job_id='stream-node4-v2') 247 self.assert_qmp(result, 'error/class', 'GenericError') 248 249 # block-commit should also fail if it touches nodes used by the stream job 250 result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[4], job_id='commit-node4') 251 self.assert_qmp(result, 'error/class', 'GenericError') 252 253 result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[1], top=self.imgs[3], job_id='commit-node1') 254 self.assert_qmp(result, 'error/class', 'GenericError') 255 256 # This fails because it needs to modify the backing string in node2, which is blocked 257 result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[0], top=self.imgs[1], job_id='commit-node0') 258 self.assert_qmp(result, 'error/class', 'GenericError') 259 260 self.wait_until_completed(drive='stream-node4') 261 self.assert_no_active_block_jobs() 262 263 # Similar to test_overlapping_1, but with block-commit 264 # blocking the other jobs 265 def test_overlapping_2(self): 266 self.assertLessEqual(9, self.num_imgs) 267 self.assert_no_active_block_jobs() 268 269 # Set a speed limit to make sure that this job blocks the rest 270 result = self.vm.qmp('block-commit', device='drive0', top=self.imgs[5], base=self.imgs[3], job_id='commit-node3', speed=1024*1024) 271 self.assert_qmp(result, 'return', {}) 272 273 result = self.vm.qmp('block-stream', device='node3', job_id='stream-node3') 274 self.assert_qmp(result, 'error/class', 'GenericError') 275 276 result = self.vm.qmp('block-stream', device='node6', base=self.imgs[2], job_id='stream-node6') 277 self.assert_qmp(result, 'error/class', 'GenericError') 278 279 result = self.vm.qmp('block-stream', device='node4', base=self.imgs[2], job_id='stream-node4') 280 self.assert_qmp(result, 'error/class', 'GenericError') 281 282 result = self.vm.qmp('block-stream', device='node6', base=self.imgs[4], job_id='stream-node6-v2') 283 self.assert_qmp(result, 'error/class', 'GenericError') 284 285 # This fails because block-commit needs to block node6, the overlay of the 'top' image 286 result = self.vm.qmp('block-stream', device='node7', base=self.imgs[5], job_id='stream-node6-v3') 287 self.assert_qmp(result, 'error/class', 'GenericError') 288 289 # This fails because block-commit currently blocks the active layer even if it's not used 290 result = self.vm.qmp('block-stream', device='drive0', base=self.imgs[5], job_id='stream-drive0') 291 self.assert_qmp(result, 'error/class', 'GenericError') 292 293 self.wait_until_completed(drive='commit-node3') 294 295 # Similar to test_overlapping_2, but here block-commit doesn't use the 'top' parameter. 296 # Internally this uses a mirror block job, hence the separate test case. 297 def test_overlapping_3(self): 298 self.assertLessEqual(8, self.num_imgs) 299 self.assert_no_active_block_jobs() 300 301 # Set a speed limit to make sure that this job blocks the rest 302 result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[3], job_id='commit-drive0', speed=1024*1024) 303 self.assert_qmp(result, 'return', {}) 304 305 result = self.vm.qmp('block-stream', device='node5', base=self.imgs[3], job_id='stream-node6') 306 self.assert_qmp(result, 'error/class', 'GenericError') 307 308 event = self.vm.get_qmp_event(wait=True) 309 self.assertEqual(event['event'], 'BLOCK_JOB_READY') 310 self.assert_qmp(event, 'data/device', 'commit-drive0') 311 self.assert_qmp(event, 'data/type', 'commit') 312 self.assert_qmp_absent(event, 'data/error') 313 314 result = self.vm.qmp('block-job-complete', device='commit-drive0') 315 self.assert_qmp(result, 'return', {}) 316 317 self.wait_until_completed(drive='commit-drive0') 318 319 # Test a block-stream and a block-commit job in parallel 320 def test_stream_commit(self): 321 self.assertLessEqual(8, self.num_imgs) 322 self.assert_no_active_block_jobs() 323 324 # Stream from node0 into node2 325 result = self.vm.qmp('block-stream', device='node2', job_id='node2') 326 self.assert_qmp(result, 'return', {}) 327 328 # Commit from the active layer into node3 329 result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[3]) 330 self.assert_qmp(result, 'return', {}) 331 332 # Wait for all jobs to be finished. 333 pending_jobs = ['node2', 'drive0'] 334 while len(pending_jobs) > 0: 335 for event in self.vm.get_qmp_events(wait=True): 336 if event['event'] == 'BLOCK_JOB_COMPLETED': 337 node_name = self.dictpath(event, 'data/device') 338 self.assertTrue(node_name in pending_jobs) 339 self.assert_qmp_absent(event, 'data/error') 340 pending_jobs.remove(node_name) 341 if event['event'] == 'BLOCK_JOB_READY': 342 self.assert_qmp(event, 'data/device', 'drive0') 343 self.assert_qmp(event, 'data/type', 'commit') 344 self.assert_qmp_absent(event, 'data/error') 345 self.assertTrue('drive0' in pending_jobs) 346 self.vm.qmp('block-job-complete', device='drive0') 347 348 self.assert_no_active_block_jobs() 349 350 # Test the base_node parameter 351 def test_stream_base_node_name(self): 352 self.assert_no_active_block_jobs() 353 354 self.assertNotEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[4]), 355 qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[3]), 356 'image file map matches backing file before streaming') 357 358 # Error: the base node does not exist 359 result = self.vm.qmp('block-stream', device='node4', base_node='none', job_id='stream') 360 self.assert_qmp(result, 'error/class', 'GenericError') 361 362 # Error: the base node is not a backing file of the top node 363 result = self.vm.qmp('block-stream', device='node4', base_node='node6', job_id='stream') 364 self.assert_qmp(result, 'error/class', 'GenericError') 365 366 # Error: the base node is the same as the top node 367 result = self.vm.qmp('block-stream', device='node4', base_node='node4', job_id='stream') 368 self.assert_qmp(result, 'error/class', 'GenericError') 369 370 # Error: cannot specify 'base' and 'base-node' at the same time 371 result = self.vm.qmp('block-stream', device='node4', base=self.imgs[2], base_node='node2', job_id='stream') 372 self.assert_qmp(result, 'error/class', 'GenericError') 373 374 # Success: the base node is a backing file of the top node 375 result = self.vm.qmp('block-stream', device='node4', base_node='node2', job_id='stream') 376 self.assert_qmp(result, 'return', {}) 377 378 self.wait_until_completed(drive='stream') 379 380 self.assert_no_active_block_jobs() 381 self.vm.shutdown() 382 383 self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[4]), 384 qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[3]), 385 'image file map matches backing file after streaming') 386 387class TestQuorum(iotests.QMPTestCase): 388 num_children = 3 389 children = [] 390 backing = [] 391 392 def setUp(self): 393 opts = ['driver=quorum', 'vote-threshold=2'] 394 395 # Initialize file names and command-line options 396 for i in range(self.num_children): 397 child_img = os.path.join(iotests.test_dir, 'img-%d.img' % i) 398 backing_img = os.path.join(iotests.test_dir, 'backing-%d.img' % i) 399 self.children.append(child_img) 400 self.backing.append(backing_img) 401 qemu_img('create', '-f', iotests.imgfmt, backing_img, '1M') 402 qemu_io('-f', iotests.imgfmt, 403 '-c', 'write -P 0x55 0 1024', backing_img) 404 qemu_img('create', '-f', iotests.imgfmt, 405 '-o', 'backing_file=%s' % backing_img, child_img) 406 opts.append("children.%d.file.filename=%s" % (i, child_img)) 407 opts.append("children.%d.node-name=node%d" % (i, i)) 408 409 # Attach the drive to the VM 410 self.vm = iotests.VM() 411 self.vm.add_drive(path = None, opts = ','.join(opts)) 412 self.vm.launch() 413 414 def tearDown(self): 415 self.vm.shutdown() 416 for img in self.children: 417 os.remove(img) 418 for img in self.backing: 419 os.remove(img) 420 421 def test_stream_quorum(self): 422 if not iotests.supports_quorum(): 423 return 424 425 self.assertNotEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', self.children[0]), 426 qemu_io('-f', iotests.imgfmt, '-c', 'map', self.backing[0]), 427 'image file map matches backing file before streaming') 428 429 self.assert_no_active_block_jobs() 430 431 result = self.vm.qmp('block-stream', device='node0', job_id='stream-node0') 432 self.assert_qmp(result, 'return', {}) 433 434 self.wait_until_completed(drive='stream-node0') 435 436 self.assert_no_active_block_jobs() 437 self.vm.shutdown() 438 439 self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', self.children[0]), 440 qemu_io('-f', iotests.imgfmt, '-c', 'map', self.backing[0]), 441 'image file map does not match backing file after streaming') 442 443class TestSmallerBackingFile(iotests.QMPTestCase): 444 backing_len = 1 * 1024 * 1024 # MB 445 image_len = 2 * backing_len 446 447 def setUp(self): 448 iotests.create_image(backing_img, self.backing_len) 449 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img, str(self.image_len)) 450 self.vm = iotests.VM().add_drive(test_img) 451 self.vm.launch() 452 453 # If this hangs, then you are missing a fix to complete streaming when the 454 # end of the backing file is reached. 455 def test_stream(self): 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 self.wait_until_completed() 462 463 self.assert_no_active_block_jobs() 464 self.vm.shutdown() 465 466class TestErrors(iotests.QMPTestCase): 467 image_len = 2 * 1024 * 1024 # MB 468 469 # this should match STREAM_BUFFER_SIZE/512 in block/stream.c 470 STREAM_BUFFER_SIZE = 512 * 1024 471 472 def create_blkdebug_file(self, name, event, errno): 473 file = open(name, 'w') 474 file.write(''' 475[inject-error] 476state = "1" 477event = "%s" 478errno = "%d" 479immediately = "off" 480once = "on" 481sector = "%d" 482 483[set-state] 484state = "1" 485event = "%s" 486new_state = "2" 487 488[set-state] 489state = "2" 490event = "%s" 491new_state = "1" 492''' % (event, errno, self.STREAM_BUFFER_SIZE / 512, event, event)) 493 file.close() 494 495class TestEIO(TestErrors): 496 def setUp(self): 497 self.blkdebug_file = backing_img + ".blkdebug" 498 iotests.create_image(backing_img, TestErrors.image_len) 499 self.create_blkdebug_file(self.blkdebug_file, "read_aio", 5) 500 qemu_img('create', '-f', iotests.imgfmt, 501 '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw' 502 % (self.blkdebug_file, backing_img), 503 test_img) 504 self.vm = iotests.VM().add_drive(test_img) 505 self.vm.launch() 506 507 def tearDown(self): 508 self.vm.shutdown() 509 os.remove(test_img) 510 os.remove(backing_img) 511 os.remove(self.blkdebug_file) 512 513 def test_report(self): 514 self.assert_no_active_block_jobs() 515 516 result = self.vm.qmp('block-stream', device='drive0') 517 self.assert_qmp(result, 'return', {}) 518 519 completed = False 520 error = False 521 while not completed: 522 for event in self.vm.get_qmp_events(wait=True): 523 if event['event'] == 'BLOCK_JOB_ERROR': 524 self.assert_qmp(event, 'data/device', 'drive0') 525 self.assert_qmp(event, 'data/operation', 'read') 526 error = True 527 elif event['event'] == 'BLOCK_JOB_COMPLETED': 528 self.assertTrue(error, 'job completed unexpectedly') 529 self.assert_qmp(event, 'data/type', 'stream') 530 self.assert_qmp(event, 'data/device', 'drive0') 531 self.assert_qmp(event, 'data/error', 'Input/output error') 532 self.assert_qmp(event, 'data/offset', self.STREAM_BUFFER_SIZE) 533 self.assert_qmp(event, 'data/len', self.image_len) 534 completed = True 535 536 self.assert_no_active_block_jobs() 537 self.vm.shutdown() 538 539 def test_ignore(self): 540 self.assert_no_active_block_jobs() 541 542 result = self.vm.qmp('block-stream', device='drive0', on_error='ignore') 543 self.assert_qmp(result, 'return', {}) 544 545 error = False 546 completed = False 547 while not completed: 548 for event in self.vm.get_qmp_events(wait=True): 549 if event['event'] == 'BLOCK_JOB_ERROR': 550 self.assert_qmp(event, 'data/device', 'drive0') 551 self.assert_qmp(event, 'data/operation', 'read') 552 result = self.vm.qmp('query-block-jobs') 553 self.assert_qmp(result, 'return[0]/paused', False) 554 error = True 555 elif event['event'] == 'BLOCK_JOB_COMPLETED': 556 self.assertTrue(error, 'job completed unexpectedly') 557 self.assert_qmp(event, 'data/type', 'stream') 558 self.assert_qmp(event, 'data/device', 'drive0') 559 self.assert_qmp(event, 'data/error', 'Input/output error') 560 self.assert_qmp(event, 'data/offset', self.image_len) 561 self.assert_qmp(event, 'data/len', self.image_len) 562 completed = True 563 564 self.assert_no_active_block_jobs() 565 self.vm.shutdown() 566 567 def test_stop(self): 568 self.assert_no_active_block_jobs() 569 570 result = self.vm.qmp('block-stream', device='drive0', on_error='stop') 571 self.assert_qmp(result, 'return', {}) 572 573 error = False 574 completed = False 575 while not completed: 576 for event in self.vm.get_qmp_events(wait=True): 577 if event['event'] == 'BLOCK_JOB_ERROR': 578 error = True 579 self.assert_qmp(event, 'data/device', 'drive0') 580 self.assert_qmp(event, 'data/operation', 'read') 581 582 result = self.vm.qmp('query-block-jobs') 583 self.assert_qmp(result, 'return[0]/paused', True) 584 self.assert_qmp(result, 'return[0]/offset', self.STREAM_BUFFER_SIZE) 585 self.assert_qmp(result, 'return[0]/io-status', 'failed') 586 587 result = self.vm.qmp('block-job-resume', device='drive0') 588 self.assert_qmp(result, 'return', {}) 589 590 result = self.vm.qmp('query-block-jobs') 591 if result == {'return': []}: 592 # Race; likely already finished. Check. 593 continue 594 self.assert_qmp(result, 'return[0]/paused', False) 595 self.assert_qmp(result, 'return[0]/io-status', 'ok') 596 elif event['event'] == 'BLOCK_JOB_COMPLETED': 597 self.assertTrue(error, 'job completed unexpectedly') 598 self.assert_qmp(event, 'data/type', 'stream') 599 self.assert_qmp(event, 'data/device', 'drive0') 600 self.assert_qmp_absent(event, 'data/error') 601 self.assert_qmp(event, 'data/offset', self.image_len) 602 self.assert_qmp(event, 'data/len', self.image_len) 603 completed = True 604 605 self.assert_no_active_block_jobs() 606 self.vm.shutdown() 607 608 def test_enospc(self): 609 self.assert_no_active_block_jobs() 610 611 result = self.vm.qmp('block-stream', device='drive0', on_error='enospc') 612 self.assert_qmp(result, 'return', {}) 613 614 completed = False 615 error = False 616 while not completed: 617 for event in self.vm.get_qmp_events(wait=True): 618 if event['event'] == 'BLOCK_JOB_ERROR': 619 self.assert_qmp(event, 'data/device', 'drive0') 620 self.assert_qmp(event, 'data/operation', 'read') 621 error = True 622 elif event['event'] == 'BLOCK_JOB_COMPLETED': 623 self.assertTrue(error, 'job completed unexpectedly') 624 self.assert_qmp(event, 'data/type', 'stream') 625 self.assert_qmp(event, 'data/device', 'drive0') 626 self.assert_qmp(event, 'data/error', 'Input/output error') 627 self.assert_qmp(event, 'data/offset', self.STREAM_BUFFER_SIZE) 628 self.assert_qmp(event, 'data/len', self.image_len) 629 completed = True 630 631 self.assert_no_active_block_jobs() 632 self.vm.shutdown() 633 634class TestENOSPC(TestErrors): 635 def setUp(self): 636 self.blkdebug_file = backing_img + ".blkdebug" 637 iotests.create_image(backing_img, TestErrors.image_len) 638 self.create_blkdebug_file(self.blkdebug_file, "read_aio", 28) 639 qemu_img('create', '-f', iotests.imgfmt, 640 '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw' 641 % (self.blkdebug_file, backing_img), 642 test_img) 643 self.vm = iotests.VM().add_drive(test_img) 644 self.vm.launch() 645 646 def tearDown(self): 647 self.vm.shutdown() 648 os.remove(test_img) 649 os.remove(backing_img) 650 os.remove(self.blkdebug_file) 651 652 def test_enospc(self): 653 self.assert_no_active_block_jobs() 654 655 result = self.vm.qmp('block-stream', device='drive0', on_error='enospc') 656 self.assert_qmp(result, 'return', {}) 657 658 error = False 659 completed = False 660 while not completed: 661 for event in self.vm.get_qmp_events(wait=True): 662 if event['event'] == 'BLOCK_JOB_ERROR': 663 self.assert_qmp(event, 'data/device', 'drive0') 664 self.assert_qmp(event, 'data/operation', 'read') 665 666 result = self.vm.qmp('query-block-jobs') 667 self.assert_qmp(result, 'return[0]/paused', True) 668 self.assert_qmp(result, 'return[0]/offset', self.STREAM_BUFFER_SIZE) 669 self.assert_qmp(result, 'return[0]/io-status', 'nospace') 670 671 result = self.vm.qmp('block-job-resume', device='drive0') 672 self.assert_qmp(result, 'return', {}) 673 674 result = self.vm.qmp('query-block-jobs') 675 self.assert_qmp(result, 'return[0]/paused', False) 676 self.assert_qmp(result, 'return[0]/io-status', 'ok') 677 error = True 678 elif event['event'] == 'BLOCK_JOB_COMPLETED': 679 self.assertTrue(error, 'job completed unexpectedly') 680 self.assert_qmp(event, 'data/type', 'stream') 681 self.assert_qmp(event, 'data/device', 'drive0') 682 self.assert_qmp_absent(event, 'data/error') 683 self.assert_qmp(event, 'data/offset', self.image_len) 684 self.assert_qmp(event, 'data/len', self.image_len) 685 completed = True 686 687 self.assert_no_active_block_jobs() 688 self.vm.shutdown() 689 690class TestStreamStop(iotests.QMPTestCase): 691 image_len = 8 * 1024 * 1024 * 1024 # GB 692 693 def setUp(self): 694 qemu_img('create', backing_img, str(TestStreamStop.image_len)) 695 qemu_io('-f', 'raw', '-c', 'write -P 0x1 0 32M', backing_img) 696 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img) 697 qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x1 32M 32M', test_img) 698 self.vm = iotests.VM().add_drive("blkdebug::" + test_img) 699 self.vm.launch() 700 701 def tearDown(self): 702 self.vm.shutdown() 703 os.remove(test_img) 704 os.remove(backing_img) 705 706 def test_stream_stop(self): 707 self.assert_no_active_block_jobs() 708 709 self.vm.pause_drive('drive0') 710 result = self.vm.qmp('block-stream', device='drive0') 711 self.assert_qmp(result, 'return', {}) 712 713 time.sleep(0.1) 714 events = self.vm.get_qmp_events(wait=False) 715 self.assertEqual(events, [], 'unexpected QMP event: %s' % events) 716 717 self.cancel_and_wait(resume=True) 718 719class TestSetSpeed(iotests.QMPTestCase): 720 image_len = 80 * 1024 * 1024 # MB 721 722 def setUp(self): 723 qemu_img('create', backing_img, str(TestSetSpeed.image_len)) 724 qemu_io('-f', 'raw', '-c', 'write -P 0x1 0 32M', backing_img) 725 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img) 726 qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x1 32M 32M', test_img) 727 self.vm = iotests.VM().add_drive('blkdebug::' + test_img) 728 self.vm.launch() 729 730 def tearDown(self): 731 self.vm.shutdown() 732 os.remove(test_img) 733 os.remove(backing_img) 734 735 # This is a short performance test which is not run by default. 736 # Invoke "IMGFMT=qed ./030 TestSetSpeed.perf_test_throughput" 737 def perf_test_throughput(self): 738 self.assert_no_active_block_jobs() 739 740 result = self.vm.qmp('block-stream', device='drive0') 741 self.assert_qmp(result, 'return', {}) 742 743 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024) 744 self.assert_qmp(result, 'return', {}) 745 746 self.wait_until_completed() 747 748 self.assert_no_active_block_jobs() 749 750 def test_set_speed(self): 751 self.assert_no_active_block_jobs() 752 753 self.vm.pause_drive('drive0') 754 result = self.vm.qmp('block-stream', device='drive0') 755 self.assert_qmp(result, 'return', {}) 756 757 # Default speed is 0 758 result = self.vm.qmp('query-block-jobs') 759 self.assert_qmp(result, 'return[0]/device', 'drive0') 760 self.assert_qmp(result, 'return[0]/speed', 0) 761 762 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024) 763 self.assert_qmp(result, 'return', {}) 764 765 # Ensure the speed we set was accepted 766 result = self.vm.qmp('query-block-jobs') 767 self.assert_qmp(result, 'return[0]/device', 'drive0') 768 self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024) 769 770 self.cancel_and_wait(resume=True) 771 self.vm.pause_drive('drive0') 772 773 # Check setting speed in block-stream works 774 result = self.vm.qmp('block-stream', device='drive0', speed=4 * 1024 * 1024) 775 self.assert_qmp(result, 'return', {}) 776 777 result = self.vm.qmp('query-block-jobs') 778 self.assert_qmp(result, 'return[0]/device', 'drive0') 779 self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024) 780 781 self.cancel_and_wait(resume=True) 782 783 def test_set_speed_invalid(self): 784 self.assert_no_active_block_jobs() 785 786 result = self.vm.qmp('block-stream', device='drive0', speed=-1) 787 self.assert_qmp(result, 'error/class', 'GenericError') 788 789 self.assert_no_active_block_jobs() 790 791 result = self.vm.qmp('block-stream', device='drive0') 792 self.assert_qmp(result, 'return', {}) 793 794 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1) 795 self.assert_qmp(result, 'error/class', 'GenericError') 796 797 self.cancel_and_wait() 798 799if __name__ == '__main__': 800 iotests.main(supported_fmts=['qcow2', 'qed']) 801