xref: /openbmc/qemu/tests/qemu-iotests/tests/inactive-node-nbd (revision 097966704932ee28dadbf75243108097b0f3e123)
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