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 @iotests.skip_if_unsupported(['compress']) 615 def test_insert_compress_filter(self): 616 # Add an image to the VM: hd (raw) -> hd0 (qcow2) -> hd0-file (file) 617 opts = {'driver': 'raw', 'node-name': 'hd', 'file': hd_opts(0)} 618 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 619 self.assert_qmp(result, 'return', {}) 620 621 # Add a 'compress' filter 622 filter_opts = {'driver': 'compress', 623 'node-name': 'compress0', 624 'file': 'hd0'} 625 result = self.vm.qmp('blockdev-add', conv_keys = False, **filter_opts) 626 self.assert_qmp(result, 'return', {}) 627 628 # Unmap the beginning of the image (we cannot write compressed 629 # data to an allocated cluster) 630 self.run_qemu_io("hd", "write -z -u 0 128k") 631 632 # Write data to the first cluster 633 self.run_qemu_io("hd", "write -P 0xa0 0 64k") 634 635 # Insert the filter then write to the second cluster 636 # hd -> compress0 -> hd0 -> hd0-file 637 self.reopen(opts, {'file': 'compress0'}) 638 self.run_qemu_io("hd", "write -P 0xa1 64k 64k") 639 640 # Remove the filter then write to the third cluster 641 # hd -> hd0 -> hd0-file 642 self.reopen(opts, {'file': 'hd0'}) 643 self.run_qemu_io("hd", "write -P 0xa2 128k 64k") 644 645 # Verify the data that we just wrote 646 self.run_qemu_io("hd", "read -P 0xa0 0 64k") 647 self.run_qemu_io("hd", "read -P 0xa1 64k 64k") 648 self.run_qemu_io("hd", "read -P 0xa2 128k 64k") 649 650 self.vm.shutdown() 651 652 # Check the first byte of the first three L2 entries and verify that 653 # the second one is compressed (0x40) while the others are not (0x80) 654 iotests.qemu_io('-f', 'raw', '-c', 'read -P 0x80 0x40000 1', 655 '-c', 'read -P 0x40 0x40008 1', 656 '-c', 'read -P 0x80 0x40010 1', hd_path[0]) 657 658 # Swap the disk images of two active block devices 659 def test_swap_files(self): 660 # Add hd0 and hd2 (none of them with backing files) 661 opts0 = hd_opts(0) 662 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts0) 663 self.assert_qmp(result, 'return', {}) 664 665 opts2 = hd_opts(2) 666 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts2) 667 self.assert_qmp(result, 'return', {}) 668 669 # Write different data to both block devices 670 self.run_qemu_io("hd0", "write -P 0xa0 0 1k") 671 self.run_qemu_io("hd2", "write -P 0xa2 0 1k") 672 673 # Check that the data reads correctly 674 self.run_qemu_io("hd0", "read -P 0xa0 0 1k") 675 self.run_qemu_io("hd2", "read -P 0xa2 0 1k") 676 677 # It's not possible to make a block device use an image that 678 # is already being used by the other device. 679 self.reopen(opts0, {'file': 'hd2-file'}, 680 "Permission conflict on node 'hd2-file': permissions " 681 "'write, resize' are both required by node 'hd2' (uses " 682 "node 'hd2-file' as 'file' child) and unshared by node " 683 "'hd0' (uses node 'hd2-file' as 'file' child).") 684 self.reopen(opts2, {'file': 'hd0-file'}, 685 "Permission conflict on node 'hd0-file': permissions " 686 "'write, resize' are both required by node 'hd0' (uses " 687 "node 'hd0-file' as 'file' child) and unshared by node " 688 "'hd2' (uses node 'hd0-file' as 'file' child).") 689 690 # But we can swap the images if we reopen both devices at the 691 # same time 692 opts0['file'] = 'hd2-file' 693 opts2['file'] = 'hd0-file' 694 self.reopenMultiple([opts0, opts2]) 695 self.run_qemu_io("hd0", "read -P 0xa2 0 1k") 696 self.run_qemu_io("hd2", "read -P 0xa0 0 1k") 697 698 # And we can of course come back to the original state 699 opts0['file'] = 'hd0-file' 700 opts2['file'] = 'hd2-file' 701 self.reopenMultiple([opts0, opts2]) 702 self.run_qemu_io("hd0", "read -P 0xa0 0 1k") 703 self.run_qemu_io("hd2", "read -P 0xa2 0 1k") 704 705 # Misc reopen tests with different block drivers 706 @iotests.skip_if_unsupported(['quorum', 'throttle']) 707 def test_misc_drivers(self): 708 #################### 709 ###### quorum ###### 710 #################### 711 for i in range(3): 712 opts = hd_opts(i) 713 # Open all three images without backing file 714 opts['backing'] = None 715 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 716 self.assert_qmp(result, 'return', {}) 717 718 opts = {'driver': 'quorum', 719 'node-name': 'quorum0', 720 'children': ['hd0', 'hd1', 'hd2'], 721 'vote-threshold': 2} 722 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 723 self.assert_qmp(result, 'return', {}) 724 725 # Quorum doesn't currently allow reopening. TODO: implement this 726 self.reopen(opts, {}, "Block format 'quorum' used by node 'quorum0'" + 727 " does not support reopening files") 728 729 # You can't make quorum0 a backing file of hd0: 730 # hd0 is already a child of quorum0. 731 self.reopen(hd_opts(0), {'backing': 'quorum0'}, 732 "Making 'quorum0' a backing child of 'hd0' would create a cycle") 733 734 # Delete quorum0 735 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'quorum0') 736 self.assert_qmp(result, 'return', {}) 737 738 # Delete hd0, hd1 and hd2 739 for i in range(3): 740 result = self.vm.qmp('blockdev-del', conv_keys = True, 741 node_name = 'hd%d' % i) 742 self.assert_qmp(result, 'return', {}) 743 744 ###################### 745 ###### blkdebug ###### 746 ###################### 747 opts = {'driver': 'blkdebug', 748 'node-name': 'bd', 749 'config': '/dev/null', 750 'image': hd_opts(0)} 751 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 752 self.assert_qmp(result, 'return', {}) 753 754 # blkdebug allows reopening if we keep the same options 755 self.reopen(opts) 756 757 # but it currently does not allow changes 758 self.reopen(opts, {'image': 'hd1'}, "Cannot change the option 'image'") 759 self.reopen(opts, {'align': 33554432}, "Cannot change the option 'align'") 760 self.reopen(opts, {'config': '/non/existent'}, "Cannot change the option 'config'") 761 del opts['config'] 762 self.reopen(opts, {}, "Option 'config' cannot be reset to its default value") 763 764 # Delete the blkdebug node 765 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'bd') 766 self.assert_qmp(result, 'return', {}) 767 768 ################## 769 ###### null ###### 770 ################## 771 opts = {'driver': 'null-co', 'node-name': 'root', 'size': 1024} 772 773 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 774 self.assert_qmp(result, 'return', {}) 775 776 # 1 << 30 is the default value, but we cannot change it explicitly 777 self.reopen(opts, {'size': (1 << 30)}, "Cannot change the option 'size'") 778 779 # We cannot change 'size' back to its default value either 780 del opts['size'] 781 self.reopen(opts, {}, "Option 'size' cannot be reset to its default value") 782 783 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'root') 784 self.assert_qmp(result, 'return', {}) 785 786 ################## 787 ###### file ###### 788 ################## 789 opts = hd_opts(0) 790 opts['file']['locking'] = 'on' 791 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 792 self.assert_qmp(result, 'return', {}) 793 794 # 'locking' cannot be changed 795 del opts['file']['locking'] 796 self.reopen(opts, {'file.locking': 'on'}) 797 self.reopen(opts, {'file.locking': 'off'}, "Cannot change the option 'locking'") 798 self.reopen(opts, {}, "Option 'locking' cannot be reset to its default value") 799 800 # Trying to reopen the 'file' node directly does not make a difference 801 opts = opts['file'] 802 self.reopen(opts, {'locking': 'on'}) 803 self.reopen(opts, {'locking': 'off'}, "Cannot change the option 'locking'") 804 self.reopen(opts, {}, "Option 'locking' cannot be reset to its default value") 805 806 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd0') 807 self.assert_qmp(result, 'return', {}) 808 809 ###################### 810 ###### throttle ###### 811 ###################### 812 opts = { 'qom-type': 'throttle-group', 'id': 'group0', 813 'limits': { 'iops-total': 1000 } } 814 result = self.vm.qmp('object-add', conv_keys = False, **opts) 815 self.assert_qmp(result, 'return', {}) 816 817 opts = { 'qom-type': 'throttle-group', 'id': 'group1', 818 'limits': { 'iops-total': 2000 } } 819 result = self.vm.qmp('object-add', conv_keys = False, **opts) 820 self.assert_qmp(result, 'return', {}) 821 822 # Add a throttle filter with group = group0 823 opts = { 'driver': 'throttle', 'node-name': 'throttle0', 824 'throttle-group': 'group0', 'file': hd_opts(0) } 825 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 826 self.assert_qmp(result, 'return', {}) 827 828 # We can reopen it if we keep the same options 829 self.reopen(opts) 830 831 # We can also reopen if 'file' is a reference to the child 832 self.reopen(opts, {'file': 'hd0'}) 833 834 # This is illegal 835 self.reopen(opts, {'throttle-group': 'notfound'}, "Throttle group 'notfound' does not exist") 836 837 # But it's possible to change the group to group1 838 self.reopen(opts, {'throttle-group': 'group1'}) 839 840 # Now group1 is in use, it cannot be deleted 841 result = self.vm.qmp('object-del', id = 'group1') 842 self.assert_qmp(result, 'error/class', 'GenericError') 843 self.assert_qmp(result, 'error/desc', "object 'group1' is in use, can not be deleted") 844 845 # Default options, this switches the group back to group0 846 self.reopen(opts) 847 848 # So now we cannot delete group0 849 result = self.vm.qmp('object-del', id = 'group0') 850 self.assert_qmp(result, 'error/class', 'GenericError') 851 self.assert_qmp(result, 'error/desc', "object 'group0' is in use, can not be deleted") 852 853 # But group1 is free this time, and it can be deleted 854 result = self.vm.qmp('object-del', id = 'group1') 855 self.assert_qmp(result, 'return', {}) 856 857 # Let's delete the filter node 858 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'throttle0') 859 self.assert_qmp(result, 'return', {}) 860 861 # And we can finally get rid of group0 862 result = self.vm.qmp('object-del', id = 'group0') 863 self.assert_qmp(result, 'return', {}) 864 865 # If an image has a backing file then the 'backing' option must be 866 # passed on reopen. We don't allow leaving the option out in this 867 # case because it's unclear what the correct semantics would be. 868 def test_missing_backing_options_1(self): 869 # hd2 870 opts = hd_opts(2) 871 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 872 self.assert_qmp(result, 'return', {}) 873 874 # hd0 875 opts = hd_opts(0) 876 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 877 self.assert_qmp(result, 'return', {}) 878 879 # hd0 has no backing file: we can omit the 'backing' option 880 self.reopen(opts) 881 882 # hd2 <- hd0 883 self.reopen(opts, {'backing': 'hd2'}) 884 885 # hd0 has a backing file: we must set the 'backing' option 886 self.reopen(opts, {}, "backing is missing for 'hd0'") 887 888 # hd2 can't be removed because it's the backing file of hd0 889 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd2') 890 self.assert_qmp(result, 'error/class', 'GenericError') 891 self.assert_qmp(result, 'error/desc', "Node 'hd2' is busy: node is used as backing hd of 'hd0'") 892 893 # Detach hd2 from hd0. 894 self.reopen(opts, {'backing': None}) 895 896 # Without a backing file, we can omit 'backing' again 897 self.reopen(opts) 898 899 # Remove both hd0 and hd2 900 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd0') 901 self.assert_qmp(result, 'return', {}) 902 903 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd2') 904 self.assert_qmp(result, 'return', {}) 905 906 # If an image has default backing file (as part of its metadata) 907 # then the 'backing' option must be passed on reopen. We don't 908 # allow leaving the option out in this case because it's unclear 909 # what the correct semantics would be. 910 def test_missing_backing_options_2(self): 911 # hd0 <- hd1 912 # (hd0 is hd1's default backing file) 913 opts = hd_opts(1) 914 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 915 self.assert_qmp(result, 'return', {}) 916 917 # hd1 has a backing file: we can't omit the 'backing' option 918 self.reopen(opts, {}, "backing is missing for 'hd1'") 919 920 # Let's detach the backing file 921 self.reopen(opts, {'backing': None}) 922 923 # No backing file attached to hd1 now, but we still can't omit the 'backing' option 924 self.reopen(opts, {}, "backing is missing for 'hd1'") 925 926 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd1') 927 self.assert_qmp(result, 'return', {}) 928 929 # Test that making 'backing' a reference to an existing child 930 # keeps its current options 931 def test_backing_reference(self): 932 # hd2 <- hd1 <- hd0 933 opts = hd_opts(0) 934 opts['backing'] = hd_opts(1) 935 opts['backing']['backing'] = hd_opts(2) 936 # Enable 'detect-zeroes' on all three nodes 937 opts['detect-zeroes'] = 'on' 938 opts['backing']['detect-zeroes'] = 'on' 939 opts['backing']['backing']['detect-zeroes'] = 'on' 940 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 941 self.assert_qmp(result, 'return', {}) 942 943 # Reopen the chain passing the minimum amount of required options. 944 # By making 'backing' a reference to hd1 (instead of a sub-dict) 945 # we tell QEMU to keep its current set of options. 946 opts = {'driver': iotests.imgfmt, 947 'node-name': 'hd0', 948 'file': 'hd0-file', 949 'backing': 'hd1' } 950 self.reopen(opts) 951 952 # This has reset 'detect-zeroes' on hd0, but not on hd1 and hd2. 953 self.assert_qmp(self.get_node('hd0'), 'detect_zeroes', 'off') 954 self.assert_qmp(self.get_node('hd1'), 'detect_zeroes', 'on') 955 self.assert_qmp(self.get_node('hd2'), 'detect_zeroes', 'on') 956 957 # Test what happens if the graph changes due to other operations 958 # such as block-stream 959 def test_block_stream_1(self): 960 # hd1 <- hd0 961 opts = hd_opts(0) 962 opts['backing'] = hd_opts(1) 963 opts['backing']['backing'] = None 964 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 965 self.assert_qmp(result, 'return', {}) 966 967 # Stream hd1 into hd0 and wait until it's done 968 result = self.vm.qmp('block-stream', conv_keys = True, job_id = 'stream0', device = 'hd0') 969 self.assert_qmp(result, 'return', {}) 970 self.wait_until_completed(drive = 'stream0') 971 972 # Now we have only hd0 973 self.assertEqual(self.get_node('hd1'), None) 974 975 # We have backing.* options but there's no backing file anymore 976 self.reopen(opts, {}, "Cannot change the option 'backing.driver'") 977 978 # If we remove the 'backing' option then we can reopen hd0 just fine 979 del opts['backing'] 980 self.reopen(opts) 981 982 # We can also reopen hd0 if we set 'backing' to null 983 self.reopen(opts, {'backing': None}) 984 985 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd0') 986 self.assert_qmp(result, 'return', {}) 987 988 # Another block_stream test 989 def test_block_stream_2(self): 990 # hd2 <- hd1 <- hd0 991 opts = hd_opts(0) 992 opts['backing'] = hd_opts(1) 993 opts['backing']['backing'] = hd_opts(2) 994 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 995 self.assert_qmp(result, 'return', {}) 996 997 # Stream hd1 into hd0 and wait until it's done 998 result = self.vm.qmp('block-stream', conv_keys = True, job_id = 'stream0', 999 device = 'hd0', base_node = 'hd2') 1000 self.assert_qmp(result, 'return', {}) 1001 self.wait_until_completed(drive = 'stream0') 1002 1003 # The chain is hd2 <- hd0 now. hd1 is missing 1004 self.assertEqual(self.get_node('hd1'), None) 1005 1006 # The backing options in the dict were meant for hd1, but we cannot 1007 # use them with hd2 because hd1 had a backing file while hd2 does not. 1008 self.reopen(opts, {}, "Cannot change the option 'backing.driver'") 1009 1010 # If we remove hd1's options from the dict then things work fine 1011 opts['backing'] = opts['backing']['backing'] 1012 self.reopen(opts) 1013 1014 # We can also reopen hd0 if we use a reference to the backing file 1015 self.reopen(opts, {'backing': 'hd2'}) 1016 1017 # But we cannot leave the option out 1018 del opts['backing'] 1019 self.reopen(opts, {}, "backing is missing for 'hd0'") 1020 1021 # Now we can delete hd0 (and hd2) 1022 result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd0') 1023 self.assert_qmp(result, 'return', {}) 1024 self.assertEqual(self.get_node('hd2'), None) 1025 1026 # Reopen the chain during a block-stream job (from hd1 to hd0) 1027 def test_block_stream_3(self): 1028 # hd2 <- hd1 <- hd0 1029 opts = hd_opts(0) 1030 opts['backing'] = hd_opts(1) 1031 opts['backing']['backing'] = hd_opts(2) 1032 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 1033 self.assert_qmp(result, 'return', {}) 1034 1035 # hd2 <- hd0 1036 result = self.vm.qmp('block-stream', conv_keys = True, job_id = 'stream0', 1037 device = 'hd0', base_node = 'hd2', 1038 auto_finalize = False) 1039 self.assert_qmp(result, 'return', {}) 1040 1041 # We can remove hd2 while the stream job is ongoing 1042 opts['backing']['backing'] = None 1043 self.reopen(opts, {}) 1044 1045 # We can't remove hd1 while the stream job is ongoing 1046 opts['backing'] = None 1047 self.reopen(opts, {}, "Cannot change frozen 'backing' link from 'hd0' to 'hd1'") 1048 1049 self.vm.run_job('stream0', auto_finalize = False, auto_dismiss = True) 1050 1051 # Reopen the chain during a block-stream job (from hd2 to hd1) 1052 def test_block_stream_4(self): 1053 # hd2 <- hd1 <- hd0 1054 opts = hd_opts(0) 1055 opts['backing'] = hd_opts(1) 1056 opts['backing']['backing'] = hd_opts(2) 1057 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 1058 self.assert_qmp(result, 'return', {}) 1059 1060 # hd1 <- hd0 1061 result = self.vm.qmp('block-stream', conv_keys = True, job_id = 'stream0', 1062 device = 'hd1', filter_node_name='cor', 1063 auto_finalize = False) 1064 self.assert_qmp(result, 'return', {}) 1065 1066 # We can't reopen with the original options because there is a filter 1067 # inserted by stream job above hd1. 1068 self.reopen(opts, {}, 1069 "Cannot change the option 'backing.backing.file.node-name'") 1070 1071 # We can't reopen hd1 to read-only, as block-stream requires it to be 1072 # read-write 1073 self.reopen(opts['backing'], {'read-only': True}, 1074 "Read-only block node 'hd1' cannot support read-write users") 1075 1076 # We can't remove hd2 while the stream job is ongoing 1077 opts['backing']['backing'] = None 1078 self.reopen(opts['backing'], {'read-only': False}, 1079 "Cannot change frozen 'backing' link from 'hd1' to 'hd2'") 1080 1081 # We can detach hd1 from hd0 because it doesn't affect the stream job 1082 opts['backing'] = None 1083 self.reopen(opts) 1084 1085 self.vm.run_job('stream0', auto_finalize = False, auto_dismiss = True) 1086 1087 # Reopen the chain during a block-commit job (from hd0 to hd2) 1088 def test_block_commit_1(self): 1089 # hd2 <- hd1 <- hd0 1090 opts = hd_opts(0) 1091 opts['backing'] = hd_opts(1) 1092 opts['backing']['backing'] = hd_opts(2) 1093 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 1094 self.assert_qmp(result, 'return', {}) 1095 1096 result = self.vm.qmp('block-commit', conv_keys = True, job_id = 'commit0', 1097 device = 'hd0') 1098 self.assert_qmp(result, 'return', {}) 1099 1100 # We can't remove hd2 while the commit job is ongoing 1101 opts['backing']['backing'] = None 1102 self.reopen(opts, {}, "Cannot change frozen 'backing' link from 'hd1' to 'hd2'") 1103 1104 # We can't remove hd1 while the commit job is ongoing 1105 opts['backing'] = None 1106 self.reopen(opts, {}, "Cannot change frozen 'backing' link from 'hd0' to 'hd1'") 1107 1108 event = self.vm.event_wait(name='BLOCK_JOB_READY') 1109 self.assert_qmp(event, 'data/device', 'commit0') 1110 self.assert_qmp(event, 'data/type', 'commit') 1111 self.assert_qmp_absent(event, 'data/error') 1112 1113 result = self.vm.qmp('block-job-complete', device='commit0') 1114 self.assert_qmp(result, 'return', {}) 1115 1116 self.wait_until_completed(drive = 'commit0') 1117 1118 # Reopen the chain during a block-commit job (from hd1 to hd2) 1119 def test_block_commit_2(self): 1120 # hd2 <- hd1 <- hd0 1121 opts = hd_opts(0) 1122 opts['backing'] = hd_opts(1) 1123 opts['backing']['backing'] = hd_opts(2) 1124 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 1125 self.assert_qmp(result, 'return', {}) 1126 1127 result = self.vm.qmp('block-commit', conv_keys = True, job_id = 'commit0', 1128 device = 'hd0', top_node = 'hd1', 1129 auto_finalize = False) 1130 self.assert_qmp(result, 'return', {}) 1131 1132 # We can't remove hd2 while the commit job is ongoing 1133 opts['backing']['backing'] = None 1134 self.reopen(opts, {}, "Cannot change the option 'backing.driver'") 1135 1136 # We can't remove hd1 while the commit job is ongoing 1137 opts['backing'] = None 1138 self.reopen(opts, {}, "Cannot replace implicit backing child of hd0") 1139 1140 # hd2 <- hd0 1141 self.vm.run_job('commit0', auto_finalize = False, auto_dismiss = True) 1142 1143 self.assert_qmp(self.get_node('hd0'), 'ro', False) 1144 self.assertEqual(self.get_node('hd1'), None) 1145 self.assert_qmp(self.get_node('hd2'), 'ro', True) 1146 1147 def run_test_iothreads(self, iothread_a, iothread_b, errmsg = None, 1148 opts_a = None, opts_b = None): 1149 opts = opts_a or hd_opts(0) 1150 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts) 1151 self.assert_qmp(result, 'return', {}) 1152 1153 opts2 = opts_b or hd_opts(2) 1154 result = self.vm.qmp('blockdev-add', conv_keys = False, **opts2) 1155 self.assert_qmp(result, 'return', {}) 1156 1157 result = self.vm.qmp('object-add', qom_type='iothread', id='iothread0') 1158 self.assert_qmp(result, 'return', {}) 1159 1160 result = self.vm.qmp('object-add', qom_type='iothread', id='iothread1') 1161 self.assert_qmp(result, 'return', {}) 1162 1163 result = self.vm.qmp('device_add', driver='virtio-scsi', id='scsi0', 1164 iothread=iothread_a) 1165 self.assert_qmp(result, 'return', {}) 1166 1167 result = self.vm.qmp('device_add', driver='virtio-scsi', id='scsi1', 1168 iothread=iothread_b) 1169 self.assert_qmp(result, 'return', {}) 1170 1171 if iothread_a: 1172 result = self.vm.qmp('device_add', driver='scsi-hd', drive='hd0', 1173 share_rw=True, bus="scsi0.0") 1174 self.assert_qmp(result, 'return', {}) 1175 1176 if iothread_b: 1177 result = self.vm.qmp('device_add', driver='scsi-hd', drive='hd2', 1178 share_rw=True, bus="scsi1.0") 1179 self.assert_qmp(result, 'return', {}) 1180 1181 # Attaching the backing file may or may not work 1182 self.reopen(opts, {'backing': 'hd2'}, errmsg) 1183 1184 # But removing the backing file should always work 1185 self.reopen(opts, {'backing': None}) 1186 1187 self.vm.shutdown() 1188 1189 # We don't allow setting a backing file that uses a different AioContext if 1190 # neither of them can switch to the other AioContext 1191 def test_iothreads_error(self): 1192 self.run_test_iothreads('iothread0', 'iothread1', 1193 "Cannot change iothread of active block backend") 1194 1195 def test_iothreads_compatible_users(self): 1196 self.run_test_iothreads('iothread0', 'iothread0') 1197 1198 def test_iothreads_switch_backing(self): 1199 self.run_test_iothreads('iothread0', '') 1200 1201 def test_iothreads_switch_overlay(self): 1202 self.run_test_iothreads('', 'iothread0') 1203 1204 def test_iothreads_with_throttling(self): 1205 # Create a throttle-group object 1206 opts = { 'qom-type': 'throttle-group', 'id': 'group0', 1207 'limits': { 'iops-total': 1000 } } 1208 result = self.vm.qmp('object-add', conv_keys = False, **opts) 1209 self.assert_qmp(result, 'return', {}) 1210 1211 # Options with a throttle filter between format and protocol 1212 opts = [ 1213 { 1214 'driver': iotests.imgfmt, 1215 'node-name': f'hd{idx}', 1216 'file' : { 1217 'node-name': f'hd{idx}-throttle', 1218 'driver': 'throttle', 1219 'throttle-group': 'group0', 1220 'file': { 1221 'driver': 'file', 1222 'node-name': f'hd{idx}-file', 1223 'filename': hd_path[idx], 1224 }, 1225 }, 1226 } 1227 for idx in (0, 2) 1228 ] 1229 1230 self.run_test_iothreads('iothread0', 'iothread0', None, 1231 opts[0], opts[1]) 1232 1233if __name__ == '__main__': 1234 iotests.activate_logging() 1235 iotests.main(supported_fmts=["qcow2"], 1236 supported_protocols=["file"]) 1237