]>
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 | ||
aca7063a FZ |
66 | self.assertNotEqual(qemu_io('-f', 'raw', '-rU', '-c', 'map', backing_img), |
67 | qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', mid_img), | |
7b8a9e5a AG |
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 | ||
f03d9d24 | 89 | self.pause_job('drive0', wait=False) |
2c93c5cb | 90 | self.vm.resume_drive('drive0') |
f03d9d24 | 91 | self.pause_wait('drive0') |
2c93c5cb | 92 | |
0c817347 PB |
93 | result = self.vm.qmp('query-block-jobs') |
94 | offset = self.dictpath(result, 'return[0]/offset') | |
95 | ||
2c93c5cb | 96 | time.sleep(0.5) |
0c817347 PB |
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 | ||
9974ad40 | 103 | self.wait_until_completed() |
0c817347 | 104 | |
ecc1c88e | 105 | self.assert_no_active_block_jobs() |
0c817347 PB |
106 | self.vm.shutdown() |
107 | ||
90c9b167 KW |
108 | self.assertEqual(qemu_io('-f', 'raw', '-c', 'map', backing_img), |
109 | qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img), | |
0c817347 PB |
110 | 'image file map does not match backing file after streaming') |
111 | ||
409d5498 AG |
112 | def test_stream_no_op(self): |
113 | self.assert_no_active_block_jobs() | |
114 | ||
115 | # The image map is empty before the operation | |
aca7063a | 116 | empty_map = qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', test_img) |
409d5498 AG |
117 | |
118 | # This is a no-op: no data should ever be copied from the base image | |
119 | result = self.vm.qmp('block-stream', device='drive0', base=mid_img) | |
120 | self.assert_qmp(result, 'return', {}) | |
121 | ||
122 | self.wait_until_completed() | |
123 | ||
124 | self.assert_no_active_block_jobs() | |
125 | self.vm.shutdown() | |
126 | ||
127 | self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img), | |
128 | empty_map, 'image file map changed after a no-op') | |
129 | ||
6e343609 | 130 | def test_stream_partial(self): |
ecc1c88e | 131 | self.assert_no_active_block_jobs() |
6e343609 | 132 | |
5e302a7d | 133 | result = self.vm.qmp('block-stream', device='drive0', base=backing_img) |
6e343609 PB |
134 | self.assert_qmp(result, 'return', {}) |
135 | ||
9974ad40 | 136 | self.wait_until_completed() |
6e343609 | 137 | |
ecc1c88e | 138 | self.assert_no_active_block_jobs() |
6e343609 PB |
139 | self.vm.shutdown() |
140 | ||
90c9b167 KW |
141 | self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', mid_img), |
142 | qemu_io('-f', iotests.imgfmt, '-c', 'map', test_img), | |
6e343609 PB |
143 | 'image file map does not match backing file after streaming') |
144 | ||
37ce63eb | 145 | def test_device_not_found(self): |
db58f9c0 | 146 | result = self.vm.qmp('block-stream', device='nonexistent') |
b6c1bae5 | 147 | self.assert_qmp(result, 'error/class', 'GenericError') |
37ce63eb | 148 | |
0bb0aea4 KW |
149 | def test_job_id_missing(self): |
150 | result = self.vm.qmp('block-stream', device='mid') | |
151 | self.assert_qmp(result, 'error/class', 'GenericError') | |
152 | ||
774a8850 | 153 | |
c1a34322 AG |
154 | class TestParallelOps(iotests.QMPTestCase): |
155 | num_ops = 4 # Number of parallel block-stream operations | |
156 | num_imgs = num_ops * 2 + 1 | |
39eaefce | 157 | image_len = num_ops * 512 * 1024 |
c1a34322 AG |
158 | imgs = [] |
159 | ||
160 | def setUp(self): | |
161 | opts = [] | |
162 | self.imgs = [] | |
163 | ||
164 | # Initialize file names and command-line options | |
165 | for i in range(self.num_imgs): | |
166 | img_depth = self.num_imgs - i - 1 | |
167 | opts.append("backing." * img_depth + "node-name=node%d" % i) | |
168 | self.imgs.append(os.path.join(iotests.test_dir, 'img-%d.img' % i)) | |
169 | ||
170 | # Create all images | |
171 | iotests.create_image(self.imgs[0], self.image_len) | |
172 | for i in range(1, self.num_imgs): | |
173 | qemu_img('create', '-f', iotests.imgfmt, | |
174 | '-o', 'backing_file=%s' % self.imgs[i-1], self.imgs[i]) | |
175 | ||
176 | # Put data into the images we are copying data from | |
39eaefce AG |
177 | odd_img_indexes = [x for x in reversed(range(self.num_imgs)) if x % 2 == 1] |
178 | for i in range(len(odd_img_indexes)): | |
179 | # Alternate between 256KB and 512KB. | |
c1a34322 | 180 | # This way jobs will not finish in the same order they were created |
39eaefce | 181 | num_kb = 256 + 256 * (i % 2) |
c1a34322 | 182 | qemu_io('-f', iotests.imgfmt, |
39eaefce AG |
183 | '-c', 'write -P 0xFF %dk %dk' % (i * 512, num_kb), |
184 | self.imgs[odd_img_indexes[i]]) | |
c1a34322 AG |
185 | |
186 | # Attach the drive to the VM | |
187 | self.vm = iotests.VM() | |
188 | self.vm.add_drive(self.imgs[-1], ','.join(opts)) | |
189 | self.vm.launch() | |
190 | ||
191 | def tearDown(self): | |
192 | self.vm.shutdown() | |
193 | for img in self.imgs: | |
194 | os.remove(img) | |
195 | ||
196 | # Test that it's possible to run several block-stream operations | |
197 | # in parallel in the same snapshot chain | |
198 | def test_stream_parallel(self): | |
199 | self.assert_no_active_block_jobs() | |
200 | ||
201 | # Check that the maps don't match before the streaming operations | |
202 | for i in range(2, self.num_imgs, 2): | |
aca7063a FZ |
203 | self.assertNotEqual(qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.imgs[i]), |
204 | qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.imgs[i-1]), | |
c1a34322 AG |
205 | 'image file map matches backing file before streaming') |
206 | ||
207 | # Create all streaming jobs | |
208 | pending_jobs = [] | |
209 | for i in range(2, self.num_imgs, 2): | |
210 | node_name = 'node%d' % i | |
211 | job_id = 'stream-%s' % node_name | |
212 | pending_jobs.append(job_id) | |
213 | result = self.vm.qmp('block-stream', device=node_name, job_id=job_id, base=self.imgs[i-2], speed=512*1024) | |
214 | self.assert_qmp(result, 'return', {}) | |
215 | ||
216 | # Wait for all jobs to be finished. | |
217 | while len(pending_jobs) > 0: | |
218 | for event in self.vm.get_qmp_events(wait=True): | |
219 | if event['event'] == 'BLOCK_JOB_COMPLETED': | |
220 | job_id = self.dictpath(event, 'data/device') | |
221 | self.assertTrue(job_id in pending_jobs) | |
222 | self.assert_qmp_absent(event, 'data/error') | |
223 | pending_jobs.remove(job_id) | |
224 | ||
225 | self.assert_no_active_block_jobs() | |
226 | self.vm.shutdown() | |
227 | ||
228 | # Check that all maps match now | |
229 | for i in range(2, self.num_imgs, 2): | |
230 | self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[i]), | |
231 | qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[i-1]), | |
232 | 'image file map does not match backing file after streaming') | |
233 | ||
eb290b78 AG |
234 | # Test that it's not possible to perform two block-stream |
235 | # operations if there are nodes involved in both. | |
236 | def test_overlapping_1(self): | |
237 | self.assert_no_active_block_jobs() | |
238 | ||
239 | # Set a speed limit to make sure that this job blocks the rest | |
240 | result = self.vm.qmp('block-stream', device='node4', job_id='stream-node4', base=self.imgs[1], speed=1024*1024) | |
241 | self.assert_qmp(result, 'return', {}) | |
242 | ||
243 | result = self.vm.qmp('block-stream', device='node5', job_id='stream-node5', base=self.imgs[2]) | |
244 | self.assert_qmp(result, 'error/class', 'GenericError') | |
245 | ||
246 | result = self.vm.qmp('block-stream', device='node3', job_id='stream-node3', base=self.imgs[2]) | |
247 | self.assert_qmp(result, 'error/class', 'GenericError') | |
248 | ||
249 | result = self.vm.qmp('block-stream', device='node4', job_id='stream-node4-v2') | |
250 | self.assert_qmp(result, 'error/class', 'GenericError') | |
251 | ||
252 | # block-commit should also fail if it touches nodes used by the stream job | |
253 | result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[4], job_id='commit-node4') | |
254 | self.assert_qmp(result, 'error/class', 'GenericError') | |
255 | ||
256 | result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[1], top=self.imgs[3], job_id='commit-node1') | |
257 | self.assert_qmp(result, 'error/class', 'GenericError') | |
258 | ||
259 | # This fails because it needs to modify the backing string in node2, which is blocked | |
260 | result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[0], top=self.imgs[1], job_id='commit-node0') | |
261 | self.assert_qmp(result, 'error/class', 'GenericError') | |
262 | ||
263 | self.wait_until_completed(drive='stream-node4') | |
264 | self.assert_no_active_block_jobs() | |
265 | ||
266 | # Similar to test_overlapping_1, but with block-commit | |
267 | # blocking the other jobs | |
268 | def test_overlapping_2(self): | |
269 | self.assertLessEqual(9, self.num_imgs) | |
270 | self.assert_no_active_block_jobs() | |
271 | ||
272 | # Set a speed limit to make sure that this job blocks the rest | |
273 | result = self.vm.qmp('block-commit', device='drive0', top=self.imgs[5], base=self.imgs[3], job_id='commit-node3', speed=1024*1024) | |
274 | self.assert_qmp(result, 'return', {}) | |
275 | ||
276 | result = self.vm.qmp('block-stream', device='node3', job_id='stream-node3') | |
277 | self.assert_qmp(result, 'error/class', 'GenericError') | |
278 | ||
279 | result = self.vm.qmp('block-stream', device='node6', base=self.imgs[2], job_id='stream-node6') | |
280 | self.assert_qmp(result, 'error/class', 'GenericError') | |
281 | ||
282 | result = self.vm.qmp('block-stream', device='node4', base=self.imgs[2], job_id='stream-node4') | |
283 | self.assert_qmp(result, 'error/class', 'GenericError') | |
284 | ||
285 | result = self.vm.qmp('block-stream', device='node6', base=self.imgs[4], job_id='stream-node6-v2') | |
286 | self.assert_qmp(result, 'error/class', 'GenericError') | |
287 | ||
eb290b78 AG |
288 | # This fails because block-commit currently blocks the active layer even if it's not used |
289 | result = self.vm.qmp('block-stream', device='drive0', base=self.imgs[5], job_id='stream-drive0') | |
290 | self.assert_qmp(result, 'error/class', 'GenericError') | |
291 | ||
292 | self.wait_until_completed(drive='commit-node3') | |
293 | ||
294 | # Similar to test_overlapping_2, but here block-commit doesn't use the 'top' parameter. | |
295 | # Internally this uses a mirror block job, hence the separate test case. | |
296 | def test_overlapping_3(self): | |
297 | self.assertLessEqual(8, self.num_imgs) | |
298 | self.assert_no_active_block_jobs() | |
299 | ||
300 | # Set a speed limit to make sure that this job blocks the rest | |
301 | result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[3], job_id='commit-drive0', speed=1024*1024) | |
302 | self.assert_qmp(result, 'return', {}) | |
303 | ||
304 | result = self.vm.qmp('block-stream', device='node5', base=self.imgs[3], job_id='stream-node6') | |
305 | self.assert_qmp(result, 'error/class', 'GenericError') | |
306 | ||
1dac83f1 | 307 | event = self.vm.event_wait(name='BLOCK_JOB_READY') |
eb290b78 AG |
308 | self.assert_qmp(event, 'data/device', 'commit-drive0') |
309 | self.assert_qmp(event, 'data/type', 'commit') | |
310 | self.assert_qmp_absent(event, 'data/error') | |
311 | ||
312 | result = self.vm.qmp('block-job-complete', device='commit-drive0') | |
313 | self.assert_qmp(result, 'return', {}) | |
314 | ||
315 | self.wait_until_completed(drive='commit-drive0') | |
704d59f1 AG |
316 | |
317 | # Test a block-stream and a block-commit job in parallel | |
39eaefce AG |
318 | # Here the stream job is supposed to finish quickly in order to reproduce |
319 | # the scenario that triggers the bug fixed in 3d5d319e1221 and 1a63a907507 | |
320 | def test_stream_commit_1(self): | |
704d59f1 AG |
321 | self.assertLessEqual(8, self.num_imgs) |
322 | self.assert_no_active_block_jobs() | |
323 | ||
324 | # Stream from node0 into node2 | |
39eaefce | 325 | result = self.vm.qmp('block-stream', device='node2', base_node='node0', job_id='node2') |
704d59f1 AG |
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 | ||
39eaefce AG |
350 | # This is similar to test_stream_commit_1 but both jobs are slowed |
351 | # down so they can run in parallel for a little while. | |
352 | def test_stream_commit_2(self): | |
353 | self.assertLessEqual(8, self.num_imgs) | |
354 | self.assert_no_active_block_jobs() | |
355 | ||
356 | # Stream from node0 into node4 | |
357 | result = self.vm.qmp('block-stream', device='node4', base_node='node0', job_id='node4', speed=1024*1024) | |
358 | self.assert_qmp(result, 'return', {}) | |
359 | ||
360 | # Commit from the active layer into node5 | |
361 | result = self.vm.qmp('block-commit', device='drive0', base=self.imgs[5], speed=1024*1024) | |
362 | self.assert_qmp(result, 'return', {}) | |
363 | ||
364 | # Wait for all jobs to be finished. | |
365 | pending_jobs = ['node4', 'drive0'] | |
366 | while len(pending_jobs) > 0: | |
367 | for event in self.vm.get_qmp_events(wait=True): | |
368 | if event['event'] == 'BLOCK_JOB_COMPLETED': | |
369 | node_name = self.dictpath(event, 'data/device') | |
370 | self.assertTrue(node_name in pending_jobs) | |
371 | self.assert_qmp_absent(event, 'data/error') | |
372 | pending_jobs.remove(node_name) | |
373 | if event['event'] == 'BLOCK_JOB_READY': | |
374 | self.assert_qmp(event, 'data/device', 'drive0') | |
375 | self.assert_qmp(event, 'data/type', 'commit') | |
376 | self.assert_qmp_absent(event, 'data/error') | |
377 | self.assertTrue('drive0' in pending_jobs) | |
378 | self.vm.qmp('block-job-complete', device='drive0') | |
379 | ||
380 | self.assert_no_active_block_jobs() | |
381 | ||
7eb13c9d AG |
382 | # Test the base_node parameter |
383 | def test_stream_base_node_name(self): | |
384 | self.assert_no_active_block_jobs() | |
385 | ||
aca7063a FZ |
386 | self.assertNotEqual(qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.imgs[4]), |
387 | qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.imgs[3]), | |
7eb13c9d AG |
388 | 'image file map matches backing file before streaming') |
389 | ||
390 | # Error: the base node does not exist | |
391 | result = self.vm.qmp('block-stream', device='node4', base_node='none', job_id='stream') | |
392 | self.assert_qmp(result, 'error/class', 'GenericError') | |
393 | ||
394 | # Error: the base node is not a backing file of the top node | |
395 | result = self.vm.qmp('block-stream', device='node4', base_node='node6', job_id='stream') | |
396 | self.assert_qmp(result, 'error/class', 'GenericError') | |
397 | ||
398 | # Error: the base node is the same as the top node | |
399 | result = self.vm.qmp('block-stream', device='node4', base_node='node4', job_id='stream') | |
400 | self.assert_qmp(result, 'error/class', 'GenericError') | |
401 | ||
402 | # Error: cannot specify 'base' and 'base-node' at the same time | |
403 | result = self.vm.qmp('block-stream', device='node4', base=self.imgs[2], base_node='node2', job_id='stream') | |
404 | self.assert_qmp(result, 'error/class', 'GenericError') | |
405 | ||
406 | # Success: the base node is a backing file of the top node | |
407 | result = self.vm.qmp('block-stream', device='node4', base_node='node2', job_id='stream') | |
408 | self.assert_qmp(result, 'return', {}) | |
409 | ||
410 | self.wait_until_completed(drive='stream') | |
411 | ||
412 | self.assert_no_active_block_jobs() | |
413 | self.vm.shutdown() | |
414 | ||
415 | self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[4]), | |
416 | qemu_io('-f', iotests.imgfmt, '-c', 'map', self.imgs[3]), | |
417 | 'image file map matches backing file after streaming') | |
418 | ||
48361afb AG |
419 | class TestQuorum(iotests.QMPTestCase): |
420 | num_children = 3 | |
421 | children = [] | |
422 | backing = [] | |
423 | ||
424 | def setUp(self): | |
425 | opts = ['driver=quorum', 'vote-threshold=2'] | |
426 | ||
427 | # Initialize file names and command-line options | |
428 | for i in range(self.num_children): | |
429 | child_img = os.path.join(iotests.test_dir, 'img-%d.img' % i) | |
430 | backing_img = os.path.join(iotests.test_dir, 'backing-%d.img' % i) | |
431 | self.children.append(child_img) | |
432 | self.backing.append(backing_img) | |
433 | qemu_img('create', '-f', iotests.imgfmt, backing_img, '1M') | |
434 | qemu_io('-f', iotests.imgfmt, | |
435 | '-c', 'write -P 0x55 0 1024', backing_img) | |
436 | qemu_img('create', '-f', iotests.imgfmt, | |
437 | '-o', 'backing_file=%s' % backing_img, child_img) | |
438 | opts.append("children.%d.file.filename=%s" % (i, child_img)) | |
439 | opts.append("children.%d.node-name=node%d" % (i, i)) | |
440 | ||
441 | # Attach the drive to the VM | |
442 | self.vm = iotests.VM() | |
443 | self.vm.add_drive(path = None, opts = ','.join(opts)) | |
444 | self.vm.launch() | |
445 | ||
446 | def tearDown(self): | |
447 | self.vm.shutdown() | |
448 | for img in self.children: | |
449 | os.remove(img) | |
450 | for img in self.backing: | |
451 | os.remove(img) | |
452 | ||
453 | def test_stream_quorum(self): | |
454 | if not iotests.supports_quorum(): | |
455 | return | |
456 | ||
aca7063a FZ |
457 | self.assertNotEqual(qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.children[0]), |
458 | qemu_io('-f', iotests.imgfmt, '-rU', '-c', 'map', self.backing[0]), | |
48361afb AG |
459 | 'image file map matches backing file before streaming') |
460 | ||
461 | self.assert_no_active_block_jobs() | |
462 | ||
463 | result = self.vm.qmp('block-stream', device='node0', job_id='stream-node0') | |
464 | self.assert_qmp(result, 'return', {}) | |
465 | ||
466 | self.wait_until_completed(drive='stream-node0') | |
467 | ||
468 | self.assert_no_active_block_jobs() | |
469 | self.vm.shutdown() | |
470 | ||
471 | self.assertEqual(qemu_io('-f', iotests.imgfmt, '-c', 'map', self.children[0]), | |
472 | qemu_io('-f', iotests.imgfmt, '-c', 'map', self.backing[0]), | |
473 | 'image file map does not match backing file after streaming') | |
474 | ||
2499a096 | 475 | class TestSmallerBackingFile(iotests.QMPTestCase): |
774a8850 SH |
476 | backing_len = 1 * 1024 * 1024 # MB |
477 | image_len = 2 * backing_len | |
478 | ||
479 | def setUp(self): | |
2499a096 | 480 | iotests.create_image(backing_img, self.backing_len) |
774a8850 SH |
481 | qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img, str(self.image_len)) |
482 | self.vm = iotests.VM().add_drive(test_img) | |
483 | self.vm.launch() | |
484 | ||
485 | # If this hangs, then you are missing a fix to complete streaming when the | |
486 | # end of the backing file is reached. | |
487 | def test_stream(self): | |
ecc1c88e | 488 | self.assert_no_active_block_jobs() |
774a8850 SH |
489 | |
490 | result = self.vm.qmp('block-stream', device='drive0') | |
491 | self.assert_qmp(result, 'return', {}) | |
492 | ||
9974ad40 | 493 | self.wait_until_completed() |
774a8850 | 494 | |
ecc1c88e | 495 | self.assert_no_active_block_jobs() |
774a8850 SH |
496 | self.vm.shutdown() |
497 | ||
2499a096 | 498 | class TestErrors(iotests.QMPTestCase): |
90f0b711 PB |
499 | image_len = 2 * 1024 * 1024 # MB |
500 | ||
501 | # this should match STREAM_BUFFER_SIZE/512 in block/stream.c | |
502 | STREAM_BUFFER_SIZE = 512 * 1024 | |
503 | ||
504 | def create_blkdebug_file(self, name, event, errno): | |
505 | file = open(name, 'w') | |
506 | file.write(''' | |
507 | [inject-error] | |
508 | state = "1" | |
509 | event = "%s" | |
510 | errno = "%d" | |
511 | immediately = "off" | |
512 | once = "on" | |
513 | sector = "%d" | |
514 | ||
515 | [set-state] | |
516 | state = "1" | |
517 | event = "%s" | |
518 | new_state = "2" | |
519 | ||
520 | [set-state] | |
521 | state = "2" | |
522 | event = "%s" | |
523 | new_state = "1" | |
524 | ''' % (event, errno, self.STREAM_BUFFER_SIZE / 512, event, event)) | |
525 | file.close() | |
526 | ||
527 | class TestEIO(TestErrors): | |
528 | def setUp(self): | |
529 | self.blkdebug_file = backing_img + ".blkdebug" | |
2499a096 | 530 | iotests.create_image(backing_img, TestErrors.image_len) |
90f0b711 PB |
531 | self.create_blkdebug_file(self.blkdebug_file, "read_aio", 5) |
532 | qemu_img('create', '-f', iotests.imgfmt, | |
533 | '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw' | |
534 | % (self.blkdebug_file, backing_img), | |
535 | test_img) | |
536 | self.vm = iotests.VM().add_drive(test_img) | |
537 | self.vm.launch() | |
538 | ||
539 | def tearDown(self): | |
540 | self.vm.shutdown() | |
541 | os.remove(test_img) | |
542 | os.remove(backing_img) | |
543 | os.remove(self.blkdebug_file) | |
544 | ||
545 | def test_report(self): | |
ecc1c88e | 546 | self.assert_no_active_block_jobs() |
90f0b711 PB |
547 | |
548 | result = self.vm.qmp('block-stream', device='drive0') | |
549 | self.assert_qmp(result, 'return', {}) | |
550 | ||
551 | completed = False | |
552 | error = False | |
553 | while not completed: | |
554 | for event in self.vm.get_qmp_events(wait=True): | |
555 | if event['event'] == 'BLOCK_JOB_ERROR': | |
556 | self.assert_qmp(event, 'data/device', 'drive0') | |
557 | self.assert_qmp(event, 'data/operation', 'read') | |
558 | error = True | |
559 | elif event['event'] == 'BLOCK_JOB_COMPLETED': | |
560 | self.assertTrue(error, 'job completed unexpectedly') | |
561 | self.assert_qmp(event, 'data/type', 'stream') | |
562 | self.assert_qmp(event, 'data/device', 'drive0') | |
563 | self.assert_qmp(event, 'data/error', 'Input/output error') | |
564 | self.assert_qmp(event, 'data/offset', self.STREAM_BUFFER_SIZE) | |
565 | self.assert_qmp(event, 'data/len', self.image_len) | |
566 | completed = True | |
1dac83f1 KW |
567 | elif event['event'] == 'JOB_STATUS_CHANGE': |
568 | self.assert_qmp(event, 'data/id', 'drive0') | |
90f0b711 | 569 | |
ecc1c88e | 570 | self.assert_no_active_block_jobs() |
90f0b711 PB |
571 | self.vm.shutdown() |
572 | ||
573 | def test_ignore(self): | |
ecc1c88e | 574 | self.assert_no_active_block_jobs() |
90f0b711 PB |
575 | |
576 | result = self.vm.qmp('block-stream', device='drive0', on_error='ignore') | |
577 | self.assert_qmp(result, 'return', {}) | |
578 | ||
579 | error = False | |
580 | completed = False | |
581 | while not completed: | |
582 | for event in self.vm.get_qmp_events(wait=True): | |
583 | if event['event'] == 'BLOCK_JOB_ERROR': | |
2c3b44da | 584 | error = True |
90f0b711 PB |
585 | self.assert_qmp(event, 'data/device', 'drive0') |
586 | self.assert_qmp(event, 'data/operation', 'read') | |
587 | result = self.vm.qmp('query-block-jobs') | |
2c3b44da JS |
588 | if result == {'return': []}: |
589 | # Job finished too quickly | |
590 | continue | |
90f0b711 | 591 | self.assert_qmp(result, 'return[0]/paused', False) |
90f0b711 PB |
592 | elif event['event'] == 'BLOCK_JOB_COMPLETED': |
593 | self.assertTrue(error, 'job completed unexpectedly') | |
594 | self.assert_qmp(event, 'data/type', 'stream') | |
595 | self.assert_qmp(event, 'data/device', 'drive0') | |
596 | self.assert_qmp(event, 'data/error', 'Input/output error') | |
597 | self.assert_qmp(event, 'data/offset', self.image_len) | |
598 | self.assert_qmp(event, 'data/len', self.image_len) | |
599 | completed = True | |
1dac83f1 KW |
600 | elif event['event'] == 'JOB_STATUS_CHANGE': |
601 | self.assert_qmp(event, 'data/id', 'drive0') | |
90f0b711 | 602 | |
ecc1c88e | 603 | self.assert_no_active_block_jobs() |
90f0b711 PB |
604 | self.vm.shutdown() |
605 | ||
606 | def test_stop(self): | |
ecc1c88e | 607 | self.assert_no_active_block_jobs() |
90f0b711 PB |
608 | |
609 | result = self.vm.qmp('block-stream', device='drive0', on_error='stop') | |
610 | self.assert_qmp(result, 'return', {}) | |
611 | ||
612 | error = False | |
613 | completed = False | |
614 | while not completed: | |
615 | for event in self.vm.get_qmp_events(wait=True): | |
616 | if event['event'] == 'BLOCK_JOB_ERROR': | |
01809194 | 617 | error = True |
90f0b711 PB |
618 | self.assert_qmp(event, 'data/device', 'drive0') |
619 | self.assert_qmp(event, 'data/operation', 'read') | |
620 | ||
621 | result = self.vm.qmp('query-block-jobs') | |
622 | self.assert_qmp(result, 'return[0]/paused', True) | |
623 | self.assert_qmp(result, 'return[0]/offset', self.STREAM_BUFFER_SIZE) | |
624 | self.assert_qmp(result, 'return[0]/io-status', 'failed') | |
625 | ||
626 | result = self.vm.qmp('block-job-resume', device='drive0') | |
627 | self.assert_qmp(result, 'return', {}) | |
628 | ||
629 | result = self.vm.qmp('query-block-jobs') | |
01809194 JS |
630 | if result == {'return': []}: |
631 | # Race; likely already finished. Check. | |
632 | continue | |
90f0b711 PB |
633 | self.assert_qmp(result, 'return[0]/paused', False) |
634 | self.assert_qmp(result, 'return[0]/io-status', 'ok') | |
90f0b711 PB |
635 | elif event['event'] == 'BLOCK_JOB_COMPLETED': |
636 | self.assertTrue(error, 'job completed unexpectedly') | |
637 | self.assert_qmp(event, 'data/type', 'stream') | |
638 | self.assert_qmp(event, 'data/device', 'drive0') | |
639 | self.assert_qmp_absent(event, 'data/error') | |
640 | self.assert_qmp(event, 'data/offset', self.image_len) | |
641 | self.assert_qmp(event, 'data/len', self.image_len) | |
642 | completed = True | |
1dac83f1 KW |
643 | elif event['event'] == 'JOB_STATUS_CHANGE': |
644 | self.assert_qmp(event, 'data/id', 'drive0') | |
90f0b711 | 645 | |
ecc1c88e | 646 | self.assert_no_active_block_jobs() |
90f0b711 PB |
647 | self.vm.shutdown() |
648 | ||
649 | def test_enospc(self): | |
ecc1c88e | 650 | self.assert_no_active_block_jobs() |
90f0b711 PB |
651 | |
652 | result = self.vm.qmp('block-stream', device='drive0', on_error='enospc') | |
653 | self.assert_qmp(result, 'return', {}) | |
654 | ||
655 | completed = False | |
656 | error = False | |
657 | while not completed: | |
658 | for event in self.vm.get_qmp_events(wait=True): | |
659 | if event['event'] == 'BLOCK_JOB_ERROR': | |
660 | self.assert_qmp(event, 'data/device', 'drive0') | |
661 | self.assert_qmp(event, 'data/operation', 'read') | |
662 | error = True | |
663 | elif event['event'] == 'BLOCK_JOB_COMPLETED': | |
664 | self.assertTrue(error, 'job completed unexpectedly') | |
665 | self.assert_qmp(event, 'data/type', 'stream') | |
666 | self.assert_qmp(event, 'data/device', 'drive0') | |
667 | self.assert_qmp(event, 'data/error', 'Input/output error') | |
668 | self.assert_qmp(event, 'data/offset', self.STREAM_BUFFER_SIZE) | |
669 | self.assert_qmp(event, 'data/len', self.image_len) | |
670 | completed = True | |
1dac83f1 KW |
671 | elif event['event'] == 'JOB_STATUS_CHANGE': |
672 | self.assert_qmp(event, 'data/id', 'drive0') | |
90f0b711 | 673 | |
ecc1c88e | 674 | self.assert_no_active_block_jobs() |
90f0b711 PB |
675 | self.vm.shutdown() |
676 | ||
677 | class TestENOSPC(TestErrors): | |
678 | def setUp(self): | |
679 | self.blkdebug_file = backing_img + ".blkdebug" | |
2499a096 | 680 | iotests.create_image(backing_img, TestErrors.image_len) |
90f0b711 PB |
681 | self.create_blkdebug_file(self.blkdebug_file, "read_aio", 28) |
682 | qemu_img('create', '-f', iotests.imgfmt, | |
683 | '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw' | |
684 | % (self.blkdebug_file, backing_img), | |
685 | test_img) | |
686 | self.vm = iotests.VM().add_drive(test_img) | |
687 | self.vm.launch() | |
688 | ||
689 | def tearDown(self): | |
690 | self.vm.shutdown() | |
691 | os.remove(test_img) | |
692 | os.remove(backing_img) | |
693 | os.remove(self.blkdebug_file) | |
694 | ||
695 | def test_enospc(self): | |
ecc1c88e | 696 | self.assert_no_active_block_jobs() |
90f0b711 PB |
697 | |
698 | result = self.vm.qmp('block-stream', device='drive0', on_error='enospc') | |
699 | self.assert_qmp(result, 'return', {}) | |
700 | ||
701 | error = False | |
702 | completed = False | |
703 | while not completed: | |
704 | for event in self.vm.get_qmp_events(wait=True): | |
705 | if event['event'] == 'BLOCK_JOB_ERROR': | |
706 | self.assert_qmp(event, 'data/device', 'drive0') | |
707 | self.assert_qmp(event, 'data/operation', 'read') | |
dca9b6a2 | 708 | error = True |
90f0b711 PB |
709 | |
710 | result = self.vm.qmp('query-block-jobs') | |
711 | self.assert_qmp(result, 'return[0]/paused', True) | |
712 | self.assert_qmp(result, 'return[0]/offset', self.STREAM_BUFFER_SIZE) | |
713 | self.assert_qmp(result, 'return[0]/io-status', 'nospace') | |
714 | ||
715 | result = self.vm.qmp('block-job-resume', device='drive0') | |
716 | self.assert_qmp(result, 'return', {}) | |
717 | ||
718 | result = self.vm.qmp('query-block-jobs') | |
dca9b6a2 HR |
719 | if result == {'return': []}: |
720 | # Race; likely already finished. Check. | |
721 | continue | |
90f0b711 PB |
722 | self.assert_qmp(result, 'return[0]/paused', False) |
723 | self.assert_qmp(result, 'return[0]/io-status', 'ok') | |
90f0b711 PB |
724 | elif event['event'] == 'BLOCK_JOB_COMPLETED': |
725 | self.assertTrue(error, 'job completed unexpectedly') | |
726 | self.assert_qmp(event, 'data/type', 'stream') | |
727 | self.assert_qmp(event, 'data/device', 'drive0') | |
728 | self.assert_qmp_absent(event, 'data/error') | |
729 | self.assert_qmp(event, 'data/offset', self.image_len) | |
730 | self.assert_qmp(event, 'data/len', self.image_len) | |
731 | completed = True | |
1dac83f1 KW |
732 | elif event['event'] == 'JOB_STATUS_CHANGE': |
733 | self.assert_qmp(event, 'data/id', 'drive0') | |
90f0b711 | 734 | |
ecc1c88e | 735 | self.assert_no_active_block_jobs() |
90f0b711 | 736 | self.vm.shutdown() |
774a8850 | 737 | |
2499a096 | 738 | class TestStreamStop(iotests.QMPTestCase): |
37ce63eb SH |
739 | image_len = 8 * 1024 * 1024 * 1024 # GB |
740 | ||
741 | def setUp(self): | |
742 | qemu_img('create', backing_img, str(TestStreamStop.image_len)) | |
90c9b167 | 743 | qemu_io('-f', 'raw', '-c', 'write -P 0x1 0 32M', backing_img) |
37ce63eb | 744 | qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img) |
90c9b167 | 745 | qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x1 32M 32M', test_img) |
b59b3d57 | 746 | self.vm = iotests.VM().add_drive("blkdebug::" + test_img) |
37ce63eb SH |
747 | self.vm.launch() |
748 | ||
749 | def tearDown(self): | |
750 | self.vm.shutdown() | |
751 | os.remove(test_img) | |
752 | os.remove(backing_img) | |
753 | ||
754 | def test_stream_stop(self): | |
ecc1c88e | 755 | self.assert_no_active_block_jobs() |
37ce63eb | 756 | |
b59b3d57 | 757 | self.vm.pause_drive('drive0') |
db58f9c0 | 758 | result = self.vm.qmp('block-stream', device='drive0') |
37ce63eb SH |
759 | self.assert_qmp(result, 'return', {}) |
760 | ||
0fd05e8d | 761 | time.sleep(0.1) |
37ce63eb | 762 | events = self.vm.get_qmp_events(wait=False) |
1dac83f1 KW |
763 | for e in events: |
764 | self.assert_qmp(e, 'event', 'JOB_STATUS_CHANGE') | |
765 | self.assert_qmp(e, 'data/id', 'drive0') | |
37ce63eb | 766 | |
b59b3d57 | 767 | self.cancel_and_wait(resume=True) |
37ce63eb | 768 | |
2499a096 | 769 | class TestSetSpeed(iotests.QMPTestCase): |
37ce63eb SH |
770 | image_len = 80 * 1024 * 1024 # MB |
771 | ||
772 | def setUp(self): | |
773 | qemu_img('create', backing_img, str(TestSetSpeed.image_len)) | |
90c9b167 | 774 | qemu_io('-f', 'raw', '-c', 'write -P 0x1 0 32M', backing_img) |
37ce63eb | 775 | qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img) |
90c9b167 | 776 | qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x1 32M 32M', test_img) |
b59b3d57 | 777 | self.vm = iotests.VM().add_drive('blkdebug::' + test_img) |
37ce63eb SH |
778 | self.vm.launch() |
779 | ||
780 | def tearDown(self): | |
781 | self.vm.shutdown() | |
782 | os.remove(test_img) | |
783 | os.remove(backing_img) | |
784 | ||
e425306a SH |
785 | # This is a short performance test which is not run by default. |
786 | # Invoke "IMGFMT=qed ./030 TestSetSpeed.perf_test_throughput" | |
787 | def perf_test_throughput(self): | |
ecc1c88e | 788 | self.assert_no_active_block_jobs() |
37ce63eb | 789 | |
db58f9c0 | 790 | result = self.vm.qmp('block-stream', device='drive0') |
37ce63eb SH |
791 | self.assert_qmp(result, 'return', {}) |
792 | ||
e425306a | 793 | result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024) |
37ce63eb SH |
794 | self.assert_qmp(result, 'return', {}) |
795 | ||
9974ad40 | 796 | self.wait_until_completed() |
37ce63eb | 797 | |
ecc1c88e | 798 | self.assert_no_active_block_jobs() |
37ce63eb | 799 | |
e425306a | 800 | def test_set_speed(self): |
ecc1c88e | 801 | self.assert_no_active_block_jobs() |
e425306a | 802 | |
b59b3d57 | 803 | self.vm.pause_drive('drive0') |
e425306a SH |
804 | result = self.vm.qmp('block-stream', device='drive0') |
805 | self.assert_qmp(result, 'return', {}) | |
806 | ||
807 | # Default speed is 0 | |
808 | result = self.vm.qmp('query-block-jobs') | |
809 | self.assert_qmp(result, 'return[0]/device', 'drive0') | |
810 | self.assert_qmp(result, 'return[0]/speed', 0) | |
811 | ||
812 | result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024) | |
813 | self.assert_qmp(result, 'return', {}) | |
814 | ||
815 | # Ensure the speed we set was accepted | |
816 | result = self.vm.qmp('query-block-jobs') | |
817 | self.assert_qmp(result, 'return[0]/device', 'drive0') | |
818 | self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024) | |
819 | ||
b59b3d57 FZ |
820 | self.cancel_and_wait(resume=True) |
821 | self.vm.pause_drive('drive0') | |
e425306a SH |
822 | |
823 | # Check setting speed in block-stream works | |
824 | result = self.vm.qmp('block-stream', device='drive0', speed=4 * 1024 * 1024) | |
825 | self.assert_qmp(result, 'return', {}) | |
826 | ||
827 | result = self.vm.qmp('query-block-jobs') | |
828 | self.assert_qmp(result, 'return[0]/device', 'drive0') | |
829 | self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024) | |
830 | ||
b59b3d57 | 831 | self.cancel_and_wait(resume=True) |
e425306a SH |
832 | |
833 | def test_set_speed_invalid(self): | |
ecc1c88e | 834 | self.assert_no_active_block_jobs() |
e425306a SH |
835 | |
836 | result = self.vm.qmp('block-stream', device='drive0', speed=-1) | |
58c8cce2 | 837 | self.assert_qmp(result, 'error/class', 'GenericError') |
e425306a | 838 | |
ecc1c88e | 839 | self.assert_no_active_block_jobs() |
e425306a | 840 | |
dca9b6a2 | 841 | self.vm.pause_drive('drive0') |
e425306a SH |
842 | result = self.vm.qmp('block-stream', device='drive0') |
843 | self.assert_qmp(result, 'return', {}) | |
844 | ||
845 | result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1) | |
58c8cce2 | 846 | self.assert_qmp(result, 'error/class', 'GenericError') |
e425306a | 847 | |
dca9b6a2 | 848 | self.cancel_and_wait(resume=True) |
e425306a | 849 | |
37ce63eb SH |
850 | if __name__ == '__main__': |
851 | iotests.main(supported_fmts=['qcow2', 'qed']) |