3 # Copyright (C) 2018 Red Hat, Inc.
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.
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.
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/>.
18 # Creator/Owner: Kevin Wolf <kwolf@redhat.com>
20 # Check using the job-* QMP commands with block jobs
24 iotests
.verify_image_format(supported_fmts
=['qcow2'])
26 def pause_wait(vm
, job_id
):
27 with iotests
.Timeout(3, "Timeout waiting for job to pause"):
29 result
= vm
.qmp('query-jobs')
30 for job
in result
['return']:
31 if job
['id'] == job_id
and job
['status'] in ['paused', 'standby']:
34 # Test that block-job-pause/resume and job-pause/resume can be mixed
35 def test_pause_resume(vm
):
36 for pause_cmd
, pause_arg
in [('block-job-pause', 'device'),
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
))
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')
48 old_progress
= result
['return'][0]['current-progress']
49 total_progress
= result
['return'][0]['total-progress']
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')
59 # Already reached the end, so the job cannot advance
60 # any further; therefore, the query-jobs result can be
62 iotests
.log(vm
.qmp('query-jobs'))
64 def test_job_lifecycle(vm
, job
, job_args
, has_ready
=False):
67 iotests
.log('Starting block job: %s (auto-finalize: %s; auto-dismiss: %s)' %
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
))
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'
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')))
88 # pause/resume should work, complete/finalize/dismiss should error out
90 iotests
.log('Pause/resume in RUNNING')
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'))
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'))
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))
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'))
111 # pause/resume/complete should work, finalize/dismiss should error out
113 iotests
.log('Pause/resume in READY')
114 test_pause_resume(vm
)
116 iotests
.log(vm
.qmp('job-finalize', id='job0'))
117 iotests
.log(vm
.qmp('job-dismiss', id='job0'))
119 iotests
.log(vm
.qmp('block-job-finalize', id='job0'))
120 iotests
.log(vm
.qmp('block-job-dismiss', id='job0'))
122 # Transition to WAITING
123 iotests
.log(vm
.qmp('job-complete', id='job0'))
125 # Move to WAITING and PENDING state
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')))
131 if not job_args
.get('auto-finalize', True):
133 # finalize should work, pause/complete/dismiss should error out
134 iotests
.log(vm
.qmp('query-jobs'))
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'))
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'))
144 # Transition to CONCLUDED
145 iotests
.log(vm
.qmp('job-finalize', id='job0'))
148 # Move to CONCLUDED state
149 iotests
.log(iotests
.filter_qmp_event(vm
.event_wait('JOB_STATUS_CHANGE')))
151 if not job_args
.get('auto-dismiss', True):
153 # dismiss should work, pause/complete/finalize should error out
154 iotests
.log(vm
.qmp('query-jobs'))
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'))
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'))
165 iotests
.log(vm
.qmp('job-dismiss', id='job0'))
168 iotests
.log(iotests
.filter_qmp_event(vm
.event_wait('JOB_STATUS_CHANGE')))
169 iotests
.log(vm
.qmp('query-jobs'))
172 with iotests
.FilePath('disk.img') as disk_path
, \
173 iotests
.FilePath('copy.img') as copy_path
, \
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
)
181 iotests
.log('Launching VM...')
182 vm
.add_blockdev(vm
.qmp_to_opts({
183 'driver': iotests
.imgfmt
,
184 'node-name': 'drive0-node',
187 'filename': disk_path
,
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
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.
204 test_job_lifecycle(vm
, 'drive-mirror', has_ready
=True, job_args
={
205 'device': 'drive0-node',
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',
219 'auto-finalize': auto_finalize
,
220 'auto-dismiss': auto_dismiss
,