1283153f1SHanna Reitz#!/usr/bin/env python3
2283153f1SHanna Reitz# group: rw migration
3283153f1SHanna Reitz#
4283153f1SHanna Reitz# Migrate a VM with a BDS with backing nodes, which runs
5283153f1SHanna Reitz# bdrv_invalidate_cache(), which for qcow2 and qed triggers reading the
6283153f1SHanna Reitz# backing file string from the image header.  Check whether this
7283153f1SHanna Reitz# interferes with bdrv_backing_overridden().
8283153f1SHanna Reitz#
9283153f1SHanna Reitz# Copyright (C) 2022 Red Hat, Inc.
10283153f1SHanna Reitz#
11283153f1SHanna Reitz# This program is free software; you can redistribute it and/or modify
12283153f1SHanna Reitz# it under the terms of the GNU General Public License as published by
13283153f1SHanna Reitz# the Free Software Foundation; either version 2 of the License, or
14283153f1SHanna Reitz# (at your option) any later version.
15283153f1SHanna Reitz#
16283153f1SHanna Reitz# This program is distributed in the hope that it will be useful,
17283153f1SHanna Reitz# but WITHOUT ANY WARRANTY; without even the implied warranty of
18283153f1SHanna Reitz# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19283153f1SHanna Reitz# GNU General Public License for more details.
20283153f1SHanna Reitz#
21283153f1SHanna Reitz# You should have received a copy of the GNU General Public License
22283153f1SHanna Reitz# along with this program.  If not, see <http://www.gnu.org/licenses/>.
23283153f1SHanna Reitz#
24283153f1SHanna Reitz
25283153f1SHanna Reitzimport json
26283153f1SHanna Reitzimport os
27283153f1SHanna Reitzfrom typing import Optional
28283153f1SHanna Reitz
29283153f1SHanna Reitzimport iotests
30283153f1SHanna Reitzfrom iotests import qemu_img_create, qemu_img_info
31283153f1SHanna Reitz
32283153f1SHanna Reitz
33283153f1SHanna Reitzimage_size = 1 * 1024 * 1024
34283153f1SHanna Reitzimgs = [os.path.join(iotests.test_dir, f'{i}.img') for i in range(0, 4)]
35283153f1SHanna Reitz
36283153f1SHanna Reitzmig_sock = os.path.join(iotests.sock_dir, 'mig.sock')
37283153f1SHanna Reitz
38283153f1SHanna Reitz
39283153f1SHanna Reitzclass TestPostMigrateFilename(iotests.QMPTestCase):
40283153f1SHanna Reitz    vm_s: Optional[iotests.VM] = None
41283153f1SHanna Reitz    vm_d: Optional[iotests.VM] = None
42283153f1SHanna Reitz
43283153f1SHanna Reitz    def setUp(self) -> None:
44283153f1SHanna Reitz        # Create backing chain of three images, where the backing file strings
45283153f1SHanna Reitz        # are json:{} filenames
46283153f1SHanna Reitz        qemu_img_create('-f', iotests.imgfmt, imgs[0], str(image_size))
47283153f1SHanna Reitz        for i in range(1, 3):
48283153f1SHanna Reitz            backing = {
49283153f1SHanna Reitz                'driver': iotests.imgfmt,
50283153f1SHanna Reitz                'file': {
51283153f1SHanna Reitz                    'driver': 'file',
52283153f1SHanna Reitz                    'filename': imgs[i - 1]
53283153f1SHanna Reitz                }
54283153f1SHanna Reitz            }
55283153f1SHanna Reitz            qemu_img_create('-f', iotests.imgfmt, '-F', iotests.imgfmt,
56283153f1SHanna Reitz                            '-b', 'json:' + json.dumps(backing),
57283153f1SHanna Reitz                            imgs[i], str(image_size))
58283153f1SHanna Reitz
59283153f1SHanna Reitz    def tearDown(self) -> None:
60283153f1SHanna Reitz        if self.vm_s is not None:
61283153f1SHanna Reitz            self.vm_s.shutdown()
62283153f1SHanna Reitz        if self.vm_d is not None:
63283153f1SHanna Reitz            self.vm_d.shutdown()
64283153f1SHanna Reitz
65283153f1SHanna Reitz        for img in imgs:
66283153f1SHanna Reitz            try:
67283153f1SHanna Reitz                os.remove(img)
68283153f1SHanna Reitz            except OSError:
69283153f1SHanna Reitz                pass
70283153f1SHanna Reitz        try:
71283153f1SHanna Reitz            os.remove(mig_sock)
72283153f1SHanna Reitz        except OSError:
73283153f1SHanna Reitz            pass
74283153f1SHanna Reitz
75283153f1SHanna Reitz    def test_migration(self) -> None:
76283153f1SHanna Reitz        """
77283153f1SHanna Reitz        Migrate a VM with the backing chain created in setUp() attached.  At
78283153f1SHanna Reitz        the end of the migration process, the destination will run
79283153f1SHanna Reitz        bdrv_invalidate_cache(), which for some image formats (qcow2 and qed)
80283153f1SHanna Reitz        means the backing file string is re-read from the image header.  If
81283153f1SHanna Reitz        this overwrites bs->auto_backing_file, doing so may cause
82283153f1SHanna Reitz        bdrv_backing_overridden() to become true: The image header reports a
83283153f1SHanna Reitz        json:{} filename, but when opening it, bdrv_refresh_filename() will
84283153f1SHanna Reitz        simplify it to a plain simple filename; and when bs->auto_backing_file
85283153f1SHanna Reitz        and bs->backing->bs->filename differ, bdrv_backing_overridden() becomes
86283153f1SHanna Reitz        true.
87283153f1SHanna Reitz        If bdrv_backing_overridden() is true, the BDS will be forced to get a
88283153f1SHanna Reitz        json:{} filename, which in general is not the end of the world, but not
89283153f1SHanna Reitz        great.  Check whether that happens, i.e. whether migration changes the
90283153f1SHanna Reitz        node's filename.
91283153f1SHanna Reitz        """
92283153f1SHanna Reitz
93283153f1SHanna Reitz        blockdev = {
94283153f1SHanna Reitz            'node-name': 'node0',
95283153f1SHanna Reitz            'driver': iotests.imgfmt,
96283153f1SHanna Reitz            'file': {
97283153f1SHanna Reitz                'driver': 'file',
98283153f1SHanna Reitz                'filename': imgs[2]
99283153f1SHanna Reitz            }
100283153f1SHanna Reitz        }
101283153f1SHanna Reitz
102283153f1SHanna Reitz        self.vm_s = iotests.VM(path_suffix='a') \
103283153f1SHanna Reitz                           .add_blockdev(json.dumps(blockdev))
104283153f1SHanna Reitz        self.vm_d = iotests.VM(path_suffix='b') \
105283153f1SHanna Reitz                           .add_blockdev(json.dumps(blockdev)) \
106283153f1SHanna Reitz                           .add_incoming(f'unix:{mig_sock}')
107283153f1SHanna Reitz
108283153f1SHanna Reitz        assert self.vm_s is not None
109283153f1SHanna Reitz        assert self.vm_d is not None
110283153f1SHanna Reitz
111283153f1SHanna Reitz        self.vm_s.launch()
112283153f1SHanna Reitz        self.vm_d.launch()
113283153f1SHanna Reitz
114283153f1SHanna Reitz        pre_mig_filename = self.vm_s.node_info('node0')['file']
115283153f1SHanna Reitz
116283153f1SHanna Reitz        self.vm_s.qmp('migrate', uri=f'unix:{mig_sock}')
117283153f1SHanna Reitz
118283153f1SHanna Reitz        # Wait for migration to be done
119283153f1SHanna Reitz        self.vm_s.event_wait('STOP')
120283153f1SHanna Reitz        self.vm_d.event_wait('RESUME')
121283153f1SHanna Reitz
122283153f1SHanna Reitz        post_mig_filename = self.vm_d.node_info('node0')['file']
123283153f1SHanna Reitz
124283153f1SHanna Reitz        # Verify that the filename hasn't changed from before the migration
125283153f1SHanna Reitz        self.assertEqual(pre_mig_filename, post_mig_filename)
126283153f1SHanna Reitz
127283153f1SHanna Reitz        self.vm_s.shutdown()
128283153f1SHanna Reitz        self.vm_s = None
129283153f1SHanna Reitz
130283153f1SHanna Reitz        # For good measure, try creating an overlay and check its backing
131283153f1SHanna Reitz        # chain below.  This is how the issue was originally found.
132b6aed193SVladimir Sementsov-Ogievskiy        self.vm_d.cmd('blockdev-snapshot-sync',
133283153f1SHanna Reitz                      format=iotests.imgfmt,
134283153f1SHanna Reitz                      snapshot_file=imgs[3],
135283153f1SHanna Reitz                      node_name='node0',
136283153f1SHanna Reitz                      snapshot_node_name='node0-overlay')
137283153f1SHanna Reitz
138283153f1SHanna Reitz        self.vm_d.shutdown()
139283153f1SHanna Reitz        self.vm_d = None
140283153f1SHanna Reitz
141283153f1SHanna Reitz        # Check the newly created overlay's backing chain
142283153f1SHanna Reitz        chain = qemu_img_info('--backing-chain', imgs[3])
143283153f1SHanna Reitz        for index, image in enumerate(chain):
144283153f1SHanna Reitz            self.assertEqual(image['filename'], imgs[3 - index])
145283153f1SHanna Reitz
146283153f1SHanna Reitz
147283153f1SHanna Reitzif __name__ == '__main__':
148283153f1SHanna Reitz    # These are the image formats that run their open() function from their
149283153f1SHanna Reitz    # .bdrv_co_invaliate_cache() implementations, so test them
150283153f1SHanna Reitz    iotests.main(supported_fmts=['qcow2', 'qed'],
151283153f1SHanna Reitz                 supported_protocols=['file'])
152