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