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