]>
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') | |
704d59f1 AG |
318 | |
319 | # Test a block-stream and a block-commit job in parallel | |
320 | def test_stream_commit(self): | |
321 | self.assertLessEqual(8, self.num_imgs) | |
322 | self.assert_no_active_block_jobs() | |
323 | ||
324 | # Stream from node0 into node2 | |
325 | result = self.vm.qmp('block-stream', device='node2', job_id='node2') | |
326 | self.assert_qmp(result, 'return', {}) | |
327 | ||
328 | # Commit from the active layer into node3 | |
329 | result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[3]) | |
330 | self.assert_qmp(result, 'return', {}) | |
331 | ||
332 | # Wait for all jobs to be finished. | |
333 | pending_jobs = ['node2', 'drive0'] | |
334 | while len(pending_jobs) > 0: | |
335 | for event in self.vm.get_qmp_events(wait=True): | |
336 | if event['event'] == 'BLOCK_JOB_COMPLETED': | |
337 | node_name = self.dictpath(event, 'data/device') | |
338 | self.assertTrue(node_name in pending_jobs) | |
339 | self.assert_qmp_absent(event, 'data/error') | |
340 | pending_jobs.remove(node_name) | |
341 | if event['event'] == 'BLOCK_JOB_READY': | |
342 | self.assert_qmp(event, 'data/device', 'drive0') | |
343 | self.assert_qmp(event, 'data/type', 'commit') | |
344 | self.assert_qmp_absent(event, 'data/error') | |
345 | self.assertTrue('drive0' in pending_jobs) | |
346 | self.vm.qmp('block-job-complete', device='drive0') | |
347 | ||
eb290b78 AG |
348 | self.assert_no_active_block_jobs() |
349 | ||
7eb13c9d AG |
350 | # Test the base_node parameter |
351 | def test_stream_base_node_name(self): | |
352 | self.assert_no_active_block_jobs() | |
353 | ||
354 | self.assertNotEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[4]), | |
355 | qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[3]), | |
356 | 'image file map matches backing file before streaming') | |
357 | ||
358 | # Error: the base node does not exist | |
359 | result = self.vm.qmp('block-stream', device='node4', base_node='none', job_id='stream') | |
360 | self.assert_qmp(result, 'error/class', 'GenericError') | |
361 | ||
362 | # Error: the base node is not a backing file of the top node | |
363 | result = self.vm.qmp('block-stream', device='node4', base_node='node6', job_id='stream') | |
364 | self.assert_qmp(result, 'error/class', 'GenericError') | |
365 | ||
366 | # Error: the base node is the same as the top node | |
367 | result = self.vm.qmp('block-stream', device='node4', base_node='node4', job_id='stream') | |
368 | self.assert_qmp(result, 'error/class', 'GenericError') | |
369 | ||
370 | # Error: cannot specify 'base' and 'base-node' at the same time | |
371 | result = self.vm.qmp('block-stream', device='node4', base=self.imgs[2], base_node='node2', job_id='stream') | |
372 | self.assert_qmp(result, 'error/class', 'GenericError') | |
373 | ||
374 | # Success: the base node is a backing file of the top node | |
375 | result = self.vm.qmp('block-stream', device='node4', base_node='node2', job_id='stream') | |
376 | self.assert_qmp(result, 'return', {}) | |
377 | ||
378 | self.wait_until_completed(drive='stream') | |
379 | ||
380 | self.assert_no_active_block_jobs() | |
381 | self.vm.shutdown() | |
382 | ||
383 | self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[4]), | |
384 | qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[3]), | |
385 | 'image file map matches backing file after streaming') | |
386 | ||
48361afb AG |
387 | class TestQuorum(iotests.QMPTestCase): |
388 | num_children = 3 | |
389 | children = [] | |
390 | backing = [] | |
391 | ||
392 | def setUp(self): | |
393 | opts = ['driver=quorum', 'vote-threshold=2'] | |
394 | ||
395 | # Initialize file names and command-line options | |
396 | for i in range(self.num_children): | |
397 | child_img = os.path.join(iotests.test_dir, 'img-%d.img' % i) | |
398 | backing_img = os.path.join(iotests.test_dir, 'backing-%d.img' % i) | |
399 | self.children.append(child_img) | |
400 | self.backing.append(backing_img) | |
401 | qemu_img('create', '-f', iotests.imgfmt, backing_img, '1M') | |
402 | qemu_io('-f', iotests.imgfmt, | |
403 | '-c', 'write -P 0x55 0 1024', backing_img) | |
404 | qemu_img('create', '-f', iotests.imgfmt, | |
405 | '-o', 'backing_file=%s' % backing_img, child_img) | |
406 | opts.append("children.%d.file.filename=%s" % (i, child_img)) | |
407 | opts.append("children.%d.node-name=node%d" % (i, i)) | |
408 | ||
409 | # Attach the drive to the VM | |
410 | self.vm = iotests.VM() | |
411 | self.vm.add_drive(path = None, opts = ','.join(opts)) | |
412 | self.vm.launch() | |
413 | ||
414 | def tearDown(self): | |
415 | self.vm.shutdown() | |
416 | for img in self.children: | |
417 | os.remove(img) | |
418 | for img in self.backing: | |
419 | os.remove(img) | |
420 | ||
421 | def test_stream_quorum(self): | |
422 | if not iotests.supports_quorum(): | |
423 | return | |
424 | ||
425 | self.assertNotEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', self.children[0]), | |
426 | qemu_io('-f', iotests.imgfmt, '-c', 'map', self.backing[0]), | |
427 | 'image file map matches backing file before streaming') | |
428 | ||
429 | self.assert_no_active_block_jobs() | |
430 | ||
431 | result = self.vm.qmp('block-stream', device='node0', job_id='stream-node0') | |
432 | self.assert_qmp(result, 'return', {}) | |
433 | ||
434 | self.wait_until_completed(drive='stream-node0') | |
435 | ||
436 | self.assert_no_active_block_jobs() | |
437 | self.vm.shutdown() | |
438 | ||
439 | self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', self.children[0]), | |
440 | qemu_io('-f', iotests.imgfmt, '-c', 'map', self.backing[0]), | |
441 | 'image file map does not match backing file after streaming') | |
442 | ||
2499a096 | 443 | class TestSmallerBackingFile(iotests.QMPTestCase): |
774a8850 SH |
444 | backing_len = 1 * 1024 * 1024 # MB |
445 | image_len = 2 * backing_len | |
446 | ||
447 | def setUp(self): | |
2499a096 | 448 | iotests.create_image(backing_img, self.backing_len) |
774a8850 SH |
449 | qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img, str(self.image_len)) |
450 | self.vm = iotests.VM().add_drive(test_img) | |
451 | self.vm.launch() | |
452 | ||
453 | # If this hangs, then you are missing a fix to complete streaming when the | |
454 | # end of the backing file is reached. | |
455 | def test_stream(self): | |
ecc1c88e | 456 | self.assert_no_active_block_jobs() |
774a8850 SH |
457 | |
458 | result = self.vm.qmp('block-stream', device='drive0') | |
459 | self.assert_qmp(result, 'return', {}) | |
460 | ||
9974ad40 | 461 | self.wait_until_completed() |
774a8850 | 462 | |
ecc1c88e | 463 | self.assert_no_active_block_jobs() |
774a8850 SH |
464 | self.vm.shutdown() |
465 | ||
2499a096 | 466 | class TestErrors(iotests.QMPTestCase): |
90f0b711 PB |
467 | image_len = 2 * 1024 * 1024 # MB |
468 | ||
469 | # this should match STREAM_BUFFER_SIZE/512 in block/stream.c | |
470 | STREAM_BUFFER_SIZE = 512 * 1024 | |
471 | ||
472 | def create_blkdebug_file(self, name, event, errno): | |
473 | file = open(name, 'w') | |
474 | file.write(''' | |
475 | [inject-error] | |
476 | state = "1" | |
477 | event = "%s" | |
478 | errno = "%d" | |
479 | immediately = "off" | |
480 | once = "on" | |
481 | sector = "%d" | |
482 | ||
483 | [set-state] | |
484 | state = "1" | |
485 | event = "%s" | |
486 | new_state = "2" | |
487 | ||
488 | [set-state] | |
489 | state = "2" | |
490 | event = "%s" | |
491 | new_state = "1" | |
492 | ''' % (event, errno, self.STREAM_BUFFER_SIZE / 512, event, event)) | |
493 | file.close() | |
494 | ||
495 | class TestEIO(TestErrors): | |
496 | def setUp(self): | |
497 | self.blkdebug_file = backing_img + ".blkdebug" | |
2499a096 | 498 | iotests.create_image(backing_img, TestErrors.image_len) |
90f0b711 PB |
499 | self.create_blkdebug_file(self.blkdebug_file, "read_aio", 5) |
500 | qemu_img('create', '-f', iotests.imgfmt, | |
501 | '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw' | |
502 | % (self.blkdebug_file, backing_img), | |
503 | test_img) | |
504 | self.vm = iotests.VM().add_drive(test_img) | |
505 | self.vm.launch() | |
506 | ||
507 | def tearDown(self): | |
508 | self.vm.shutdown() | |
509 | os.remove(test_img) | |
510 | os.remove(backing_img) | |
511 | os.remove(self.blkdebug_file) | |
512 | ||
513 | def test_report(self): | |
ecc1c88e | 514 | self.assert_no_active_block_jobs() |
90f0b711 PB |
515 | |
516 | result = self.vm.qmp('block-stream', device='drive0') | |
517 | self.assert_qmp(result, 'return', {}) | |
518 | ||
519 | completed = False | |
520 | error = False | |
521 | while not completed: | |
522 | for event in self.vm.get_qmp_events(wait=True): | |
523 | if event['event'] == 'BLOCK_JOB_ERROR': | |
524 | self.assert_qmp(event, 'data/device', 'drive0') | |
525 | self.assert_qmp(event, 'data/operation', 'read') | |
526 | error = True | |
527 | elif event['event'] == 'BLOCK_JOB_COMPLETED': | |
528 | self.assertTrue(error, 'job completed unexpectedly') | |
529 | self.assert_qmp(event, 'data/type', 'stream') | |
530 | self.assert_qmp(event, 'data/device', 'drive0') | |
531 | self.assert_qmp(event, 'data/error', 'Input/output error') | |
532 | self.assert_qmp(event, 'data/offset', self.STREAM_BUFFER_SIZE) | |
533 | self.assert_qmp(event, 'data/len', self.image_len) | |
534 | completed = True | |
535 | ||
ecc1c88e | 536 | self.assert_no_active_block_jobs() |
90f0b711 PB |
537 | self.vm.shutdown() |
538 | ||
539 | def test_ignore(self): | |
ecc1c88e | 540 | self.assert_no_active_block_jobs() |
90f0b711 PB |
541 | |
542 | result = self.vm.qmp('block-stream', device='drive0', on_error='ignore') | |
543 | self.assert_qmp(result, 'return', {}) | |
544 | ||
545 | error = False | |
546 | completed = False | |
547 | while not completed: | |
548 | for event in self.vm.get_qmp_events(wait=True): | |
549 | if event['event'] == 'BLOCK_JOB_ERROR': | |
550 | self.assert_qmp(event, 'data/device', 'drive0') | |
551 | self.assert_qmp(event, 'data/operation', 'read') | |
552 | result = self.vm.qmp('query-block-jobs') | |
553 | self.assert_qmp(result, 'return[0]/paused', False) | |
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(event, 'data/error', 'Input/output 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 PB |
565 | self.vm.shutdown() |
566 | ||
567 | def test_stop(self): | |
ecc1c88e | 568 | self.assert_no_active_block_jobs() |
90f0b711 PB |
569 | |
570 | result = self.vm.qmp('block-stream', device='drive0', on_error='stop') | |
571 | self.assert_qmp(result, 'return', {}) | |
572 | ||
573 | error = False | |
574 | completed = False | |
575 | while not completed: | |
576 | for event in self.vm.get_qmp_events(wait=True): | |
577 | if event['event'] == 'BLOCK_JOB_ERROR': | |
01809194 | 578 | error = True |
90f0b711 PB |
579 | self.assert_qmp(event, 'data/device', 'drive0') |
580 | self.assert_qmp(event, 'data/operation', 'read') | |
581 | ||
582 | result = self.vm.qmp('query-block-jobs') | |
583 | self.assert_qmp(result, 'return[0]/paused', True) | |
584 | self.assert_qmp(result, 'return[0]/offset', self.STREAM_BUFFER_SIZE) | |
585 | self.assert_qmp(result, 'return[0]/io-status', 'failed') | |
586 | ||
587 | result = self.vm.qmp('block-job-resume', device='drive0') | |
588 | self.assert_qmp(result, 'return', {}) | |
589 | ||
590 | result = self.vm.qmp('query-block-jobs') | |
01809194 JS |
591 | if result == {'return': []}: |
592 | # Race; likely already finished. Check. | |
593 | continue | |
90f0b711 PB |
594 | self.assert_qmp(result, 'return[0]/paused', False) |
595 | self.assert_qmp(result, 'return[0]/io-status', 'ok') | |
90f0b711 PB |
596 | elif event['event'] == 'BLOCK_JOB_COMPLETED': |
597 | self.assertTrue(error, 'job completed unexpectedly') | |
598 | self.assert_qmp(event, 'data/type', 'stream') | |
599 | self.assert_qmp(event, 'data/device', 'drive0') | |
600 | self.assert_qmp_absent(event, 'data/error') | |
601 | self.assert_qmp(event, 'data/offset', self.image_len) | |
602 | self.assert_qmp(event, 'data/len', self.image_len) | |
603 | completed = True | |
604 | ||
ecc1c88e | 605 | self.assert_no_active_block_jobs() |
90f0b711 PB |
606 | self.vm.shutdown() |
607 | ||
608 | def test_enospc(self): | |
ecc1c88e | 609 | self.assert_no_active_block_jobs() |
90f0b711 PB |
610 | |
611 | result = self.vm.qmp('block-stream', device='drive0', on_error='enospc') | |
612 | self.assert_qmp(result, 'return', {}) | |
613 | ||
614 | completed = False | |
615 | error = False | |
616 | while not completed: | |
617 | for event in self.vm.get_qmp_events(wait=True): | |
618 | if event['event'] == 'BLOCK_JOB_ERROR': | |
619 | self.assert_qmp(event, 'data/device', 'drive0') | |
620 | self.assert_qmp(event, 'data/operation', 'read') | |
621 | error = True | |
622 | elif event['event'] == 'BLOCK_JOB_COMPLETED': | |
623 | self.assertTrue(error, 'job completed unexpectedly') | |
624 | self.assert_qmp(event, 'data/type', 'stream') | |
625 | self.assert_qmp(event, 'data/device', 'drive0') | |
626 | self.assert_qmp(event, 'data/error', 'Input/output error') | |
627 | self.assert_qmp(event, 'data/offset', self.STREAM_BUFFER_SIZE) | |
628 | self.assert_qmp(event, 'data/len', self.image_len) | |
629 | completed = True | |
630 | ||
ecc1c88e | 631 | self.assert_no_active_block_jobs() |
90f0b711 PB |
632 | self.vm.shutdown() |
633 | ||
634 | class TestENOSPC(TestErrors): | |
635 | def setUp(self): | |
636 | self.blkdebug_file = backing_img + ".blkdebug" | |
2499a096 | 637 | iotests.create_image(backing_img, TestErrors.image_len) |
90f0b711 PB |
638 | self.create_blkdebug_file(self.blkdebug_file, "read_aio", 28) |
639 | qemu_img('create', '-f', iotests.imgfmt, | |
640 | '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw' | |
641 | % (self.blkdebug_file, backing_img), | |
642 | test_img) | |
643 | self.vm = iotests.VM().add_drive(test_img) | |
644 | self.vm.launch() | |
645 | ||
646 | def tearDown(self): | |
647 | self.vm.shutdown() | |
648 | os.remove(test_img) | |
649 | os.remove(backing_img) | |
650 | os.remove(self.blkdebug_file) | |
651 | ||
652 | def test_enospc(self): | |
ecc1c88e | 653 | self.assert_no_active_block_jobs() |
90f0b711 PB |
654 | |
655 | result = self.vm.qmp('block-stream', device='drive0', on_error='enospc') | |
656 | self.assert_qmp(result, 'return', {}) | |
657 | ||
658 | error = False | |
659 | completed = False | |
660 | while not completed: | |
661 | for event in self.vm.get_qmp_events(wait=True): | |
662 | if event['event'] == 'BLOCK_JOB_ERROR': | |
663 | self.assert_qmp(event, 'data/device', 'drive0') | |
664 | self.assert_qmp(event, 'data/operation', 'read') | |
665 | ||
666 | result = self.vm.qmp('query-block-jobs') | |
667 | self.assert_qmp(result, 'return[0]/paused', True) | |
668 | self.assert_qmp(result, 'return[0]/offset', self.STREAM_BUFFER_SIZE) | |
669 | self.assert_qmp(result, 'return[0]/io-status', 'nospace') | |
670 | ||
671 | result = self.vm.qmp('block-job-resume', device='drive0') | |
672 | self.assert_qmp(result, 'return', {}) | |
673 | ||
674 | result = self.vm.qmp('query-block-jobs') | |
675 | self.assert_qmp(result, 'return[0]/paused', False) | |
676 | self.assert_qmp(result, 'return[0]/io-status', 'ok') | |
677 | error = True | |
678 | elif event['event'] == 'BLOCK_JOB_COMPLETED': | |
679 | self.assertTrue(error, 'job completed unexpectedly') | |
680 | self.assert_qmp(event, 'data/type', 'stream') | |
681 | self.assert_qmp(event, 'data/device', 'drive0') | |
682 | self.assert_qmp_absent(event, 'data/error') | |
683 | self.assert_qmp(event, 'data/offset', self.image_len) | |
684 | self.assert_qmp(event, 'data/len', self.image_len) | |
685 | completed = True | |
686 | ||
ecc1c88e | 687 | self.assert_no_active_block_jobs() |
90f0b711 | 688 | self.vm.shutdown() |
774a8850 | 689 | |
2499a096 | 690 | class TestStreamStop(iotests.QMPTestCase): |
37ce63eb SH |
691 | image_len = 8 * 1024 * 1024 * 1024 # GB |
692 | ||
693 | def setUp(self): | |
694 | qemu_img('create', backing_img, str(TestStreamStop.image_len)) | |
90c9b167 | 695 | qemu_io('-f', 'raw', '-c', 'write -P 0x1 0 32M', backing_img) |
37ce63eb | 696 | qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img) |
90c9b167 | 697 | qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x1 32M 32M', test_img) |
b59b3d57 | 698 | self.vm = iotests.VM().add_drive("blkdebug::" + test_img) |
37ce63eb SH |
699 | self.vm.launch() |
700 | ||
701 | def tearDown(self): | |
702 | self.vm.shutdown() | |
703 | os.remove(test_img) | |
704 | os.remove(backing_img) | |
705 | ||
706 | def test_stream_stop(self): | |
ecc1c88e | 707 | self.assert_no_active_block_jobs() |
37ce63eb | 708 | |
b59b3d57 | 709 | self.vm.pause_drive('drive0') |
db58f9c0 | 710 | result = self.vm.qmp('block-stream', device='drive0') |
37ce63eb SH |
711 | self.assert_qmp(result, 'return', {}) |
712 | ||
0fd05e8d | 713 | time.sleep(0.1) |
37ce63eb SH |
714 | events = self.vm.get_qmp_events(wait=False) |
715 | self.assertEqual(events, [], 'unexpected QMP event: %s' % events) | |
716 | ||
b59b3d57 | 717 | self.cancel_and_wait(resume=True) |
37ce63eb | 718 | |
2499a096 | 719 | class TestSetSpeed(iotests.QMPTestCase): |
37ce63eb SH |
720 | image_len = 80 * 1024 * 1024 # MB |
721 | ||
722 | def setUp(self): | |
723 | qemu_img('create', backing_img, str(TestSetSpeed.image_len)) | |
90c9b167 | 724 | qemu_io('-f', 'raw', '-c', 'write -P 0x1 0 32M', backing_img) |
37ce63eb | 725 | qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img) |
90c9b167 | 726 | qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x1 32M 32M', test_img) |
b59b3d57 | 727 | self.vm = iotests.VM().add_drive('blkdebug::' + test_img) |
37ce63eb SH |
728 | self.vm.launch() |
729 | ||
730 | def tearDown(self): | |
731 | self.vm.shutdown() | |
732 | os.remove(test_img) | |
733 | os.remove(backing_img) | |
734 | ||
e425306a SH |
735 | # This is a short performance test which is not run by default. |
736 | # Invoke "IMGFMT=qed ./030 TestSetSpeed.perf_test_throughput" | |
737 | def perf_test_throughput(self): | |
ecc1c88e | 738 | self.assert_no_active_block_jobs() |
37ce63eb | 739 | |
db58f9c0 | 740 | result = self.vm.qmp('block-stream', device='drive0') |
37ce63eb SH |
741 | self.assert_qmp(result, 'return', {}) |
742 | ||
e425306a | 743 | result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024) |
37ce63eb SH |
744 | self.assert_qmp(result, 'return', {}) |
745 | ||
9974ad40 | 746 | self.wait_until_completed() |
37ce63eb | 747 | |
ecc1c88e | 748 | self.assert_no_active_block_jobs() |
37ce63eb | 749 | |
e425306a | 750 | def test_set_speed(self): |
ecc1c88e | 751 | self.assert_no_active_block_jobs() |
e425306a | 752 | |
b59b3d57 | 753 | self.vm.pause_drive('drive0') |
e425306a SH |
754 | result = self.vm.qmp('block-stream', device='drive0') |
755 | self.assert_qmp(result, 'return', {}) | |
756 | ||
757 | # Default speed is 0 | |
758 | result = self.vm.qmp('query-block-jobs') | |
759 | self.assert_qmp(result, 'return[0]/device', 'drive0') | |
760 | self.assert_qmp(result, 'return[0]/speed', 0) | |
761 | ||
762 | result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024) | |
763 | self.assert_qmp(result, 'return', {}) | |
764 | ||
765 | # Ensure the speed we set was accepted | |
766 | result = self.vm.qmp('query-block-jobs') | |
767 | self.assert_qmp(result, 'return[0]/device', 'drive0') | |
768 | self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024) | |
769 | ||
b59b3d57 FZ |
770 | self.cancel_and_wait(resume=True) |
771 | self.vm.pause_drive('drive0') | |
e425306a SH |
772 | |
773 | # Check setting speed in block-stream works | |
774 | result = self.vm.qmp('block-stream', device='drive0', speed=4 * 1024 * 1024) | |
775 | self.assert_qmp(result, 'return', {}) | |
776 | ||
777 | result = self.vm.qmp('query-block-jobs') | |
778 | self.assert_qmp(result, 'return[0]/device', 'drive0') | |
779 | self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024) | |
780 | ||
b59b3d57 | 781 | self.cancel_and_wait(resume=True) |
e425306a SH |
782 | |
783 | def test_set_speed_invalid(self): | |
ecc1c88e | 784 | self.assert_no_active_block_jobs() |
e425306a SH |
785 | |
786 | result = self.vm.qmp('block-stream', device='drive0', speed=-1) | |
58c8cce2 | 787 | self.assert_qmp(result, 'error/class', 'GenericError') |
e425306a | 788 | |
ecc1c88e | 789 | self.assert_no_active_block_jobs() |
e425306a SH |
790 | |
791 | result = self.vm.qmp('block-stream', device='drive0') | |
792 | self.assert_qmp(result, 'return', {}) | |
793 | ||
794 | result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1) | |
58c8cce2 | 795 | self.assert_qmp(result, 'error/class', 'GenericError') |
e425306a SH |
796 | |
797 | self.cancel_and_wait() | |
798 | ||
37ce63eb SH |
799 | if __name__ == '__main__': |
800 | iotests.main(supported_fmts=['qcow2', 'qed']) |