]>
Commit | Line | Data |
---|---|---|
bdebdc71 KW |
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 <[email protected]> | |
19 | # | |
20 | # Check using the job-* QMP commands with block jobs | |
21 | ||
22 | import iotests | |
23 | ||
24 | iotests.verify_image_format(supported_fmts=['qcow2']) | |
25 | ||
d9efe938 HR |
26 | img_size = 4 * 1024 * 1024 |
27 | ||
bdebdc71 KW |
28 | def pause_wait(vm, job_id): |
29 | with iotests.Timeout(3, "Timeout waiting for job to pause"): | |
30 | while True: | |
31 | result = vm.qmp('query-jobs') | |
32 | for job in result['return']: | |
33 | if job['id'] == job_id and job['status'] in ['paused', 'standby']: | |
34 | return job | |
35 | ||
36 | # Test that block-job-pause/resume and job-pause/resume can be mixed | |
37 | def test_pause_resume(vm): | |
38 | for pause_cmd, pause_arg in [('block-job-pause', 'device'), | |
39 | ('job-pause', 'id')]: | |
40 | for resume_cmd, resume_arg in [('block-job-resume', 'device'), | |
41 | ('job-resume', 'id')]: | |
42 | iotests.log('=== Testing %s/%s ===' % (pause_cmd, resume_cmd)) | |
43 | ||
44 | iotests.log(vm.qmp(pause_cmd, **{pause_arg: 'job0'})) | |
45 | pause_wait(vm, 'job0') | |
46 | iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) | |
83f90b53 HR |
47 | result = vm.qmp('query-jobs') |
48 | iotests.log(result) | |
49 | ||
50 | old_progress = result['return'][0]['current-progress'] | |
51 | total_progress = result['return'][0]['total-progress'] | |
bdebdc71 KW |
52 | |
53 | iotests.log(vm.qmp(resume_cmd, **{resume_arg: 'job0'})) | |
54 | iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) | |
83f90b53 HR |
55 | if old_progress < total_progress: |
56 | # Wait for the job to advance | |
57 | while result['return'][0]['current-progress'] == old_progress: | |
58 | result = vm.qmp('query-jobs') | |
59 | iotests.log(result) | |
60 | else: | |
61 | # Already reached the end, so the job cannot advance | |
62 | # any further; therefore, the query-jobs result can be | |
63 | # logged immediately | |
64 | iotests.log(vm.qmp('query-jobs')) | |
bdebdc71 KW |
65 | |
66 | def test_job_lifecycle(vm, job, job_args, has_ready=False): | |
d9efe938 HR |
67 | global img_size |
68 | ||
bdebdc71 KW |
69 | iotests.log('') |
70 | iotests.log('') | |
71 | iotests.log('Starting block job: %s (auto-finalize: %s; auto-dismiss: %s)' % | |
72 | (job, | |
73 | job_args.get('auto-finalize', True), | |
74 | job_args.get('auto-dismiss', True))) | |
75 | iotests.log(vm.qmp(job, job_id='job0', **job_args)) | |
76 | ||
77 | # Depending on the storage, the first request may or may not have completed | |
83f90b53 HR |
78 | # yet (and the total progress may not have been fully determined yet), so |
79 | # filter out the progress. Later query-job calls don't need the filtering | |
80 | # because the progress is made deterministic by the block job speed | |
bdebdc71 KW |
81 | result = vm.qmp('query-jobs') |
82 | for j in result['return']: | |
83f90b53 HR |
83 | j['current-progress'] = 'FILTERED' |
84 | j['total-progress'] = 'FILTERED' | |
bdebdc71 KW |
85 | iotests.log(result) |
86 | ||
87 | # undefined -> created -> running | |
88 | iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) | |
89 | iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) | |
90 | ||
d9efe938 HR |
91 | # Wait for total-progress to stabilize |
92 | while vm.qmp('query-jobs')['return'][0]['total-progress'] < img_size: | |
93 | pass | |
94 | ||
bdebdc71 KW |
95 | # RUNNING state: |
96 | # pause/resume should work, complete/finalize/dismiss should error out | |
97 | iotests.log('') | |
98 | iotests.log('Pause/resume in RUNNING') | |
99 | test_pause_resume(vm) | |
100 | ||
101 | iotests.log(vm.qmp('job-complete', id='job0')) | |
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-complete', device='job0')) | |
106 | iotests.log(vm.qmp('block-job-finalize', id='job0')) | |
107 | iotests.log(vm.qmp('block-job-dismiss', id='job0')) | |
108 | ||
109 | # Let the job complete (or transition to READY if it supports that) | |
110 | iotests.log(vm.qmp('block-job-set-speed', device='job0', speed=0)) | |
111 | if has_ready: | |
112 | iotests.log('') | |
113 | iotests.log('Waiting for READY state...') | |
114 | vm.event_wait('BLOCK_JOB_READY') | |
115 | iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) | |
116 | iotests.log(vm.qmp('query-jobs')) | |
117 | ||
118 | # READY state: | |
119 | # pause/resume/complete should work, finalize/dismiss should error out | |
120 | iotests.log('') | |
121 | iotests.log('Pause/resume in READY') | |
122 | test_pause_resume(vm) | |
123 | ||
124 | iotests.log(vm.qmp('job-finalize', id='job0')) | |
125 | iotests.log(vm.qmp('job-dismiss', id='job0')) | |
126 | ||
127 | iotests.log(vm.qmp('block-job-finalize', id='job0')) | |
128 | iotests.log(vm.qmp('block-job-dismiss', id='job0')) | |
129 | ||
130 | # Transition to WAITING | |
131 | iotests.log(vm.qmp('job-complete', id='job0')) | |
132 | ||
133 | # Move to WAITING and PENDING state | |
134 | iotests.log('') | |
135 | iotests.log('Waiting for PENDING state...') | |
136 | iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) | |
137 | iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) | |
138 | ||
139 | if not job_args.get('auto-finalize', True): | |
140 | # PENDING state: | |
141 | # finalize should work, pause/complete/dismiss should error out | |
142 | iotests.log(vm.qmp('query-jobs')) | |
143 | ||
144 | iotests.log(vm.qmp('job-pause', id='job0')) | |
145 | iotests.log(vm.qmp('job-complete', id='job0')) | |
146 | iotests.log(vm.qmp('job-dismiss', id='job0')) | |
147 | ||
148 | iotests.log(vm.qmp('block-job-pause', device='job0')) | |
149 | iotests.log(vm.qmp('block-job-complete', device='job0')) | |
150 | iotests.log(vm.qmp('block-job-dismiss', id='job0')) | |
151 | ||
152 | # Transition to CONCLUDED | |
153 | iotests.log(vm.qmp('job-finalize', id='job0')) | |
154 | ||
155 | ||
156 | # Move to CONCLUDED state | |
157 | iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) | |
158 | ||
159 | if not job_args.get('auto-dismiss', True): | |
160 | # CONCLUDED state: | |
161 | # dismiss should work, pause/complete/finalize should error out | |
162 | iotests.log(vm.qmp('query-jobs')) | |
163 | ||
164 | iotests.log(vm.qmp('job-pause', id='job0')) | |
165 | iotests.log(vm.qmp('job-complete', id='job0')) | |
166 | iotests.log(vm.qmp('job-finalize', id='job0')) | |
167 | ||
168 | iotests.log(vm.qmp('block-job-pause', device='job0')) | |
169 | iotests.log(vm.qmp('block-job-complete', device='job0')) | |
170 | iotests.log(vm.qmp('block-job-finalize', id='job0')) | |
171 | ||
172 | # Transition to NULL | |
173 | iotests.log(vm.qmp('job-dismiss', id='job0')) | |
174 | ||
175 | # Move to NULL state | |
176 | iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) | |
177 | iotests.log(vm.qmp('query-jobs')) | |
178 | ||
179 | ||
180 | with iotests.FilePath('disk.img') as disk_path, \ | |
181 | iotests.FilePath('copy.img') as copy_path, \ | |
182 | iotests.VM() as vm: | |
183 | ||
d9efe938 HR |
184 | iotests.qemu_img_create('-f', iotests.imgfmt, disk_path, str(img_size)) |
185 | iotests.qemu_io('-c', 'write 0 %i' % (img_size), | |
bdebdc71 KW |
186 | '-f', iotests.imgfmt, disk_path) |
187 | ||
188 | iotests.log('Launching VM...') | |
189 | vm.add_blockdev(vm.qmp_to_opts({ | |
190 | 'driver': iotests.imgfmt, | |
191 | 'node-name': 'drive0-node', | |
192 | 'file': { | |
193 | 'driver': 'file', | |
194 | 'filename': disk_path, | |
195 | }, | |
196 | })) | |
197 | vm.launch() | |
198 | ||
199 | # In order to keep things deterministic (especially progress in query-job, | |
200 | # but related to this also automatic state transitions like job | |
201 | # completion), but still get pause points often enough to avoid making this | |
202 | # test very slow, it's important to have the right ratio between speed and | |
203 | # buf_size. | |
204 | # | |
205 | # For backup, buf_size is hard-coded to the source image cluster size (64k), | |
206 | # so we'll pick the same for mirror. The slice time, i.e. the granularity | |
207 | # of the rate limiting is 100ms. With a speed of 256k per second, we can | |
208 | # get four pause points per second. This gives us 250ms per iteration, | |
209 | # which should be enough to stay deterministic. | |
210 | ||
211 | test_job_lifecycle(vm, 'drive-mirror', has_ready=True, job_args={ | |
212 | 'device': 'drive0-node', | |
213 | 'target': copy_path, | |
214 | 'sync': 'full', | |
215 | 'speed': 262144, | |
216 | 'buf_size': 65536, | |
217 | }) | |
218 | ||
219 | for auto_finalize in [True, False]: | |
220 | for auto_dismiss in [True, False]: | |
221 | test_job_lifecycle(vm, 'drive-backup', job_args={ | |
222 | 'device': 'drive0-node', | |
223 | 'target': copy_path, | |
224 | 'sync': 'full', | |
225 | 'speed': 262144, | |
226 | 'auto-finalize': auto_finalize, | |
227 | 'auto-dismiss': auto_dismiss, | |
228 | }) | |
229 | ||
230 | vm.shutdown() |