qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

245 (53561B)


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