1#!/usr/bin/env python 2# 3# Very specific tests for adjacent commit/stream block jobs 4# 5# Copyright (C) 2019 Red Hat, Inc. 6# 7# This program is free software; you can redistribute it and/or modify 8# it under the terms of the GNU General Public License as published by 9# the Free Software Foundation; either version 2 of the License, or 10# (at your option) any later version. 11# 12# This program is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU General Public License for more details. 16# 17# You should have received a copy of the GNU General Public License 18# along with this program. If not, see <http://www.gnu.org/licenses/>. 19# 20# Creator/Owner: Max Reitz <mreitz@redhat.com> 21 22import iotests 23from iotests import log, qemu_img, qemu_io_silent, \ 24 filter_qmp_testfiles, filter_qmp_imgfmt 25 26# Need backing file and change-backing-file support 27iotests.verify_image_format(supported_fmts=['qcow2', 'qed']) 28iotests.verify_platform(['linux']) 29 30 31# Returns a node for blockdev-add 32def node(node_name, path, backing=None, fmt=None, throttle=None): 33 if fmt is None: 34 fmt = iotests.imgfmt 35 36 res = { 37 'node-name': node_name, 38 'driver': fmt, 39 'file': { 40 'driver': 'file', 41 'filename': path 42 } 43 } 44 45 if backing is not None: 46 res['backing'] = backing 47 48 if throttle: 49 res['file'] = { 50 'driver': 'throttle', 51 'throttle-group': throttle, 52 'file': res['file'] 53 } 54 55 return res 56 57# Finds a node in the debug block graph 58def find_graph_node(graph, node_id): 59 return next(node for node in graph['nodes'] if node['id'] == node_id) 60 61 62def test_concurrent_finish(write_to_stream_node): 63 log('') 64 log('=== Commit and stream finish concurrently (letting %s write) ===' % \ 65 ('stream' if write_to_stream_node else 'commit')) 66 log('') 67 68 # All chosen in such a way that when the commit job wants to 69 # finish, it polls and thus makes stream finish concurrently -- 70 # and the other way around, depending on whether the commit job 71 # is finalized before stream completes or not. 72 73 with iotests.FilePath('node4.img') as node4_path, \ 74 iotests.FilePath('node3.img') as node3_path, \ 75 iotests.FilePath('node2.img') as node2_path, \ 76 iotests.FilePath('node1.img') as node1_path, \ 77 iotests.FilePath('node0.img') as node0_path, \ 78 iotests.VM() as vm: 79 80 # It is important to use raw for the base layer (so that 81 # permissions are just handed through to the protocol layer) 82 assert qemu_img('create', '-f', 'raw', node0_path, '64M') == 0 83 84 stream_throttle=None 85 commit_throttle=None 86 87 for path in [node1_path, node2_path, node3_path, node4_path]: 88 assert qemu_img('create', '-f', iotests.imgfmt, path, '64M') == 0 89 90 if write_to_stream_node: 91 # This is what (most of the time) makes commit finish 92 # earlier and then pull in stream 93 assert qemu_io_silent(node2_path, 94 '-c', 'write %iK 64K' % (65536 - 192), 95 '-c', 'write %iK 64K' % (65536 - 64)) == 0 96 97 stream_throttle='tg' 98 else: 99 # And this makes stream finish earlier 100 assert qemu_io_silent(node1_path, 101 '-c', 'write %iK 64K' % (65536 - 64)) == 0 102 103 commit_throttle='tg' 104 105 vm.launch() 106 107 vm.qmp_log('object-add', 108 qom_type='throttle-group', 109 id='tg', 110 props={ 111 'x-iops-write': 1, 112 'x-iops-write-max': 1 113 }) 114 115 vm.qmp_log('blockdev-add', 116 filters=[filter_qmp_testfiles, filter_qmp_imgfmt], 117 **node('node4', node4_path, throttle=stream_throttle, 118 backing=node('node3', node3_path, 119 backing=node('node2', node2_path, 120 backing=node('node1', node1_path, 121 backing=node('node0', node0_path, throttle=commit_throttle, 122 fmt='raw')))))) 123 124 vm.qmp_log('block-commit', 125 job_id='commit', 126 device='node4', 127 filter_node_name='commit-filter', 128 top_node='node1', 129 base_node='node0', 130 auto_finalize=False) 131 132 vm.qmp_log('block-stream', 133 job_id='stream', 134 device='node3', 135 base_node='commit-filter') 136 137 if write_to_stream_node: 138 vm.run_job('commit', auto_finalize=False, auto_dismiss=True) 139 vm.run_job('stream', auto_finalize=True, auto_dismiss=True) 140 else: 141 # No, the jobs do not really finish concurrently here, 142 # the stream job does complete strictly before commit. 143 # But still, this is close enough for what we want to 144 # test. 145 vm.run_job('stream', auto_finalize=True, auto_dismiss=True) 146 vm.run_job('commit', auto_finalize=False, auto_dismiss=True) 147 148 # Assert that the backing node of node3 is node 0 now 149 graph = vm.qmp('x-debug-query-block-graph')['return'] 150 for edge in graph['edges']: 151 if edge['name'] == 'backing' and \ 152 find_graph_node(graph, edge['parent'])['name'] == 'node3': 153 assert find_graph_node(graph, edge['child'])['name'] == 'node0' 154 break 155 156 157def main(): 158 log('Running tests:') 159 test_concurrent_finish(True) 160 test_concurrent_finish(False) 161 162if __name__ == '__main__': 163 main() 164