1#!/usr/bin/env python3 2# group: rw 3# 4# Test cases for the QMP 'blockdev-reopen' command 5# 6# Copyright (C) 2018-2019 Igalia, S.L. 7# Author: Alberto Garcia <berto@igalia.com> 8# 9# This program is free software; you can redistribute it and/or modify 10# it under the terms of the GNU General Public License as published by 11# the Free Software Foundation; either version 2 of the License, or 12# (at your option) any later version. 13# 14# This program is distributed in the hope that it will be useful, 15# but WITHOUT ANY WARRANTY; without even the implied warranty of 16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17# GNU General Public License for more details. 18# 19# You should have received a copy of the GNU General Public License 20# along with this program. If not, see <http://www.gnu.org/licenses/>. 21# 22 23import copy 24import json 25import os 26import re 27from subprocess import CalledProcessError 28 29import iotests 30from iotests import qemu_img, qemu_io 31 32hd_path = [ 33 os.path.join(iotests.test_dir, 'hd0.img'), 34 os.path.join(iotests.test_dir, 'hd1.img'), 35 os.path.join(iotests.test_dir, 'hd2.img') 36] 37 38def hd_opts(idx): 39 return {'driver': iotests.imgfmt, 40 'node-name': 'hd%d' % idx, 41 'file': {'driver': 'file', 42 'node-name': 'hd%d-file' % idx, 43 'filename': hd_path[idx] } } 44 45class TestBlockdevReopen(iotests.QMPTestCase): 46 total_io_cmds = 0 47 48 def setUp(self): 49 qemu_img('create', '-f', iotests.imgfmt, hd_path[0], '3M') 50 qemu_img('create', '-f', iotests.imgfmt, '-b', hd_path[0], 51 '-F', iotests.imgfmt, hd_path[1]) 52 qemu_img('create', '-f', iotests.imgfmt, hd_path[2], '3M') 53 qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0xa0 0 1M', hd_path[0]) 54 qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0xa1 1M 1M', hd_path[1]) 55 qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0xa2 2M 1M', hd_path[2]) 56 self.vm = iotests.VM() 57 self.vm.launch() 58 59 def tearDown(self): 60 self.vm.shutdown() 61 self.check_qemu_io_errors() 62 os.remove(hd_path[0]) 63 os.remove(hd_path[1]) 64 os.remove(hd_path[2]) 65 66 # The output of qemu-io is not returned by vm.hmp_qemu_io() but 67 # it's stored in the log and can only be read when the VM has been 68 # shut down. This function runs qemu-io and keeps track of the 69 # number of times it's been called. 70 def run_qemu_io(self, img, cmd): 71 result = self.vm.hmp_qemu_io(img, cmd) 72 self.assert_qmp(result, 'return', '') 73 self.total_io_cmds += 1 74 75 # Once the VM is shut down we can parse the log and see if qemu-io 76 # ran without errors. 77 def check_qemu_io_errors(self): 78 self.assertFalse(self.vm.is_running()) 79 found = 0 80 log = self.vm.get_log() 81 for line in log.split("\n"): 82 if line.startswith("Pattern verification failed"): 83 raise Exception("%s (command #%d)" % (line, found)) 84 if re.match("(read|wrote) .*/.* bytes at offset", line): 85 found += 1 86 self.assertEqual(found, self.total_io_cmds, 87 "Expected output of %d qemu-io commands, found %d" % 88 (found, self.total_io_cmds)) 89 90 # Run blockdev-reopen on a list of block devices 91 def reopenMultiple(self, opts, errmsg = None): 92 result = self.vm.qmp('blockdev-reopen', conv_keys=False, options=opts) 93 if errmsg: 94 self.assert_qmp(result, 'error/class', 'GenericError') 95 self.assert_qmp(result, 'error/desc', errmsg) 96 else: 97 self.assert_qmp(result, 'return', {}) 98 99 # Run blockdev-reopen on a single block device (specified by 100 # 'opts') but applying 'newopts' on top of it. The original 'opts' 101 # dict is unmodified 102 def reopen(self, opts, newopts = {}, errmsg = None): 103 opts = copy.deepcopy(opts) 104 105 # Apply the changes from 'newopts' on top of 'opts' 106 for key in newopts: 107 value = newopts[key] 108 # If key has the form "foo.bar" then we need to do 109 # opts["foo"]["bar"] = value, not opts["foo.bar"] = value 110 subdict = opts 111 while key.find('.') != -1: 112 [prefix, key] = key.split('.', 1) 113 subdict = opts[prefix] 114 subdict[key] = value 115 116 self.reopenMultiple([ opts ], errmsg) 117 118 119 # Run query-named-block-nodes and return the specified entry 120 def get_node(self, node_name): 121 result = self.vm.qmp('query-named-block-nodes') 122 for node in result['return']: 123 if node['node-name'] == node_name: 124 return node 125 return None 126 127 # Run 'query-named-block-nodes' and compare its output with the 128 # value passed by the user in 'graph' 129 def check_node_graph(self, graph): 130 result = self.vm.qmp('query-named-block-nodes') 131 self.assertEqual(json.dumps(graph, sort_keys=True), 132 json.dumps(result, sort_keys=True)) 133 134 # This test opens one single disk image (without backing files) 135 # and tries to reopen it with illegal / incorrect parameters. 136 def test_incorrect_parameters_single_file(self): 137 # Open 'hd0' only (no backing files) 138 opts = hd_opts(0) 139 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 140 self.assert_qmp(result, 'return', {}) 141 original_graph = self.vm.qmp('query-named-block-nodes') 142 143 # We can reopen the image passing the same options 144 self.reopen(opts) 145 146 # We can also reopen passing a child reference in 'file' 147 self.reopen(opts, {'file': 'hd0-file'}) 148 149 # We cannot change any of these 150 self.reopen(opts, {'node-name': 'not-found'}, "Failed to find node with node-name='not-found'") 151 self.reopen(opts, {'node-name': ''}, "Failed to find node with node-name=''") 152 self.reopen(opts, {'node-name': None}, "Invalid parameter type for 'options[0].node-name', expected: string") 153 self.reopen(opts, {'driver': 'raw'}, "Cannot change the option 'driver'") 154 self.reopen(opts, {'driver': ''}, "Parameter 'driver' does not accept value ''") 155 self.reopen(opts, {'driver': None}, "Invalid parameter type for 'options[0].driver', expected: string") 156 self.reopen(opts, {'file': 'not-found'}, "Cannot find device='' nor node-name='not-found'") 157 self.reopen(opts, {'file': ''}, "Cannot find device='' nor node-name=''") 158 self.reopen(opts, {'file': None}, "Invalid parameter type for 'file', expected: BlockdevRef") 159 self.reopen(opts, {'file.node-name': 'newname'}, "Cannot change the option 'node-name'") 160 self.reopen(opts, {'file.driver': 'host_device'}, "Cannot change the option 'driver'") 161 self.reopen(opts, {'file.filename': hd_path[1]}, "Cannot change the option 'filename'") 162 self.reopen(opts, {'file.aio': 'native'}, "Cannot change the option 'aio'") 163 self.reopen(opts, {'file.locking': 'off'}, "Cannot change the option 'locking'") 164 self.reopen(opts, {'file.filename': None}, "Invalid parameter type for 'options[0].file.filename', expected: string") 165 166 # node-name is optional in BlockdevOptions, but blockdev-reopen needs it 167 del opts['node-name'] 168 self.reopen(opts, {}, "node-name not specified") 169 170 # Check that nothing has changed 171 self.check_node_graph(original_graph) 172 173 # Remove the node 174 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd0') 175 self.assert_qmp(result, 'return', {}) 176 177 # This test opens an image with a backing file and tries to reopen 178 # it with illegal / incorrect parameters. 179 def test_incorrect_parameters_backing_file(self): 180 # Open hd1 omitting the backing options (hd0 will be opened 181 # with the default options) 182 opts = hd_opts(1) 183 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 184 self.assert_qmp(result, 'return', {}) 185 original_graph = self.vm.qmp('query-named-block-nodes') 186 187 # We can't reopen the image passing the same options, 'backing' is mandatory 188 self.reopen(opts, {}, "backing is missing for 'hd1'") 189 190 # Everything works if we pass 'backing' using the existing node name 191 for node in original_graph['return']: 192 if node['drv'] == iotests.imgfmt and node['file'] == hd_path[0]: 193 backing_node_name = node['node-name'] 194 self.reopen(opts, {'backing': backing_node_name}) 195 196 # We can't use a non-existing or empty (non-NULL) node as the backing image 197 self.reopen(opts, {'backing': 'not-found'}, "Cannot find device=\'\' nor node-name=\'not-found\'") 198 self.reopen(opts, {'backing': ''}, "Cannot find device=\'\' nor node-name=\'\'") 199 200 # We can reopen the image just fine if we specify the backing options 201 opts['backing'] = {'driver': iotests.imgfmt, 202 'file': {'driver': 'file', 203 'filename': hd_path[0]}} 204 self.reopen(opts) 205 206 # We cannot change any of these options 207 self.reopen(opts, {'backing.node-name': 'newname'}, "Cannot change the option 'node-name'") 208 self.reopen(opts, {'backing.driver': 'raw'}, "Cannot change the option 'driver'") 209 self.reopen(opts, {'backing.file.node-name': 'newname'}, "Cannot change the option 'node-name'") 210 self.reopen(opts, {'backing.file.driver': 'host_device'}, "Cannot change the option 'driver'") 211 212 # Check that nothing has changed since the beginning 213 self.check_node_graph(original_graph) 214 215 # Remove the node 216 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd1') 217 self.assert_qmp(result, 'return', {}) 218 219 # Reopen an image several times changing some of its options 220 def test_reopen(self): 221 try: 222 qemu_io('-f', 'raw', '-t', 'none', '-c', 'quit', hd_path[0]) 223 supports_direct = True 224 except CalledProcessError as exc: 225 if 'O_DIRECT' in exc.stdout: 226 supports_direct = False 227 else: 228 raise 229 230 # Open the hd1 image passing all backing options 231 opts = hd_opts(1) 232 opts['backing'] = hd_opts(0) 233 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 234 self.assert_qmp(result, 'return', {}) 235 original_graph = self.vm.qmp('query-named-block-nodes') 236 237 # We can reopen the image passing the same options 238 self.reopen(opts) 239 240 # Reopen in read-only mode 241 self.assert_qmp(self.get_node('hd1'), 'ro', False) 242 243 self.reopen(opts, {'read-only': True}) 244 self.assert_qmp(self.get_node('hd1'), 'ro', True) 245 self.reopen(opts) 246 self.assert_qmp(self.get_node('hd1'), 'ro', False) 247 248 # Change the cache options 249 self.assert_qmp(self.get_node('hd1'), 'cache/writeback', True) 250 self.assert_qmp(self.get_node('hd1'), 'cache/direct', False) 251 self.assert_qmp(self.get_node('hd1'), 'cache/no-flush', False) 252 self.reopen(opts, {'cache': { 'direct': supports_direct, 'no-flush': True }}) 253 self.assert_qmp(self.get_node('hd1'), 'cache/writeback', True) 254 self.assert_qmp(self.get_node('hd1'), 'cache/direct', supports_direct) 255 self.assert_qmp(self.get_node('hd1'), 'cache/no-flush', True) 256 257 # Reopen again with the original options 258 self.reopen(opts) 259 self.assert_qmp(self.get_node('hd1'), 'cache/writeback', True) 260 self.assert_qmp(self.get_node('hd1'), 'cache/direct', False) 261 self.assert_qmp(self.get_node('hd1'), 'cache/no-flush', False) 262 263 # Change 'detect-zeroes' and 'discard' 264 self.assert_qmp(self.get_node('hd1'), 'detect_zeroes', 'off') 265 self.reopen(opts, {'detect-zeroes': 'on'}) 266 self.assert_qmp(self.get_node('hd1'), 'detect_zeroes', 'on') 267 self.reopen(opts, {'detect-zeroes': 'unmap'}, 268 "setting detect-zeroes to unmap is not allowed " + 269 "without setting discard operation to unmap") 270 self.assert_qmp(self.get_node('hd1'), 'detect_zeroes', 'on') 271 self.reopen(opts, {'detect-zeroes': 'unmap', 'discard': 'unmap'}) 272 self.assert_qmp(self.get_node('hd1'), 'detect_zeroes', 'unmap') 273 self.reopen(opts) 274 self.assert_qmp(self.get_node('hd1'), 'detect_zeroes', 'off') 275 276 # Changing 'force-share' is currently not supported 277 self.reopen(opts, {'force-share': True}, "Cannot change the option 'force-share'") 278 279 # Change some qcow2-specific options 280 # No way to test for success other than checking the return message 281 if iotests.imgfmt == 'qcow2': 282 self.reopen(opts, {'l2-cache-entry-size': 128 * 1024}, 283 "L2 cache entry size must be a power of two "+ 284 "between 512 and the cluster size (65536)") 285 self.reopen(opts, {'l2-cache-size': 1024 * 1024, 286 'cache-size': 512 * 1024}, 287 "l2-cache-size may not exceed cache-size") 288 self.reopen(opts, {'l2-cache-size': 4 * 1024 * 1024, 289 'refcount-cache-size': 4 * 1024 * 1024, 290 'l2-cache-entry-size': 32 * 1024}) 291 self.reopen(opts, {'pass-discard-request': True}) 292 293 # Check that nothing has changed since the beginning 294 # (from the parameters that we can check) 295 self.check_node_graph(original_graph) 296 297 # Check that the node names (other than the top-level one) are optional 298 del opts['file']['node-name'] 299 del opts['backing']['node-name'] 300 del opts['backing']['file']['node-name'] 301 self.reopen(opts) 302 self.check_node_graph(original_graph) 303 304 # Reopen setting backing = null, this removes the backing image from the chain 305 self.reopen(opts, {'backing': None}) 306 self.assert_qmp_absent(self.get_node('hd1'), 'image/backing-image') 307 308 # Open the 'hd0' image 309 result = self.vm.qmp('blockdev-add', conv_keys = False, **hd_opts(0)) 310 self.assert_qmp(result, 'return', {}) 311 312 # Reopen the hd1 image setting 'hd0' as its backing image 313 self.reopen(opts, {'backing': 'hd0'}) 314 self.assert_qmp(self.get_node('hd1'), 'image/backing-image/filename', hd_path[0]) 315 316 # Check that nothing has changed since the beginning 317 self.reopen(hd_opts(0), {'read-only': True}) 318 self.check_node_graph(original_graph) 319 320 # The backing file (hd0) is now a reference, we cannot change backing.* anymore 321 self.reopen(opts, {}, "Cannot change the option 'backing.driver'") 322 323 # We can't remove 'hd0' while it's a backing image of 'hd1' 324 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd0') 325 self.assert_qmp(result, 'error/class', 'GenericError') 326 self.assert_qmp(result, 'error/desc', "Node 'hd0' is busy: node is used as backing hd of 'hd1'") 327 328 # But we can remove both nodes if done in the proper order 329 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd1') 330 self.assert_qmp(result, 'return', {}) 331 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd0') 332 self.assert_qmp(result, 'return', {}) 333 334 # Reopen a raw image and see the effect of changing the 'offset' option 335 def test_reopen_raw(self): 336 opts = {'driver': 'raw', 'node-name': 'hd0', 337 'file': { 'driver': 'file', 338 'filename': hd_path[0], 339 'node-name': 'hd0-file' } } 340 341 # First we create a 2MB raw file, and fill each half with a 342 # different value 343 qemu_img('create', '-f', 'raw', hd_path[0], '2M') 344 qemu_io('-f', 'raw', '-c', 'write -P 0xa0 0 1M', hd_path[0]) 345 qemu_io('-f', 'raw', '-c', 'write -P 0xa1 1M 1M', hd_path[0]) 346 347 # Open the raw file with QEMU 348 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 349 self.assert_qmp(result, 'return', {}) 350 351 # Read 1MB from offset 0 352 self.run_qemu_io("hd0", "read -P 0xa0 0 1M") 353 354 # Reopen the image with a 1MB offset. 355 # Now the results are different 356 self.reopen(opts, {'offset': 1024*1024}) 357 self.run_qemu_io("hd0", "read -P 0xa1 0 1M") 358 359 # Reopen again with the original options. 360 # We get the original results again 361 self.reopen(opts) 362 self.run_qemu_io("hd0", "read -P 0xa0 0 1M") 363 364 # Remove the block device 365 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd0') 366 self.assert_qmp(result, 'return', {}) 367 368 # Omitting an option should reset it to the default value, but if 369 # an option cannot be changed it shouldn't be possible to reset it 370 # to its default value either 371 def test_reset_default_values(self): 372 opts = {'driver': 'qcow2', 'node-name': 'hd0', 373 'file': { 'driver': 'file', 374 'filename': hd_path[0], 375 'x-check-cache-dropped': True, # This one can be changed 376 'locking': 'off', # This one can NOT be changed 377 'node-name': 'hd0-file' } } 378 379 # Open the file with QEMU 380 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 381 self.assert_qmp(result, 'return', {}) 382 383 # file.x-check-cache-dropped can be changed... 384 self.reopen(opts, { 'file.x-check-cache-dropped': False }) 385 # ...and dropped completely (resetting to the default value) 386 del opts['file']['x-check-cache-dropped'] 387 self.reopen(opts) 388 389 # file.locking cannot be changed nor reset to the default value 390 self.reopen(opts, { 'file.locking': 'on' }, "Cannot change the option 'locking'") 391 del opts['file']['locking'] 392 self.reopen(opts, {}, "Option 'locking' cannot be reset to its default value") 393 # But we can reopen it if we maintain its previous value 394 self.reopen(opts, { 'file.locking': 'off' }) 395 396 # Remove the block device 397 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd0') 398 self.assert_qmp(result, 'return', {}) 399 400 # This test modifies the node graph a few times by changing the 401 # 'backing' option on reopen and verifies that the guest data that 402 # is read afterwards is consistent with the graph changes. 403 def test_io_with_graph_changes(self): 404 opts = [] 405 406 # Open hd0, hd1 and hd2 without any backing image 407 for i in range(3): 408 opts.append(hd_opts(i)) 409 opts[i]['backing'] = None 410 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts[i]) 411 self.assert_qmp(result, 'return', {}) 412 413 # hd0 414 self.run_qemu_io("hd0", "read -P 0xa0 0 1M") 415 self.run_qemu_io("hd0", "read -P 0 1M 1M") 416 self.run_qemu_io("hd0", "read -P 0 2M 1M") 417 418 # hd1 <- hd0 419 self.reopen(opts[0], {'backing': 'hd1'}) 420 421 self.run_qemu_io("hd0", "read -P 0xa0 0 1M") 422 self.run_qemu_io("hd0", "read -P 0xa1 1M 1M") 423 self.run_qemu_io("hd0", "read -P 0 2M 1M") 424 425 # hd1 <- hd0 , hd1 <- hd2 426 self.reopen(opts[2], {'backing': 'hd1'}) 427 428 self.run_qemu_io("hd2", "read -P 0 0 1M") 429 self.run_qemu_io("hd2", "read -P 0xa1 1M 1M") 430 self.run_qemu_io("hd2", "read -P 0xa2 2M 1M") 431 432 # hd1 <- hd2 <- hd0 433 self.reopen(opts[0], {'backing': 'hd2'}) 434 435 self.run_qemu_io("hd0", "read -P 0xa0 0 1M") 436 self.run_qemu_io("hd0", "read -P 0xa1 1M 1M") 437 self.run_qemu_io("hd0", "read -P 0xa2 2M 1M") 438 439 # hd2 <- hd0 440 self.reopen(opts[2], {'backing': None}) 441 442 self.run_qemu_io("hd0", "read -P 0xa0 0 1M") 443 self.run_qemu_io("hd0", "read -P 0 1M 1M") 444 self.run_qemu_io("hd0", "read -P 0xa2 2M 1M") 445 446 # hd2 <- hd1 <- hd0 447 self.reopen(opts[1], {'backing': 'hd2'}) 448 self.reopen(opts[0], {'backing': 'hd1'}) 449 450 self.run_qemu_io("hd0", "read -P 0xa0 0 1M") 451 self.run_qemu_io("hd0", "read -P 0xa1 1M 1M") 452 self.run_qemu_io("hd0", "read -P 0xa2 2M 1M") 453 454 # Illegal operation: hd2 is a child of hd1 455 self.reopen(opts[2], {'backing': 'hd1'}, 456 "Making 'hd1' a backing child of 'hd2' would create a cycle") 457 458 # hd2 <- hd0, hd2 <- hd1 459 self.reopen(opts[0], {'backing': 'hd2'}) 460 461 self.run_qemu_io("hd1", "read -P 0 0 1M") 462 self.run_qemu_io("hd1", "read -P 0xa1 1M 1M") 463 self.run_qemu_io("hd1", "read -P 0xa2 2M 1M") 464 465 # More illegal operations 466 self.reopen(opts[2], {'backing': 'hd1'}, 467 "Making 'hd1' a backing child of 'hd2' would create a cycle") 468 self.reopen(opts[2], {'file': 'hd0-file'}, 469 "Permission conflict on node 'hd0-file': permissions 'write, resize' are both required by node 'hd0' (uses node 'hd0-file' as 'file' child) and unshared by node 'hd2' (uses node 'hd0-file' as 'file' child).") 470 471 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd2') 472 self.assert_qmp(result, 'error/class', 'GenericError') 473 self.assert_qmp(result, 'error/desc', "Node 'hd2' is busy: node is used as backing hd of 'hd0'") 474 475 # hd1 doesn't have a backing file now 476 self.reopen(opts[1], {'backing': None}) 477 478 self.run_qemu_io("hd1", "read -P 0 0 1M") 479 self.run_qemu_io("hd1", "read -P 0xa1 1M 1M") 480 self.run_qemu_io("hd1", "read -P 0 2M 1M") 481 482 # We can't remove the 'backing' option if the image has a 483 # default backing file 484 del opts[1]['backing'] 485 self.reopen(opts[1], {}, "backing is missing for 'hd1'") 486 487 self.run_qemu_io("hd1", "read -P 0 0 1M") 488 self.run_qemu_io("hd1", "read -P 0xa1 1M 1M") 489 self.run_qemu_io("hd1", "read -P 0 2M 1M") 490 491 # This test verifies that we can't change the children of a block 492 # device during a reopen operation in a way that would create 493 # cycles in the node graph 494 @iotests.skip_if_unsupported(['blkverify']) 495 def test_graph_cycles(self): 496 opts = [] 497 498 # Open all three images without backing file 499 for i in range(3): 500 opts.append(hd_opts(i)) 501 opts[i]['backing'] = None 502 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts[i]) 503 self.assert_qmp(result, 'return', {}) 504 505 # hd1 <- hd0, hd1 <- hd2 506 self.reopen(opts[0], {'backing': 'hd1'}) 507 self.reopen(opts[2], {'backing': 'hd1'}) 508 509 # Illegal: hd2 is backed by hd1 510 self.reopen(opts[1], {'backing': 'hd2'}, 511 "Making 'hd2' a backing child of 'hd1' would create a cycle") 512 513 # hd1 <- hd0 <- hd2 514 self.reopen(opts[2], {'backing': 'hd0'}) 515 516 # Illegal: hd2 is backed by hd0, which is backed by hd1 517 self.reopen(opts[1], {'backing': 'hd2'}, 518 "Making 'hd2' a backing child of 'hd1' would create a cycle") 519 520 # Illegal: hd1 cannot point to itself 521 self.reopen(opts[1], {'backing': 'hd1'}, 522 "Making 'hd1' a backing child of 'hd1' would create a cycle") 523 524 # Remove all backing files 525 self.reopen(opts[0]) 526 self.reopen(opts[2]) 527 528 ########################################## 529 # Add a blkverify node using hd0 and hd1 # 530 ########################################## 531 bvopts = {'driver': 'blkverify', 532 'node-name': 'bv', 533 'test': 'hd0', 534 'raw': 'hd1'} 535 result = self.vm.qmp('blockdev-add', conv_keys = False, **bvopts) 536 self.assert_qmp(result, 'return', {}) 537 538 # blkverify doesn't currently allow reopening. TODO: implement this 539 self.reopen(bvopts, {}, "Block format 'blkverify' used by node 'bv'" + 540 " does not support reopening files") 541 542 # Illegal: hd0 is a child of the blkverify node 543 self.reopen(opts[0], {'backing': 'bv'}, 544 "Making 'bv' a backing child of 'hd0' would create a cycle") 545 546 # Delete the blkverify node 547 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'bv') 548 self.assert_qmp(result, 'return', {}) 549 550 # Replace the protocol layer ('file' parameter) of a disk image 551 def test_replace_file(self): 552 # Create two small raw images and add them to a running VM 553 qemu_img('create', '-f', 'raw', hd_path[0], '10k') 554 qemu_img('create', '-f', 'raw', hd_path[1], '10k') 555 556 hd0_opts = {'driver': 'file', 'node-name': 'hd0-file', 'filename': hd_path[0] } 557 hd1_opts = {'driver': 'file', 'node-name': 'hd1-file', 'filename': hd_path[1] } 558 559 result = self.vm.qmp('blockdev-add', conv_keys = False, **hd0_opts) 560 self.assert_qmp(result, 'return', {}) 561 result = self.vm.qmp('blockdev-add', conv_keys = False, **hd1_opts) 562 self.assert_qmp(result, 'return', {}) 563 564 # Add a raw format layer that uses hd0-file as its protocol layer 565 opts = {'driver': 'raw', 'node-name': 'hd', 'file': 'hd0-file'} 566 567 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 568 self.assert_qmp(result, 'return', {}) 569 570 # Fill the image with data 571 self.run_qemu_io("hd", "read -P 0 0 10k") 572 self.run_qemu_io("hd", "write -P 0xa0 0 10k") 573 574 # Replace hd0-file with hd1-file and fill it with (different) data 575 self.reopen(opts, {'file': 'hd1-file'}) 576 self.run_qemu_io("hd", "read -P 0 0 10k") 577 self.run_qemu_io("hd", "write -P 0xa1 0 10k") 578 579 # Use hd0-file again and check that it contains the expected data 580 self.reopen(opts, {'file': 'hd0-file'}) 581 self.run_qemu_io("hd", "read -P 0xa0 0 10k") 582 583 # And finally do the same with hd1-file 584 self.reopen(opts, {'file': 'hd1-file'}) 585 self.run_qemu_io("hd", "read -P 0xa1 0 10k") 586 587 # Insert (and remove) a throttle filter 588 def test_insert_throttle_filter(self): 589 # Add an image to the VM 590 hd0_opts = hd_opts(0) 591 result = self.vm.qmp('blockdev-add', conv_keys = False, **hd0_opts) 592 self.assert_qmp(result, 'return', {}) 593 594 # Create a throttle-group object 595 opts = { 'qom-type': 'throttle-group', 'id': 'group0', 596 'limits': { 'iops-total': 1000 } } 597 result = self.vm.qmp('object-add', conv_keys = False, **opts) 598 self.assert_qmp(result, 'return', {}) 599 600 # Add a throttle filter with the group that we just created. 601 # The filter is not used by anyone yet 602 opts = { 'driver': 'throttle', 'node-name': 'throttle0', 603 'throttle-group': 'group0', 'file': 'hd0-file' } 604 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 605 self.assert_qmp(result, 'return', {}) 606 607 # Insert the throttle filter between hd0 and hd0-file 608 self.reopen(hd0_opts, {'file': 'throttle0'}) 609 610 # Remove the throttle filter from hd0 611 self.reopen(hd0_opts, {'file': 'hd0-file'}) 612 613 # Insert (and remove) a compress filter 614 def test_insert_compress_filter(self): 615 # Add an image to the VM: hd (raw) -> hd0 (qcow2) -> hd0-file (file) 616 opts = {'driver': 'raw', 'node-name': 'hd', 'file': hd_opts(0)} 617 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 618 self.assert_qmp(result, 'return', {}) 619 620 # Add a 'compress' filter 621 filter_opts = {'driver': 'compress', 622 'node-name': 'compress0', 623 'file': 'hd0'} 624 result = self.vm.qmp('blockdev-add', conv_keys = False, **filter_opts) 625 self.assert_qmp(result, 'return', {}) 626 627 # Unmap the beginning of the image (we cannot write compressed 628 # data to an allocated cluster) 629 self.run_qemu_io("hd", "write -z -u 0 128k") 630 631 # Write data to the first cluster 632 self.run_qemu_io("hd", "write -P 0xa0 0 64k") 633 634 # Insert the filter then write to the second cluster 635 # hd -> compress0 -> hd0 -> hd0-file 636 self.reopen(opts, {'file': 'compress0'}) 637 self.run_qemu_io("hd", "write -P 0xa1 64k 64k") 638 639 # Remove the filter then write to the third cluster 640 # hd -> hd0 -> hd0-file 641 self.reopen(opts, {'file': 'hd0'}) 642 self.run_qemu_io("hd", "write -P 0xa2 128k 64k") 643 644 # Verify the data that we just wrote 645 self.run_qemu_io("hd", "read -P 0xa0 0 64k") 646 self.run_qemu_io("hd", "read -P 0xa1 64k 64k") 647 self.run_qemu_io("hd", "read -P 0xa2 128k 64k") 648 649 self.vm.shutdown() 650 651 # Check the first byte of the first three L2 entries and verify that 652 # the second one is compressed (0x40) while the others are not (0x80) 653 iotests.qemu_io_log('-f', 'raw', '-c', 'read -P 0x80 0x40000 1', 654 '-c', 'read -P 0x40 0x40008 1', 655 '-c', 'read -P 0x80 0x40010 1', hd_path[0]) 656 657 # Swap the disk images of two active block devices 658 def test_swap_files(self): 659 # Add hd0 and hd2 (none of them with backing files) 660 opts0 = hd_opts(0) 661 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts0) 662 self.assert_qmp(result, 'return', {}) 663 664 opts2 = hd_opts(2) 665 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts2) 666 self.assert_qmp(result, 'return', {}) 667 668 # Write different data to both block devices 669 self.run_qemu_io("hd0", "write -P 0xa0 0 1k") 670 self.run_qemu_io("hd2", "write -P 0xa2 0 1k") 671 672 # Check that the data reads correctly 673 self.run_qemu_io("hd0", "read -P 0xa0 0 1k") 674 self.run_qemu_io("hd2", "read -P 0xa2 0 1k") 675 676 # It's not possible to make a block device use an image that 677 # is already being used by the other device. 678 self.reopen(opts0, {'file': 'hd2-file'}, 679 "Permission conflict on node 'hd2-file': permissions " 680 "'write, resize' are both required by node 'hd2' (uses " 681 "node 'hd2-file' as 'file' child) and unshared by node " 682 "'hd0' (uses node 'hd2-file' as 'file' child).") 683 self.reopen(opts2, {'file': 'hd0-file'}, 684 "Permission conflict on node 'hd0-file': permissions " 685 "'write, resize' are both required by node 'hd0' (uses " 686 "node 'hd0-file' as 'file' child) and unshared by node " 687 "'hd2' (uses node 'hd0-file' as 'file' child).") 688 689 # But we can swap the images if we reopen both devices at the 690 # same time 691 opts0['file'] = 'hd2-file' 692 opts2['file'] = 'hd0-file' 693 self.reopenMultiple([opts0, opts2]) 694 self.run_qemu_io("hd0", "read -P 0xa2 0 1k") 695 self.run_qemu_io("hd2", "read -P 0xa0 0 1k") 696 697 # And we can of course come back to the original state 698 opts0['file'] = 'hd0-file' 699 opts2['file'] = 'hd2-file' 700 self.reopenMultiple([opts0, opts2]) 701 self.run_qemu_io("hd0", "read -P 0xa0 0 1k") 702 self.run_qemu_io("hd2", "read -P 0xa2 0 1k") 703 704 # Misc reopen tests with different block drivers 705 @iotests.skip_if_unsupported(['quorum', 'throttle']) 706 def test_misc_drivers(self): 707 #################### 708 ###### quorum ###### 709 #################### 710 for i in range(3): 711 opts = hd_opts(i) 712 # Open all three images without backing file 713 opts['backing'] = None 714 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 715 self.assert_qmp(result, 'return', {}) 716 717 opts = {'driver': 'quorum', 718 'node-name': 'quorum0', 719 'children': ['hd0', 'hd1', 'hd2'], 720 'vote-threshold': 2} 721 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 722 self.assert_qmp(result, 'return', {}) 723 724 # Quorum doesn't currently allow reopening. TODO: implement this 725 self.reopen(opts, {}, "Block format 'quorum' used by node 'quorum0'" + 726 " does not support reopening files") 727 728 # You can't make quorum0 a backing file of hd0: 729 # hd0 is already a child of quorum0. 730 self.reopen(hd_opts(0), {'backing': 'quorum0'}, 731 "Making 'quorum0' a backing child of 'hd0' would create a cycle") 732 733 # Delete quorum0 734 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'quorum0') 735 self.assert_qmp(result, 'return', {}) 736 737 # Delete hd0, hd1 and hd2 738 for i in range(3): 739 result = self.vm.qmp('blockdev-del', conv_keys = True, 740 node_name = 'hd%d' % i) 741 self.assert_qmp(result, 'return', {}) 742 743 ###################### 744 ###### blkdebug ###### 745 ###################### 746 opts = {'driver': 'blkdebug', 747 'node-name': 'bd', 748 'config': '/dev/null', 749 'image': hd_opts(0)} 750 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 751 self.assert_qmp(result, 'return', {}) 752 753 # blkdebug allows reopening if we keep the same options 754 self.reopen(opts) 755 756 # but it currently does not allow changes 757 self.reopen(opts, {'image': 'hd1'}, "Cannot change the option 'image'") 758 self.reopen(opts, {'align': 33554432}, "Cannot change the option 'align'") 759 self.reopen(opts, {'config': '/non/existent'}, "Cannot change the option 'config'") 760 del opts['config'] 761 self.reopen(opts, {}, "Option 'config' cannot be reset to its default value") 762 763 # Delete the blkdebug node 764 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'bd') 765 self.assert_qmp(result, 'return', {}) 766 767 ################## 768 ###### null ###### 769 ################## 770 opts = {'driver': 'null-co', 'node-name': 'root', 'size': 1024} 771 772 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 773 self.assert_qmp(result, 'return', {}) 774 775 # 1 << 30 is the default value, but we cannot change it explicitly 776 self.reopen(opts, {'size': (1 << 30)}, "Cannot change the option 'size'") 777 778 # We cannot change 'size' back to its default value either 779 del opts['size'] 780 self.reopen(opts, {}, "Option 'size' cannot be reset to its default value") 781 782 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'root') 783 self.assert_qmp(result, 'return', {}) 784 785 ################## 786 ###### file ###### 787 ################## 788 opts = hd_opts(0) 789 opts['file']['locking'] = 'on' 790 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 791 self.assert_qmp(result, 'return', {}) 792 793 # 'locking' cannot be changed 794 del opts['file']['locking'] 795 self.reopen(opts, {'file.locking': 'on'}) 796 self.reopen(opts, {'file.locking': 'off'}, "Cannot change the option 'locking'") 797 self.reopen(opts, {}, "Option 'locking' cannot be reset to its default value") 798 799 # Trying to reopen the 'file' node directly does not make a difference 800 opts = opts['file'] 801 self.reopen(opts, {'locking': 'on'}) 802 self.reopen(opts, {'locking': 'off'}, "Cannot change the option 'locking'") 803 self.reopen(opts, {}, "Option 'locking' cannot be reset to its default value") 804 805 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd0') 806 self.assert_qmp(result, 'return', {}) 807 808 ###################### 809 ###### throttle ###### 810 ###################### 811 opts = { 'qom-type': 'throttle-group', 'id': 'group0', 812 'limits': { 'iops-total': 1000 } } 813 result = self.vm.qmp('object-add', conv_keys = False, **opts) 814 self.assert_qmp(result, 'return', {}) 815 816 opts = { 'qom-type': 'throttle-group', 'id': 'group1', 817 'limits': { 'iops-total': 2000 } } 818 result = self.vm.qmp('object-add', conv_keys = False, **opts) 819 self.assert_qmp(result, 'return', {}) 820 821 # Add a throttle filter with group = group0 822 opts = { 'driver': 'throttle', 'node-name': 'throttle0', 823 'throttle-group': 'group0', 'file': hd_opts(0) } 824 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 825 self.assert_qmp(result, 'return', {}) 826 827 # We can reopen it if we keep the same options 828 self.reopen(opts) 829 830 # We can also reopen if 'file' is a reference to the child 831 self.reopen(opts, {'file': 'hd0'}) 832 833 # This is illegal 834 self.reopen(opts, {'throttle-group': 'notfound'}, "Throttle group 'notfound' does not exist") 835 836 # But it's possible to change the group to group1 837 self.reopen(opts, {'throttle-group': 'group1'}) 838 839 # Now group1 is in use, it cannot be deleted 840 result = self.vm.qmp('object-del', id = 'group1') 841 self.assert_qmp(result, 'error/class', 'GenericError') 842 self.assert_qmp(result, 'error/desc', "object 'group1' is in use, can not be deleted") 843 844 # Default options, this switches the group back to group0 845 self.reopen(opts) 846 847 # So now we cannot delete group0 848 result = self.vm.qmp('object-del', id = 'group0') 849 self.assert_qmp(result, 'error/class', 'GenericError') 850 self.assert_qmp(result, 'error/desc', "object 'group0' is in use, can not be deleted") 851 852 # But group1 is free this time, and it can be deleted 853 result = self.vm.qmp('object-del', id = 'group1') 854 self.assert_qmp(result, 'return', {}) 855 856 # Let's delete the filter node 857 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'throttle0') 858 self.assert_qmp(result, 'return', {}) 859 860 # And we can finally get rid of group0 861 result = self.vm.qmp('object-del', id = 'group0') 862 self.assert_qmp(result, 'return', {}) 863 864 # If an image has a backing file then the 'backing' option must be 865 # passed on reopen. We don't allow leaving the option out in this 866 # case because it's unclear what the correct semantics would be. 867 def test_missing_backing_options_1(self): 868 # hd2 869 opts = hd_opts(2) 870 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 871 self.assert_qmp(result, 'return', {}) 872 873 # hd0 874 opts = hd_opts(0) 875 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 876 self.assert_qmp(result, 'return', {}) 877 878 # hd0 has no backing file: we can omit the 'backing' option 879 self.reopen(opts) 880 881 # hd2 <- hd0 882 self.reopen(opts, {'backing': 'hd2'}) 883 884 # hd0 has a backing file: we must set the 'backing' option 885 self.reopen(opts, {}, "backing is missing for 'hd0'") 886 887 # hd2 can't be removed because it's the backing file of hd0 888 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd2') 889 self.assert_qmp(result, 'error/class', 'GenericError') 890 self.assert_qmp(result, 'error/desc', "Node 'hd2' is busy: node is used as backing hd of 'hd0'") 891 892 # Detach hd2 from hd0. 893 self.reopen(opts, {'backing': None}) 894 895 # Without a backing file, we can omit 'backing' again 896 self.reopen(opts) 897 898 # Remove both hd0 and hd2 899 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd0') 900 self.assert_qmp(result, 'return', {}) 901 902 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd2') 903 self.assert_qmp(result, 'return', {}) 904 905 # If an image has default backing file (as part of its metadata) 906 # then the 'backing' option must be passed on reopen. We don't 907 # allow leaving the option out in this case because it's unclear 908 # what the correct semantics would be. 909 def test_missing_backing_options_2(self): 910 # hd0 <- hd1 911 # (hd0 is hd1's default backing file) 912 opts = hd_opts(1) 913 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 914 self.assert_qmp(result, 'return', {}) 915 916 # hd1 has a backing file: we can't omit the 'backing' option 917 self.reopen(opts, {}, "backing is missing for 'hd1'") 918 919 # Let's detach the backing file 920 self.reopen(opts, {'backing': None}) 921 922 # No backing file attached to hd1 now, but we still can't omit the 'backing' option 923 self.reopen(opts, {}, "backing is missing for 'hd1'") 924 925 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd1') 926 self.assert_qmp(result, 'return', {}) 927 928 # Test that making 'backing' a reference to an existing child 929 # keeps its current options 930 def test_backing_reference(self): 931 # hd2 <- hd1 <- hd0 932 opts = hd_opts(0) 933 opts['backing'] = hd_opts(1) 934 opts['backing']['backing'] = hd_opts(2) 935 # Enable 'detect-zeroes' on all three nodes 936 opts['detect-zeroes'] = 'on' 937 opts['backing']['detect-zeroes'] = 'on' 938 opts['backing']['backing']['detect-zeroes'] = 'on' 939 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 940 self.assert_qmp(result, 'return', {}) 941 942 # Reopen the chain passing the minimum amount of required options. 943 # By making 'backing' a reference to hd1 (instead of a sub-dict) 944 # we tell QEMU to keep its current set of options. 945 opts = {'driver': iotests.imgfmt, 946 'node-name': 'hd0', 947 'file': 'hd0-file', 948 'backing': 'hd1' } 949 self.reopen(opts) 950 951 # This has reset 'detect-zeroes' on hd0, but not on hd1 and hd2. 952 self.assert_qmp(self.get_node('hd0'), 'detect_zeroes', 'off') 953 self.assert_qmp(self.get_node('hd1'), 'detect_zeroes', 'on') 954 self.assert_qmp(self.get_node('hd2'), 'detect_zeroes', 'on') 955 956 # Test what happens if the graph changes due to other operations 957 # such as block-stream 958 def test_block_stream_1(self): 959 # hd1 <- hd0 960 opts = hd_opts(0) 961 opts['backing'] = hd_opts(1) 962 opts['backing']['backing'] = None 963 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 964 self.assert_qmp(result, 'return', {}) 965 966 # Stream hd1 into hd0 and wait until it's done 967 result = self.vm.qmp('block-stream', conv_keys = True, job_id = 'stream0', device = 'hd0') 968 self.assert_qmp(result, 'return', {}) 969 self.wait_until_completed(drive = 'stream0') 970 971 # Now we have only hd0 972 self.assertEqual(self.get_node('hd1'), None) 973 974 # We have backing.* options but there's no backing file anymore 975 self.reopen(opts, {}, "Cannot change the option 'backing.driver'") 976 977 # If we remove the 'backing' option then we can reopen hd0 just fine 978 del opts['backing'] 979 self.reopen(opts) 980 981 # We can also reopen hd0 if we set 'backing' to null 982 self.reopen(opts, {'backing': None}) 983 984 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd0') 985 self.assert_qmp(result, 'return', {}) 986 987 # Another block_stream test 988 def test_block_stream_2(self): 989 # hd2 <- hd1 <- hd0 990 opts = hd_opts(0) 991 opts['backing'] = hd_opts(1) 992 opts['backing']['backing'] = hd_opts(2) 993 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 994 self.assert_qmp(result, 'return', {}) 995 996 # Stream hd1 into hd0 and wait until it's done 997 result = self.vm.qmp('block-stream', conv_keys = True, job_id = 'stream0', 998 device = 'hd0', base_node = 'hd2') 999 self.assert_qmp(result, 'return', {}) 1000 self.wait_until_completed(drive = 'stream0') 1001 1002 # The chain is hd2 <- hd0 now. hd1 is missing 1003 self.assertEqual(self.get_node('hd1'), None) 1004 1005 # The backing options in the dict were meant for hd1, but we cannot 1006 # use them with hd2 because hd1 had a backing file while hd2 does not. 1007 self.reopen(opts, {}, "Cannot change the option 'backing.driver'") 1008 1009 # If we remove hd1's options from the dict then things work fine 1010 opts['backing'] = opts['backing']['backing'] 1011 self.reopen(opts) 1012 1013 # We can also reopen hd0 if we use a reference to the backing file 1014 self.reopen(opts, {'backing': 'hd2'}) 1015 1016 # But we cannot leave the option out 1017 del opts['backing'] 1018 self.reopen(opts, {}, "backing is missing for 'hd0'") 1019 1020 # Now we can delete hd0 (and hd2) 1021 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd0') 1022 self.assert_qmp(result, 'return', {}) 1023 self.assertEqual(self.get_node('hd2'), None) 1024 1025 # Reopen the chain during a block-stream job (from hd1 to hd0) 1026 def test_block_stream_3(self): 1027 # hd2 <- hd1 <- hd0 1028 opts = hd_opts(0) 1029 opts['backing'] = hd_opts(1) 1030 opts['backing']['backing'] = hd_opts(2) 1031 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 1032 self.assert_qmp(result, 'return', {}) 1033 1034 # hd2 <- hd0 1035 result = self.vm.qmp('block-stream', conv_keys = True, job_id = 'stream0', 1036 device = 'hd0', base_node = 'hd2', 1037 auto_finalize = False) 1038 self.assert_qmp(result, 'return', {}) 1039 1040 # We can remove hd2 while the stream job is ongoing 1041 opts['backing']['backing'] = None 1042 self.reopen(opts, {}) 1043 1044 # We can't remove hd1 while the stream job is ongoing 1045 opts['backing'] = None 1046 self.reopen(opts, {}, "Cannot change frozen 'backing' link from 'hd0' to 'hd1'") 1047 1048 self.vm.run_job('stream0', auto_finalize = False, auto_dismiss = True) 1049 1050 # Reopen the chain during a block-stream job (from hd2 to hd1) 1051 def test_block_stream_4(self): 1052 # hd2 <- hd1 <- hd0 1053 opts = hd_opts(0) 1054 opts['backing'] = hd_opts(1) 1055 opts['backing']['backing'] = hd_opts(2) 1056 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 1057 self.assert_qmp(result, 'return', {}) 1058 1059 # hd1 <- hd0 1060 result = self.vm.qmp('block-stream', conv_keys = True, job_id = 'stream0', 1061 device = 'hd1', filter_node_name='cor', 1062 auto_finalize = False) 1063 self.assert_qmp(result, 'return', {}) 1064 1065 # We can't reopen with the original options because there is a filter 1066 # inserted by stream job above hd1. 1067 self.reopen(opts, {}, 1068 "Cannot change the option 'backing.backing.file.node-name'") 1069 1070 # We can't reopen hd1 to read-only, as block-stream requires it to be 1071 # read-write 1072 self.reopen(opts['backing'], {'read-only': True}, 1073 "Read-only block node 'hd1' cannot support read-write users") 1074 1075 # We can't remove hd2 while the stream job is ongoing 1076 opts['backing']['backing'] = None 1077 self.reopen(opts['backing'], {'read-only': False}, 1078 "Cannot change frozen 'backing' link from 'hd1' to 'hd2'") 1079 1080 # We can detach hd1 from hd0 because it doesn't affect the stream job 1081 opts['backing'] = None 1082 self.reopen(opts) 1083 1084 self.vm.run_job('stream0', auto_finalize = False, auto_dismiss = True) 1085 1086 # Reopen the chain during a block-commit job (from hd0 to hd2) 1087 def test_block_commit_1(self): 1088 # hd2 <- hd1 <- hd0 1089 opts = hd_opts(0) 1090 opts['backing'] = hd_opts(1) 1091 opts['backing']['backing'] = hd_opts(2) 1092 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 1093 self.assert_qmp(result, 'return', {}) 1094 1095 result = self.vm.qmp('block-commit', conv_keys = True, job_id = 'commit0', 1096 device = 'hd0') 1097 self.assert_qmp(result, 'return', {}) 1098 1099 # We can't remove hd2 while the commit job is ongoing 1100 opts['backing']['backing'] = None 1101 self.reopen(opts, {}, "Cannot change frozen 'backing' link from 'hd1' to 'hd2'") 1102 1103 # We can't remove hd1 while the commit job is ongoing 1104 opts['backing'] = None 1105 self.reopen(opts, {}, "Cannot change frozen 'backing' link from 'hd0' to 'hd1'") 1106 1107 event = self.vm.event_wait(name='BLOCK_JOB_READY') 1108 self.assert_qmp(event, 'data/device', 'commit0') 1109 self.assert_qmp(event, 'data/type', 'commit') 1110 self.assert_qmp_absent(event, 'data/error') 1111 1112 result = self.vm.qmp('block-job-complete', device='commit0') 1113 self.assert_qmp(result, 'return', {}) 1114 1115 self.wait_until_completed(drive = 'commit0') 1116 1117 # Reopen the chain during a block-commit job (from hd1 to hd2) 1118 def test_block_commit_2(self): 1119 # hd2 <- hd1 <- hd0 1120 opts = hd_opts(0) 1121 opts['backing'] = hd_opts(1) 1122 opts['backing']['backing'] = hd_opts(2) 1123 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 1124 self.assert_qmp(result, 'return', {}) 1125 1126 result = self.vm.qmp('block-commit', conv_keys = True, job_id = 'commit0', 1127 device = 'hd0', top_node = 'hd1', 1128 auto_finalize = False) 1129 self.assert_qmp(result, 'return', {}) 1130 1131 # We can't remove hd2 while the commit job is ongoing 1132 opts['backing']['backing'] = None 1133 self.reopen(opts, {}, "Cannot change the option 'backing.driver'") 1134 1135 # We can't remove hd1 while the commit job is ongoing 1136 opts['backing'] = None 1137 self.reopen(opts, {}, "Cannot replace implicit backing child of hd0") 1138 1139 # hd2 <- hd0 1140 self.vm.run_job('commit0', auto_finalize = False, auto_dismiss = True) 1141 1142 self.assert_qmp(self.get_node('hd0'), 'ro', False) 1143 self.assertEqual(self.get_node('hd1'), None) 1144 self.assert_qmp(self.get_node('hd2'), 'ro', True) 1145 1146 def run_test_iothreads(self, iothread_a, iothread_b, errmsg = None, 1147 opts_a = None, opts_b = None): 1148 opts = opts_a or hd_opts(0) 1149 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 1150 self.assert_qmp(result, 'return', {}) 1151 1152 opts2 = opts_b or hd_opts(2) 1153 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts2) 1154 self.assert_qmp(result, 'return', {}) 1155 1156 result = self.vm.qmp('object-add', qom_type='iothread', id='iothread0') 1157 self.assert_qmp(result, 'return', {}) 1158 1159 result = self.vm.qmp('object-add', qom_type='iothread', id='iothread1') 1160 self.assert_qmp(result, 'return', {}) 1161 1162 result = self.vm.qmp('device_add', driver='virtio-scsi', id='scsi0', 1163 iothread=iothread_a) 1164 self.assert_qmp(result, 'return', {}) 1165 1166 result = self.vm.qmp('device_add', driver='virtio-scsi', id='scsi1', 1167 iothread=iothread_b) 1168 self.assert_qmp(result, 'return', {}) 1169 1170 if iothread_a: 1171 result = self.vm.qmp('device_add', driver='scsi-hd', drive='hd0', 1172 share_rw=True, bus="scsi0.0") 1173 self.assert_qmp(result, 'return', {}) 1174 1175 if iothread_b: 1176 result = self.vm.qmp('device_add', driver='scsi-hd', drive='hd2', 1177 share_rw=True, bus="scsi1.0") 1178 self.assert_qmp(result, 'return', {}) 1179 1180 # Attaching the backing file may or may not work 1181 self.reopen(opts, {'backing': 'hd2'}, errmsg) 1182 1183 # But removing the backing file should always work 1184 self.reopen(opts, {'backing': None}) 1185 1186 self.vm.shutdown() 1187 1188 # We don't allow setting a backing file that uses a different AioContext if 1189 # neither of them can switch to the other AioContext 1190 def test_iothreads_error(self): 1191 self.run_test_iothreads('iothread0', 'iothread1', 1192 "Cannot change iothread of active block backend") 1193 1194 def test_iothreads_compatible_users(self): 1195 self.run_test_iothreads('iothread0', 'iothread0') 1196 1197 def test_iothreads_switch_backing(self): 1198 self.run_test_iothreads('iothread0', '') 1199 1200 def test_iothreads_switch_overlay(self): 1201 self.run_test_iothreads('', 'iothread0') 1202 1203 def test_iothreads_with_throttling(self): 1204 # Create a throttle-group object 1205 opts = { 'qom-type': 'throttle-group', 'id': 'group0', 1206 'limits': { 'iops-total': 1000 } } 1207 result = self.vm.qmp('object-add', conv_keys = False, **opts) 1208 self.assert_qmp(result, 'return', {}) 1209 1210 # Options with a throttle filter between format and protocol 1211 opts = [ 1212 { 1213 'driver': iotests.imgfmt, 1214 'node-name': f'hd{idx}', 1215 'file' : { 1216 'node-name': f'hd{idx}-throttle', 1217 'driver': 'throttle', 1218 'throttle-group': 'group0', 1219 'file': { 1220 'driver': 'file', 1221 'node-name': f'hd{idx}-file', 1222 'filename': hd_path[idx], 1223 }, 1224 }, 1225 } 1226 for idx in (0, 2) 1227 ] 1228 1229 self.run_test_iothreads('iothread0', 'iothread0', None, 1230 opts[0], opts[1]) 1231 1232if __name__ == '__main__': 1233 iotests.activate_logging() 1234 iotests.main(supported_fmts=["qcow2"], 1235 supported_protocols=["file"]) 1236