1#!/usr/bin/env python3 2# group: rw backing 3# 4# Tests for image streaming. 5# 6# Copyright (C) 2012 IBM Corp. 7# 8# This program is free software; you can redistribute it and/or modify 9# it under the terms of the GNU General Public License as published by 10# the Free Software Foundation; either version 2 of the License, or 11# (at your option) any later version. 12# 13# This program is distributed in the hope that it will be useful, 14# but WITHOUT ANY WARRANTY; without even the implied warranty of 15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16# GNU General Public License for more details. 17# 18# You should have received a copy of the GNU General Public License 19# along with this program. If not, see <http://www.gnu.org/licenses/>. 20# 21 22import time 23import os 24import iotests 25import unittest 26from iotests import qemu_img, qemu_io 27 28backing_img = os.path.join(iotests.test_dir, 'backing.img') 29mid_img = os.path.join(iotests.test_dir, 'mid.img') 30test_img = os.path.join(iotests.test_dir, 'test.img') 31 32class TestSingleDrive(iotests.QMPTestCase): 33 image_len = 1 * 1024 * 1024 # MB 34 35 def setUp(self): 36 iotests.create_image(backing_img, TestSingleDrive.image_len) 37 qemu_img('create', '-f', iotests.imgfmt, 38 '-o', 'backing_file=%s' % backing_img, 39 '-F', 'raw', mid_img) 40 qemu_img('create', '-f', iotests.imgfmt, 41 '-o', 'backing_file=%s' % mid_img, 42 '-F', iotests.imgfmt, test_img) 43 qemu_io('-f', 'raw', '-c', 'write -P 0x1 0 512', backing_img) 44 qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x1 524288 512', mid_img) 45 self.vm = iotests.VM().add_drive("blkdebug::" + test_img, 46 "backing.node-name=mid," + 47 "backing.backing.node-name=base") 48 self.vm.launch() 49 50 def tearDown(self): 51 self.vm.shutdown() 52 os.remove(test_img) 53 os.remove(mid_img) 54 os.remove(backing_img) 55 56 def test_stream(self): 57 self.assert_no_active_block_jobs() 58 59 result = self.vm.qmp('block-stream', device='drive0') 60 self.assert_qmp(result, 'return', {}) 61 62 self.wait_until_completed() 63 64 self.assert_no_active_block_jobs() 65 self.vm.shutdown() 66 67 self.assertEqual( 68 qemu_io('-f', 'raw', '-c', 'map', backing_img).stdout, 69 qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img).stdout, 70 'image file map does not match backing file after streaming') 71 72 def test_stream_intermediate(self): 73 self.assert_no_active_block_jobs() 74 75 self.assertNotEqual( 76 qemu_io('-f', 'raw', '-rU', '-c', 'map', backing_img).stdout, 77 qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', mid_img).stdout, 78 'image file map matches backing file before streaming') 79 80 result = self.vm.qmp('block-stream', device='mid', job_id='stream-mid') 81 self.assert_qmp(result, 'return', {}) 82 83 self.wait_until_completed(drive='stream-mid') 84 85 self.assert_no_active_block_jobs() 86 self.vm.shutdown() 87 88 self.assertEqual( 89 qemu_io('-f', 'raw', '-c', 'map', backing_img).stdout, 90 qemu_io('-f', iotests.imgfmt, '-c', 'map', mid_img).stdout, 91 'image file map does not match backing file after streaming') 92 93 def test_stream_pause(self): 94 self.assert_no_active_block_jobs() 95 96 self.vm.pause_drive('drive0') 97 result = self.vm.qmp('block-stream', device='drive0') 98 self.assert_qmp(result, 'return', {}) 99 100 self.pause_job('drive0', wait=False) 101 self.vm.resume_drive('drive0') 102 self.pause_wait('drive0') 103 104 result = self.vm.qmp('query-block-jobs') 105 offset = self.dictpath(result, 'return[0]/offset') 106 107 time.sleep(0.5) 108 result = self.vm.qmp('query-block-jobs') 109 self.assert_qmp(result, 'return[0]/offset', offset) 110 111 result = self.vm.qmp('block-job-resume', device='drive0') 112 self.assert_qmp(result, 'return', {}) 113 114 self.wait_until_completed() 115 116 self.assert_no_active_block_jobs() 117 self.vm.shutdown() 118 119 self.assertEqual( 120 qemu_io('-f', 'raw', '-c', 'map', backing_img).stdout, 121 qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img).stdout, 122 'image file map does not match backing file after streaming') 123 124 def test_stream_no_op(self): 125 self.assert_no_active_block_jobs() 126 127 # The image map is empty before the operation 128 empty_map = qemu_io( 129 '-f', iotests.imgfmt, '-rU', '-c', 'map', test_img).stdout 130 131 # This is a no-op: no data should ever be copied from the base image 132 result = self.vm.qmp('block-stream', device='drive0', base=mid_img) 133 self.assert_qmp(result, 'return', {}) 134 135 self.wait_until_completed() 136 137 self.assert_no_active_block_jobs() 138 self.vm.shutdown() 139 140 self.assertEqual( 141 qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img).stdout, 142 empty_map, 'image file map changed after a no-op') 143 144 def test_stream_partial(self): 145 self.assert_no_active_block_jobs() 146 147 result = self.vm.qmp('block-stream', device='drive0', base=backing_img) 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 155 self.assertEqual( 156 qemu_io('-f', iotests.imgfmt, '-c', 'map', mid_img).stdout, 157 qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img).stdout, 158 'image file map does not match backing file after streaming') 159 160 def test_device_not_found(self): 161 result = self.vm.qmp('block-stream', device='nonexistent') 162 self.assert_qmp(result, 'error/desc', 163 'Cannot find device=\'nonexistent\' nor node-name=\'nonexistent\'') 164 165 def test_job_id_missing(self): 166 result = self.vm.qmp('block-stream', device='mid') 167 self.assert_qmp(result, 'error/desc', "Invalid job ID ''") 168 169 def test_read_only(self): 170 # Create a new file that we can attach (we need a read-only top) 171 with iotests.FilePath('ro-top.img') as ro_top_path: 172 qemu_img('create', '-f', iotests.imgfmt, ro_top_path, 173 str(self.image_len)) 174 175 result = self.vm.qmp('blockdev-add', 176 node_name='ro-top', 177 driver=iotests.imgfmt, 178 read_only=True, 179 file={ 180 'driver': 'file', 181 'filename': ro_top_path, 182 'read-only': True 183 }, 184 backing='mid') 185 self.assert_qmp(result, 'return', {}) 186 187 result = self.vm.qmp('block-stream', job_id='stream', 188 device='ro-top', base_node='base') 189 self.assert_qmp(result, 'error/desc', 'Block node is read-only') 190 191 result = self.vm.qmp('blockdev-del', node_name='ro-top') 192 self.assert_qmp(result, 'return', {}) 193 194 195class TestParallelOps(iotests.QMPTestCase): 196 num_ops = 4 # Number of parallel block-stream operations 197 num_imgs = num_ops * 2 + 1 198 image_len = num_ops * 4 * 1024 * 1024 199 imgs = [] 200 201 def setUp(self): 202 opts = [] 203 self.imgs = [] 204 205 # Initialize file names and command-line options 206 for i in range(self.num_imgs): 207 img_depth = self.num_imgs - i - 1 208 opts.append("backing." * img_depth + "node-name=node%d" % i) 209 self.imgs.append(os.path.join(iotests.test_dir, 'img-%d.img' % i)) 210 211 # Create all images 212 iotests.create_image(self.imgs[0], self.image_len) 213 for i in range(1, self.num_imgs): 214 qemu_img('create', '-f', iotests.imgfmt, 215 '-o', 'backing_file=%s' % self.imgs[i-1], 216 '-F', 'raw' if i == 1 else iotests.imgfmt, self.imgs[i]) 217 218 # Put data into the images we are copying data from 219 odd_img_indexes = [x for x in reversed(range(self.num_imgs)) if x % 2 == 1] 220 for i in range(len(odd_img_indexes)): 221 # Alternate between 2MB and 4MB. 222 # This way jobs will not finish in the same order they were created 223 num_mb = 2 + 2 * (i % 2) 224 qemu_io('-f', iotests.imgfmt, 225 '-c', 'write -P 0xFF %dM %dM' % (i * 4, num_mb), 226 self.imgs[odd_img_indexes[i]]) 227 228 # Attach the drive to the VM 229 self.vm = iotests.VM() 230 self.vm.add_drive(self.imgs[-1], ','.join(opts)) 231 self.vm.launch() 232 233 def tearDown(self): 234 self.vm.shutdown() 235 for img in self.imgs: 236 os.remove(img) 237 238 # Test that it's possible to run several block-stream operations 239 # in parallel in the same snapshot chain 240 @unittest.skipIf(os.environ.get('QEMU_CHECK_BLOCK_AUTO'), 'disabled in CI') 241 def test_stream_parallel(self): 242 self.assert_no_active_block_jobs() 243 244 # Check that the maps don't match before the streaming operations 245 for i in range(2, self.num_imgs, 2): 246 self.assertNotEqual( 247 qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.imgs[i]).stdout, 248 qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.imgs[i-1]).stdout, 249 'image file map matches backing file before streaming') 250 251 # Create all streaming jobs 252 pending_jobs = [] 253 for i in range(2, self.num_imgs, 2): 254 node_name = 'node%d' % i 255 job_id = 'stream-%s' % node_name 256 pending_jobs.append(job_id) 257 result = self.vm.qmp('block-stream', device=node_name, 258 job_id=job_id, bottom=f'node{i-1}', 259 speed=1024) 260 self.assert_qmp(result, 'return', {}) 261 262 # Do this in reverse: After unthrottling them, some jobs may finish 263 # before we have unthrottled all of them. This will drain their 264 # subgraph, and this will make jobs above them advance (despite those 265 # jobs on top being throttled). In the worst case, all jobs below the 266 # top one are finished before we can unthrottle it, and this makes it 267 # advance so far that it completes before we can unthrottle it - which 268 # results in an error. 269 # Starting from the top (i.e. in reverse) does not have this problem: 270 # When a job finishes, the ones below it are not advanced. 271 for job in reversed(pending_jobs): 272 result = self.vm.qmp('block-job-set-speed', device=job, speed=0) 273 self.assert_qmp(result, 'return', {}) 274 275 # Wait for all jobs to be finished. 276 while len(pending_jobs) > 0: 277 for event in self.vm.get_qmp_events(wait=True): 278 if event['event'] == 'BLOCK_JOB_COMPLETED': 279 job_id = self.dictpath(event, 'data/device') 280 self.assertTrue(job_id in pending_jobs) 281 self.assert_qmp_absent(event, 'data/error') 282 pending_jobs.remove(job_id) 283 284 self.assert_no_active_block_jobs() 285 self.vm.shutdown() 286 287 # Check that all maps match now 288 for i in range(2, self.num_imgs, 2): 289 self.assertEqual( 290 qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[i]).stdout, 291 qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[i-1]).stdout, 292 'image file map does not match backing file after streaming') 293 294 # Test that it's not possible to perform two block-stream 295 # operations if there are nodes involved in both. 296 def test_overlapping_1(self): 297 self.assert_no_active_block_jobs() 298 299 # Set a speed limit to make sure that this job blocks the rest 300 result = self.vm.qmp('block-stream', device='node4', 301 job_id='stream-node4', base=self.imgs[1], 302 filter_node_name='stream-filter', speed=1024*1024) 303 self.assert_qmp(result, 'return', {}) 304 305 result = self.vm.qmp('block-stream', device='node5', job_id='stream-node5', base=self.imgs[2]) 306 self.assert_qmp(result, 'error/desc', 307 "Node 'stream-filter' is busy: block device is in use by block job: stream") 308 309 result = self.vm.qmp('block-stream', device='node3', job_id='stream-node3', base=self.imgs[2]) 310 self.assert_qmp(result, 'error/desc', 311 "Node 'node3' is busy: block device is in use by block job: stream") 312 313 result = self.vm.qmp('block-stream', device='node4', job_id='stream-node4-v2') 314 self.assert_qmp(result, 'error/desc', 315 "Node 'node4' is busy: block device is in use by block job: stream") 316 317 # block-commit should also fail if it touches nodes used by the stream job 318 result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[4], job_id='commit-node4') 319 self.assert_qmp(result, 'error/desc', 320 "Node 'stream-filter' is busy: block device is in use by block job: stream") 321 322 result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[1], top=self.imgs[3], job_id='commit-node1') 323 self.assert_qmp(result, 'error/desc', 324 "Node 'node3' is busy: block device is in use by block job: stream") 325 326 # This fails because it needs to modify the backing string in node2, which is blocked 327 result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[0], top=self.imgs[1], job_id='commit-node0') 328 self.assert_qmp(result, 'error/desc', 329 "Node 'node2' is busy: block device is in use by block job: stream") 330 331 result = self.vm.qmp('block-job-set-speed', device='stream-node4', speed=0) 332 self.assert_qmp(result, 'return', {}) 333 334 self.wait_until_completed(drive='stream-node4') 335 self.assert_no_active_block_jobs() 336 337 # Similar to test_overlapping_1, but with block-commit 338 # blocking the other jobs 339 def test_overlapping_2(self): 340 self.assertLessEqual(9, self.num_imgs) 341 self.assert_no_active_block_jobs() 342 343 # Set a speed limit to make sure that this job blocks the rest 344 result = self.vm.qmp('block-commit', device='drive0', top=self.imgs[5], base=self.imgs[3], job_id='commit-node3', speed=1024*1024) 345 self.assert_qmp(result, 'return', {}) 346 347 result = self.vm.qmp('block-stream', device='node3', job_id='stream-node3') 348 self.assert_qmp(result, 'error/desc', 349 "Node 'node3' is busy: block device is in use by block job: commit") 350 351 result = self.vm.qmp('block-stream', device='node6', base=self.imgs[2], job_id='stream-node6') 352 self.assert_qmp(result, 'error/desc', 353 "Node 'node5' is busy: block device is in use by block job: commit") 354 355 result = self.vm.qmp('block-stream', device='node4', base=self.imgs[2], job_id='stream-node4') 356 self.assert_qmp(result, 'error/desc', 357 "Node 'node4' is busy: block device is in use by block job: commit") 358 359 result = self.vm.qmp('block-stream', device='node6', base=self.imgs[4], job_id='stream-node6-v2') 360 self.assert_qmp(result, 'error/desc', 361 "Node 'node5' is busy: block device is in use by block job: commit") 362 363 # This fails because block-commit currently blocks the active layer even if it's not used 364 result = self.vm.qmp('block-stream', device='drive0', base=self.imgs[5], job_id='stream-drive0') 365 self.assert_qmp(result, 'error/desc', 366 "Node 'drive0' is busy: block device is in use by block job: commit") 367 368 result = self.vm.qmp('block-job-set-speed', device='commit-node3', speed=0) 369 self.assert_qmp(result, 'return', {}) 370 371 self.wait_until_completed(drive='commit-node3') 372 373 # Similar to test_overlapping_2, but here block-commit doesn't use the 'top' parameter. 374 # Internally this uses a mirror block job, hence the separate test case. 375 def test_overlapping_3(self): 376 self.assertLessEqual(8, self.num_imgs) 377 self.assert_no_active_block_jobs() 378 379 # Set a speed limit to make sure that this job blocks the rest 380 result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[3], job_id='commit-drive0', speed=1024*1024) 381 self.assert_qmp(result, 'return', {}) 382 383 result = self.vm.qmp('block-stream', device='node5', base=self.imgs[3], job_id='stream-node6') 384 self.assert_qmp(result, 'error/desc', 385 "Node 'node5' is busy: block device is in use by block job: commit") 386 387 result = self.vm.qmp('block-job-set-speed', device='commit-drive0', speed=0) 388 self.assert_qmp(result, 'return', {}) 389 390 event = self.vm.event_wait(name='BLOCK_JOB_READY') 391 self.assert_qmp(event, 'data/device', 'commit-drive0') 392 self.assert_qmp(event, 'data/type', 'commit') 393 self.assert_qmp_absent(event, 'data/error') 394 395 result = self.vm.qmp('block-job-complete', device='commit-drive0') 396 self.assert_qmp(result, 'return', {}) 397 398 self.wait_until_completed(drive='commit-drive0') 399 400 # In this case the base node of the stream job is the same as the 401 # top node of commit job. Since this results in the commit filter 402 # node being part of the stream chain, this is not allowed. 403 def test_overlapping_4(self): 404 self.assert_no_active_block_jobs() 405 406 # Commit from node2 into node0 407 result = self.vm.qmp('block-commit', device='drive0', 408 top=self.imgs[2], base=self.imgs[0], 409 filter_node_name='commit-filter', speed=1024*1024) 410 self.assert_qmp(result, 'return', {}) 411 412 # Stream from node2 into node4 413 result = self.vm.qmp('block-stream', device='node4', base_node='node2', job_id='node4') 414 self.assert_qmp(result, 'error/desc', 415 "Cannot freeze 'backing' link to 'commit-filter'") 416 417 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=0) 418 self.assert_qmp(result, 'return', {}) 419 420 self.wait_until_completed() 421 self.assert_no_active_block_jobs() 422 423 # In this case the base node of the stream job is the commit job's 424 # filter node. stream does not have a real dependency on its base 425 # node, so even though commit removes it when it is done, there is 426 # no conflict. 427 def test_overlapping_5(self): 428 self.assert_no_active_block_jobs() 429 430 # Commit from node2 into node0 431 result = self.vm.qmp('block-commit', device='drive0', 432 top_node='node2', base_node='node0', 433 filter_node_name='commit-filter', speed=1024*1024) 434 self.assert_qmp(result, 'return', {}) 435 436 # Stream from node2 into node4 437 result = self.vm.qmp('block-stream', device='node4', 438 base_node='commit-filter', job_id='node4') 439 self.assert_qmp(result, 'return', {}) 440 441 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=0) 442 self.assert_qmp(result, 'return', {}) 443 444 self.vm.run_job(job='drive0', auto_dismiss=True) 445 self.vm.run_job(job='node4', auto_dismiss=True) 446 self.assert_no_active_block_jobs() 447 448 # Assert that node0 is now the backing node of node4 449 result = self.vm.qmp('query-named-block-nodes') 450 node4 = next(node for node in result['return'] if node['node-name'] == 'node4') 451 self.assertEqual(node4['image']['backing-image']['filename'], self.imgs[0]) 452 453 # Test a block-stream and a block-commit job in parallel 454 # Here the stream job is supposed to finish quickly in order to reproduce 455 # the scenario that triggers the bug fixed in 3d5d319e1221 and 1a63a907507 456 def test_stream_commit_1(self): 457 self.assertLessEqual(8, self.num_imgs) 458 self.assert_no_active_block_jobs() 459 460 # Stream from node0 into node2 461 result = self.vm.qmp('block-stream', device='node2', base_node='node0', job_id='node2') 462 self.assert_qmp(result, 'return', {}) 463 464 # Commit from the active layer into node3 465 result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[3]) 466 self.assert_qmp(result, 'return', {}) 467 468 # Wait for all jobs to be finished. 469 pending_jobs = ['node2', 'drive0'] 470 while len(pending_jobs) > 0: 471 for event in self.vm.get_qmp_events(wait=True): 472 if event['event'] == 'BLOCK_JOB_COMPLETED': 473 node_name = self.dictpath(event, 'data/device') 474 self.assertTrue(node_name in pending_jobs) 475 self.assert_qmp_absent(event, 'data/error') 476 pending_jobs.remove(node_name) 477 if event['event'] == 'BLOCK_JOB_READY': 478 self.assert_qmp(event, 'data/device', 'drive0') 479 self.assert_qmp(event, 'data/type', 'commit') 480 self.assert_qmp_absent(event, 'data/error') 481 self.assertTrue('drive0' in pending_jobs) 482 self.vm.qmp('block-job-complete', device='drive0') 483 484 self.assert_no_active_block_jobs() 485 486 # This is similar to test_stream_commit_1 but both jobs are slowed 487 # down so they can run in parallel for a little while. 488 def test_stream_commit_2(self): 489 self.assertLessEqual(8, self.num_imgs) 490 self.assert_no_active_block_jobs() 491 492 # Stream from node0 into node4 493 result = self.vm.qmp('block-stream', device='node4', base_node='node0', job_id='node4', speed=1024*1024) 494 self.assert_qmp(result, 'return', {}) 495 496 # Commit from the active layer into node5 497 result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[5], speed=1024*1024) 498 self.assert_qmp(result, 'return', {}) 499 500 for job in ['drive0', 'node4']: 501 result = self.vm.qmp('block-job-set-speed', device=job, speed=0) 502 self.assert_qmp(result, 'return', {}) 503 504 # Wait for all jobs to be finished. 505 pending_jobs = ['node4', 'drive0'] 506 while len(pending_jobs) > 0: 507 for event in self.vm.get_qmp_events(wait=True): 508 if event['event'] == 'BLOCK_JOB_COMPLETED': 509 node_name = self.dictpath(event, 'data/device') 510 self.assertTrue(node_name in pending_jobs) 511 self.assert_qmp_absent(event, 'data/error') 512 pending_jobs.remove(node_name) 513 if event['event'] == 'BLOCK_JOB_READY': 514 self.assert_qmp(event, 'data/device', 'drive0') 515 self.assert_qmp(event, 'data/type', 'commit') 516 self.assert_qmp_absent(event, 'data/error') 517 self.assertTrue('drive0' in pending_jobs) 518 self.vm.qmp('block-job-complete', device='drive0') 519 520 self.assert_no_active_block_jobs() 521 522 # Test the base_node parameter 523 def test_stream_base_node_name(self): 524 self.assert_no_active_block_jobs() 525 526 self.assertNotEqual( 527 qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.imgs[4]).stdout, 528 qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.imgs[3]).stdout, 529 'image file map matches backing file before streaming') 530 531 # Error: the base node does not exist 532 result = self.vm.qmp('block-stream', device='node4', base_node='none', job_id='stream') 533 self.assert_qmp(result, 'error/desc', 534 'Cannot find device=\'\' nor node-name=\'none\'') 535 536 # Error: the base node is not a backing file of the top node 537 result = self.vm.qmp('block-stream', device='node4', base_node='node6', job_id='stream') 538 self.assert_qmp(result, 'error/desc', 539 "Node 'node6' is not a backing image of 'node4'") 540 541 # Error: the base node is the same as the top node 542 result = self.vm.qmp('block-stream', device='node4', base_node='node4', job_id='stream') 543 self.assert_qmp(result, 'error/desc', 544 "Node 'node4' is not a backing image of 'node4'") 545 546 # Error: cannot specify 'base' and 'base-node' at the same time 547 result = self.vm.qmp('block-stream', device='node4', base=self.imgs[2], base_node='node2', job_id='stream') 548 self.assert_qmp(result, 'error/desc', 549 "'base' and 'base-node' cannot be specified at the same time") 550 551 # Success: the base node is a backing file of the top node 552 result = self.vm.qmp('block-stream', device='node4', base_node='node2', job_id='stream') 553 self.assert_qmp(result, 'return', {}) 554 555 self.wait_until_completed(drive='stream') 556 557 self.assert_no_active_block_jobs() 558 self.vm.shutdown() 559 560 self.assertEqual( 561 qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[4]).stdout, 562 qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[3]).stdout, 563 'image file map matches backing file after streaming') 564 565class TestQuorum(iotests.QMPTestCase): 566 num_children = 3 567 children = [] 568 backing = [] 569 570 @iotests.skip_if_unsupported(['quorum']) 571 def setUp(self): 572 opts = ['driver=quorum', 'vote-threshold=2'] 573 574 # Initialize file names and command-line options 575 for i in range(self.num_children): 576 child_img = os.path.join(iotests.test_dir, 'img-%d.img' % i) 577 backing_img = os.path.join(iotests.test_dir, 'backing-%d.img' % i) 578 self.children.append(child_img) 579 self.backing.append(backing_img) 580 qemu_img('create', '-f', iotests.imgfmt, backing_img, '1M') 581 qemu_io('-f', iotests.imgfmt, 582 '-c', 'write -P 0x55 0 1024', backing_img) 583 qemu_img('create', '-f', iotests.imgfmt, 584 '-o', 'backing_file=%s' % backing_img, 585 '-F', iotests.imgfmt, child_img) 586 opts.append("children.%d.file.filename=%s" % (i, child_img)) 587 opts.append("children.%d.node-name=node%d" % (i, i)) 588 589 # Attach the drive to the VM 590 self.vm = iotests.VM() 591 self.vm.add_drive(path = None, opts = ','.join(opts)) 592 self.vm.launch() 593 594 def tearDown(self): 595 self.vm.shutdown() 596 for img in self.children: 597 os.remove(img) 598 for img in self.backing: 599 os.remove(img) 600 601 def test_stream_quorum(self): 602 self.assertNotEqual( 603 qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.children[0]).stdout, 604 qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.backing[0]).stdout, 605 'image file map matches backing file before streaming') 606 607 self.assert_no_active_block_jobs() 608 609 result = self.vm.qmp('block-stream', device='node0', job_id='stream-node0') 610 self.assert_qmp(result, 'return', {}) 611 612 self.wait_until_completed(drive='stream-node0') 613 614 self.assert_no_active_block_jobs() 615 self.vm.shutdown() 616 617 self.assertEqual( 618 qemu_io('-f', iotests.imgfmt, '-c', 'map', self.children[0]).stdout, 619 qemu_io('-f', iotests.imgfmt, '-c', 'map', self.backing[0]).stdout, 620 'image file map does not match backing file after streaming') 621 622class TestSmallerBackingFile(iotests.QMPTestCase): 623 backing_len = 1 * 1024 * 1024 # MB 624 image_len = 2 * backing_len 625 626 def setUp(self): 627 iotests.create_image(backing_img, self.backing_len) 628 qemu_img('create', '-f', iotests.imgfmt, 629 '-o', 'backing_file=%s' % backing_img, 630 '-F', 'raw', test_img, str(self.image_len)) 631 self.vm = iotests.VM().add_drive(test_img) 632 self.vm.launch() 633 634 # If this hangs, then you are missing a fix to complete streaming when the 635 # end of the backing file is reached. 636 def test_stream(self): 637 self.assert_no_active_block_jobs() 638 639 result = self.vm.qmp('block-stream', device='drive0') 640 self.assert_qmp(result, 'return', {}) 641 642 self.wait_until_completed() 643 644 self.assert_no_active_block_jobs() 645 self.vm.shutdown() 646 647class TestErrors(iotests.QMPTestCase): 648 image_len = 2 * 1024 * 1024 # MB 649 650 # this should match STREAM_BUFFER_SIZE/512 in block/stream.c 651 STREAM_BUFFER_SIZE = 512 * 1024 652 653 def create_blkdebug_file(self, name, event, errno): 654 file = open(name, 'w') 655 file.write(''' 656[inject-error] 657state = "1" 658event = "%s" 659errno = "%d" 660immediately = "off" 661once = "on" 662sector = "%d" 663 664[set-state] 665state = "1" 666event = "%s" 667new_state = "2" 668 669[set-state] 670state = "2" 671event = "%s" 672new_state = "1" 673''' % (event, errno, self.STREAM_BUFFER_SIZE // 512, event, event)) 674 file.close() 675 676class TestEIO(TestErrors): 677 def setUp(self): 678 self.blkdebug_file = backing_img + ".blkdebug" 679 iotests.create_image(backing_img, TestErrors.image_len) 680 self.create_blkdebug_file(self.blkdebug_file, "read_aio", 5) 681 qemu_img('create', '-f', iotests.imgfmt, 682 '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw' 683 % (self.blkdebug_file, backing_img), 684 test_img) 685 self.vm = iotests.VM().add_drive(test_img) 686 self.vm.launch() 687 688 def tearDown(self): 689 self.vm.shutdown() 690 os.remove(test_img) 691 os.remove(backing_img) 692 os.remove(self.blkdebug_file) 693 694 def test_report(self): 695 self.assert_no_active_block_jobs() 696 697 result = self.vm.qmp('block-stream', device='drive0') 698 self.assert_qmp(result, 'return', {}) 699 700 completed = False 701 error = False 702 while not completed: 703 for event in self.vm.get_qmp_events(wait=True): 704 if event['event'] == 'BLOCK_JOB_ERROR': 705 self.assert_qmp(event, 'data/device', 'drive0') 706 self.assert_qmp(event, 'data/operation', 'read') 707 error = True 708 elif event['event'] == 'BLOCK_JOB_COMPLETED': 709 self.assertTrue(error, 'job completed unexpectedly') 710 self.assert_qmp(event, 'data/type', 'stream') 711 self.assert_qmp(event, 'data/device', 'drive0') 712 self.assert_qmp(event, 'data/error', 'Input/output error') 713 self.assert_qmp(event, 'data/offset', self.STREAM_BUFFER_SIZE) 714 self.assert_qmp(event, 'data/len', self.image_len) 715 completed = True 716 elif event['event'] == 'JOB_STATUS_CHANGE': 717 self.assert_qmp(event, 'data/id', 'drive0') 718 719 self.assert_no_active_block_jobs() 720 self.vm.shutdown() 721 722 def test_ignore(self): 723 self.assert_no_active_block_jobs() 724 725 result = self.vm.qmp('block-stream', device='drive0', on_error='ignore') 726 self.assert_qmp(result, 'return', {}) 727 728 error = False 729 completed = False 730 while not completed: 731 for event in self.vm.get_qmp_events(wait=True): 732 if event['event'] == 'BLOCK_JOB_ERROR': 733 error = True 734 self.assert_qmp(event, 'data/device', 'drive0') 735 self.assert_qmp(event, 'data/operation', 'read') 736 result = self.vm.qmp('query-block-jobs') 737 if result == {'return': []}: 738 # Job finished too quickly 739 continue 740 self.assertIn(result['return'][0]['status'], 741 ['running', 'pending', 'aborting', 'concluded']) 742 elif event['event'] == 'BLOCK_JOB_COMPLETED': 743 self.assertTrue(error, 'job completed unexpectedly') 744 self.assert_qmp(event, 'data/type', 'stream') 745 self.assert_qmp(event, 'data/device', 'drive0') 746 self.assert_qmp(event, 'data/error', 'Input/output error') 747 self.assert_qmp(event, 'data/offset', self.image_len) 748 self.assert_qmp(event, 'data/len', self.image_len) 749 completed = True 750 elif event['event'] == 'JOB_STATUS_CHANGE': 751 self.assert_qmp(event, 'data/id', 'drive0') 752 753 self.assert_no_active_block_jobs() 754 self.vm.shutdown() 755 756 def test_stop(self): 757 self.assert_no_active_block_jobs() 758 759 result = self.vm.qmp('block-stream', device='drive0', on_error='stop') 760 self.assert_qmp(result, 'return', {}) 761 762 error = False 763 completed = False 764 while not completed: 765 for event in self.vm.get_qmp_events(wait=True): 766 if event['event'] == 'BLOCK_JOB_ERROR': 767 error = True 768 self.assert_qmp(event, 'data/device', 'drive0') 769 self.assert_qmp(event, 'data/operation', 'read') 770 771 if self.vm.qmp('query-block-jobs')['return'][0]['status'] != 'paused': 772 self.vm.events_wait([( 773 'JOB_STATUS_CHANGE', 774 {'data': {'id': 'drive0', 'status': 'paused'}} 775 )]) 776 777 result = self.vm.qmp('query-block-jobs') 778 self.assert_qmp(result, 'return[0]/status', 'paused') 779 self.assert_qmp(result, 'return[0]/offset', self.STREAM_BUFFER_SIZE) 780 self.assert_qmp(result, 'return[0]/io-status', 'failed') 781 782 result = self.vm.qmp('block-job-resume', device='drive0') 783 self.assert_qmp(result, 'return', {}) 784 785 result = self.vm.qmp('query-block-jobs') 786 if result == {'return': []}: 787 # Race; likely already finished. Check. 788 continue 789 self.assertIn(result['return'][0]['status'], 790 ['running', 'pending', 'aborting', 'concluded']) 791 self.assert_qmp(result, 'return[0]/io-status', 'ok') 792 elif event['event'] == 'BLOCK_JOB_COMPLETED': 793 self.assertTrue(error, 'job completed unexpectedly') 794 self.assert_qmp(event, 'data/type', 'stream') 795 self.assert_qmp(event, 'data/device', 'drive0') 796 self.assert_qmp_absent(event, 'data/error') 797 self.assert_qmp(event, 'data/offset', self.image_len) 798 self.assert_qmp(event, 'data/len', self.image_len) 799 completed = True 800 elif event['event'] == 'JOB_STATUS_CHANGE': 801 self.assert_qmp(event, 'data/id', 'drive0') 802 803 self.assert_no_active_block_jobs() 804 self.vm.shutdown() 805 806 def test_enospc(self): 807 self.assert_no_active_block_jobs() 808 809 result = self.vm.qmp('block-stream', device='drive0', on_error='enospc') 810 self.assert_qmp(result, 'return', {}) 811 812 completed = False 813 error = False 814 while not completed: 815 for event in self.vm.get_qmp_events(wait=True): 816 if event['event'] == 'BLOCK_JOB_ERROR': 817 self.assert_qmp(event, 'data/device', 'drive0') 818 self.assert_qmp(event, 'data/operation', 'read') 819 error = True 820 elif event['event'] == 'BLOCK_JOB_COMPLETED': 821 self.assertTrue(error, 'job completed unexpectedly') 822 self.assert_qmp(event, 'data/type', 'stream') 823 self.assert_qmp(event, 'data/device', 'drive0') 824 self.assert_qmp(event, 'data/error', 'Input/output error') 825 self.assert_qmp(event, 'data/offset', self.STREAM_BUFFER_SIZE) 826 self.assert_qmp(event, 'data/len', self.image_len) 827 completed = True 828 elif event['event'] == 'JOB_STATUS_CHANGE': 829 self.assert_qmp(event, 'data/id', 'drive0') 830 831 self.assert_no_active_block_jobs() 832 self.vm.shutdown() 833 834class TestENOSPC(TestErrors): 835 def setUp(self): 836 self.blkdebug_file = backing_img + ".blkdebug" 837 iotests.create_image(backing_img, TestErrors.image_len) 838 self.create_blkdebug_file(self.blkdebug_file, "read_aio", 28) 839 qemu_img('create', '-f', iotests.imgfmt, 840 '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw' 841 % (self.blkdebug_file, backing_img), 842 test_img) 843 self.vm = iotests.VM().add_drive(test_img) 844 self.vm.launch() 845 846 def tearDown(self): 847 self.vm.shutdown() 848 os.remove(test_img) 849 os.remove(backing_img) 850 os.remove(self.blkdebug_file) 851 852 def test_enospc(self): 853 self.assert_no_active_block_jobs() 854 855 result = self.vm.qmp('block-stream', device='drive0', on_error='enospc') 856 self.assert_qmp(result, 'return', {}) 857 858 error = False 859 completed = False 860 while not completed: 861 for event in self.vm.get_qmp_events(wait=True): 862 if event['event'] == 'BLOCK_JOB_ERROR': 863 self.assert_qmp(event, 'data/device', 'drive0') 864 self.assert_qmp(event, 'data/operation', 'read') 865 error = True 866 867 if self.vm.qmp('query-block-jobs')['return'][0]['status'] != 'paused': 868 self.vm.events_wait([( 869 'JOB_STATUS_CHANGE', 870 {'data': {'id': 'drive0', 'status': 'paused'}} 871 )]) 872 873 result = self.vm.qmp('query-block-jobs') 874 self.assert_qmp(result, 'return[0]/status', 'paused') 875 self.assert_qmp(result, 'return[0]/offset', self.STREAM_BUFFER_SIZE) 876 self.assert_qmp(result, 'return[0]/io-status', 'nospace') 877 878 result = self.vm.qmp('block-job-resume', device='drive0') 879 self.assert_qmp(result, 'return', {}) 880 881 result = self.vm.qmp('query-block-jobs') 882 if result == {'return': []}: 883 # Race; likely already finished. Check. 884 continue 885 self.assertIn(result['return'][0]['status'], 886 ['running', 'pending', 'aborting', 'concluded']) 887 self.assert_qmp(result, 'return[0]/io-status', 'ok') 888 elif event['event'] == 'BLOCK_JOB_COMPLETED': 889 self.assertTrue(error, 'job completed unexpectedly') 890 self.assert_qmp(event, 'data/type', 'stream') 891 self.assert_qmp(event, 'data/device', 'drive0') 892 self.assert_qmp_absent(event, 'data/error') 893 self.assert_qmp(event, 'data/offset', self.image_len) 894 self.assert_qmp(event, 'data/len', self.image_len) 895 completed = True 896 elif event['event'] == 'JOB_STATUS_CHANGE': 897 self.assert_qmp(event, 'data/id', 'drive0') 898 899 self.assert_no_active_block_jobs() 900 self.vm.shutdown() 901 902class TestStreamStop(iotests.QMPTestCase): 903 image_len = 8 * 1024 * 1024 * 1024 # GB 904 905 def setUp(self): 906 qemu_img('create', backing_img, str(TestStreamStop.image_len)) 907 qemu_io('-f', 'raw', '-c', 'write -P 0x1 0 32M', backing_img) 908 qemu_img('create', '-f', iotests.imgfmt, 909 '-o', 'backing_file=%s' % backing_img, 910 '-F', 'raw', test_img) 911 qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x1 32M 32M', test_img) 912 self.vm = iotests.VM().add_drive("blkdebug::" + test_img) 913 self.vm.launch() 914 915 def tearDown(self): 916 self.vm.shutdown() 917 os.remove(test_img) 918 os.remove(backing_img) 919 920 def test_stream_stop(self): 921 self.assert_no_active_block_jobs() 922 923 self.vm.pause_drive('drive0') 924 result = self.vm.qmp('block-stream', device='drive0') 925 self.assert_qmp(result, 'return', {}) 926 927 time.sleep(0.1) 928 events = self.vm.get_qmp_events(wait=False) 929 for e in events: 930 self.assert_qmp(e, 'event', 'JOB_STATUS_CHANGE') 931 self.assert_qmp(e, 'data/id', 'drive0') 932 933 self.cancel_and_wait(resume=True) 934 935class TestSetSpeed(iotests.QMPTestCase): 936 image_len = 80 * 1024 * 1024 # MB 937 938 def setUp(self): 939 qemu_img('create', backing_img, str(TestSetSpeed.image_len)) 940 qemu_io('-f', 'raw', '-c', 'write -P 0x1 0 32M', backing_img) 941 qemu_img('create', '-f', iotests.imgfmt, 942 '-o', 'backing_file=%s' % backing_img, 943 '-F', 'raw', test_img) 944 qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x1 32M 32M', test_img) 945 self.vm = iotests.VM().add_drive('blkdebug::' + test_img) 946 self.vm.launch() 947 948 def tearDown(self): 949 self.vm.shutdown() 950 os.remove(test_img) 951 os.remove(backing_img) 952 953 # This is a short performance test which is not run by default. 954 # Invoke "IMGFMT=qed ./030 TestSetSpeed.perf_test_throughput" 955 def perf_test_throughput(self): 956 self.assert_no_active_block_jobs() 957 958 result = self.vm.qmp('block-stream', device='drive0') 959 self.assert_qmp(result, 'return', {}) 960 961 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024) 962 self.assert_qmp(result, 'return', {}) 963 964 self.wait_until_completed() 965 966 self.assert_no_active_block_jobs() 967 968 def test_set_speed(self): 969 self.assert_no_active_block_jobs() 970 971 self.vm.pause_drive('drive0') 972 result = self.vm.qmp('block-stream', device='drive0') 973 self.assert_qmp(result, 'return', {}) 974 975 # Default speed is 0 976 result = self.vm.qmp('query-block-jobs') 977 self.assert_qmp(result, 'return[0]/device', 'drive0') 978 self.assert_qmp(result, 'return[0]/speed', 0) 979 980 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024) 981 self.assert_qmp(result, 'return', {}) 982 983 # Ensure the speed we set was accepted 984 result = self.vm.qmp('query-block-jobs') 985 self.assert_qmp(result, 'return[0]/device', 'drive0') 986 self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024) 987 988 self.cancel_and_wait(resume=True) 989 self.vm.pause_drive('drive0') 990 991 # Check setting speed in block-stream works 992 result = self.vm.qmp('block-stream', device='drive0', speed=4 * 1024 * 1024) 993 self.assert_qmp(result, 'return', {}) 994 995 result = self.vm.qmp('query-block-jobs') 996 self.assert_qmp(result, 'return[0]/device', 'drive0') 997 self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024) 998 999 self.cancel_and_wait(resume=True) 1000 1001 def test_set_speed_invalid(self): 1002 self.assert_no_active_block_jobs() 1003 1004 result = self.vm.qmp('block-stream', device='drive0', speed=-1) 1005 self.assert_qmp(result, 'error/desc', "Parameter 'speed' expects a non-negative value") 1006 1007 self.assert_no_active_block_jobs() 1008 1009 self.vm.pause_drive('drive0') 1010 result = self.vm.qmp('block-stream', device='drive0') 1011 self.assert_qmp(result, 'return', {}) 1012 1013 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1) 1014 self.assert_qmp(result, 'error/desc', "Parameter 'speed' expects a non-negative value") 1015 1016 self.cancel_and_wait(resume=True) 1017 1018if __name__ == '__main__': 1019 iotests.main(supported_fmts=['qcow2', 'qed'], 1020 supported_protocols=['file']) 1021