1#!/usr/bin/env python3 2# group: rw quick 3# 4# Copyright (C) Red Hat, Inc. 5# 6# This program is free software; you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation; either version 2 of the License, or 9# (at your option) any later version. 10# 11# This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15# 16# You should have received a copy of the GNU General Public License 17# along with this program. If not, see <http://www.gnu.org/licenses/>. 18# 19# Creator/Owner: Kevin Wolf <kwolf@redhat.com> 20 21import iotests 22 23from iotests import QemuIoInteractive 24from iotests import filter_qemu_io, filter_qtest, filter_qmp_testfiles 25 26iotests.script_initialize(supported_fmts=['generic'], 27 unsupported_fmts=['luks'], 28 supported_protocols=['file'], 29 supported_platforms=['linux']) 30 31def get_export(node_name='disk-fmt', allow_inactive=None): 32 exp = { 33 'id': 'exp0', 34 'type': 'nbd', 35 'node-name': node_name, 36 'writable': True, 37 } 38 39 if allow_inactive is not None: 40 exp['allow-inactive'] = allow_inactive 41 42 return exp 43 44def node_is_active(_vm, node_name): 45 nodes = _vm.cmd('query-named-block-nodes', flat=True) 46 node = next(n for n in nodes if n['node-name'] == node_name) 47 return node['active'] 48 49with iotests.FilePath('disk.img') as path, \ 50 iotests.FilePath('snap.qcow2') as snap_path, \ 51 iotests.FilePath('snap2.qcow2') as snap2_path, \ 52 iotests.FilePath('target.img') as target_path, \ 53 iotests.FilePath('nbd.sock', base_dir=iotests.sock_dir) as nbd_sock, \ 54 iotests.VM() as vm: 55 56 img_size = '10M' 57 58 iotests.log('Preparing disk...') 59 iotests.qemu_img_create('-f', iotests.imgfmt, path, img_size) 60 iotests.qemu_img_create('-f', iotests.imgfmt, target_path, img_size) 61 62 iotests.qemu_img_create('-f', 'qcow2', '-b', path, '-F', iotests.imgfmt, 63 snap_path) 64 iotests.qemu_img_create('-f', 'qcow2', '-b', snap_path, '-F', 'qcow2', 65 snap2_path) 66 67 iotests.log('Launching VM...') 68 vm.add_blockdev(f'file,node-name=disk-file,filename={path}') 69 vm.add_blockdev(f'{iotests.imgfmt},file=disk-file,node-name=disk-fmt,' 70 'active=off') 71 vm.add_blockdev(f'file,node-name=target-file,filename={target_path}') 72 vm.add_blockdev(f'{iotests.imgfmt},file=target-file,node-name=target-fmt') 73 vm.add_blockdev(f'file,node-name=snap-file,filename={snap_path}') 74 vm.add_blockdev(f'file,node-name=snap2-file,filename={snap2_path}') 75 76 # Actually running the VM activates all images 77 vm.add_paused() 78 79 vm.launch() 80 vm.qmp_log('nbd-server-start', 81 addr={'type': 'unix', 'data':{'path': nbd_sock}}, 82 filters=[filter_qmp_testfiles]) 83 84 iotests.log('\n=== Creating export of inactive node ===') 85 86 iotests.log('\nExports activate nodes without allow-inactive') 87 iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) 88 vm.qmp_log('block-export-add', **get_export()) 89 iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) 90 vm.qmp_log('query-block-exports') 91 vm.qmp_log('block-export-del', id='exp0') 92 vm.event_wait('BLOCK_EXPORT_DELETED') 93 vm.qmp_log('query-block-exports') 94 95 iotests.log('\nExports activate nodes with allow-inactive=false') 96 vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=False) 97 iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) 98 vm.qmp_log('block-export-add', **get_export(allow_inactive=False)) 99 iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) 100 vm.qmp_log('query-block-exports') 101 vm.qmp_log('block-export-del', id='exp0') 102 vm.event_wait('BLOCK_EXPORT_DELETED') 103 vm.qmp_log('query-block-exports') 104 105 iotests.log('\nExport leaves nodes inactive with allow-inactive=true') 106 vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=False) 107 iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) 108 vm.qmp_log('block-export-add', **get_export(allow_inactive=True)) 109 iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) 110 vm.qmp_log('query-block-exports') 111 vm.qmp_log('block-export-del', id='exp0') 112 vm.event_wait('BLOCK_EXPORT_DELETED') 113 vm.qmp_log('query-block-exports') 114 115 iotests.log('\n=== Inactivating node with existing export ===') 116 117 iotests.log('\nInactivating nodes with an export fails without ' 118 'allow-inactive') 119 vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=True) 120 vm.qmp_log('block-export-add', **get_export(node_name='disk-fmt')) 121 vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=False) 122 iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) 123 vm.qmp_log('query-block-exports') 124 vm.qmp_log('block-export-del', id='exp0') 125 vm.event_wait('BLOCK_EXPORT_DELETED') 126 vm.qmp_log('query-block-exports') 127 128 iotests.log('\nInactivating nodes with an export fails with ' 129 'allow-inactive=false') 130 vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=True) 131 vm.qmp_log('block-export-add', 132 **get_export(node_name='disk-fmt', allow_inactive=False)) 133 vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=False) 134 iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) 135 vm.qmp_log('query-block-exports') 136 vm.qmp_log('block-export-del', id='exp0') 137 vm.event_wait('BLOCK_EXPORT_DELETED') 138 vm.qmp_log('query-block-exports') 139 140 iotests.log('\nInactivating nodes with an export works with ' 141 'allow-inactive=true') 142 vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=True) 143 vm.qmp_log('block-export-add', 144 **get_export(node_name='disk-fmt', allow_inactive=True)) 145 vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=False) 146 iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) 147 vm.qmp_log('query-block-exports') 148 vm.qmp_log('block-export-del', id='exp0') 149 vm.event_wait('BLOCK_EXPORT_DELETED') 150 vm.qmp_log('query-block-exports') 151 152 iotests.log('\n=== Inactive nodes with parent ===') 153 154 iotests.log('\nInactivating nodes with an active parent fails') 155 vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=True) 156 vm.qmp_log('blockdev-set-active', node_name='disk-file', active=False) 157 iotests.log('disk-file active: %s' % node_is_active(vm, 'disk-file')) 158 iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) 159 160 iotests.log('\nInactivating nodes with an inactive parent works') 161 vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=False) 162 vm.qmp_log('blockdev-set-active', node_name='disk-file', active=False) 163 iotests.log('disk-file active: %s' % node_is_active(vm, 'disk-file')) 164 iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) 165 166 iotests.log('\nCreating active parent node with an inactive child fails') 167 vm.qmp_log('blockdev-add', driver='raw', file='disk-fmt', 168 node_name='disk-filter') 169 vm.qmp_log('blockdev-add', driver='raw', file='disk-fmt', 170 node_name='disk-filter', active=True) 171 172 iotests.log('\nCreating inactive parent node with an inactive child works') 173 vm.qmp_log('blockdev-add', driver='raw', file='disk-fmt', 174 node_name='disk-filter', active=False) 175 vm.qmp_log('blockdev-del', node_name='disk-filter') 176 177 iotests.log('\n=== Resizing an inactive node ===') 178 vm.qmp_log('block_resize', node_name='disk-fmt', size=16*1024*1024) 179 180 iotests.log('\n=== Taking a snapshot of an inactive node ===') 181 182 iotests.log('\nActive overlay over inactive backing file automatically ' 183 'makes both inactive for compatibility') 184 vm.qmp_log('blockdev-add', driver='qcow2', node_name='snap-fmt', 185 file='snap-file', backing=None) 186 iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) 187 iotests.log('snap-fmt active: %s' % node_is_active(vm, 'snap-fmt')) 188 vm.qmp_log('blockdev-snapshot', node='disk-fmt', overlay='snap-fmt') 189 iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) 190 iotests.log('snap-fmt active: %s' % node_is_active(vm, 'snap-fmt')) 191 vm.qmp_log('blockdev-del', node_name='snap-fmt') 192 193 iotests.log('\nInactive overlay over inactive backing file just works') 194 vm.qmp_log('blockdev-add', driver='qcow2', node_name='snap-fmt', 195 file='snap-file', backing=None, active=False) 196 vm.qmp_log('blockdev-snapshot', node='disk-fmt', overlay='snap-fmt') 197 198 iotests.log('\n=== Block jobs with inactive nodes ===') 199 200 iotests.log('\nStreaming into an inactive node') 201 vm.qmp_log('block-stream', device='snap-fmt', 202 filters=[iotests.filter_qmp_generated_node_ids]) 203 204 iotests.log('\nCommitting an inactive root node (active commit)') 205 vm.qmp_log('block-commit', job_id='job0', device='snap-fmt', 206 filters=[iotests.filter_qmp_generated_node_ids]) 207 208 iotests.log('\nCommitting an inactive intermediate node to inactive base') 209 vm.qmp_log('blockdev-add', driver='qcow2', node_name='snap2-fmt', 210 file='snap2-file', backing='snap-fmt', active=False) 211 212 iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) 213 iotests.log('snap-fmt active: %s' % node_is_active(vm, 'snap-fmt')) 214 iotests.log('snap2-fmt active: %s' % node_is_active(vm, 'snap2-fmt')) 215 216 vm.qmp_log('block-commit', job_id='job0', device='snap2-fmt', 217 top_node='snap-fmt', 218 filters=[iotests.filter_qmp_generated_node_ids]) 219 220 iotests.log('\nCommitting an inactive intermediate node to active base') 221 vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=True) 222 vm.qmp_log('block-commit', job_id='job0', device='snap2-fmt', 223 top_node='snap-fmt', 224 filters=[iotests.filter_qmp_generated_node_ids]) 225 226 iotests.log('\nMirror from inactive source to active target') 227 vm.qmp_log('blockdev-mirror', job_id='job0', device='snap2-fmt', 228 target='target-fmt', sync='full', 229 filters=[iotests.filter_qmp_generated_node_ids]) 230 231 iotests.log('\nMirror from active source to inactive target') 232 233 iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) 234 iotests.log('snap-fmt active: %s' % node_is_active(vm, 'snap-fmt')) 235 iotests.log('snap2-fmt active: %s' % node_is_active(vm, 'snap2-fmt')) 236 iotests.log('target-fmt active: %s' % node_is_active(vm, 'target-fmt')) 237 238 # Activating snap2-fmt recursively activates the whole backing chain 239 vm.qmp_log('blockdev-set-active', node_name='snap2-fmt', active=True) 240 vm.qmp_log('blockdev-set-active', node_name='target-fmt', active=False) 241 242 iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) 243 iotests.log('snap-fmt active: %s' % node_is_active(vm, 'snap-fmt')) 244 iotests.log('snap2-fmt active: %s' % node_is_active(vm, 'snap2-fmt')) 245 iotests.log('target-fmt active: %s' % node_is_active(vm, 'target-fmt')) 246 247 vm.qmp_log('blockdev-mirror', job_id='job0', device='snap2-fmt', 248 target='target-fmt', sync='full', 249 filters=[iotests.filter_qmp_generated_node_ids]) 250 251 iotests.log('\nBackup from active source to inactive target') 252 253 vm.qmp_log('blockdev-backup', job_id='job0', device='snap2-fmt', 254 target='target-fmt', sync='full', 255 filters=[iotests.filter_qmp_generated_node_ids]) 256 257 iotests.log('\nBackup from inactive source to active target') 258 259 # Inactivating snap2-fmt recursively inactivates the whole backing chain 260 vm.qmp_log('blockdev-set-active', node_name='snap2-fmt', active=False) 261 vm.qmp_log('blockdev-set-active', node_name='target-fmt', active=True) 262 263 iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) 264 iotests.log('snap-fmt active: %s' % node_is_active(vm, 'snap-fmt')) 265 iotests.log('snap2-fmt active: %s' % node_is_active(vm, 'snap2-fmt')) 266 iotests.log('target-fmt active: %s' % node_is_active(vm, 'target-fmt')) 267 268 vm.qmp_log('blockdev-backup', job_id='job0', device='snap2-fmt', 269 target='target-fmt', sync='full', 270 filters=[iotests.filter_qmp_generated_node_ids]) 271 272 iotests.log('\n=== Accessing export on inactive node ===') 273 274 # Use the target node because it has the right image format and isn't the 275 # (read-only) backing file of a qcow2 node 276 vm.qmp_log('blockdev-set-active', node_name='target-fmt', active=False) 277 vm.qmp_log('block-export-add', 278 **get_export(node_name='target-fmt', allow_inactive=True)) 279 280 # The read should succeed, everything else should fail gracefully 281 qemu_io = QemuIoInteractive('-f', 'raw', 282 f'nbd+unix:///target-fmt?socket={nbd_sock}') 283 iotests.log(qemu_io.cmd('read 0 64k'), filters=[filter_qemu_io]) 284 iotests.log(qemu_io.cmd('write 0 64k'), filters=[filter_qemu_io]) 285 iotests.log(qemu_io.cmd('write -z 0 64k'), filters=[filter_qemu_io]) 286 iotests.log(qemu_io.cmd('write -zu 0 64k'), filters=[filter_qemu_io]) 287 iotests.log(qemu_io.cmd('discard 0 64k'), filters=[filter_qemu_io]) 288 iotests.log(qemu_io.cmd('flush'), filters=[filter_qemu_io]) 289 iotests.log(qemu_io.cmd('map'), filters=[filter_qemu_io]) 290 qemu_io.close() 291 292 iotests.log('\n=== Resuming VM activates all images ===') 293 vm.qmp_log('cont') 294 295 iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) 296 iotests.log('snap-fmt active: %s' % node_is_active(vm, 'snap-fmt')) 297 iotests.log('snap2-fmt active: %s' % node_is_active(vm, 'snap2-fmt')) 298 iotests.log('target-fmt active: %s' % node_is_active(vm, 'target-fmt')) 299 300 iotests.log('\nShutting down...') 301 vm.shutdown() 302 log = vm.get_log() 303 if log: 304 iotests.log(log, [filter_qtest, filter_qemu_io]) 305