1#!/usr/bin/env python 2# 3# Copyright (C) 2018 Red Hat, Inc. 4# 5# This program is free software; you can redistribute it and/or modify 6# it under the terms of the GNU General Public License as published by 7# the Free Software Foundation; either version 2 of the License, or 8# (at your option) any later version. 9# 10# This program is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with this program. If not, see <http://www.gnu.org/licenses/>. 17# 18# Creator/Owner: Kevin Wolf <kwolf@redhat.com> 19# 20# Check using the job-* QMP commands with block jobs 21 22import iotests 23 24iotests.verify_image_format(supported_fmts=['qcow2']) 25 26def pause_wait(vm, job_id): 27 with iotests.Timeout(3, "Timeout waiting for job to pause"): 28 while True: 29 result = vm.qmp('query-jobs') 30 for job in result['return']: 31 if job['id'] == job_id and job['status'] in ['paused', 'standby']: 32 return job 33 34# Test that block-job-pause/resume and job-pause/resume can be mixed 35def test_pause_resume(vm): 36 for pause_cmd, pause_arg in [('block-job-pause', 'device'), 37 ('job-pause', 'id')]: 38 for resume_cmd, resume_arg in [('block-job-resume', 'device'), 39 ('job-resume', 'id')]: 40 iotests.log('=== Testing %s/%s ===' % (pause_cmd, resume_cmd)) 41 42 iotests.log(vm.qmp(pause_cmd, **{pause_arg: 'job0'})) 43 pause_wait(vm, 'job0') 44 iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) 45 result = vm.qmp('query-jobs') 46 iotests.log(result) 47 48 old_progress = result['return'][0]['current-progress'] 49 total_progress = result['return'][0]['total-progress'] 50 51 iotests.log(vm.qmp(resume_cmd, **{resume_arg: 'job0'})) 52 iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) 53 if old_progress < total_progress: 54 # Wait for the job to advance 55 while result['return'][0]['current-progress'] == old_progress: 56 result = vm.qmp('query-jobs') 57 iotests.log(result) 58 else: 59 # Already reached the end, so the job cannot advance 60 # any further; therefore, the query-jobs result can be 61 # logged immediately 62 iotests.log(vm.qmp('query-jobs')) 63 64def test_job_lifecycle(vm, job, job_args, has_ready=False): 65 iotests.log('') 66 iotests.log('') 67 iotests.log('Starting block job: %s (auto-finalize: %s; auto-dismiss: %s)' % 68 (job, 69 job_args.get('auto-finalize', True), 70 job_args.get('auto-dismiss', True))) 71 iotests.log(vm.qmp(job, job_id='job0', **job_args)) 72 73 # Depending on the storage, the first request may or may not have completed 74 # yet (and the total progress may not have been fully determined yet), so 75 # filter out the progress. Later query-job calls don't need the filtering 76 # because the progress is made deterministic by the block job speed 77 result = vm.qmp('query-jobs') 78 for j in result['return']: 79 j['current-progress'] = 'FILTERED' 80 j['total-progress'] = 'FILTERED' 81 iotests.log(result) 82 83 # undefined -> created -> running 84 iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) 85 iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) 86 87 # RUNNING state: 88 # pause/resume should work, complete/finalize/dismiss should error out 89 iotests.log('') 90 iotests.log('Pause/resume in RUNNING') 91 test_pause_resume(vm) 92 93 iotests.log(vm.qmp('job-complete', id='job0')) 94 iotests.log(vm.qmp('job-finalize', id='job0')) 95 iotests.log(vm.qmp('job-dismiss', id='job0')) 96 97 iotests.log(vm.qmp('block-job-complete', device='job0')) 98 iotests.log(vm.qmp('block-job-finalize', id='job0')) 99 iotests.log(vm.qmp('block-job-dismiss', id='job0')) 100 101 # Let the job complete (or transition to READY if it supports that) 102 iotests.log(vm.qmp('block-job-set-speed', device='job0', speed=0)) 103 if has_ready: 104 iotests.log('') 105 iotests.log('Waiting for READY state...') 106 vm.event_wait('BLOCK_JOB_READY') 107 iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) 108 iotests.log(vm.qmp('query-jobs')) 109 110 # READY state: 111 # pause/resume/complete should work, finalize/dismiss should error out 112 iotests.log('') 113 iotests.log('Pause/resume in READY') 114 test_pause_resume(vm) 115 116 iotests.log(vm.qmp('job-finalize', id='job0')) 117 iotests.log(vm.qmp('job-dismiss', id='job0')) 118 119 iotests.log(vm.qmp('block-job-finalize', id='job0')) 120 iotests.log(vm.qmp('block-job-dismiss', id='job0')) 121 122 # Transition to WAITING 123 iotests.log(vm.qmp('job-complete', id='job0')) 124 125 # Move to WAITING and PENDING state 126 iotests.log('') 127 iotests.log('Waiting for PENDING state...') 128 iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) 129 iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) 130 131 if not job_args.get('auto-finalize', True): 132 # PENDING state: 133 # finalize should work, pause/complete/dismiss should error out 134 iotests.log(vm.qmp('query-jobs')) 135 136 iotests.log(vm.qmp('job-pause', id='job0')) 137 iotests.log(vm.qmp('job-complete', id='job0')) 138 iotests.log(vm.qmp('job-dismiss', id='job0')) 139 140 iotests.log(vm.qmp('block-job-pause', device='job0')) 141 iotests.log(vm.qmp('block-job-complete', device='job0')) 142 iotests.log(vm.qmp('block-job-dismiss', id='job0')) 143 144 # Transition to CONCLUDED 145 iotests.log(vm.qmp('job-finalize', id='job0')) 146 147 148 # Move to CONCLUDED state 149 iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) 150 151 if not job_args.get('auto-dismiss', True): 152 # CONCLUDED state: 153 # dismiss should work, pause/complete/finalize should error out 154 iotests.log(vm.qmp('query-jobs')) 155 156 iotests.log(vm.qmp('job-pause', id='job0')) 157 iotests.log(vm.qmp('job-complete', id='job0')) 158 iotests.log(vm.qmp('job-finalize', id='job0')) 159 160 iotests.log(vm.qmp('block-job-pause', device='job0')) 161 iotests.log(vm.qmp('block-job-complete', device='job0')) 162 iotests.log(vm.qmp('block-job-finalize', id='job0')) 163 164 # Transition to NULL 165 iotests.log(vm.qmp('job-dismiss', id='job0')) 166 167 # Move to NULL state 168 iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) 169 iotests.log(vm.qmp('query-jobs')) 170 171 172with iotests.FilePath('disk.img') as disk_path, \ 173 iotests.FilePath('copy.img') as copy_path, \ 174 iotests.VM() as vm: 175 176 img_size = '4M' 177 iotests.qemu_img_create('-f', iotests.imgfmt, disk_path, img_size) 178 iotests.qemu_io('-c', 'write 0 %s' % (img_size), 179 '-f', iotests.imgfmt, disk_path) 180 181 iotests.log('Launching VM...') 182 vm.add_blockdev(vm.qmp_to_opts({ 183 'driver': iotests.imgfmt, 184 'node-name': 'drive0-node', 185 'file': { 186 'driver': 'file', 187 'filename': disk_path, 188 }, 189 })) 190 vm.launch() 191 192 # In order to keep things deterministic (especially progress in query-job, 193 # but related to this also automatic state transitions like job 194 # completion), but still get pause points often enough to avoid making this 195 # test very slow, it's important to have the right ratio between speed and 196 # buf_size. 197 # 198 # For backup, buf_size is hard-coded to the source image cluster size (64k), 199 # so we'll pick the same for mirror. The slice time, i.e. the granularity 200 # of the rate limiting is 100ms. With a speed of 256k per second, we can 201 # get four pause points per second. This gives us 250ms per iteration, 202 # which should be enough to stay deterministic. 203 204 test_job_lifecycle(vm, 'drive-mirror', has_ready=True, job_args={ 205 'device': 'drive0-node', 206 'target': copy_path, 207 'sync': 'full', 208 'speed': 262144, 209 'buf_size': 65536, 210 }) 211 212 for auto_finalize in [True, False]: 213 for auto_dismiss in [True, False]: 214 test_job_lifecycle(vm, 'drive-backup', job_args={ 215 'device': 'drive0-node', 216 'target': copy_path, 217 'sync': 'full', 218 'speed': 262144, 219 'auto-finalize': auto_finalize, 220 'auto-dismiss': auto_dismiss, 221 }) 222 223 vm.shutdown() 224