1#!/usr/bin/env python3
2#
3# Test for when a backing file is considered overridden (thus, a
4# json:{} filename is generated for the overlay) and when it is not
5#
6# Copyright (C) 2018 Red Hat, Inc.
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation; either version 2 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with this program.  If not, see <http://www.gnu.org/licenses/>.
20#
21# Creator/Owner: Max Reitz <mreitz@redhat.com>
22
23import iotests
24from iotests import log, qemu_img, filter_testfiles, filter_imgfmt, \
25        filter_qmp_testfiles, filter_qmp_imgfmt
26
27# Need backing file and change-backing-file support
28iotests.verify_image_format(supported_fmts=['qcow2', 'qed'])
29iotests.verify_platform(['linux'])
30
31
32def log_node_info(node):
33    log('')
34
35    log('bs->filename: ' + node['image']['filename'],
36        filters=[filter_testfiles, filter_imgfmt])
37    log('bs->backing_file: ' + node['backing_file'],
38        filters=[filter_testfiles, filter_imgfmt])
39
40    if 'backing-image' in node['image']:
41        log('bs->backing->bs->filename: ' +
42            node['image']['backing-image']['filename'],
43            filters=[filter_testfiles, filter_imgfmt])
44    else:
45        log('bs->backing: (none)')
46
47    log('')
48
49
50with iotests.FilePath('base.img') as base_img_path, \
51     iotests.FilePath('top.img') as top_img_path, \
52     iotests.VM() as vm:
53
54    assert qemu_img('create', '-f', iotests.imgfmt, base_img_path, '64M') == 0
55    # Choose a funny way to describe the backing filename
56    assert qemu_img('create', '-f', iotests.imgfmt, '-b',
57                    'file:' + base_img_path, top_img_path) == 0
58
59    vm.launch()
60
61    log('--- Implicit backing file ---')
62    log('')
63
64    vm.qmp_log('blockdev-add',
65                node_name='node0',
66                driver=iotests.imgfmt,
67                file={
68                    'driver': 'file',
69                    'filename': top_img_path
70                },
71                filters=[filter_qmp_testfiles, filter_qmp_imgfmt])
72
73    # Filename should be plain, and the backing filename should not
74    # contain the "file:" prefix
75    log_node_info(vm.node_info('node0'))
76
77    vm.qmp_log('blockdev-del', node_name='node0')
78
79    log('')
80    log('--- change-backing-file ---')
81    log('')
82
83    vm.qmp_log('blockdev-add',
84               node_name='node0',
85               driver=iotests.imgfmt,
86               file={
87                   'driver': 'file',
88                   'filename': top_img_path
89               },
90               filters=[filter_qmp_testfiles, filter_qmp_imgfmt])
91
92    # Changing the backing file to a qemu-reported filename should
93    # result in qemu accepting the corresponding BDS as the implicit
94    # backing BDS (and thus not generate a json:{} filename).
95    # So, first, query the backing filename.
96
97    backing_filename = \
98        vm.node_info('node0')['image']['backing-image']['filename']
99
100    # Next, change the backing file to something different
101
102    vm.qmp_log('change-backing-file',
103               image_node_name='node0',
104               device='node0',
105               backing_file='null-co://',
106               filters=[filter_qmp_testfiles])
107
108    # Now, verify that we get a json:{} filename
109    # (Image header says "null-co://", actual backing file still is
110    # base_img_path)
111
112    log_node_info(vm.node_info('node0'))
113
114    # Change it back
115    # (To get header and backing file in sync)
116
117    vm.qmp_log('change-backing-file',
118               image_node_name='node0',
119               device='node0',
120               backing_file=backing_filename,
121               filters=[filter_qmp_testfiles])
122
123    # And verify that we get our original results
124
125    log_node_info(vm.node_info('node0'))
126
127    # Finally, try a "file:" prefix.  While this is actually what we
128    # originally had in the image header, qemu will not reopen the
129    # backing file here, so it cannot verify that this filename
130    # "resolves" to the actual backing BDS's filename and will thus
131    # consider both to be different.
132    # (This may be fixed in the future.)
133
134    vm.qmp_log('change-backing-file',
135               image_node_name='node0',
136               device='node0',
137               backing_file=('file:' + backing_filename),
138               filters=[filter_qmp_testfiles])
139
140    # So now we should get a json:{} filename
141
142    log_node_info(vm.node_info('node0'))
143
144    # Remove and re-attach so we can see that (as in our first try),
145    # opening the image anew helps qemu resolve the header backing
146    # filename.
147
148    vm.qmp_log('blockdev-del', node_name='node0')
149
150    vm.qmp_log('blockdev-add',
151               node_name='node0',
152               driver=iotests.imgfmt,
153               file={
154                   'driver': 'file',
155                   'filename': top_img_path
156               },
157               filters=[filter_qmp_testfiles, filter_qmp_imgfmt])
158
159    log_node_info(vm.node_info('node0'))
160
161    vm.qmp_log('blockdev-del', node_name='node0')
162
163    log('')
164    log('--- Override backing file ---')
165    log('')
166
167    # For this test, we need the plain filename in the image header
168    # (because qemu cannot "canonicalize"/"resolve" the backing
169    # filename unless the backing file is opened implicitly with the
170    # overlay)
171    assert qemu_img('create', '-f', iotests.imgfmt, '-b', base_img_path,
172                    top_img_path) == 0
173
174    # You can only reliably override backing options by using a node
175    # reference (or by specifying file.filename, but, well...)
176    vm.qmp_log('blockdev-add', node_name='null', driver='null-co')
177
178    vm.qmp_log('blockdev-add',
179               node_name='node0',
180               driver=iotests.imgfmt,
181               file={
182                   'driver': 'file',
183                   'filename': top_img_path
184               },
185               backing='null',
186               filters=[filter_qmp_testfiles, filter_qmp_imgfmt])
187
188    # Should get a json:{} filename (and bs->backing_file is
189    # null-co://, because that field actually has not much to do
190    # with the header backing filename (except that it is changed by
191    # change-backing-file))
192
193    log_node_info(vm.node_info('node0'))
194
195    # Detach the backing file by reopening the whole thing
196
197    vm.qmp_log('blockdev-del', node_name='node0')
198    vm.qmp_log('blockdev-del', node_name='null')
199
200    vm.qmp_log('blockdev-add',
201               node_name='node0',
202               driver=iotests.imgfmt,
203               file={
204                   'driver': 'file',
205                   'filename': top_img_path
206               },
207               backing=None,
208               filters=[filter_qmp_testfiles, filter_qmp_imgfmt])
209
210    # Should get a json:{} filename (because we overrode the backing
211    # file to not be there)
212
213    log_node_info(vm.node_info('node0'))
214
215    # Open the original backing file
216
217    vm.qmp_log('blockdev-add',
218               node_name='original-backing',
219               driver=iotests.imgfmt,
220               file={
221                   'driver': 'file',
222                   'filename': base_img_path
223               },
224               filters=[filter_qmp_testfiles, filter_qmp_imgfmt])
225
226    # Attach the original backing file to its overlay
227
228    vm.qmp_log('blockdev-snapshot',
229               node='original-backing',
230               overlay='node0')
231
232    # This should give us the original plain result
233
234    log_node_info(vm.node_info('node0'))
235
236    vm.qmp_log('blockdev-del', node_name='node0')
237    vm.qmp_log('blockdev-del', node_name='original-backing')
238
239    vm.shutdown()
240