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 25import iotests 26from iotests import log, qemu_img, qemu_io, qemu_io_silent, \ 27 qemu_io_pipe_and_status 28 29iotests.script_initialize( 30 supported_fmts=['qcow2'], 31 supported_platforms=['linux'], 32 required_fmts=['copy-before-write'], 33 unsupported_imgopts=['compat'] 34) 35 36patterns = [('0x5d', '0', '64k'), 37 ('0xd5', '1M', '64k'), 38 ('0xdc', '32M', '64k'), 39 ('0xcd', '0x3ff0000', '64k')] # 64M - 64K 40 41overwrite = [('0xab', '0', '64k'), # Full overwrite 42 ('0xad', '0x00f8000', '64k'), # Partial-left (1M-32K) 43 ('0x1d', '0x2008000', '64k'), # Partial-right (32M+32K) 44 ('0xea', '0x3fe0000', '64k')] # Adjacent-left (64M - 128K) 45 46zeroes = [('0', '0x00f8000', '32k'), # Left-end of partial-left (1M-32K) 47 ('0', '0x2010000', '32k'), # Right-end of partial-right (32M+64K) 48 ('0', '0x3fe0000', '64k')] # overwrite[3] 49 50remainder = [('0xd5', '0x108000', '32k'), # Right-end of partial-left [1] 51 ('0xdc', '32M', '32k'), # Left-end of partial-right [2] 52 ('0xcd', '0x3ff0000', '64k')] # patterns[3] 53 54def do_test(vm, use_cbw, use_snapshot_access_filter, base_img_path, 55 fleece_img_path, nbd_sock_path=None, 56 target_img_path=None, 57 bitmap=False): 58 push_backup = target_img_path is not None 59 assert (nbd_sock_path is not None) != push_backup 60 if push_backup: 61 assert use_cbw 62 63 log('--- Setting up images ---') 64 log('') 65 66 qemu_img('create', '-f', iotests.imgfmt, base_img_path, '64M') 67 if bitmap: 68 qemu_img('bitmap', '--add', base_img_path, 'bitmap0') 69 70 if use_snapshot_access_filter: 71 assert use_cbw 72 qemu_img('create', '-f', 'raw', fleece_img_path, '64M') 73 else: 74 qemu_img('create', '-f', 'qcow2', fleece_img_path, '64M') 75 76 if push_backup: 77 qemu_img('create', '-f', 'qcow2', target_img_path, '64M') 78 79 for p in patterns: 80 qemu_io('-f', iotests.imgfmt, 81 '-c', 'write -P%s %s %s' % p, base_img_path) 82 83 log('Done') 84 85 log('') 86 log('--- Launching VM ---') 87 log('') 88 89 src_node = 'source' 90 tmp_node = 'temp' 91 qom_path = '/machine/peripheral/sda' 92 vm.add_blockdev(f'driver={iotests.imgfmt},file.driver=file,' 93 f'file.filename={base_img_path},node-name={src_node}') 94 vm.add_device('virtio-scsi') 95 vm.add_device(f'scsi-hd,id=sda,drive={src_node}') 96 vm.launch() 97 log('Done') 98 99 log('') 100 log('--- Setting up Fleecing Graph ---') 101 log('') 102 103 104 if use_snapshot_access_filter: 105 log(vm.qmp('blockdev-add', { 106 'node-name': tmp_node, 107 'driver': 'file', 108 'filename': fleece_img_path, 109 })) 110 else: 111 # create tmp_node backed by src_node 112 log(vm.qmp('blockdev-add', { 113 'driver': 'qcow2', 114 'node-name': tmp_node, 115 'file': { 116 'driver': 'file', 117 'filename': fleece_img_path, 118 }, 119 'backing': src_node, 120 })) 121 122 # Establish CBW from source to fleecing node 123 if use_cbw: 124 fl_cbw = { 125 'driver': 'copy-before-write', 126 'node-name': 'fl-cbw', 127 'file': src_node, 128 'target': tmp_node 129 } 130 131 if bitmap: 132 fl_cbw['bitmap'] = {'node': src_node, 'name': 'bitmap0'} 133 134 log(vm.qmp('blockdev-add', fl_cbw)) 135 136 log(vm.qmp('qom-set', path=qom_path, property='drive', value='fl-cbw')) 137 138 if use_snapshot_access_filter: 139 log(vm.qmp('blockdev-add', { 140 'driver': 'snapshot-access', 141 'node-name': 'fl-access', 142 'file': 'fl-cbw', 143 })) 144 else: 145 log(vm.qmp('blockdev-backup', 146 job_id='fleecing', 147 device=src_node, 148 target=tmp_node, 149 sync='none')) 150 151 export_node = 'fl-access' if use_snapshot_access_filter else tmp_node 152 153 if push_backup: 154 log('') 155 log('--- Starting actual backup ---') 156 log('') 157 158 log(vm.qmp('blockdev-add', **{ 159 'driver': iotests.imgfmt, 160 'node-name': 'target', 161 'file': { 162 'driver': 'file', 163 'filename': target_img_path 164 } 165 })) 166 log(vm.qmp('blockdev-backup', device=export_node, 167 sync='full', target='target', 168 job_id='push-backup', speed=1)) 169 else: 170 log('') 171 log('--- Setting up NBD Export ---') 172 log('') 173 174 nbd_uri = 'nbd+unix:///%s?socket=%s' % (export_node, nbd_sock_path) 175 log(vm.qmp('nbd-server-start', 176 {'addr': { 'type': 'unix', 177 'data': { 'path': nbd_sock_path } } })) 178 179 log(vm.qmp('nbd-server-add', device=export_node)) 180 181 log('') 182 log('--- Sanity Check ---') 183 log('') 184 185 for p in patterns + zeroes: 186 cmd = 'read -P%s %s %s' % p 187 log(cmd) 188 out, ret = qemu_io_pipe_and_status('-r', '-f', 'raw', '-c', cmd, 189 nbd_uri) 190 if ret != 0: 191 print(out) 192 193 log('') 194 log('--- Testing COW ---') 195 log('') 196 197 for p in overwrite: 198 cmd = 'write -P%s %s %s' % p 199 log(cmd) 200 log(vm.hmp_qemu_io(qom_path, cmd, qdev=True)) 201 202 if push_backup: 203 # Check that previous operations were done during backup, not after 204 # If backup is already finished, it's possible that it was finished 205 # even before hmp qemu_io write, and we didn't actually test 206 # copy-before-write operation. This should not happen, as we use 207 # speed=1. But worth checking. 208 result = vm.qmp('query-block-jobs') 209 assert len(result['return']) == 1 210 211 result = vm.qmp('block-job-set-speed', device='push-backup', speed=0) 212 assert result == {'return': {}} 213 214 log(vm.event_wait(name='BLOCK_JOB_COMPLETED', 215 match={'data': {'device': 'push-backup'}}), 216 filters=[iotests.filter_qmp_event]) 217 log(vm.qmp('blockdev-del', node_name='target')) 218 219 log('') 220 log('--- Verifying Data ---') 221 log('') 222 223 for p in patterns + zeroes: 224 cmd = 'read -P%s %s %s' % p 225 log(cmd) 226 args = ['-r', '-c', cmd] 227 if push_backup: 228 args += [target_img_path] 229 else: 230 args += ['-f', 'raw', nbd_uri] 231 out, ret = qemu_io_pipe_and_status(*args) 232 if ret != 0: 233 print(out) 234 235 log('') 236 log('--- Cleanup ---') 237 log('') 238 239 if not push_backup: 240 log(vm.qmp('nbd-server-stop')) 241 242 if use_cbw: 243 if use_snapshot_access_filter: 244 log(vm.qmp('blockdev-del', node_name='fl-access')) 245 log(vm.qmp('qom-set', path=qom_path, property='drive', value=src_node)) 246 log(vm.qmp('blockdev-del', node_name='fl-cbw')) 247 else: 248 log(vm.qmp('block-job-cancel', device='fleecing')) 249 e = vm.event_wait('BLOCK_JOB_CANCELLED') 250 assert e is not None 251 log(e, filters=[iotests.filter_qmp_event]) 252 253 log(vm.qmp('blockdev-del', node_name=tmp_node)) 254 vm.shutdown() 255 256 log('') 257 log('--- Confirming writes ---') 258 log('') 259 260 for p in overwrite + remainder: 261 cmd = 'read -P%s %s %s' % p 262 log(cmd) 263 assert qemu_io_silent(base_img_path, '-c', cmd) == 0 264 265 log('') 266 log('Done') 267 268 269def test(use_cbw, use_snapshot_access_filter, 270 nbd_sock_path=None, target_img_path=None, bitmap=False): 271 with iotests.FilePath('base.img') as base_img_path, \ 272 iotests.FilePath('fleece.img') as fleece_img_path, \ 273 iotests.VM() as vm: 274 do_test(vm, use_cbw, use_snapshot_access_filter, base_img_path, 275 fleece_img_path, nbd_sock_path, target_img_path, 276 bitmap=bitmap) 277 278def test_pull(use_cbw, use_snapshot_access_filter, bitmap=False): 279 with iotests.FilePath('nbd.sock', 280 base_dir=iotests.sock_dir) as nbd_sock_path: 281 test(use_cbw, use_snapshot_access_filter, nbd_sock_path, None, 282 bitmap=bitmap) 283 284def test_push(): 285 with iotests.FilePath('target.img') as target_img_path: 286 test(True, True, None, target_img_path) 287 288 289log('=== Test backup(sync=none) based fleecing ===\n') 290test_pull(False, False) 291 292log('=== Test cbw-filter based fleecing ===\n') 293test_pull(True, False) 294 295log('=== Test fleecing-format based fleecing ===\n') 296test_pull(True, True) 297 298log('=== Test fleecing-format based fleecing with bitmap ===\n') 299test_pull(True, True, bitmap=True) 300 301log('=== Test push backup with fleecing ===\n') 302test_push() 303