]>
Commit | Line | Data |
---|---|---|
37ce63eb SH |
1 | #!/usr/bin/env python |
2 | # | |
3 | # Tests for image streaming. | |
4 | # | |
5 | # Copyright (C) 2012 IBM Corp. | |
6 | # | |
7 | # This program is free software; you can redistribute it and/or modify | |
8 | # it under the terms of the GNU General Public License as published by | |
9 | # the Free Software Foundation; either version 2 of the License, or | |
10 | # (at your option) any later version. | |
11 | # | |
12 | # This program is distributed in the hope that it will be useful, | |
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | # GNU General Public License for more details. | |
16 | # | |
17 | # You should have received a copy of the GNU General Public License | |
18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 | # | |
20 | ||
0c817347 | 21 | import time |
37ce63eb SH |
22 | import os |
23 | import iotests | |
24 | from iotests import qemu_img, qemu_io | |
25 | ||
26 | backing_img = os.path.join(iotests.test_dir, 'backing.img') | |
6e343609 | 27 | mid_img = os.path.join(iotests.test_dir, 'mid.img') |
37ce63eb SH |
28 | test_img = os.path.join(iotests.test_dir, 'test.img') |
29 | ||
2499a096 | 30 | class TestSingleDrive(iotests.QMPTestCase): |
37ce63eb SH |
31 | image_len = 1 * 1024 * 1024 # MB |
32 | ||
33 | def setUp(self): | |
2499a096 | 34 | iotests.create_image(backing_img, TestSingleDrive.image_len) |
6e343609 PB |
35 | qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, mid_img) |
36 | qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % mid_img, test_img) | |
90c9b167 | 37 | qemu_io('-f', 'raw', '-c', 'write -P 0x1 0 512', backing_img) |
5e302a7d | 38 | qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x1 524288 512', mid_img) |
7b8a9e5a | 39 | self.vm = iotests.VM().add_drive("blkdebug::" + test_img, "backing.node-name=mid") |
37ce63eb SH |
40 | self.vm.launch() |
41 | ||
42 | def tearDown(self): | |
43 | self.vm.shutdown() | |
44 | os.remove(test_img) | |
6e343609 | 45 | os.remove(mid_img) |
37ce63eb SH |
46 | os.remove(backing_img) |
47 | ||
48 | def test_stream(self): | |
ecc1c88e | 49 | self.assert_no_active_block_jobs() |
37ce63eb | 50 | |
db58f9c0 | 51 | result = self.vm.qmp('block-stream', device='drive0') |
37ce63eb SH |
52 | self.assert_qmp(result, 'return', {}) |
53 | ||
9974ad40 | 54 | self.wait_until_completed() |
37ce63eb | 55 | |
ecc1c88e | 56 | self.assert_no_active_block_jobs() |
863a5d04 | 57 | self.vm.shutdown() |
37ce63eb | 58 | |
90c9b167 KW |
59 | self.assertEqual(qemu_io('-f', 'raw', '-c', 'map', backing_img), |
60 | qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img), | |
efcc7a23 | 61 | 'image file map does not match backing file after streaming') |
37ce63eb | 62 | |
7b8a9e5a AG |
63 | def test_stream_intermediate(self): |
64 | self.assert_no_active_block_jobs() | |
65 | ||
66 | self.assertNotEqual(qemu_io('-f', 'raw', '-c', 'map', backing_img), | |
67 | qemu_io('-f', iotests.imgfmt, '-c', 'map', mid_img), | |
68 | 'image file map matches backing file before streaming') | |
69 | ||
70 | result = self.vm.qmp('block-stream', device='mid', job_id='stream-mid') | |
71 | self.assert_qmp(result, 'return', {}) | |
72 | ||
73 | self.wait_until_completed(drive='stream-mid') | |
74 | ||
75 | self.assert_no_active_block_jobs() | |
76 | self.vm.shutdown() | |
77 | ||
78 | self.assertEqual(qemu_io('-f', 'raw', '-c', 'map', backing_img), | |
79 | qemu_io('-f', iotests.imgfmt, '-c', 'map', mid_img), | |
80 | 'image file map does not match backing file after streaming') | |
81 | ||
0c817347 | 82 | def test_stream_pause(self): |
ecc1c88e | 83 | self.assert_no_active_block_jobs() |
0c817347 | 84 | |
b59b3d57 | 85 | self.vm.pause_drive('drive0') |
0c817347 PB |
86 | result = self.vm.qmp('block-stream', device='drive0') |
87 | self.assert_qmp(result, 'return', {}) | |
88 | ||
89 | result = self.vm.qmp('block-job-pause', device='drive0') | |
90 | self.assert_qmp(result, 'return', {}) | |
91 | ||
92 | time.sleep(1) | |
93 | result = self.vm.qmp('query-block-jobs') | |
94 | offset = self.dictpath(result, 'return[0]/offset') | |
95 | ||
96 | time.sleep(1) | |
97 | result = self.vm.qmp('query-block-jobs') | |
98 | self.assert_qmp(result, 'return[0]/offset', offset) | |
99 | ||
100 | result = self.vm.qmp('block-job-resume', device='drive0') | |
101 | self.assert_qmp(result, 'return', {}) | |
102 | ||
b59b3d57 | 103 | self.vm.resume_drive('drive0') |
9974ad40 | 104 | self.wait_until_completed() |
0c817347 | 105 | |
ecc1c88e | 106 | self.assert_no_active_block_jobs() |
0c817347 PB |
107 | self.vm.shutdown() |
108 | ||
90c9b167 KW |
109 | self.assertEqual(qemu_io('-f', 'raw', '-c', 'map', backing_img), |
110 | qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img), | |
0c817347 PB |
111 | 'image file map does not match backing file after streaming') |
112 | ||
409d5498 AG |
113 | def test_stream_no_op(self): |
114 | self.assert_no_active_block_jobs() | |
115 | ||
116 | # The image map is empty before the operation | |
117 | empty_map = qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img) | |
118 | ||
119 | # This is a no-op: no data should ever be copied from the base image | |
120 | result = self.vm.qmp('block-stream', device='drive0', base=mid_img) | |
121 | self.assert_qmp(result, 'return', {}) | |
122 | ||
123 | self.wait_until_completed() | |
124 | ||
125 | self.assert_no_active_block_jobs() | |
126 | self.vm.shutdown() | |
127 | ||
128 | self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img), | |
129 | empty_map, 'image file map changed after a no-op') | |
130 | ||
6e343609 | 131 | def test_stream_partial(self): |
ecc1c88e | 132 | self.assert_no_active_block_jobs() |
6e343609 | 133 | |
5e302a7d | 134 | result = self.vm.qmp('block-stream', device='drive0', base=backing_img) |
6e343609 PB |
135 | self.assert_qmp(result, 'return', {}) |
136 | ||
9974ad40 | 137 | self.wait_until_completed() |
6e343609 | 138 | |
ecc1c88e | 139 | self.assert_no_active_block_jobs() |
6e343609 PB |
140 | self.vm.shutdown() |
141 | ||
90c9b167 KW |
142 | self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', mid_img), |
143 | qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img), | |
6e343609 PB |
144 | 'image file map does not match backing file after streaming') |
145 | ||
37ce63eb | 146 | def test_device_not_found(self): |
db58f9c0 | 147 | result = self.vm.qmp('block-stream', device='nonexistent') |
b6c1bae5 | 148 | self.assert_qmp(result, 'error/class', 'GenericError') |
37ce63eb | 149 | |
774a8850 | 150 | |
c1a34322 AG |
151 | class TestParallelOps(iotests.QMPTestCase): |
152 | num_ops = 4 # Number of parallel block-stream operations | |
153 | num_imgs = num_ops * 2 + 1 | |
154 | image_len = num_ops * 1024 * 1024 | |
155 | imgs = [] | |
156 | ||
157 | def setUp(self): | |
158 | opts = [] | |
159 | self.imgs = [] | |
160 | ||
161 | # Initialize file names and command-line options | |
162 | for i in range(self.num_imgs): | |
163 | img_depth = self.num_imgs - i - 1 | |
164 | opts.append("backing." * img_depth + "node-name=node%d" % i) | |
165 | self.imgs.append(os.path.join(iotests.test_dir, 'img-%d.img' % i)) | |
166 | ||
167 | # Create all images | |
168 | iotests.create_image(self.imgs[0], self.image_len) | |
169 | for i in range(1, self.num_imgs): | |
170 | qemu_img('create', '-f', iotests.imgfmt, | |
171 | '-o', 'backing_file=%s' % self.imgs[i-1], self.imgs[i]) | |
172 | ||
173 | # Put data into the images we are copying data from | |
174 | for i in range(self.num_imgs / 2): | |
175 | img_index = i * 2 + 1 | |
176 | # Alternate between 512k and 1M. | |
177 | # This way jobs will not finish in the same order they were created | |
178 | num_kb = 512 + 512 * (i % 2) | |
179 | qemu_io('-f', iotests.imgfmt, | |
180 | '-c', 'write -P %d %d %d' % (i, i*1024*1024, num_kb * 1024), | |
181 | self.imgs[img_index]) | |
182 | ||
183 | # Attach the drive to the VM | |
184 | self.vm = iotests.VM() | |
185 | self.vm.add_drive(self.imgs[-1], ','.join(opts)) | |
186 | self.vm.launch() | |
187 | ||
188 | def tearDown(self): | |
189 | self.vm.shutdown() | |
190 | for img in self.imgs: | |
191 | os.remove(img) | |
192 | ||
193 | # Test that it's possible to run several block-stream operations | |
194 | # in parallel in the same snapshot chain | |
195 | def test_stream_parallel(self): | |
196 | self.assert_no_active_block_jobs() | |
197 | ||
198 | # Check that the maps don't match before the streaming operations | |
199 | for i in range(2, self.num_imgs, 2): | |
200 | self.assertNotEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[i]), | |
201 | qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[i-1]), | |
202 | 'image file map matches backing file before streaming') | |
203 | ||
204 | # Create all streaming jobs | |
205 | pending_jobs = [] | |
206 | for i in range(2, self.num_imgs, 2): | |
207 | node_name = 'node%d' % i | |
208 | job_id = 'stream-%s' % node_name | |
209 | pending_jobs.append(job_id) | |
210 | result = self.vm.qmp('block-stream', device=node_name, job_id=job_id, base=self.imgs[i-2], speed=512*1024) | |
211 | self.assert_qmp(result, 'return', {}) | |
212 | ||
213 | # Wait for all jobs to be finished. | |
214 | while len(pending_jobs) > 0: | |
215 | for event in self.vm.get_qmp_events(wait=True): | |
216 | if event['event'] == 'BLOCK_JOB_COMPLETED': | |
217 | job_id = self.dictpath(event, 'data/device') | |
218 | self.assertTrue(job_id in pending_jobs) | |
219 | self.assert_qmp_absent(event, 'data/error') | |
220 | pending_jobs.remove(job_id) | |
221 | ||
222 | self.assert_no_active_block_jobs() | |
223 | self.vm.shutdown() | |
224 | ||
225 | # Check that all maps match now | |
226 | for i in range(2, self.num_imgs, 2): | |
227 | self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[i]), | |
228 | qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[i-1]), | |
229 | 'image file map does not match backing file after streaming') | |
230 | ||
eb290b78 AG |
231 | # Test that it's not possible to perform two block-stream |
232 | # operations if there are nodes involved in both. | |
233 | def test_overlapping_1(self): | |
234 | self.assert_no_active_block_jobs() | |
235 | ||
236 | # Set a speed limit to make sure that this job blocks the rest | |
237 | result = self.vm.qmp('block-stream', device='node4', job_id='stream-node4', base=self.imgs[1], speed=1024*1024) | |
238 | self.assert_qmp(result, 'return', {}) | |
239 | ||
240 | result = self.vm.qmp('block-stream', device='node5', job_id='stream-node5', base=self.imgs[2]) | |
241 | self.assert_qmp(result, 'error/class', 'GenericError') | |
242 | ||
243 | result = self.vm.qmp('block-stream', device='node3', job_id='stream-node3', base=self.imgs[2]) | |
244 | self.assert_qmp(result, 'error/class', 'GenericError') | |
245 | ||
246 | result = self.vm.qmp('block-stream', device='node4', job_id='stream-node4-v2') | |
247 | self.assert_qmp(result, 'error/class', 'GenericError') | |
248 | ||
249 | # block-commit should also fail if it touches nodes used by the stream job | |
250 | result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[4], job_id='commit-node4') | |
251 | self.assert_qmp(result, 'error/class', 'GenericError') | |
252 | ||
253 | result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[1], top=self.imgs[3], job_id='commit-node1') | |
254 | self.assert_qmp(result, 'error/class', 'GenericError') | |
255 | ||
256 | # This fails because it needs to modify the backing string in node2, which is blocked | |
257 | result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[0], top=self.imgs[1], job_id='commit-node0') | |
258 | self.assert_qmp(result, 'error/class', 'GenericError') | |
259 | ||
260 | self.wait_until_completed(drive='stream-node4') | |
261 | self.assert_no_active_block_jobs() | |
262 | ||
263 | # Similar to test_overlapping_1, but with block-commit | |
264 | # blocking the other jobs | |
265 | def test_overlapping_2(self): | |
266 | self.assertLessEqual(9, self.num_imgs) | |
267 | self.assert_no_active_block_jobs() | |
268 | ||
269 | # Set a speed limit to make sure that this job blocks the rest | |
270 | result = self.vm.qmp('block-commit', device='drive0', top=self.imgs[5], base=self.imgs[3], job_id='commit-node3', speed=1024*1024) | |
271 | self.assert_qmp(result, 'return', {}) | |
272 | ||
273 | result = self.vm.qmp('block-stream', device='node3', job_id='stream-node3') | |
274 | self.assert_qmp(result, 'error/class', 'GenericError') | |
275 | ||
276 | result = self.vm.qmp('block-stream', device='node6', base=self.imgs[2], job_id='stream-node6') | |
277 | self.assert_qmp(result, 'error/class', 'GenericError') | |
278 | ||
279 | result = self.vm.qmp('block-stream', device='node4', base=self.imgs[2], job_id='stream-node4') | |
280 | self.assert_qmp(result, 'error/class', 'GenericError') | |
281 | ||
282 | result = self.vm.qmp('block-stream', device='node6', base=self.imgs[4], job_id='stream-node6-v2') | |
283 | self.assert_qmp(result, 'error/class', 'GenericError') | |
284 | ||
285 | # This fails because block-commit needs to block node6, the overlay of the 'top' image | |
286 | result = self.vm.qmp('block-stream', device='node7', base=self.imgs[5], job_id='stream-node6-v3') | |
287 | self.assert_qmp(result, 'error/class', 'GenericError') | |
288 | ||
289 | # This fails because block-commit currently blocks the active layer even if it's not used | |
290 | result = self.vm.qmp('block-stream', device='drive0', base=self.imgs[5], job_id='stream-drive0') | |
291 | self.assert_qmp(result, 'error/class', 'GenericError') | |
292 | ||
293 | self.wait_until_completed(drive='commit-node3') | |
294 | ||
295 | # Similar to test_overlapping_2, but here block-commit doesn't use the 'top' parameter. | |
296 | # Internally this uses a mirror block job, hence the separate test case. | |
297 | def test_overlapping_3(self): | |
298 | self.assertLessEqual(8, self.num_imgs) | |
299 | self.assert_no_active_block_jobs() | |
300 | ||
301 | # Set a speed limit to make sure that this job blocks the rest | |
302 | result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[3], job_id='commit-drive0', speed=1024*1024) | |
303 | self.assert_qmp(result, 'return', {}) | |
304 | ||
305 | result = self.vm.qmp('block-stream', device='node5', base=self.imgs[3], job_id='stream-node6') | |
306 | self.assert_qmp(result, 'error/class', 'GenericError') | |
307 | ||
308 | event = self.vm.get_qmp_event(wait=True) | |
309 | self.assertEqual(event['event'], 'BLOCK_JOB_READY') | |
310 | self.assert_qmp(event, 'data/device', 'commit-drive0') | |
311 | self.assert_qmp(event, 'data/type', 'commit') | |
312 | self.assert_qmp_absent(event, 'data/error') | |
313 | ||
314 | result = self.vm.qmp('block-job-complete', device='commit-drive0') | |
315 | self.assert_qmp(result, 'return', {}) | |
316 | ||
317 | self.wait_until_completed(drive='commit-drive0') | |
318 | self.assert_no_active_block_jobs() | |
319 | ||
2499a096 | 320 | class TestSmallerBackingFile(iotests.QMPTestCase): |
774a8850 SH |
321 | backing_len = 1 * 1024 * 1024 # MB |
322 | image_len = 2 * backing_len | |
323 | ||
324 | def setUp(self): | |
2499a096 | 325 | iotests.create_image(backing_img, self.backing_len) |
774a8850 SH |
326 | qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img, str(self.image_len)) |
327 | self.vm = iotests.VM().add_drive(test_img) | |
328 | self.vm.launch() | |
329 | ||
330 | # If this hangs, then you are missing a fix to complete streaming when the | |
331 | # end of the backing file is reached. | |
332 | def test_stream(self): | |
ecc1c88e | 333 | self.assert_no_active_block_jobs() |
774a8850 SH |
334 | |
335 | result = self.vm.qmp('block-stream', device='drive0') | |
336 | self.assert_qmp(result, 'return', {}) | |
337 | ||
9974ad40 | 338 | self.wait_until_completed() |
774a8850 | 339 | |
ecc1c88e | 340 | self.assert_no_active_block_jobs() |
774a8850 SH |
341 | self.vm.shutdown() |
342 | ||
2499a096 | 343 | class TestErrors(iotests.QMPTestCase): |
90f0b711 PB |
344 | image_len = 2 * 1024 * 1024 # MB |
345 | ||
346 | # this should match STREAM_BUFFER_SIZE/512 in block/stream.c | |
347 | STREAM_BUFFER_SIZE = 512 * 1024 | |
348 | ||
349 | def create_blkdebug_file(self, name, event, errno): | |
350 | file = open(name, 'w') | |
351 | file.write(''' | |
352 | [inject-error] | |
353 | state = "1" | |
354 | event = "%s" | |
355 | errno = "%d" | |
356 | immediately = "off" | |
357 | once = "on" | |
358 | sector = "%d" | |
359 | ||
360 | [set-state] | |
361 | state = "1" | |
362 | event = "%s" | |
363 | new_state = "2" | |
364 | ||
365 | [set-state] | |
366 | state = "2" | |
367 | event = "%s" | |
368 | new_state = "1" | |
369 | ''' % (event, errno, self.STREAM_BUFFER_SIZE / 512, event, event)) | |
370 | file.close() | |
371 | ||
372 | class TestEIO(TestErrors): | |
373 | def setUp(self): | |
374 | self.blkdebug_file = backing_img + ".blkdebug" | |
2499a096 | 375 | iotests.create_image(backing_img, TestErrors.image_len) |
90f0b711 PB |
376 | self.create_blkdebug_file(self.blkdebug_file, "read_aio", 5) |
377 | qemu_img('create', '-f', iotests.imgfmt, | |
378 | '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw' | |
379 | % (self.blkdebug_file, backing_img), | |
380 | test_img) | |
381 | self.vm = iotests.VM().add_drive(test_img) | |
382 | self.vm.launch() | |
383 | ||
384 | def tearDown(self): | |
385 | self.vm.shutdown() | |
386 | os.remove(test_img) | |
387 | os.remove(backing_img) | |
388 | os.remove(self.blkdebug_file) | |
389 | ||
390 | def test_report(self): | |
ecc1c88e | 391 | self.assert_no_active_block_jobs() |
90f0b711 PB |
392 | |
393 | result = self.vm.qmp('block-stream', device='drive0') | |
394 | self.assert_qmp(result, 'return', {}) | |
395 | ||
396 | completed = False | |
397 | error = False | |
398 | while not completed: | |
399 | for event in self.vm.get_qmp_events(wait=True): | |
400 | if event['event'] == 'BLOCK_JOB_ERROR': | |
401 | self.assert_qmp(event, 'data/device', 'drive0') | |
402 | self.assert_qmp(event, 'data/operation', 'read') | |
403 | error = True | |
404 | elif event['event'] == 'BLOCK_JOB_COMPLETED': | |
405 | self.assertTrue(error, 'job completed unexpectedly') | |
406 | self.assert_qmp(event, 'data/type', 'stream') | |
407 | self.assert_qmp(event, 'data/device', 'drive0') | |
408 | self.assert_qmp(event, 'data/error', 'Input/output error') | |
409 | self.assert_qmp(event, 'data/offset', self.STREAM_BUFFER_SIZE) | |
410 | self.assert_qmp(event, 'data/len', self.image_len) | |
411 | completed = True | |
412 | ||
ecc1c88e | 413 | self.assert_no_active_block_jobs() |
90f0b711 PB |
414 | self.vm.shutdown() |
415 | ||
416 | def test_ignore(self): | |
ecc1c88e | 417 | self.assert_no_active_block_jobs() |
90f0b711 PB |
418 | |
419 | result = self.vm.qmp('block-stream', device='drive0', on_error='ignore') | |
420 | self.assert_qmp(result, 'return', {}) | |
421 | ||
422 | error = False | |
423 | completed = False | |
424 | while not completed: | |
425 | for event in self.vm.get_qmp_events(wait=True): | |
426 | if event['event'] == 'BLOCK_JOB_ERROR': | |
427 | self.assert_qmp(event, 'data/device', 'drive0') | |
428 | self.assert_qmp(event, 'data/operation', 'read') | |
429 | result = self.vm.qmp('query-block-jobs') | |
430 | self.assert_qmp(result, 'return[0]/paused', False) | |
431 | error = True | |
432 | elif event['event'] == 'BLOCK_JOB_COMPLETED': | |
433 | self.assertTrue(error, 'job completed unexpectedly') | |
434 | self.assert_qmp(event, 'data/type', 'stream') | |
435 | self.assert_qmp(event, 'data/device', 'drive0') | |
436 | self.assert_qmp(event, 'data/error', 'Input/output error') | |
437 | self.assert_qmp(event, 'data/offset', self.image_len) | |
438 | self.assert_qmp(event, 'data/len', self.image_len) | |
439 | completed = True | |
440 | ||
ecc1c88e | 441 | self.assert_no_active_block_jobs() |
90f0b711 PB |
442 | self.vm.shutdown() |
443 | ||
444 | def test_stop(self): | |
ecc1c88e | 445 | self.assert_no_active_block_jobs() |
90f0b711 PB |
446 | |
447 | result = self.vm.qmp('block-stream', device='drive0', on_error='stop') | |
448 | self.assert_qmp(result, 'return', {}) | |
449 | ||
450 | error = False | |
451 | completed = False | |
452 | while not completed: | |
453 | for event in self.vm.get_qmp_events(wait=True): | |
454 | if event['event'] == 'BLOCK_JOB_ERROR': | |
01809194 | 455 | error = True |
90f0b711 PB |
456 | self.assert_qmp(event, 'data/device', 'drive0') |
457 | self.assert_qmp(event, 'data/operation', 'read') | |
458 | ||
459 | result = self.vm.qmp('query-block-jobs') | |
460 | self.assert_qmp(result, 'return[0]/paused', True) | |
461 | self.assert_qmp(result, 'return[0]/offset', self.STREAM_BUFFER_SIZE) | |
462 | self.assert_qmp(result, 'return[0]/io-status', 'failed') | |
463 | ||
464 | result = self.vm.qmp('block-job-resume', device='drive0') | |
465 | self.assert_qmp(result, 'return', {}) | |
466 | ||
467 | result = self.vm.qmp('query-block-jobs') | |
01809194 JS |
468 | if result == {'return': []}: |
469 | # Race; likely already finished. Check. | |
470 | continue | |
90f0b711 PB |
471 | self.assert_qmp(result, 'return[0]/paused', False) |
472 | self.assert_qmp(result, 'return[0]/io-status', 'ok') | |
90f0b711 PB |
473 | elif event['event'] == 'BLOCK_JOB_COMPLETED': |
474 | self.assertTrue(error, 'job completed unexpectedly') | |
475 | self.assert_qmp(event, 'data/type', 'stream') | |
476 | self.assert_qmp(event, 'data/device', 'drive0') | |
477 | self.assert_qmp_absent(event, 'data/error') | |
478 | self.assert_qmp(event, 'data/offset', self.image_len) | |
479 | self.assert_qmp(event, 'data/len', self.image_len) | |
480 | completed = True | |
481 | ||
ecc1c88e | 482 | self.assert_no_active_block_jobs() |
90f0b711 PB |
483 | self.vm.shutdown() |
484 | ||
485 | def test_enospc(self): | |
ecc1c88e | 486 | self.assert_no_active_block_jobs() |
90f0b711 PB |
487 | |
488 | result = self.vm.qmp('block-stream', device='drive0', on_error='enospc') | |
489 | self.assert_qmp(result, 'return', {}) | |
490 | ||
491 | completed = False | |
492 | error = False | |
493 | while not completed: | |
494 | for event in self.vm.get_qmp_events(wait=True): | |
495 | if event['event'] == 'BLOCK_JOB_ERROR': | |
496 | self.assert_qmp(event, 'data/device', 'drive0') | |
497 | self.assert_qmp(event, 'data/operation', 'read') | |
498 | error = True | |
499 | elif event['event'] == 'BLOCK_JOB_COMPLETED': | |
500 | self.assertTrue(error, 'job completed unexpectedly') | |
501 | self.assert_qmp(event, 'data/type', 'stream') | |
502 | self.assert_qmp(event, 'data/device', 'drive0') | |
503 | self.assert_qmp(event, 'data/error', 'Input/output error') | |
504 | self.assert_qmp(event, 'data/offset', self.STREAM_BUFFER_SIZE) | |
505 | self.assert_qmp(event, 'data/len', self.image_len) | |
506 | completed = True | |
507 | ||
ecc1c88e | 508 | self.assert_no_active_block_jobs() |
90f0b711 PB |
509 | self.vm.shutdown() |
510 | ||
511 | class TestENOSPC(TestErrors): | |
512 | def setUp(self): | |
513 | self.blkdebug_file = backing_img + ".blkdebug" | |
2499a096 | 514 | iotests.create_image(backing_img, TestErrors.image_len) |
90f0b711 PB |
515 | self.create_blkdebug_file(self.blkdebug_file, "read_aio", 28) |
516 | qemu_img('create', '-f', iotests.imgfmt, | |
517 | '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw' | |
518 | % (self.blkdebug_file, backing_img), | |
519 | test_img) | |
520 | self.vm = iotests.VM().add_drive(test_img) | |
521 | self.vm.launch() | |
522 | ||
523 | def tearDown(self): | |
524 | self.vm.shutdown() | |
525 | os.remove(test_img) | |
526 | os.remove(backing_img) | |
527 | os.remove(self.blkdebug_file) | |
528 | ||
529 | def test_enospc(self): | |
ecc1c88e | 530 | self.assert_no_active_block_jobs() |
90f0b711 PB |
531 | |
532 | result = self.vm.qmp('block-stream', device='drive0', on_error='enospc') | |
533 | self.assert_qmp(result, 'return', {}) | |
534 | ||
535 | error = False | |
536 | completed = False | |
537 | while not completed: | |
538 | for event in self.vm.get_qmp_events(wait=True): | |
539 | if event['event'] == 'BLOCK_JOB_ERROR': | |
540 | self.assert_qmp(event, 'data/device', 'drive0') | |
541 | self.assert_qmp(event, 'data/operation', 'read') | |
542 | ||
543 | result = self.vm.qmp('query-block-jobs') | |
544 | self.assert_qmp(result, 'return[0]/paused', True) | |
545 | self.assert_qmp(result, 'return[0]/offset', self.STREAM_BUFFER_SIZE) | |
546 | self.assert_qmp(result, 'return[0]/io-status', 'nospace') | |
547 | ||
548 | result = self.vm.qmp('block-job-resume', device='drive0') | |
549 | self.assert_qmp(result, 'return', {}) | |
550 | ||
551 | result = self.vm.qmp('query-block-jobs') | |
552 | self.assert_qmp(result, 'return[0]/paused', False) | |
553 | self.assert_qmp(result, 'return[0]/io-status', 'ok') | |
554 | error = True | |
555 | elif event['event'] == 'BLOCK_JOB_COMPLETED': | |
556 | self.assertTrue(error, 'job completed unexpectedly') | |
557 | self.assert_qmp(event, 'data/type', 'stream') | |
558 | self.assert_qmp(event, 'data/device', 'drive0') | |
559 | self.assert_qmp_absent(event, 'data/error') | |
560 | self.assert_qmp(event, 'data/offset', self.image_len) | |
561 | self.assert_qmp(event, 'data/len', self.image_len) | |
562 | completed = True | |
563 | ||
ecc1c88e | 564 | self.assert_no_active_block_jobs() |
90f0b711 | 565 | self.vm.shutdown() |
774a8850 | 566 | |
2499a096 | 567 | class TestStreamStop(iotests.QMPTestCase): |
37ce63eb SH |
568 | image_len = 8 * 1024 * 1024 * 1024 # GB |
569 | ||
570 | def setUp(self): | |
571 | qemu_img('create', backing_img, str(TestStreamStop.image_len)) | |
90c9b167 | 572 | qemu_io('-f', 'raw', '-c', 'write -P 0x1 0 32M', backing_img) |
37ce63eb | 573 | qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img) |
90c9b167 | 574 | qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x1 32M 32M', test_img) |
b59b3d57 | 575 | self.vm = iotests.VM().add_drive("blkdebug::" + test_img) |
37ce63eb SH |
576 | self.vm.launch() |
577 | ||
578 | def tearDown(self): | |
579 | self.vm.shutdown() | |
580 | os.remove(test_img) | |
581 | os.remove(backing_img) | |
582 | ||
583 | def test_stream_stop(self): | |
ecc1c88e | 584 | self.assert_no_active_block_jobs() |
37ce63eb | 585 | |
b59b3d57 | 586 | self.vm.pause_drive('drive0') |
db58f9c0 | 587 | result = self.vm.qmp('block-stream', device='drive0') |
37ce63eb SH |
588 | self.assert_qmp(result, 'return', {}) |
589 | ||
0fd05e8d | 590 | time.sleep(0.1) |
37ce63eb SH |
591 | events = self.vm.get_qmp_events(wait=False) |
592 | self.assertEqual(events, [], 'unexpected QMP event: %s' % events) | |
593 | ||
b59b3d57 | 594 | self.cancel_and_wait(resume=True) |
37ce63eb | 595 | |
2499a096 | 596 | class TestSetSpeed(iotests.QMPTestCase): |
37ce63eb SH |
597 | image_len = 80 * 1024 * 1024 # MB |
598 | ||
599 | def setUp(self): | |
600 | qemu_img('create', backing_img, str(TestSetSpeed.image_len)) | |
90c9b167 | 601 | qemu_io('-f', 'raw', '-c', 'write -P 0x1 0 32M', backing_img) |
37ce63eb | 602 | qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img) |
90c9b167 | 603 | qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x1 32M 32M', test_img) |
b59b3d57 | 604 | self.vm = iotests.VM().add_drive('blkdebug::' + test_img) |
37ce63eb SH |
605 | self.vm.launch() |
606 | ||
607 | def tearDown(self): | |
608 | self.vm.shutdown() | |
609 | os.remove(test_img) | |
610 | os.remove(backing_img) | |
611 | ||
e425306a SH |
612 | # This is a short performance test which is not run by default. |
613 | # Invoke "IMGFMT=qed ./030 TestSetSpeed.perf_test_throughput" | |
614 | def perf_test_throughput(self): | |
ecc1c88e | 615 | self.assert_no_active_block_jobs() |
37ce63eb | 616 | |
db58f9c0 | 617 | result = self.vm.qmp('block-stream', device='drive0') |
37ce63eb SH |
618 | self.assert_qmp(result, 'return', {}) |
619 | ||
e425306a | 620 | result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024) |
37ce63eb SH |
621 | self.assert_qmp(result, 'return', {}) |
622 | ||
9974ad40 | 623 | self.wait_until_completed() |
37ce63eb | 624 | |
ecc1c88e | 625 | self.assert_no_active_block_jobs() |
37ce63eb | 626 | |
e425306a | 627 | def test_set_speed(self): |
ecc1c88e | 628 | self.assert_no_active_block_jobs() |
e425306a | 629 | |
b59b3d57 | 630 | self.vm.pause_drive('drive0') |
e425306a SH |
631 | result = self.vm.qmp('block-stream', device='drive0') |
632 | self.assert_qmp(result, 'return', {}) | |
633 | ||
634 | # Default speed is 0 | |
635 | result = self.vm.qmp('query-block-jobs') | |
636 | self.assert_qmp(result, 'return[0]/device', 'drive0') | |
637 | self.assert_qmp(result, 'return[0]/speed', 0) | |
638 | ||
639 | result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024) | |
640 | self.assert_qmp(result, 'return', {}) | |
641 | ||
642 | # Ensure the speed we set was accepted | |
643 | result = self.vm.qmp('query-block-jobs') | |
644 | self.assert_qmp(result, 'return[0]/device', 'drive0') | |
645 | self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024) | |
646 | ||
b59b3d57 FZ |
647 | self.cancel_and_wait(resume=True) |
648 | self.vm.pause_drive('drive0') | |
e425306a SH |
649 | |
650 | # Check setting speed in block-stream works | |
651 | result = self.vm.qmp('block-stream', device='drive0', speed=4 * 1024 * 1024) | |
652 | self.assert_qmp(result, 'return', {}) | |
653 | ||
654 | result = self.vm.qmp('query-block-jobs') | |
655 | self.assert_qmp(result, 'return[0]/device', 'drive0') | |
656 | self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024) | |
657 | ||
b59b3d57 | 658 | self.cancel_and_wait(resume=True) |
e425306a SH |
659 | |
660 | def test_set_speed_invalid(self): | |
ecc1c88e | 661 | self.assert_no_active_block_jobs() |
e425306a SH |
662 | |
663 | result = self.vm.qmp('block-stream', device='drive0', speed=-1) | |
58c8cce2 | 664 | self.assert_qmp(result, 'error/class', 'GenericError') |
e425306a | 665 | |
ecc1c88e | 666 | self.assert_no_active_block_jobs() |
e425306a SH |
667 | |
668 | result = self.vm.qmp('block-stream', device='drive0') | |
669 | self.assert_qmp(result, 'return', {}) | |
670 | ||
671 | result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1) | |
58c8cce2 | 672 | self.assert_qmp(result, 'error/class', 'GenericError') |
e425306a SH |
673 | |
674 | self.cancel_and_wait() | |
675 | ||
37ce63eb SH |
676 | if __name__ == '__main__': |
677 | iotests.main(supported_fmts=['qcow2', 'qed']) |