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
28iotests.script_initialize(
29    supported_fmts=['qcow2', 'qcow', 'qed', 'vmdk', 'vhdx', 'raw'],
30    supported_platforms=['linux'],
31)
32
33patterns = [('0x5d', '0',         '64k'),
34            ('0xd5', '1M',        '64k'),
35            ('0xdc', '32M',       '64k'),
36            ('0xcd', '0x3ff0000', '64k')]  # 64M - 64K
37
38overwrite = [('0xab', '0',         '64k'), # Full overwrite
39             ('0xad', '0x00f8000', '64k'), # Partial-left (1M-32K)
40             ('0x1d', '0x2008000', '64k'), # Partial-right (32M+32K)
41             ('0xea', '0x3fe0000', '64k')] # Adjacent-left (64M - 128K)
42
43zeroes = [('0', '0x00f8000', '32k'), # Left-end of partial-left (1M-32K)
44          ('0', '0x2010000', '32k'), # Right-end of partial-right (32M+64K)
45          ('0', '0x3fe0000', '64k')] # overwrite[3]
46
47remainder = [('0xd5', '0x108000',  '32k'), # Right-end of partial-left [1]
48             ('0xdc', '32M',       '32k'), # Left-end of partial-right [2]
49             ('0xcd', '0x3ff0000', '64k')] # patterns[3]
50
51with iotests.FilePath('base.img') as base_img_path, \
52     iotests.FilePath('fleece.img') as fleece_img_path, \
53     iotests.FilePath('nbd.sock',
54                      base_dir=iotests.sock_dir) as nbd_sock_path, \
55     iotests.VM() as vm:
56
57    log('--- Setting up images ---')
58    log('')
59
60    assert qemu_img('create', '-f', iotests.imgfmt, base_img_path, '64M') == 0
61    assert qemu_img('create', '-f', 'qcow2', fleece_img_path, '64M') == 0
62
63    for p in patterns:
64        qemu_io('-f', iotests.imgfmt,
65                '-c', 'write -P%s %s %s' % p, base_img_path)
66
67    log('Done')
68
69    log('')
70    log('--- Launching VM ---')
71    log('')
72
73    vm.add_drive(base_img_path)
74    vm.launch()
75    log('Done')
76
77    log('')
78    log('--- Setting up Fleecing Graph ---')
79    log('')
80
81    src_node = 'drive0'
82    tgt_node = 'fleeceNode'
83
84    # create tgt_node backed by src_node
85    log(vm.qmp('blockdev-add', {
86        'driver': 'qcow2',
87        'node-name': tgt_node,
88        'file': {
89            'driver': 'file',
90            'filename': fleece_img_path,
91        },
92        'backing': src_node,
93    }))
94
95    # Establish COW from source to fleecing node
96    log(vm.qmp('blockdev-backup',
97               device=src_node,
98               target=tgt_node,
99               sync='none'))
100
101    log('')
102    log('--- Setting up NBD Export ---')
103    log('')
104
105    nbd_uri = 'nbd+unix:///%s?socket=%s' % (tgt_node, nbd_sock_path)
106    log(vm.qmp('nbd-server-start',
107               {'addr': { 'type': 'unix',
108                          'data': { 'path': nbd_sock_path } } }))
109
110    log(vm.qmp('nbd-server-add', device=tgt_node))
111
112    log('')
113    log('--- Sanity Check ---')
114    log('')
115
116    for p in patterns + zeroes:
117        cmd = 'read -P%s %s %s' % p
118        log(cmd)
119        assert qemu_io_silent('-r', '-f', 'raw', '-c', cmd, nbd_uri) == 0
120
121    log('')
122    log('--- Testing COW ---')
123    log('')
124
125    for p in overwrite:
126        cmd = 'write -P%s %s %s' % p
127        log(cmd)
128        log(vm.hmp_qemu_io(src_node, cmd))
129
130    log('')
131    log('--- Verifying Data ---')
132    log('')
133
134    for p in patterns + zeroes:
135        cmd = 'read -P%s %s %s' % p
136        log(cmd)
137        assert qemu_io_silent('-r', '-f', 'raw', '-c', cmd, nbd_uri) == 0
138
139    log('')
140    log('--- Cleanup ---')
141    log('')
142
143    log(vm.qmp('block-job-cancel', device=src_node))
144    e = vm.event_wait('BLOCK_JOB_CANCELLED')
145    assert e is not None
146    log(e, filters=[iotests.filter_qmp_event])
147    log(vm.qmp('nbd-server-stop'))
148    log(vm.qmp('blockdev-del', node_name=tgt_node))
149    vm.shutdown()
150
151    log('')
152    log('--- Confirming writes ---')
153    log('')
154
155    for p in overwrite + remainder:
156        cmd = 'read -P%s %s %s' % p
157        log(cmd)
158        assert qemu_io_silent(base_img_path, '-c', cmd) == 0
159
160    log('')
161    log('Done')
162