4 # Copyright (C) 2018 Red Hat, Inc.
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.
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.
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/>.
19 # Creator/Owner: Kevin Wolf <kwolf@redhat.com>
21 # Check using the job-* QMP commands with block jobs
25 iotests.script_initialize(supported_fmts=['qcow2'])
27 img_size = 4 * 1024 * 1024
29 def pause_wait(vm, job_id):
30 with iotests.Timeout(3, "Timeout waiting for job to pause"):
32 result = vm.qmp('query-jobs')
33 for job in result['return']:
34 if job['id'] == job_id and job['status'] in ['paused', 'standby']:
37 # Test that block-job-pause/resume and job-pause/resume can be mixed
38 def test_pause_resume(vm):
39 for pause_cmd, pause_arg in [('block-job-pause', 'device'),
41 for resume_cmd, resume_arg in [('block-job-resume', 'device'),
42 ('job-resume', 'id')]:
43 iotests.log('=== Testing %s/%s ===' % (pause_cmd, resume_cmd))
45 iotests.log(vm.qmp(pause_cmd, **{pause_arg: 'job0'}))
46 pause_wait(vm, 'job0')
47 iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
48 result = vm.qmp('query-jobs')
51 old_progress = result['return'][0]['current-progress']
52 total_progress = result['return'][0]['total-progress']
54 iotests.log(vm.qmp(resume_cmd, **{resume_arg: 'job0'}))
55 iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
56 if old_progress < total_progress:
57 # Wait for the job to advance
58 while result['return'][0]['current-progress'] == old_progress:
59 result = vm.qmp('query-jobs')
62 # Already reached the end, so the job cannot advance
63 # any further; therefore, the query-jobs result can be
65 iotests.log(vm.qmp('query-jobs'))
67 def test_job_lifecycle(vm, job, job_args, has_ready=False, is_mirror=False):
72 iotests.log('Starting block job: %s (auto-finalize: %s; auto-dismiss: %s)' %
74 job_args.get('auto-finalize', True),
75 job_args.get('auto-dismiss', True)))
76 iotests.log(vm.qmp(job, job_id='job0', **job_args))
78 # Depending on the storage, the first request may or may not have completed
79 # yet (and the total progress may not have been fully determined yet), so
80 # filter out the progress. Later query-job calls don't need the filtering
81 # because the progress is made deterministic by the block job speed
82 result = vm.qmp('query-jobs')
83 for j in result['return']:
84 j['current-progress'] = 'FILTERED'
85 j['total-progress'] = 'FILTERED'
88 # undefined -> created -> running
89 iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
90 iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
92 # Wait for total-progress to stabilize
93 while vm.qmp('query-jobs')['return'][0]['total-progress'] < img_size:
97 # pause/resume should work, complete/finalize/dismiss should error out
99 iotests.log('Pause/resume in RUNNING')
100 test_pause_resume(vm)
102 iotests.log(vm.qmp('job-complete', id='job0'))
103 iotests.log(vm.qmp('job-finalize', id='job0'))
104 iotests.log(vm.qmp('job-dismiss', id='job0'))
106 iotests.log(vm.qmp('block-job-complete', device='job0'))
107 iotests.log(vm.qmp('block-job-finalize', id='job0'))
108 iotests.log(vm.qmp('block-job-dismiss', id='job0'))
110 # Let the job complete (or transition to READY if it supports that)
111 iotests.log(vm.qmp('block-job-set-speed', device='job0', speed=0))
114 iotests.log('Waiting for READY state...')
115 vm.event_wait('BLOCK_JOB_READY')
116 iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
117 iotests.log(vm.qmp('query-jobs'))
120 # pause/resume/complete should work, finalize/dismiss should error out
122 iotests.log('Pause/resume in READY')
123 test_pause_resume(vm)
125 iotests.log(vm.qmp('job-finalize', id='job0'))
126 iotests.log(vm.qmp('job-dismiss', id='job0'))
128 iotests.log(vm.qmp('block-job-finalize', id='job0'))
129 iotests.log(vm.qmp('block-job-dismiss', id='job0'))
131 # Transition to WAITING
132 iotests.log(vm.qmp('job-complete', id='job0'))
134 # Move to WAITING and PENDING state
136 iotests.log('Waiting for PENDING state...')
137 iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
138 iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
140 iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
141 iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
143 if not job_args.get('auto-finalize', True):
145 # finalize should work, pause/complete/dismiss should error out
146 iotests.log(vm.qmp('query-jobs'))
148 iotests.log(vm.qmp('job-pause', id='job0'))
149 iotests.log(vm.qmp('job-complete', id='job0'))
150 iotests.log(vm.qmp('job-dismiss', id='job0'))
152 iotests.log(vm.qmp('block-job-pause', device='job0'))
153 iotests.log(vm.qmp('block-job-complete', device='job0'))
154 iotests.log(vm.qmp('block-job-dismiss', id='job0'))
156 # Transition to CONCLUDED
157 iotests.log(vm.qmp('job-finalize', id='job0'))
160 # Move to CONCLUDED state
161 iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
163 if not job_args.get('auto-dismiss', True):
165 # dismiss should work, pause/complete/finalize should error out
166 iotests.log(vm.qmp('query-jobs'))
168 iotests.log(vm.qmp('job-pause', id='job0'))
169 iotests.log(vm.qmp('job-complete', id='job0'))
170 iotests.log(vm.qmp('job-finalize', id='job0'))
172 iotests.log(vm.qmp('block-job-pause', device='job0'))
173 iotests.log(vm.qmp('block-job-complete', device='job0'))
174 iotests.log(vm.qmp('block-job-finalize', id='job0'))
177 iotests.log(vm.qmp('job-dismiss', id='job0'))
180 iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE')))
181 iotests.log(vm.qmp('query-jobs'))
184 with iotests.FilePath('disk.img') as disk_path, \
185 iotests.FilePath('copy.img') as copy_path, \
188 iotests.qemu_img_create('-f', iotests.imgfmt, disk_path, str(img_size))
189 iotests.qemu_io('-c', 'write 0 %i' % (img_size),
190 '-f', iotests.imgfmt, disk_path)
192 iotests.log('Launching VM...')
193 vm.add_blockdev(vm.qmp_to_opts({
194 'driver': iotests.imgfmt,
195 'node-name': 'drive0-node',
198 'filename': disk_path,
203 # In order to keep things deterministic (especially progress in query-job,
204 # but related to this also automatic state transitions like job
205 # completion), but still get pause points often enough to avoid making this
206 # test very slow, it's important to have the right ratio between speed and
209 # Chose 64k copy-chunk-size both for mirror (by buf_size) and backup (by
210 # x-max-chunk). The slice time, i.e. the granularity of the rate limiting
211 # is 100ms. With a speed of 256k per second, we can get four pause points
212 # per second. This gives us 250ms per iteration, which should be enough to
213 # stay deterministic.
215 test_job_lifecycle(vm, 'drive-mirror', has_ready=True, job_args={
216 'device': 'drive0-node',
223 for auto_finalize in [True, False]:
224 for auto_dismiss in [True, False]:
225 test_job_lifecycle(vm, 'drive-backup', is_mirror=True, job_args={
226 'device': 'drive0-node',
230 'x-perf': {'max-chunk': 65536},
231 'auto-finalize': auto_finalize,
232 'auto-dismiss': auto_dismiss,