1#!/usr/bin/env python3 2# group: rw quick 3# 4# This test covers the basic fleecing workflow, which provides a 5# point-in-time snapshot of a node that can be queried over NBD. 6# 7# Copyright (C) 2018 Red Hat, Inc. 8# John helped, too. 9# 10# This program is free software; you can redistribute it and/or modify 11# it under the terms of the GNU General Public License as published by 12# the Free Software Foundation; either version 2 of the License, or 13# (at your option) any later version. 14# 15# This program is distributed in the hope that it will be useful, 16# but WITHOUT ANY WARRANTY; without even the implied warranty of 17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18# GNU General Public License for more details. 19# 20# You should have received a copy of the GNU General Public License 21# along with this program. If not, see <http://www.gnu.org/licenses/>. 22# 23# Creator/Owner: John Snow <jsnow@redhat.com> 24 25from subprocess import CalledProcessError 26 27import iotests 28from iotests import log, qemu_img, qemu_io 29 30iotests.script_initialize( 31 supported_fmts=['qcow2'], 32 supported_platforms=['linux'], 33 required_fmts=['copy-before-write'], 34 unsupported_imgopts=['compat'] 35) 36 37patterns = [('0x5d', '0', '64k'), 38 ('0xd5', '1M', '64k'), 39 ('0xdc', '32M', '64k'), 40 ('0xcd', '0x3ff0000', '64k')] # 64M - 64K 41 42overwrite = [('0xab', '0', '64k'), # Full overwrite 43 ('0xad', '0x00f8000', '64k'), # Partial-left (1M-32K) 44 ('0x1d', '0x2008000', '64k'), # Partial-right (32M+32K) 45 ('0xea', '0x3fe0000', '64k')] # Adjacent-left (64M - 128K) 46 47zeroes = [('0', '0x00f8000', '32k'), # Left-end of partial-left (1M-32K) 48 ('0', '0x2010000', '32k'), # Right-end of partial-right (32M+64K) 49 ('0', '0x3fe0000', '64k')] # overwrite[3] 50 51remainder = [('0xd5', '0x108000', '32k'), # Right-end of partial-left [1] 52 ('0xdc', '32M', '32k'), # Left-end of partial-right [2] 53 ('0xcd', '0x3ff0000', '64k')] # patterns[3] 54 55def do_test(vm, use_cbw, use_snapshot_access_filter, base_img_path, 56 fleece_img_path, nbd_sock_path=None, 57 target_img_path=None, 58 bitmap=False): 59 push_backup = target_img_path is not None 60 assert (nbd_sock_path is not None) != push_backup 61 if push_backup: 62 assert use_cbw 63 64 log('--- Setting up images ---') 65 log('') 66 67 qemu_img('create', '-f', iotests.imgfmt, base_img_path, '64M') 68 if bitmap: 69 qemu_img('bitmap', '--add', base_img_path, 'bitmap0') 70 71 if use_snapshot_access_filter: 72 assert use_cbw 73 qemu_img('create', '-f', 'raw', fleece_img_path, '64M') 74 else: 75 qemu_img('create', '-f', 'qcow2', fleece_img_path, '64M') 76 77 if push_backup: 78 qemu_img('create', '-f', 'qcow2', target_img_path, '64M') 79 80 for p in patterns: 81 qemu_io('-f', iotests.imgfmt, 82 '-c', 'write -P%s %s %s' % p, base_img_path) 83 84 log('Done') 85 86 log('') 87 log('--- Launching VM ---') 88 log('') 89 90 src_node = 'source' 91 tmp_node = 'temp' 92 qom_path = '/machine/peripheral/sda' 93 vm.add_blockdev(f'driver={iotests.imgfmt},file.driver=file,' 94 f'file.filename={base_img_path},node-name={src_node}') 95 vm.add_device('virtio-scsi') 96 vm.add_device(f'scsi-hd,id=sda,drive={src_node}') 97 vm.launch() 98 log('Done') 99 100 log('') 101 log('--- Setting up Fleecing Graph ---') 102 log('') 103 104 105 if use_snapshot_access_filter: 106 log(vm.qmp('blockdev-add', { 107 'node-name': tmp_node, 108 'driver': 'file', 109 'filename': fleece_img_path, 110 })) 111 else: 112 # create tmp_node backed by src_node 113 log(vm.qmp('blockdev-add', { 114 'driver': 'qcow2', 115 'node-name': tmp_node, 116 'file': { 117 'driver': 'file', 118 'filename': fleece_img_path, 119 }, 120 'backing': src_node, 121 })) 122 123 # Establish CBW from source to fleecing node 124 if use_cbw: 125 fl_cbw = { 126 'driver': 'copy-before-write', 127 'node-name': 'fl-cbw', 128 'file': src_node, 129 'target': tmp_node 130 } 131 132 if bitmap: 133 fl_cbw['bitmap'] = {'node': src_node, 'name': 'bitmap0'} 134 135 log(vm.qmp('blockdev-add', fl_cbw)) 136 137 log(vm.qmp('qom-set', path=qom_path, property='drive', value='fl-cbw')) 138 139 if use_snapshot_access_filter: 140 log(vm.qmp('blockdev-add', { 141 'driver': 'snapshot-access', 142 'node-name': 'fl-access', 143 'file': 'fl-cbw', 144 })) 145 else: 146 log(vm.qmp('blockdev-backup', 147 job_id='fleecing', 148 device=src_node, 149 target=tmp_node, 150 sync='none')) 151 152 export_node = 'fl-access' if use_snapshot_access_filter else tmp_node 153 154 if push_backup: 155 log('') 156 log('--- Starting actual backup ---') 157 log('') 158 159 log(vm.qmp('blockdev-add', **{ 160 'driver': iotests.imgfmt, 161 'node-name': 'target', 162 'file': { 163 'driver': 'file', 164 'filename': target_img_path 165 } 166 })) 167 log(vm.qmp('blockdev-backup', device=export_node, 168 sync='full', target='target', 169 job_id='push-backup', speed=1)) 170 else: 171 log('') 172 log('--- Setting up NBD Export ---') 173 log('') 174 175 nbd_uri = 'nbd+unix:///%s?socket=%s' % (export_node, nbd_sock_path) 176 log(vm.qmp('nbd-server-start', 177 {'addr': { 'type': 'unix', 178 'data': { 'path': nbd_sock_path } } })) 179 180 log(vm.qmp('nbd-server-add', device=export_node)) 181 182 log('') 183 log('--- Sanity Check ---') 184 log('') 185 186 for p in patterns + zeroes: 187 cmd = 'read -P%s %s %s' % p 188 log(cmd) 189 190 try: 191 qemu_io('-r', '-f', 'raw', '-c', cmd, nbd_uri) 192 except CalledProcessError as exc: 193 if bitmap and p in zeroes: 194 log(exc.stdout) 195 else: 196 raise 197 198 log('') 199 log('--- Testing COW ---') 200 log('') 201 202 for p in overwrite: 203 cmd = 'write -P%s %s %s' % p 204 log(cmd) 205 log(vm.hmp_qemu_io(qom_path, cmd, qdev=True)) 206 207 if push_backup: 208 # Check that previous operations were done during backup, not after 209 # If backup is already finished, it's possible that it was finished 210 # even before hmp qemu_io write, and we didn't actually test 211 # copy-before-write operation. This should not happen, as we use 212 # speed=1. But worth checking. 213 result = vm.qmp('query-block-jobs') 214 assert len(result['return']) == 1 215 216 vm.cmd('block-job-set-speed', device='push-backup', speed=0) 217 218 log(vm.event_wait(name='BLOCK_JOB_COMPLETED', 219 match={'data': {'device': 'push-backup'}}), 220 filters=[iotests.filter_qmp_event]) 221 log(vm.qmp('blockdev-del', node_name='target')) 222 223 log('') 224 log('--- Verifying Data ---') 225 log('') 226 227 for p in patterns + zeroes: 228 cmd = 'read -P%s %s %s' % p 229 log(cmd) 230 args = ['-r', '-c', cmd] 231 if push_backup: 232 args += [target_img_path] 233 else: 234 args += ['-f', 'raw', nbd_uri] 235 236 try: 237 qemu_io(*args) 238 except CalledProcessError as exc: 239 if bitmap and p in zeroes: 240 log(exc.stdout) 241 else: 242 raise 243 244 log('') 245 log('--- Cleanup ---') 246 log('') 247 248 if not push_backup: 249 log(vm.qmp('nbd-server-stop')) 250 251 if use_cbw: 252 if use_snapshot_access_filter: 253 log(vm.qmp('blockdev-del', node_name='fl-access')) 254 log(vm.qmp('qom-set', path=qom_path, property='drive', value=src_node)) 255 log(vm.qmp('blockdev-del', node_name='fl-cbw')) 256 else: 257 log(vm.qmp('block-job-cancel', device='fleecing')) 258 e = vm.event_wait('BLOCK_JOB_CANCELLED') 259 assert e is not None 260 log(e, filters=[iotests.filter_qmp_event]) 261 262 log(vm.qmp('blockdev-del', node_name=tmp_node)) 263 vm.shutdown() 264 265 log('') 266 log('--- Confirming writes ---') 267 log('') 268 269 for p in overwrite + remainder: 270 cmd = 'read -P%s %s %s' % p 271 log(cmd) 272 qemu_io(base_img_path, '-c', cmd) 273 274 log('') 275 log('Done') 276 277 278def test(use_cbw, use_snapshot_access_filter, 279 nbd_sock_path=None, target_img_path=None, bitmap=False): 280 with iotests.FilePath('base.img') as base_img_path, \ 281 iotests.FilePath('fleece.img') as fleece_img_path, \ 282 iotests.VM() as vm: 283 do_test(vm, use_cbw, use_snapshot_access_filter, base_img_path, 284 fleece_img_path, nbd_sock_path, target_img_path, 285 bitmap=bitmap) 286 287def test_pull(use_cbw, use_snapshot_access_filter, bitmap=False): 288 with iotests.FilePath('nbd.sock', 289 base_dir=iotests.sock_dir) as nbd_sock_path: 290 test(use_cbw, use_snapshot_access_filter, nbd_sock_path, None, 291 bitmap=bitmap) 292 293def test_push(): 294 with iotests.FilePath('target.img') as target_img_path: 295 test(True, True, None, target_img_path) 296 297 298log('=== Test backup(sync=none) based fleecing ===\n') 299test_pull(False, False) 300 301log('=== Test cbw-filter based fleecing ===\n') 302test_pull(True, False) 303 304log('=== Test fleecing-format based fleecing ===\n') 305test_pull(True, True) 306 307log('=== Test fleecing-format based fleecing with bitmap ===\n') 308test_pull(True, True, bitmap=True) 309 310log('=== Test push backup with fleecing ===\n') 311test_push() 312