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