1#!/usr/bin/env python 2# 3# Tests for image block commit. 4# 5# Copyright (C) 2012 IBM, Corp. 6# Copyright (C) 2012 Red Hat, Inc. 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# Test for live block commit 22# Derived from Image Streaming Test 030 23 24import time 25import os 26import iotests 27from iotests import qemu_img, qemu_io 28import struct 29import errno 30 31backing_img = os.path.join(iotests.test_dir, 'backing.img') 32mid_img = os.path.join(iotests.test_dir, 'mid.img') 33test_img = os.path.join(iotests.test_dir, 'test.img') 34 35class ImageCommitTestCase(iotests.QMPTestCase): 36 '''Abstract base class for image commit test cases''' 37 38 def wait_for_complete(self, need_ready=False): 39 completed = False 40 ready = False 41 while not completed: 42 for event in self.vm.get_qmp_events(wait=True): 43 if event['event'] == 'BLOCK_JOB_COMPLETED': 44 self.assert_qmp_absent(event, 'data/error') 45 self.assert_qmp(event, 'data/type', 'commit') 46 self.assert_qmp(event, 'data/device', 'drive0') 47 self.assert_qmp(event, 'data/offset', event['data']['len']) 48 if need_ready: 49 self.assertTrue(ready, "Expecting BLOCK_JOB_COMPLETED event") 50 completed = True 51 elif event['event'] == 'BLOCK_JOB_READY': 52 ready = True 53 self.assert_qmp(event, 'data/type', 'commit') 54 self.assert_qmp(event, 'data/device', 'drive0') 55 self.vm.qmp('block-job-complete', device='drive0') 56 57 self.assert_no_active_block_jobs() 58 self.vm.shutdown() 59 60 def run_commit_test(self, top, base, need_ready=False, node_names=False): 61 self.assert_no_active_block_jobs() 62 if node_names: 63 result = self.vm.qmp('block-commit', device='drive0', top_node=top, base_node=base) 64 else: 65 result = self.vm.qmp('block-commit', device='drive0', top=top, base=base) 66 self.assert_qmp(result, 'return', {}) 67 self.wait_for_complete(need_ready) 68 69 def run_default_commit_test(self): 70 self.assert_no_active_block_jobs() 71 result = self.vm.qmp('block-commit', device='drive0') 72 self.assert_qmp(result, 'return', {}) 73 self.wait_for_complete() 74 75class TestSingleDrive(ImageCommitTestCase): 76 # Need some space after the copied data so that throttling is effective in 77 # tests that use it rather than just completing the job immediately 78 image_len = 2 * 1024 * 1024 79 test_len = 1 * 1024 * 256 80 81 def setUp(self): 82 iotests.create_image(backing_img, self.image_len) 83 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, mid_img) 84 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % mid_img, test_img) 85 qemu_io('-f', 'raw', '-c', 'write -P 0xab 0 524288', backing_img) 86 qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0xef 524288 524288', mid_img) 87 self.vm = iotests.VM().add_drive(test_img, "node-name=top,backing.node-name=mid,backing.backing.node-name=base", interface="none") 88 if iotests.qemu_default_machine == 's390-ccw-virtio': 89 self.vm.add_device("virtio-scsi-ccw") 90 else: 91 self.vm.add_device("virtio-scsi-pci") 92 93 self.vm.add_device("scsi-hd,id=scsi0,drive=drive0") 94 self.vm.launch() 95 self.has_quit = False 96 97 def tearDown(self): 98 self.vm.shutdown(has_quit=self.has_quit) 99 os.remove(test_img) 100 os.remove(mid_img) 101 os.remove(backing_img) 102 103 def test_commit(self): 104 self.run_commit_test(mid_img, backing_img) 105 self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', backing_img).find("verification failed")) 106 self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', backing_img).find("verification failed")) 107 108 def test_commit_node(self): 109 self.run_commit_test("mid", "base", node_names=True) 110 self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', backing_img).find("verification failed")) 111 self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', backing_img).find("verification failed")) 112 113 def test_commit_with_filter_and_quit(self): 114 result = self.vm.qmp('object-add', qom_type='throttle-group', id='tg') 115 self.assert_qmp(result, 'return', {}) 116 117 # Add a filter outside of the backing chain 118 result = self.vm.qmp('blockdev-add', driver='throttle', node_name='filter', throttle_group='tg', file='mid') 119 self.assert_qmp(result, 'return', {}) 120 121 result = self.vm.qmp('block-commit', device='drive0') 122 self.assert_qmp(result, 'return', {}) 123 124 # Quit immediately, thus forcing a simultaneous cancel of the 125 # block job and a bdrv_drain_all() 126 result = self.vm.qmp('quit') 127 self.assert_qmp(result, 'return', {}) 128 129 self.has_quit = True 130 131 # Same as above, but this time we add the filter after starting the job 132 def test_commit_plus_filter_and_quit(self): 133 result = self.vm.qmp('object-add', qom_type='throttle-group', id='tg') 134 self.assert_qmp(result, 'return', {}) 135 136 result = self.vm.qmp('block-commit', device='drive0') 137 self.assert_qmp(result, 'return', {}) 138 139 # Add a filter outside of the backing chain 140 result = self.vm.qmp('blockdev-add', driver='throttle', node_name='filter', throttle_group='tg', file='mid') 141 self.assert_qmp(result, 'return', {}) 142 143 # Quit immediately, thus forcing a simultaneous cancel of the 144 # block job and a bdrv_drain_all() 145 result = self.vm.qmp('quit') 146 self.assert_qmp(result, 'return', {}) 147 148 self.has_quit = True 149 150 def test_device_not_found(self): 151 result = self.vm.qmp('block-commit', device='nonexistent', top='%s' % mid_img) 152 self.assert_qmp(result, 'error/class', 'DeviceNotFound') 153 154 def test_top_same_base(self): 155 self.assert_no_active_block_jobs() 156 result = self.vm.qmp('block-commit', device='drive0', top='%s' % backing_img, base='%s' % backing_img) 157 self.assert_qmp(result, 'error/class', 'GenericError') 158 self.assert_qmp(result, 'error/desc', 'Base \'%s\' not found' % backing_img) 159 160 def test_top_invalid(self): 161 self.assert_no_active_block_jobs() 162 result = self.vm.qmp('block-commit', device='drive0', top='badfile', base='%s' % backing_img) 163 self.assert_qmp(result, 'error/class', 'GenericError') 164 self.assert_qmp(result, 'error/desc', 'Top image file badfile not found') 165 166 def test_base_invalid(self): 167 self.assert_no_active_block_jobs() 168 result = self.vm.qmp('block-commit', device='drive0', top='%s' % mid_img, base='badfile') 169 self.assert_qmp(result, 'error/class', 'GenericError') 170 self.assert_qmp(result, 'error/desc', 'Base \'badfile\' not found') 171 172 def test_top_node_invalid(self): 173 self.assert_no_active_block_jobs() 174 result = self.vm.qmp('block-commit', device='drive0', top_node='badfile', base_node='base') 175 self.assert_qmp(result, 'error/class', 'GenericError') 176 self.assert_qmp(result, 'error/desc', "Cannot find device= nor node_name=badfile") 177 178 def test_base_node_invalid(self): 179 self.assert_no_active_block_jobs() 180 result = self.vm.qmp('block-commit', device='drive0', top_node='mid', base_node='badfile') 181 self.assert_qmp(result, 'error/class', 'GenericError') 182 self.assert_qmp(result, 'error/desc', "Cannot find device= nor node_name=badfile") 183 184 def test_top_path_and_node(self): 185 self.assert_no_active_block_jobs() 186 result = self.vm.qmp('block-commit', device='drive0', top_node='mid', base_node='base', top='%s' % mid_img) 187 self.assert_qmp(result, 'error/class', 'GenericError') 188 self.assert_qmp(result, 'error/desc', "'top-node' and 'top' are mutually exclusive") 189 190 def test_base_path_and_node(self): 191 self.assert_no_active_block_jobs() 192 result = self.vm.qmp('block-commit', device='drive0', top_node='mid', base_node='base', base='%s' % backing_img) 193 self.assert_qmp(result, 'error/class', 'GenericError') 194 self.assert_qmp(result, 'error/desc', "'base-node' and 'base' are mutually exclusive") 195 196 def test_top_is_active(self): 197 self.run_commit_test(test_img, backing_img, need_ready=True) 198 self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', backing_img).find("verification failed")) 199 self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', backing_img).find("verification failed")) 200 201 def test_top_is_default_active(self): 202 self.run_default_commit_test() 203 self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', backing_img).find("verification failed")) 204 self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', backing_img).find("verification failed")) 205 206 def test_top_and_base_reversed(self): 207 self.assert_no_active_block_jobs() 208 result = self.vm.qmp('block-commit', device='drive0', top='%s' % backing_img, base='%s' % mid_img) 209 self.assert_qmp(result, 'error/class', 'GenericError') 210 self.assert_qmp(result, 'error/desc', 'Base \'%s\' not found' % mid_img) 211 212 def test_top_and_base_node_reversed(self): 213 self.assert_no_active_block_jobs() 214 result = self.vm.qmp('block-commit', device='drive0', top_node='base', base_node='top') 215 self.assert_qmp(result, 'error/class', 'GenericError') 216 self.assert_qmp(result, 'error/desc', "'top' is not in this backing file chain") 217 218 def test_top_node_in_wrong_chain(self): 219 self.assert_no_active_block_jobs() 220 221 result = self.vm.qmp('blockdev-add', driver='null-co', node_name='null') 222 self.assert_qmp(result, 'return', {}) 223 224 result = self.vm.qmp('block-commit', device='drive0', top_node='null', base_node='base') 225 self.assert_qmp(result, 'error/class', 'GenericError') 226 self.assert_qmp(result, 'error/desc', "'null' is not in this backing file chain") 227 228 # When the job is running on a BB that is automatically deleted on hot 229 # unplug, the job is cancelled when the device disappears 230 def test_hot_unplug(self): 231 if self.image_len == 0: 232 return 233 234 self.assert_no_active_block_jobs() 235 result = self.vm.qmp('block-commit', device='drive0', top=mid_img, 236 base=backing_img, speed=(self.image_len // 4)) 237 self.assert_qmp(result, 'return', {}) 238 result = self.vm.qmp('device_del', id='scsi0') 239 self.assert_qmp(result, 'return', {}) 240 241 cancelled = False 242 deleted = False 243 while not cancelled or not deleted: 244 for event in self.vm.get_qmp_events(wait=True): 245 if event['event'] == 'DEVICE_DELETED': 246 self.assert_qmp(event, 'data/device', 'scsi0') 247 deleted = True 248 elif event['event'] == 'BLOCK_JOB_CANCELLED': 249 self.assert_qmp(event, 'data/device', 'drive0') 250 cancelled = True 251 elif event['event'] == 'JOB_STATUS_CHANGE': 252 self.assert_qmp(event, 'data/id', 'drive0') 253 else: 254 self.fail("Unexpected event %s" % (event['event'])) 255 256 self.assert_no_active_block_jobs() 257 258 # Tests that the insertion of the commit_top filter node doesn't make a 259 # difference to query-blockstat 260 def test_implicit_node(self): 261 if self.image_len == 0: 262 return 263 264 self.assert_no_active_block_jobs() 265 result = self.vm.qmp('block-commit', device='drive0', top=mid_img, 266 base=backing_img, speed=(self.image_len // 4)) 267 self.assert_qmp(result, 'return', {}) 268 269 result = self.vm.qmp('query-block') 270 self.assert_qmp(result, 'return[0]/inserted/file', test_img) 271 self.assert_qmp(result, 'return[0]/inserted/drv', iotests.imgfmt) 272 self.assert_qmp(result, 'return[0]/inserted/backing_file', mid_img) 273 self.assert_qmp(result, 'return[0]/inserted/backing_file_depth', 2) 274 self.assert_qmp(result, 'return[0]/inserted/image/filename', test_img) 275 self.assert_qmp(result, 'return[0]/inserted/image/backing-image/filename', mid_img) 276 self.assert_qmp(result, 'return[0]/inserted/image/backing-image/backing-image/filename', backing_img) 277 278 result = self.vm.qmp('query-blockstats') 279 self.assert_qmp(result, 'return[0]/node-name', 'top') 280 self.assert_qmp(result, 'return[0]/backing/node-name', 'mid') 281 self.assert_qmp(result, 'return[0]/backing/backing/node-name', 'base') 282 283 self.cancel_and_wait() 284 self.assert_no_active_block_jobs() 285 286class TestRelativePaths(ImageCommitTestCase): 287 image_len = 1 * 1024 * 1024 288 test_len = 1 * 1024 * 256 289 290 dir1 = "dir1" 291 dir2 = "dir2/" 292 dir3 = "dir2/dir3/" 293 294 test_img = os.path.join(iotests.test_dir, dir3, 'test.img') 295 mid_img = "../mid.img" 296 backing_img = "../dir1/backing.img" 297 298 backing_img_abs = os.path.join(iotests.test_dir, dir1, 'backing.img') 299 mid_img_abs = os.path.join(iotests.test_dir, dir2, 'mid.img') 300 301 def setUp(self): 302 try: 303 os.mkdir(os.path.join(iotests.test_dir, self.dir1)) 304 os.mkdir(os.path.join(iotests.test_dir, self.dir2)) 305 os.mkdir(os.path.join(iotests.test_dir, self.dir3)) 306 except OSError as exception: 307 if exception.errno != errno.EEXIST: 308 raise 309 iotests.create_image(self.backing_img_abs, TestRelativePaths.image_len) 310 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % self.backing_img_abs, self.mid_img_abs) 311 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % self.mid_img_abs, self.test_img) 312 qemu_img('rebase', '-u', '-b', self.backing_img, self.mid_img_abs) 313 qemu_img('rebase', '-u', '-b', self.mid_img, self.test_img) 314 qemu_io('-f', 'raw', '-c', 'write -P 0xab 0 524288', self.backing_img_abs) 315 qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0xef 524288 524288', self.mid_img_abs) 316 self.vm = iotests.VM().add_drive(self.test_img) 317 self.vm.launch() 318 319 def tearDown(self): 320 self.vm.shutdown() 321 os.remove(self.test_img) 322 os.remove(self.mid_img_abs) 323 os.remove(self.backing_img_abs) 324 try: 325 os.rmdir(os.path.join(iotests.test_dir, self.dir1)) 326 os.rmdir(os.path.join(iotests.test_dir, self.dir3)) 327 os.rmdir(os.path.join(iotests.test_dir, self.dir2)) 328 except OSError as exception: 329 if exception.errno != errno.EEXIST and exception.errno != errno.ENOTEMPTY: 330 raise 331 332 def test_commit(self): 333 self.run_commit_test(self.mid_img, self.backing_img) 334 self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', self.backing_img_abs).find("verification failed")) 335 self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', self.backing_img_abs).find("verification failed")) 336 337 def test_device_not_found(self): 338 result = self.vm.qmp('block-commit', device='nonexistent', top='%s' % self.mid_img) 339 self.assert_qmp(result, 'error/class', 'DeviceNotFound') 340 341 def test_top_same_base(self): 342 self.assert_no_active_block_jobs() 343 result = self.vm.qmp('block-commit', device='drive0', top='%s' % self.mid_img, base='%s' % self.mid_img) 344 self.assert_qmp(result, 'error/class', 'GenericError') 345 self.assert_qmp(result, 'error/desc', 'Base \'%s\' not found' % self.mid_img) 346 347 def test_top_invalid(self): 348 self.assert_no_active_block_jobs() 349 result = self.vm.qmp('block-commit', device='drive0', top='badfile', base='%s' % self.backing_img) 350 self.assert_qmp(result, 'error/class', 'GenericError') 351 self.assert_qmp(result, 'error/desc', 'Top image file badfile not found') 352 353 def test_base_invalid(self): 354 self.assert_no_active_block_jobs() 355 result = self.vm.qmp('block-commit', device='drive0', top='%s' % self.mid_img, base='badfile') 356 self.assert_qmp(result, 'error/class', 'GenericError') 357 self.assert_qmp(result, 'error/desc', 'Base \'badfile\' not found') 358 359 def test_top_is_active(self): 360 self.run_commit_test(self.test_img, self.backing_img) 361 self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xab 0 524288', self.backing_img_abs).find("verification failed")) 362 self.assertEqual(-1, qemu_io('-f', 'raw', '-c', 'read -P 0xef 524288 524288', self.backing_img_abs).find("verification failed")) 363 364 def test_top_and_base_reversed(self): 365 self.assert_no_active_block_jobs() 366 result = self.vm.qmp('block-commit', device='drive0', top='%s' % self.backing_img, base='%s' % self.mid_img) 367 self.assert_qmp(result, 'error/class', 'GenericError') 368 self.assert_qmp(result, 'error/desc', 'Base \'%s\' not found' % self.mid_img) 369 370 371class TestSetSpeed(ImageCommitTestCase): 372 image_len = 80 * 1024 * 1024 # MB 373 374 def setUp(self): 375 qemu_img('create', backing_img, str(TestSetSpeed.image_len)) 376 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, mid_img) 377 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % mid_img, test_img) 378 qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x1 0 512', test_img) 379 qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0xef 524288 524288', mid_img) 380 self.vm = iotests.VM().add_drive('blkdebug::' + test_img) 381 self.vm.launch() 382 383 def tearDown(self): 384 self.vm.shutdown() 385 os.remove(test_img) 386 os.remove(mid_img) 387 os.remove(backing_img) 388 389 def test_set_speed(self): 390 self.assert_no_active_block_jobs() 391 392 self.vm.pause_drive('drive0') 393 result = self.vm.qmp('block-commit', device='drive0', top=mid_img, speed=1024 * 1024) 394 self.assert_qmp(result, 'return', {}) 395 396 # Ensure the speed we set was accepted 397 result = self.vm.qmp('query-block-jobs') 398 self.assert_qmp(result, 'return[0]/device', 'drive0') 399 self.assert_qmp(result, 'return[0]/speed', 1024 * 1024) 400 401 self.cancel_and_wait(resume=True) 402 403class TestActiveZeroLengthImage(TestSingleDrive): 404 image_len = 0 405 406class TestReopenOverlay(ImageCommitTestCase): 407 image_len = 1024 * 1024 408 img0 = os.path.join(iotests.test_dir, '0.img') 409 img1 = os.path.join(iotests.test_dir, '1.img') 410 img2 = os.path.join(iotests.test_dir, '2.img') 411 img3 = os.path.join(iotests.test_dir, '3.img') 412 413 def setUp(self): 414 iotests.create_image(self.img0, self.image_len) 415 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % self.img0, self.img1) 416 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % self.img1, self.img2) 417 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % self.img2, self.img3) 418 qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0xab 0 128K', self.img1) 419 self.vm = iotests.VM().add_drive(self.img3) 420 self.vm.launch() 421 422 def tearDown(self): 423 self.vm.shutdown() 424 os.remove(self.img0) 425 os.remove(self.img1) 426 os.remove(self.img2) 427 os.remove(self.img3) 428 429 # This tests what happens when the overlay image of the 'top' node 430 # needs to be reopened in read-write mode in order to update the 431 # backing image string. 432 def test_reopen_overlay(self): 433 self.run_commit_test(self.img1, self.img0) 434 435if __name__ == '__main__': 436 iotests.main(supported_fmts=['qcow2', 'qed']) 437