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 iotests.log(vm.qmp('query-jobs')) 46 47 iotests.log(vm.qmp(resume_cmd, **{resume_arg: 'job0'})) 48 iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) 49 iotests.log(vm.qmp('query-jobs')) 50 51def test_job_lifecycle(vm, job, job_args, has_ready=False): 52 iotests.log('') 53 iotests.log('') 54 iotests.log('Starting block job: %s (auto-finalize: %s; auto-dismiss: %s)' % 55 (job, 56 job_args.get('auto-finalize', True), 57 job_args.get('auto-dismiss', True))) 58 iotests.log(vm.qmp(job, job_id='job0', **job_args)) 59 60 # Depending on the storage, the first request may or may not have completed 61 # yet, so filter out the progress. Later query-job calls don't need the 62 # filtering because the progress is made deterministic by the block job 63 # speed 64 result = vm.qmp('query-jobs') 65 for j in result['return']: 66 del j['current-progress'] 67 iotests.log(result) 68 69 # undefined -> created -> running 70 iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) 71 iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) 72 73 # RUNNING state: 74 # pause/resume should work, complete/finalize/dismiss should error out 75 iotests.log('') 76 iotests.log('Pause/resume in RUNNING') 77 test_pause_resume(vm) 78 79 iotests.log(vm.qmp('job-complete', id='job0')) 80 iotests.log(vm.qmp('job-finalize', id='job0')) 81 iotests.log(vm.qmp('job-dismiss', id='job0')) 82 83 iotests.log(vm.qmp('block-job-complete', device='job0')) 84 iotests.log(vm.qmp('block-job-finalize', id='job0')) 85 iotests.log(vm.qmp('block-job-dismiss', id='job0')) 86 87 # Let the job complete (or transition to READY if it supports that) 88 iotests.log(vm.qmp('block-job-set-speed', device='job0', speed=0)) 89 if has_ready: 90 iotests.log('') 91 iotests.log('Waiting for READY state...') 92 vm.event_wait('BLOCK_JOB_READY') 93 iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) 94 iotests.log(vm.qmp('query-jobs')) 95 96 # READY state: 97 # pause/resume/complete should work, finalize/dismiss should error out 98 iotests.log('') 99 iotests.log('Pause/resume in READY') 100 test_pause_resume(vm) 101 102 iotests.log(vm.qmp('job-finalize', id='job0')) 103 iotests.log(vm.qmp('job-dismiss', id='job0')) 104 105 iotests.log(vm.qmp('block-job-finalize', id='job0')) 106 iotests.log(vm.qmp('block-job-dismiss', id='job0')) 107 108 # Transition to WAITING 109 iotests.log(vm.qmp('job-complete', id='job0')) 110 111 # Move to WAITING and PENDING state 112 iotests.log('') 113 iotests.log('Waiting for PENDING state...') 114 iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) 115 iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) 116 117 if not job_args.get('auto-finalize', True): 118 # PENDING state: 119 # finalize should work, pause/complete/dismiss should error out 120 iotests.log(vm.qmp('query-jobs')) 121 122 iotests.log(vm.qmp('job-pause', id='job0')) 123 iotests.log(vm.qmp('job-complete', id='job0')) 124 iotests.log(vm.qmp('job-dismiss', id='job0')) 125 126 iotests.log(vm.qmp('block-job-pause', device='job0')) 127 iotests.log(vm.qmp('block-job-complete', device='job0')) 128 iotests.log(vm.qmp('block-job-dismiss', id='job0')) 129 130 # Transition to CONCLUDED 131 iotests.log(vm.qmp('job-finalize', id='job0')) 132 133 134 # Move to CONCLUDED state 135 iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) 136 137 if not job_args.get('auto-dismiss', True): 138 # CONCLUDED state: 139 # dismiss should work, pause/complete/finalize should error out 140 iotests.log(vm.qmp('query-jobs')) 141 142 iotests.log(vm.qmp('job-pause', id='job0')) 143 iotests.log(vm.qmp('job-complete', id='job0')) 144 iotests.log(vm.qmp('job-finalize', id='job0')) 145 146 iotests.log(vm.qmp('block-job-pause', device='job0')) 147 iotests.log(vm.qmp('block-job-complete', device='job0')) 148 iotests.log(vm.qmp('block-job-finalize', id='job0')) 149 150 # Transition to NULL 151 iotests.log(vm.qmp('job-dismiss', id='job0')) 152 153 # Move to NULL state 154 iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) 155 iotests.log(vm.qmp('query-jobs')) 156 157 158with iotests.FilePath('disk.img') as disk_path, \ 159 iotests.FilePath('copy.img') as copy_path, \ 160 iotests.VM() as vm: 161 162 img_size = '4M' 163 iotests.qemu_img_create('-f', iotests.imgfmt, disk_path, img_size) 164 iotests.qemu_io('-c', 'write 0 %s' % (img_size), 165 '-f', iotests.imgfmt, disk_path) 166 167 iotests.log('Launching VM...') 168 vm.add_blockdev(vm.qmp_to_opts({ 169 'driver': iotests.imgfmt, 170 'node-name': 'drive0-node', 171 'file': { 172 'driver': 'file', 173 'filename': disk_path, 174 }, 175 })) 176 vm.launch() 177 178 # In order to keep things deterministic (especially progress in query-job, 179 # but related to this also automatic state transitions like job 180 # completion), but still get pause points often enough to avoid making this 181 # test very slow, it's important to have the right ratio between speed and 182 # buf_size. 183 # 184 # For backup, buf_size is hard-coded to the source image cluster size (64k), 185 # so we'll pick the same for mirror. The slice time, i.e. the granularity 186 # of the rate limiting is 100ms. With a speed of 256k per second, we can 187 # get four pause points per second. This gives us 250ms per iteration, 188 # which should be enough to stay deterministic. 189 190 test_job_lifecycle(vm, 'drive-mirror', has_ready=True, job_args={ 191 'device': 'drive0-node', 192 'target': copy_path, 193 'sync': 'full', 194 'speed': 262144, 195 'buf_size': 65536, 196 }) 197 198 for auto_finalize in [True, False]: 199 for auto_dismiss in [True, False]: 200 test_job_lifecycle(vm, 'drive-backup', job_args={ 201 'device': 'drive0-node', 202 'target': copy_path, 203 'sync': 'full', 204 'speed': 262144, 205 'auto-finalize': auto_finalize, 206 'auto-dismiss': auto_dismiss, 207 }) 208 209 vm.shutdown() 210