cachepc-qemu

Fork of AMDESE/qemu with changes for cachepc side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-qemu
Log | Files | Refs | Submodules | LICENSE | sfeed.txt

245 (52306B)


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