]>
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 | ||
26 | def 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 | |
35 | def 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'))) | |
83f90b53 HR |
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'] | |
bdebdc71 KW |
50 | |
51 | iotests.log(vm.qmp(resume_cmd, **{resume_arg: 'job0'})) | |
52 | iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) | |
83f90b53 HR |
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')) | |
bdebdc71 KW |
63 | |
64 | def 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 | |
83f90b53 HR |
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 | |
bdebdc71 KW |
77 | result = vm.qmp('query-jobs') |
78 | for j in result['return']: | |
83f90b53 HR |
79 | j['current-progress'] = 'FILTERED' |
80 | j['total-progress'] = 'FILTERED' | |
bdebdc71 KW |
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 | ||
172 | with 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() |