1#!/usr/bin/env python3 2# group: rw migration 3# 4# Migrate a VM with a BDS with backing nodes, which runs 5# bdrv_invalidate_cache(), which for qcow2 and qed triggers reading the 6# backing file string from the image header. Check whether this 7# interferes with bdrv_backing_overridden(). 8# 9# Copyright (C) 2022 Red Hat, Inc. 10# 11# This program is free software; you can redistribute it and/or modify 12# it under the terms of the GNU General Public License as published by 13# the Free Software Foundation; either version 2 of the License, or 14# (at your option) any later version. 15# 16# This program is distributed in the hope that it will be useful, 17# but WITHOUT ANY WARRANTY; without even the implied warranty of 18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19# GNU General Public License for more details. 20# 21# You should have received a copy of the GNU General Public License 22# along with this program. If not, see <http://www.gnu.org/licenses/>. 23# 24 25import json 26import os 27from typing import Optional 28 29import iotests 30from iotests import qemu_img_create, qemu_img_info 31 32 33image_size = 1 * 1024 * 1024 34imgs = [os.path.join(iotests.test_dir, f'{i}.img') for i in range(0, 4)] 35 36mig_sock = os.path.join(iotests.sock_dir, 'mig.sock') 37 38 39class TestPostMigrateFilename(iotests.QMPTestCase): 40 vm_s: Optional[iotests.VM] = None 41 vm_d: Optional[iotests.VM] = None 42 43 def setUp(self) -> None: 44 # Create backing chain of three images, where the backing file strings 45 # are json:{} filenames 46 qemu_img_create('-f', iotests.imgfmt, imgs[0], str(image_size)) 47 for i in range(1, 3): 48 backing = { 49 'driver': iotests.imgfmt, 50 'file': { 51 'driver': 'file', 52 'filename': imgs[i - 1] 53 } 54 } 55 qemu_img_create('-f', iotests.imgfmt, '-F', iotests.imgfmt, 56 '-b', 'json:' + json.dumps(backing), 57 imgs[i], str(image_size)) 58 59 def tearDown(self) -> None: 60 if self.vm_s is not None: 61 self.vm_s.shutdown() 62 if self.vm_d is not None: 63 self.vm_d.shutdown() 64 65 for img in imgs: 66 try: 67 os.remove(img) 68 except OSError: 69 pass 70 try: 71 os.remove(mig_sock) 72 except OSError: 73 pass 74 75 def test_migration(self) -> None: 76 """ 77 Migrate a VM with the backing chain created in setUp() attached. At 78 the end of the migration process, the destination will run 79 bdrv_invalidate_cache(), which for some image formats (qcow2 and qed) 80 means the backing file string is re-read from the image header. If 81 this overwrites bs->auto_backing_file, doing so may cause 82 bdrv_backing_overridden() to become true: The image header reports a 83 json:{} filename, but when opening it, bdrv_refresh_filename() will 84 simplify it to a plain simple filename; and when bs->auto_backing_file 85 and bs->backing->bs->filename differ, bdrv_backing_overridden() becomes 86 true. 87 If bdrv_backing_overridden() is true, the BDS will be forced to get a 88 json:{} filename, which in general is not the end of the world, but not 89 great. Check whether that happens, i.e. whether migration changes the 90 node's filename. 91 """ 92 93 blockdev = { 94 'node-name': 'node0', 95 'driver': iotests.imgfmt, 96 'file': { 97 'driver': 'file', 98 'filename': imgs[2] 99 } 100 } 101 102 self.vm_s = iotests.VM(path_suffix='a') \ 103 .add_blockdev(json.dumps(blockdev)) 104 self.vm_d = iotests.VM(path_suffix='b') \ 105 .add_blockdev(json.dumps(blockdev)) \ 106 .add_incoming(f'unix:{mig_sock}') 107 108 assert self.vm_s is not None 109 assert self.vm_d is not None 110 111 self.vm_s.launch() 112 self.vm_d.launch() 113 114 pre_mig_filename = self.vm_s.node_info('node0')['file'] 115 116 self.vm_s.qmp('migrate', uri=f'unix:{mig_sock}') 117 118 # Wait for migration to be done 119 self.vm_s.event_wait('STOP') 120 self.vm_d.event_wait('RESUME') 121 122 post_mig_filename = self.vm_d.node_info('node0')['file'] 123 124 # Verify that the filename hasn't changed from before the migration 125 self.assertEqual(pre_mig_filename, post_mig_filename) 126 127 self.vm_s.shutdown() 128 self.vm_s = None 129 130 # For good measure, try creating an overlay and check its backing 131 # chain below. This is how the issue was originally found. 132 self.vm_d.cmd('blockdev-snapshot-sync', 133 format=iotests.imgfmt, 134 snapshot_file=imgs[3], 135 node_name='node0', 136 snapshot_node_name='node0-overlay') 137 138 self.vm_d.shutdown() 139 self.vm_d = None 140 141 # Check the newly created overlay's backing chain 142 chain = qemu_img_info('--backing-chain', imgs[3]) 143 for index, image in enumerate(chain): 144 self.assertEqual(image['filename'], imgs[3 - index]) 145 146 147if __name__ == '__main__': 148 # These are the image formats that run their open() function from their 149 # .bdrv_co_invaliate_cache() implementations, so test them 150 iotests.main(supported_fmts=['qcow2', 'qed'], 151 supported_protocols=['file']) 152