]>
Commit | Line | Data |
---|---|---|
903cb1bf | 1 | #!/usr/bin/env python3 |
44c7ca5e PB |
2 | # |
3 | # Tests for image mirroring. | |
4 | # | |
5 | # Copyright (C) 2012 Red Hat, Inc. | |
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 | ||
21 | import time | |
22 | import os | |
a1da1878 | 23 | import re |
44c7ca5e PB |
24 | import iotests |
25 | from iotests import qemu_img, qemu_io | |
44c7ca5e PB |
26 | |
27 | backing_img = os.path.join(iotests.test_dir, 'backing.img') | |
28 | target_backing_img = os.path.join(iotests.test_dir, 'target-backing.img') | |
29 | test_img = os.path.join(iotests.test_dir, 'test.img') | |
30 | target_img = os.path.join(iotests.test_dir, 'target.img') | |
31 | ||
d88964ae BC |
32 | quorum_img1 = os.path.join(iotests.test_dir, 'quorum1.img') |
33 | quorum_img2 = os.path.join(iotests.test_dir, 'quorum2.img') | |
34 | quorum_img3 = os.path.join(iotests.test_dir, 'quorum3.img') | |
35 | quorum_repair_img = os.path.join(iotests.test_dir, 'quorum_repair.img') | |
36 | quorum_snapshot_file = os.path.join(iotests.test_dir, 'quorum_snapshot.img') | |
37 | ||
e5ac52d8 | 38 | nbd_sock_path = os.path.join(iotests.sock_dir, 'nbd.sock') |
a1da1878 | 39 | |
866323f3 | 40 | class TestSingleDrive(iotests.QMPTestCase): |
44c7ca5e | 41 | image_len = 1 * 1024 * 1024 # MB |
94ca2c73 FZ |
42 | qmp_cmd = 'drive-mirror' |
43 | qmp_target = target_img | |
44c7ca5e PB |
44 | |
45 | def setUp(self): | |
3b9f27d2 | 46 | iotests.create_image(backing_img, self.image_len) |
b66ff2c2 EB |
47 | qemu_img('create', '-f', iotests.imgfmt, |
48 | '-o', 'backing_file=%s' % backing_img, '-F', 'raw', test_img) | |
d3c8c674 | 49 | self.vm = iotests.VM().add_drive(test_img, "node-name=top,backing.node-name=base") |
0ed82f7a HR |
50 | if iotests.qemu_default_machine == 'pc': |
51 | self.vm.add_drive(None, 'media=cdrom', 'ide') | |
44c7ca5e PB |
52 | self.vm.launch() |
53 | ||
54 | def tearDown(self): | |
55 | self.vm.shutdown() | |
56 | os.remove(test_img) | |
57 | os.remove(backing_img) | |
58 | try: | |
59 | os.remove(target_img) | |
60 | except OSError: | |
61 | pass | |
62 | ||
63 | def test_complete(self): | |
ecc1c88e | 64 | self.assert_no_active_block_jobs() |
44c7ca5e | 65 | |
94ca2c73 FZ |
66 | result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', |
67 | target=self.qmp_target) | |
44c7ca5e PB |
68 | self.assert_qmp(result, 'return', {}) |
69 | ||
70 | self.complete_and_wait() | |
71 | result = self.vm.qmp('query-block') | |
72 | self.assert_qmp(result, 'return[0]/inserted/file', target_img) | |
73 | self.vm.shutdown() | |
3a3918c3 | 74 | self.assertTrue(iotests.compare_images(test_img, target_img), |
44c7ca5e PB |
75 | 'target image does not match source after mirroring') |
76 | ||
77 | def test_cancel(self): | |
ecc1c88e | 78 | self.assert_no_active_block_jobs() |
44c7ca5e | 79 | |
94ca2c73 FZ |
80 | result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', |
81 | target=self.qmp_target) | |
44c7ca5e PB |
82 | self.assert_qmp(result, 'return', {}) |
83 | ||
2575fe16 | 84 | self.cancel_and_wait(force=True) |
44c7ca5e PB |
85 | result = self.vm.qmp('query-block') |
86 | self.assert_qmp(result, 'return[0]/inserted/file', test_img) | |
44c7ca5e PB |
87 | |
88 | def test_cancel_after_ready(self): | |
ecc1c88e | 89 | self.assert_no_active_block_jobs() |
44c7ca5e | 90 | |
94ca2c73 FZ |
91 | result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', |
92 | target=self.qmp_target) | |
44c7ca5e PB |
93 | self.assert_qmp(result, 'return', {}) |
94 | ||
2575fe16 | 95 | self.wait_ready_and_cancel() |
44c7ca5e PB |
96 | result = self.vm.qmp('query-block') |
97 | self.assert_qmp(result, 'return[0]/inserted/file', test_img) | |
98 | self.vm.shutdown() | |
3a3918c3 | 99 | self.assertTrue(iotests.compare_images(test_img, target_img), |
44c7ca5e PB |
100 | 'target image does not match source after mirroring') |
101 | ||
102 | def test_pause(self): | |
ecc1c88e | 103 | self.assert_no_active_block_jobs() |
44c7ca5e | 104 | |
94ca2c73 FZ |
105 | result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', |
106 | target=self.qmp_target) | |
44c7ca5e PB |
107 | self.assert_qmp(result, 'return', {}) |
108 | ||
2c93c5cb | 109 | self.pause_job('drive0') |
44c7ca5e | 110 | |
44c7ca5e PB |
111 | result = self.vm.qmp('query-block-jobs') |
112 | offset = self.dictpath(result, 'return[0]/offset') | |
113 | ||
2c93c5cb | 114 | time.sleep(0.5) |
44c7ca5e PB |
115 | result = self.vm.qmp('query-block-jobs') |
116 | self.assert_qmp(result, 'return[0]/offset', offset) | |
117 | ||
118 | result = self.vm.qmp('block-job-resume', device='drive0') | |
119 | self.assert_qmp(result, 'return', {}) | |
120 | ||
121 | self.complete_and_wait() | |
122 | self.vm.shutdown() | |
3a3918c3 | 123 | self.assertTrue(iotests.compare_images(test_img, target_img), |
08e4ed6c PB |
124 | 'target image does not match source after mirroring') |
125 | ||
126 | def test_small_buffer(self): | |
ecc1c88e | 127 | self.assert_no_active_block_jobs() |
08e4ed6c PB |
128 | |
129 | # A small buffer is rounded up automatically | |
94ca2c73 FZ |
130 | result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', |
131 | buf_size=4096, target=self.qmp_target) | |
08e4ed6c PB |
132 | self.assert_qmp(result, 'return', {}) |
133 | ||
134 | self.complete_and_wait() | |
135 | result = self.vm.qmp('query-block') | |
136 | self.assert_qmp(result, 'return[0]/inserted/file', target_img) | |
137 | self.vm.shutdown() | |
3a3918c3 | 138 | self.assertTrue(iotests.compare_images(test_img, target_img), |
08e4ed6c PB |
139 | 'target image does not match source after mirroring') |
140 | ||
141 | def test_small_buffer2(self): | |
ecc1c88e | 142 | self.assert_no_active_block_jobs() |
08e4ed6c PB |
143 | |
144 | qemu_img('create', '-f', iotests.imgfmt, '-o', 'cluster_size=%d,size=%d' | |
3b9f27d2 | 145 | % (self.image_len, self.image_len), target_img) |
94ca2c73 FZ |
146 | result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', |
147 | buf_size=65536, mode='existing', target=self.qmp_target) | |
08e4ed6c PB |
148 | self.assert_qmp(result, 'return', {}) |
149 | ||
150 | self.complete_and_wait() | |
151 | result = self.vm.qmp('query-block') | |
152 | self.assert_qmp(result, 'return[0]/inserted/file', target_img) | |
153 | self.vm.shutdown() | |
3a3918c3 | 154 | self.assertTrue(iotests.compare_images(test_img, target_img), |
44c7ca5e PB |
155 | 'target image does not match source after mirroring') |
156 | ||
157 | def test_large_cluster(self): | |
ecc1c88e | 158 | self.assert_no_active_block_jobs() |
44c7ca5e PB |
159 | |
160 | qemu_img('create', '-f', iotests.imgfmt, '-o', 'cluster_size=%d,backing_file=%s' | |
b66ff2c2 EB |
161 | % (self.image_len, backing_img), |
162 | '-F', 'raw', target_img) | |
94ca2c73 FZ |
163 | result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', |
164 | mode='existing', target=self.qmp_target) | |
44c7ca5e PB |
165 | self.assert_qmp(result, 'return', {}) |
166 | ||
167 | self.complete_and_wait() | |
168 | result = self.vm.qmp('query-block') | |
169 | self.assert_qmp(result, 'return[0]/inserted/file', target_img) | |
170 | self.vm.shutdown() | |
3a3918c3 | 171 | self.assertTrue(iotests.compare_images(test_img, target_img), |
44c7ca5e PB |
172 | 'target image does not match source after mirroring') |
173 | ||
d3c8c674 KW |
174 | # Tests that the insertion of the mirror_top filter node doesn't make a |
175 | # difference to query-block | |
176 | def test_implicit_node(self): | |
177 | self.assert_no_active_block_jobs() | |
178 | ||
179 | result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', | |
180 | target=self.qmp_target) | |
181 | self.assert_qmp(result, 'return', {}) | |
182 | ||
183 | result = self.vm.qmp('query-block') | |
184 | self.assert_qmp(result, 'return[0]/inserted/file', test_img) | |
185 | self.assert_qmp(result, 'return[0]/inserted/drv', iotests.imgfmt) | |
186 | self.assert_qmp(result, 'return[0]/inserted/backing_file', backing_img) | |
187 | self.assert_qmp(result, 'return[0]/inserted/backing_file_depth', 1) | |
188 | self.assert_qmp(result, 'return[0]/inserted/image/filename', test_img) | |
189 | self.assert_qmp(result, 'return[0]/inserted/image/backing-image/filename', backing_img) | |
190 | ||
191 | result = self.vm.qmp('query-blockstats') | |
192 | self.assert_qmp(result, 'return[0]/node-name', 'top') | |
193 | self.assert_qmp(result, 'return[0]/backing/node-name', 'base') | |
194 | ||
195 | self.cancel_and_wait(force=True) | |
196 | result = self.vm.qmp('query-block') | |
197 | self.assert_qmp(result, 'return[0]/inserted/file', test_img) | |
198 | self.assert_qmp(result, 'return[0]/inserted/drv', iotests.imgfmt) | |
199 | self.assert_qmp(result, 'return[0]/inserted/backing_file', backing_img) | |
200 | self.assert_qmp(result, 'return[0]/inserted/backing_file_depth', 1) | |
201 | self.assert_qmp(result, 'return[0]/inserted/image/filename', test_img) | |
202 | self.assert_qmp(result, 'return[0]/inserted/image/backing-image/filename', backing_img) | |
203 | ||
204 | result = self.vm.qmp('query-blockstats') | |
205 | self.assert_qmp(result, 'return[0]/node-name', 'top') | |
206 | self.assert_qmp(result, 'return[0]/backing/node-name', 'base') | |
207 | ||
44c7ca5e | 208 | def test_medium_not_found(self): |
d8683155 BT |
209 | if iotests.qemu_default_machine != 'pc': |
210 | return | |
211 | ||
94ca2c73 FZ |
212 | result = self.vm.qmp(self.qmp_cmd, device='ide1-cd0', sync='full', |
213 | target=self.qmp_target) | |
0524e93a | 214 | self.assert_qmp(result, 'error/class', 'GenericError') |
44c7ca5e PB |
215 | |
216 | def test_image_not_found(self): | |
94ca2c73 FZ |
217 | result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', |
218 | mode='existing', target=self.qmp_target) | |
44c7ca5e PB |
219 | self.assert_qmp(result, 'error/class', 'GenericError') |
220 | ||
221 | def test_device_not_found(self): | |
94ca2c73 FZ |
222 | result = self.vm.qmp(self.qmp_cmd, device='nonexistent', sync='full', |
223 | target=self.qmp_target) | |
0524e93a | 224 | self.assert_qmp(result, 'error/class', 'GenericError') |
94ca2c73 FZ |
225 | |
226 | class TestSingleBlockdev(TestSingleDrive): | |
227 | qmp_cmd = 'blockdev-mirror' | |
228 | qmp_target = 'node1' | |
94ca2c73 FZ |
229 | |
230 | def setUp(self): | |
231 | TestSingleDrive.setUp(self) | |
b66ff2c2 EB |
232 | qemu_img('create', '-f', iotests.imgfmt, |
233 | '-o', 'backing_file=%s' % backing_img, '-F', 'raw', target_img) | |
0153d2f5 KW |
234 | args = {'driver': iotests.imgfmt, |
235 | 'node-name': self.qmp_target, | |
236 | 'file': { 'filename': target_img, 'driver': 'file' } } | |
94ca2c73 FZ |
237 | result = self.vm.qmp("blockdev-add", **args) |
238 | self.assert_qmp(result, 'return', {}) | |
239 | ||
86fae10c KW |
240 | def test_mirror_to_self(self): |
241 | result = self.vm.qmp(self.qmp_cmd, job_id='job0', | |
242 | device=self.qmp_target, sync='full', | |
243 | target=self.qmp_target) | |
244 | self.assert_qmp(result, 'error/class', 'GenericError') | |
245 | ||
16cea4ee KW |
246 | def do_test_resize(self, device, node): |
247 | def pre_finalize(): | |
248 | if device: | |
249 | result = self.vm.qmp('block_resize', device=device, size=65536) | |
250 | self.assert_qmp(result, 'error/class', 'GenericError') | |
251 | ||
252 | result = self.vm.qmp('block_resize', node_name=node, size=65536) | |
253 | self.assert_qmp(result, 'error/class', 'GenericError') | |
254 | ||
255 | result = self.vm.qmp(self.qmp_cmd, job_id='job0', device='drive0', | |
256 | sync='full', target=self.qmp_target, | |
257 | auto_finalize=False, auto_dismiss=False) | |
258 | self.assert_qmp(result, 'return', {}) | |
259 | ||
260 | result = self.vm.run_job('job0', auto_finalize=False, | |
261 | pre_finalize=pre_finalize) | |
262 | self.assertEqual(result, None) | |
263 | ||
264 | def test_source_resize(self): | |
265 | self.do_test_resize('drive0', 'top') | |
266 | ||
267 | def test_target_resize(self): | |
268 | self.do_test_resize(None, self.qmp_target) | |
269 | ||
270 | def do_test_target_size(self, size): | |
271 | result = self.vm.qmp('block_resize', node_name=self.qmp_target, | |
272 | size=size) | |
273 | self.assert_qmp(result, 'return', {}) | |
274 | ||
275 | result = self.vm.qmp(self.qmp_cmd, job_id='job0', | |
276 | device='drive0', sync='full', auto_dismiss=False, | |
277 | target=self.qmp_target) | |
278 | self.assert_qmp(result, 'return', {}) | |
279 | ||
280 | result = self.vm.run_job('job0') | |
281 | self.assertEqual(result, 'Source and target image have different sizes') | |
282 | ||
c7070942 HR |
283 | # qed does not support shrinking |
284 | @iotests.skip_for_formats(('qed')) | |
16cea4ee KW |
285 | def test_small_target(self): |
286 | self.do_test_target_size(self.image_len // 2) | |
287 | ||
288 | def test_large_target(self): | |
289 | self.do_test_target_size(self.image_len * 2) | |
290 | ||
94ca2c73 FZ |
291 | test_large_cluster = None |
292 | test_image_not_found = None | |
293 | test_small_buffer2 = None | |
294 | ||
3b9f27d2 FZ |
295 | class TestSingleDriveZeroLength(TestSingleDrive): |
296 | image_len = 0 | |
297 | test_small_buffer2 = None | |
298 | test_large_cluster = None | |
299 | ||
94ca2c73 FZ |
300 | class TestSingleBlockdevZeroLength(TestSingleBlockdev): |
301 | image_len = 0 | |
16cea4ee KW |
302 | test_small_target = None |
303 | test_large_target = None | |
94ca2c73 | 304 | |
5a0f6fd5 KW |
305 | class TestSingleDriveUnalignedLength(TestSingleDrive): |
306 | image_len = 1025 * 1024 | |
307 | test_small_buffer2 = None | |
308 | test_large_cluster = None | |
309 | ||
94ca2c73 FZ |
310 | class TestSingleBlockdevUnalignedLength(TestSingleBlockdev): |
311 | image_len = 1025 * 1024 | |
312 | ||
866323f3 | 313 | class TestMirrorNoBacking(iotests.QMPTestCase): |
44c7ca5e PB |
314 | image_len = 2 * 1024 * 1024 # MB |
315 | ||
44c7ca5e | 316 | def setUp(self): |
2499a096 | 317 | iotests.create_image(backing_img, TestMirrorNoBacking.image_len) |
b66ff2c2 EB |
318 | qemu_img('create', '-f', iotests.imgfmt, |
319 | '-o', 'backing_file=%s' % backing_img, '-F', 'raw', test_img) | |
44c7ca5e PB |
320 | self.vm = iotests.VM().add_drive(test_img) |
321 | self.vm.launch() | |
322 | ||
323 | def tearDown(self): | |
324 | self.vm.shutdown() | |
325 | os.remove(test_img) | |
326 | os.remove(backing_img) | |
866323f3 FZ |
327 | try: |
328 | os.remove(target_backing_img) | |
329 | except: | |
330 | pass | |
44c7ca5e PB |
331 | os.remove(target_img) |
332 | ||
333 | def test_complete(self): | |
ecc1c88e | 334 | self.assert_no_active_block_jobs() |
44c7ca5e | 335 | |
b66ff2c2 EB |
336 | qemu_img('create', '-f', iotests.imgfmt, |
337 | '-o', 'backing_file=%s' % backing_img, '-F', 'raw', target_img) | |
44c7ca5e PB |
338 | result = self.vm.qmp('drive-mirror', device='drive0', sync='full', |
339 | mode='existing', target=target_img) | |
340 | self.assert_qmp(result, 'return', {}) | |
341 | ||
342 | self.complete_and_wait() | |
343 | result = self.vm.qmp('query-block') | |
344 | self.assert_qmp(result, 'return[0]/inserted/file', target_img) | |
345 | self.vm.shutdown() | |
866323f3 | 346 | self.assertTrue(iotests.compare_images(test_img, target_img), |
44c7ca5e PB |
347 | 'target image does not match source after mirroring') |
348 | ||
349 | def test_cancel(self): | |
ecc1c88e | 350 | self.assert_no_active_block_jobs() |
44c7ca5e | 351 | |
b66ff2c2 EB |
352 | qemu_img('create', '-f', iotests.imgfmt, |
353 | '-o', 'backing_file=%s' % backing_img, '-F', 'raw', target_img) | |
44c7ca5e PB |
354 | result = self.vm.qmp('drive-mirror', device='drive0', sync='full', |
355 | mode='existing', target=target_img) | |
356 | self.assert_qmp(result, 'return', {}) | |
357 | ||
2575fe16 | 358 | self.wait_ready_and_cancel() |
44c7ca5e PB |
359 | result = self.vm.qmp('query-block') |
360 | self.assert_qmp(result, 'return[0]/inserted/file', test_img) | |
361 | self.vm.shutdown() | |
866323f3 | 362 | self.assertTrue(iotests.compare_images(test_img, target_img), |
44c7ca5e PB |
363 | 'target image does not match source after mirroring') |
364 | ||
b812f671 | 365 | def test_large_cluster(self): |
ecc1c88e | 366 | self.assert_no_active_block_jobs() |
b812f671 PB |
367 | |
368 | # qemu-img create fails if the image is not there | |
369 | qemu_img('create', '-f', iotests.imgfmt, '-o', 'size=%d' | |
370 | %(TestMirrorNoBacking.image_len), target_backing_img) | |
371 | qemu_img('create', '-f', iotests.imgfmt, '-o', 'cluster_size=%d,backing_file=%s' | |
b66ff2c2 EB |
372 | % (TestMirrorNoBacking.image_len, target_backing_img), |
373 | '-F', iotests.imgfmt, target_img) | |
b812f671 PB |
374 | |
375 | result = self.vm.qmp('drive-mirror', device='drive0', sync='full', | |
376 | mode='existing', target=target_img) | |
377 | self.assert_qmp(result, 'return', {}) | |
378 | ||
379 | self.complete_and_wait() | |
380 | result = self.vm.qmp('query-block') | |
381 | self.assert_qmp(result, 'return[0]/inserted/file', target_img) | |
382 | self.vm.shutdown() | |
866323f3 | 383 | self.assertTrue(iotests.compare_images(test_img, target_img), |
b812f671 PB |
384 | 'target image does not match source after mirroring') |
385 | ||
866323f3 | 386 | class TestMirrorResized(iotests.QMPTestCase): |
a04eca10 VI |
387 | backing_len = 1 * 1024 * 1024 # MB |
388 | image_len = 2 * 1024 * 1024 # MB | |
389 | ||
390 | def setUp(self): | |
2499a096 | 391 | iotests.create_image(backing_img, TestMirrorResized.backing_len) |
b66ff2c2 EB |
392 | qemu_img('create', '-f', iotests.imgfmt, |
393 | '-o', 'backing_file=%s' % backing_img, '-F', 'raw', test_img) | |
a04eca10 VI |
394 | qemu_img('resize', test_img, '2M') |
395 | self.vm = iotests.VM().add_drive(test_img) | |
396 | self.vm.launch() | |
397 | ||
398 | def tearDown(self): | |
399 | self.vm.shutdown() | |
400 | os.remove(test_img) | |
401 | os.remove(backing_img) | |
402 | try: | |
403 | os.remove(target_img) | |
404 | except OSError: | |
405 | pass | |
406 | ||
407 | def test_complete_top(self): | |
ecc1c88e | 408 | self.assert_no_active_block_jobs() |
a04eca10 VI |
409 | |
410 | result = self.vm.qmp('drive-mirror', device='drive0', sync='top', | |
411 | target=target_img) | |
412 | self.assert_qmp(result, 'return', {}) | |
413 | ||
414 | self.complete_and_wait() | |
415 | result = self.vm.qmp('query-block') | |
416 | self.assert_qmp(result, 'return[0]/inserted/file', target_img) | |
417 | self.vm.shutdown() | |
3a3918c3 | 418 | self.assertTrue(iotests.compare_images(test_img, target_img), |
a04eca10 VI |
419 | 'target image does not match source after mirroring') |
420 | ||
421 | def test_complete_full(self): | |
ecc1c88e | 422 | self.assert_no_active_block_jobs() |
a04eca10 VI |
423 | |
424 | result = self.vm.qmp('drive-mirror', device='drive0', sync='full', | |
425 | target=target_img) | |
426 | self.assert_qmp(result, 'return', {}) | |
427 | ||
428 | self.complete_and_wait() | |
429 | result = self.vm.qmp('query-block') | |
430 | self.assert_qmp(result, 'return[0]/inserted/file', target_img) | |
431 | self.vm.shutdown() | |
3a3918c3 | 432 | self.assertTrue(iotests.compare_images(test_img, target_img), |
a04eca10 VI |
433 | 'target image does not match source after mirroring') |
434 | ||
866323f3 | 435 | class TestReadErrors(iotests.QMPTestCase): |
9dfa9f59 PB |
436 | image_len = 2 * 1024 * 1024 # MB |
437 | ||
438 | # this should be a multiple of twice the default granularity | |
439 | # so that we hit this offset first in state 1 | |
440 | MIRROR_GRANULARITY = 1024 * 1024 | |
441 | ||
442 | def create_blkdebug_file(self, name, event, errno): | |
443 | file = open(name, 'w') | |
444 | file.write(''' | |
445 | [inject-error] | |
446 | state = "1" | |
447 | event = "%s" | |
448 | errno = "%d" | |
449 | immediately = "off" | |
450 | once = "on" | |
451 | sector = "%d" | |
452 | ||
453 | [set-state] | |
454 | state = "1" | |
455 | event = "%s" | |
456 | new_state = "2" | |
457 | ||
458 | [set-state] | |
459 | state = "2" | |
460 | event = "%s" | |
461 | new_state = "1" | |
9a3a9a63 | 462 | ''' % (event, errno, self.MIRROR_GRANULARITY // 512, event, event)) |
9dfa9f59 PB |
463 | file.close() |
464 | ||
465 | def setUp(self): | |
466 | self.blkdebug_file = backing_img + ".blkdebug" | |
2499a096 | 467 | iotests.create_image(backing_img, TestReadErrors.image_len) |
9dfa9f59 PB |
468 | self.create_blkdebug_file(self.blkdebug_file, "read_aio", 5) |
469 | qemu_img('create', '-f', iotests.imgfmt, | |
470 | '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw' | |
471 | % (self.blkdebug_file, backing_img), | |
472 | test_img) | |
b812f671 PB |
473 | # Write something for tests that use sync='top' |
474 | qemu_io('-c', 'write %d 512' % (self.MIRROR_GRANULARITY + 65536), | |
475 | test_img) | |
9dfa9f59 PB |
476 | self.vm = iotests.VM().add_drive(test_img) |
477 | self.vm.launch() | |
478 | ||
479 | def tearDown(self): | |
480 | self.vm.shutdown() | |
481 | os.remove(test_img) | |
db11d1ee | 482 | os.remove(target_img) |
9dfa9f59 PB |
483 | os.remove(backing_img) |
484 | os.remove(self.blkdebug_file) | |
485 | ||
486 | def test_report_read(self): | |
ecc1c88e | 487 | self.assert_no_active_block_jobs() |
9dfa9f59 PB |
488 | |
489 | result = self.vm.qmp('drive-mirror', device='drive0', sync='full', | |
490 | target=target_img) | |
491 | self.assert_qmp(result, 'return', {}) | |
492 | ||
493 | completed = False | |
494 | error = False | |
495 | while not completed: | |
496 | for event in self.vm.get_qmp_events(wait=True): | |
497 | if event['event'] == 'BLOCK_JOB_ERROR': | |
498 | self.assert_qmp(event, 'data/device', 'drive0') | |
499 | self.assert_qmp(event, 'data/operation', 'read') | |
500 | error = True | |
501 | elif event['event'] == 'BLOCK_JOB_READY': | |
502 | self.assertTrue(False, 'job completed unexpectedly') | |
503 | elif event['event'] == 'BLOCK_JOB_COMPLETED': | |
504 | self.assertTrue(error, 'job completed unexpectedly') | |
505 | self.assert_qmp(event, 'data/type', 'mirror') | |
506 | self.assert_qmp(event, 'data/device', 'drive0') | |
507 | self.assert_qmp(event, 'data/error', 'Input/output error') | |
9dfa9f59 | 508 | completed = True |
1dac83f1 KW |
509 | elif event['event'] == 'JOB_STATUS_CHANGE': |
510 | self.assert_qmp(event, 'data/id', 'drive0') | |
9dfa9f59 | 511 | |
ecc1c88e | 512 | self.assert_no_active_block_jobs() |
9dfa9f59 PB |
513 | |
514 | def test_ignore_read(self): | |
ecc1c88e | 515 | self.assert_no_active_block_jobs() |
9dfa9f59 PB |
516 | |
517 | result = self.vm.qmp('drive-mirror', device='drive0', sync='full', | |
518 | target=target_img, on_source_error='ignore') | |
519 | self.assert_qmp(result, 'return', {}) | |
520 | ||
521 | event = self.vm.get_qmp_event(wait=True) | |
1dac83f1 KW |
522 | while event['event'] == 'JOB_STATUS_CHANGE': |
523 | self.assert_qmp(event, 'data/id', 'drive0') | |
524 | event = self.vm.get_qmp_event(wait=True) | |
525 | ||
fa1cfb40 | 526 | self.assertEqual(event['event'], 'BLOCK_JOB_ERROR') |
9dfa9f59 PB |
527 | self.assert_qmp(event, 'data/device', 'drive0') |
528 | self.assert_qmp(event, 'data/operation', 'read') | |
529 | result = self.vm.qmp('query-block-jobs') | |
530 | self.assert_qmp(result, 'return[0]/paused', False) | |
531 | self.complete_and_wait() | |
9dfa9f59 | 532 | |
b812f671 | 533 | def test_large_cluster(self): |
ecc1c88e | 534 | self.assert_no_active_block_jobs() |
b812f671 PB |
535 | |
536 | # Test COW into the target image. The first half of the | |
537 | # cluster at MIRROR_GRANULARITY has to be copied from | |
538 | # backing_img, even though sync='top'. | |
b66ff2c2 EB |
539 | qemu_img('create', '-f', iotests.imgfmt, |
540 | '-ocluster_size=131072,backing_file=%s' %(backing_img), | |
541 | '-F', 'raw', target_img) | |
b812f671 PB |
542 | result = self.vm.qmp('drive-mirror', device='drive0', sync='top', |
543 | on_source_error='ignore', | |
544 | mode='existing', target=target_img) | |
545 | self.assert_qmp(result, 'return', {}) | |
546 | ||
547 | event = self.vm.get_qmp_event(wait=True) | |
1dac83f1 KW |
548 | while event['event'] == 'JOB_STATUS_CHANGE': |
549 | self.assert_qmp(event, 'data/id', 'drive0') | |
550 | event = self.vm.get_qmp_event(wait=True) | |
551 | ||
fa1cfb40 | 552 | self.assertEqual(event['event'], 'BLOCK_JOB_ERROR') |
b812f671 PB |
553 | self.assert_qmp(event, 'data/device', 'drive0') |
554 | self.assert_qmp(event, 'data/operation', 'read') | |
555 | result = self.vm.qmp('query-block-jobs') | |
556 | self.assert_qmp(result, 'return[0]/paused', False) | |
557 | self.complete_and_wait() | |
558 | self.vm.shutdown() | |
559 | ||
560 | # Detach blkdebug to compare images successfully | |
b66ff2c2 EB |
561 | qemu_img('rebase', '-f', iotests.imgfmt, '-u', '-b', backing_img, |
562 | '-F', 'raw', test_img) | |
3a3918c3 | 563 | self.assertTrue(iotests.compare_images(test_img, target_img), |
b812f671 PB |
564 | 'target image does not match source after mirroring') |
565 | ||
9dfa9f59 | 566 | def test_stop_read(self): |
ecc1c88e | 567 | self.assert_no_active_block_jobs() |
9dfa9f59 PB |
568 | |
569 | result = self.vm.qmp('drive-mirror', device='drive0', sync='full', | |
570 | target=target_img, on_source_error='stop') | |
571 | self.assert_qmp(result, 'return', {}) | |
572 | ||
573 | error = False | |
574 | ready = False | |
575 | while not ready: | |
576 | for event in self.vm.get_qmp_events(wait=True): | |
577 | if event['event'] == 'BLOCK_JOB_ERROR': | |
578 | self.assert_qmp(event, 'data/device', 'drive0') | |
579 | self.assert_qmp(event, 'data/operation', 'read') | |
580 | ||
581 | result = self.vm.qmp('query-block-jobs') | |
582 | self.assert_qmp(result, 'return[0]/paused', True) | |
583 | self.assert_qmp(result, 'return[0]/io-status', 'failed') | |
584 | ||
585 | result = self.vm.qmp('block-job-resume', device='drive0') | |
586 | self.assert_qmp(result, 'return', {}) | |
587 | error = True | |
588 | elif event['event'] == 'BLOCK_JOB_READY': | |
589 | self.assertTrue(error, 'job completed unexpectedly') | |
590 | self.assert_qmp(event, 'data/device', 'drive0') | |
591 | ready = True | |
592 | ||
593 | result = self.vm.qmp('query-block-jobs') | |
594 | self.assert_qmp(result, 'return[0]/paused', False) | |
595 | self.assert_qmp(result, 'return[0]/io-status', 'ok') | |
596 | ||
597 | self.complete_and_wait(wait_ready=False) | |
ecc1c88e | 598 | self.assert_no_active_block_jobs() |
9dfa9f59 | 599 | |
866323f3 | 600 | class TestWriteErrors(iotests.QMPTestCase): |
9dfa9f59 PB |
601 | image_len = 2 * 1024 * 1024 # MB |
602 | ||
603 | # this should be a multiple of twice the default granularity | |
604 | # so that we hit this offset first in state 1 | |
605 | MIRROR_GRANULARITY = 1024 * 1024 | |
606 | ||
607 | def create_blkdebug_file(self, name, event, errno): | |
608 | file = open(name, 'w') | |
609 | file.write(''' | |
610 | [inject-error] | |
611 | state = "1" | |
612 | event = "%s" | |
613 | errno = "%d" | |
614 | immediately = "off" | |
615 | once = "on" | |
616 | sector = "%d" | |
617 | ||
618 | [set-state] | |
619 | state = "1" | |
620 | event = "%s" | |
621 | new_state = "2" | |
622 | ||
623 | [set-state] | |
624 | state = "2" | |
625 | event = "%s" | |
626 | new_state = "1" | |
9a3a9a63 | 627 | ''' % (event, errno, self.MIRROR_GRANULARITY // 512, event, event)) |
9dfa9f59 PB |
628 | file.close() |
629 | ||
630 | def setUp(self): | |
631 | self.blkdebug_file = target_img + ".blkdebug" | |
2499a096 | 632 | iotests.create_image(backing_img, TestWriteErrors.image_len) |
9dfa9f59 | 633 | self.create_blkdebug_file(self.blkdebug_file, "write_aio", 5) |
b66ff2c2 EB |
634 | qemu_img('create', '-f', iotests.imgfmt, |
635 | '-obacking_file=%s' %(backing_img), '-F', 'raw', test_img) | |
9dfa9f59 PB |
636 | self.vm = iotests.VM().add_drive(test_img) |
637 | self.target_img = 'blkdebug:%s:%s' % (self.blkdebug_file, target_img) | |
638 | qemu_img('create', '-f', iotests.imgfmt, '-osize=%d' %(TestWriteErrors.image_len), target_img) | |
639 | self.vm.launch() | |
640 | ||
641 | def tearDown(self): | |
642 | self.vm.shutdown() | |
643 | os.remove(test_img) | |
db11d1ee | 644 | os.remove(target_img) |
9dfa9f59 PB |
645 | os.remove(backing_img) |
646 | os.remove(self.blkdebug_file) | |
647 | ||
648 | def test_report_write(self): | |
ecc1c88e | 649 | self.assert_no_active_block_jobs() |
9dfa9f59 PB |
650 | |
651 | result = self.vm.qmp('drive-mirror', device='drive0', sync='full', | |
652 | mode='existing', target=self.target_img) | |
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', 'write') | |
662 | error = True | |
663 | elif event['event'] == 'BLOCK_JOB_READY': | |
664 | self.assertTrue(False, 'job completed unexpectedly') | |
665 | elif event['event'] == 'BLOCK_JOB_COMPLETED': | |
666 | self.assertTrue(error, 'job completed unexpectedly') | |
667 | self.assert_qmp(event, 'data/type', 'mirror') | |
668 | self.assert_qmp(event, 'data/device', 'drive0') | |
669 | self.assert_qmp(event, 'data/error', 'Input/output error') | |
9dfa9f59 PB |
670 | completed = True |
671 | ||
ecc1c88e | 672 | self.assert_no_active_block_jobs() |
9dfa9f59 PB |
673 | |
674 | def test_ignore_write(self): | |
ecc1c88e | 675 | self.assert_no_active_block_jobs() |
9dfa9f59 PB |
676 | |
677 | result = self.vm.qmp('drive-mirror', device='drive0', sync='full', | |
678 | mode='existing', target=self.target_img, | |
679 | on_target_error='ignore') | |
680 | self.assert_qmp(result, 'return', {}) | |
681 | ||
1dac83f1 | 682 | event = self.vm.event_wait(name='BLOCK_JOB_ERROR') |
fa1cfb40 | 683 | self.assertEqual(event['event'], 'BLOCK_JOB_ERROR') |
9dfa9f59 PB |
684 | self.assert_qmp(event, 'data/device', 'drive0') |
685 | self.assert_qmp(event, 'data/operation', 'write') | |
686 | result = self.vm.qmp('query-block-jobs') | |
687 | self.assert_qmp(result, 'return[0]/paused', False) | |
688 | self.complete_and_wait() | |
9dfa9f59 PB |
689 | |
690 | def test_stop_write(self): | |
ecc1c88e | 691 | self.assert_no_active_block_jobs() |
9dfa9f59 PB |
692 | |
693 | result = self.vm.qmp('drive-mirror', device='drive0', sync='full', | |
694 | mode='existing', target=self.target_img, | |
695 | on_target_error='stop') | |
696 | self.assert_qmp(result, 'return', {}) | |
697 | ||
698 | error = False | |
699 | ready = False | |
700 | while not ready: | |
701 | for event in self.vm.get_qmp_events(wait=True): | |
702 | if event['event'] == 'BLOCK_JOB_ERROR': | |
703 | self.assert_qmp(event, 'data/device', 'drive0') | |
704 | self.assert_qmp(event, 'data/operation', 'write') | |
705 | ||
706 | result = self.vm.qmp('query-block-jobs') | |
707 | self.assert_qmp(result, 'return[0]/paused', True) | |
708 | self.assert_qmp(result, 'return[0]/io-status', 'failed') | |
709 | ||
710 | result = self.vm.qmp('block-job-resume', device='drive0') | |
711 | self.assert_qmp(result, 'return', {}) | |
712 | ||
713 | result = self.vm.qmp('query-block-jobs') | |
714 | self.assert_qmp(result, 'return[0]/paused', False) | |
715 | self.assert_qmp(result, 'return[0]/io-status', 'ok') | |
716 | error = True | |
717 | elif event['event'] == 'BLOCK_JOB_READY': | |
718 | self.assertTrue(error, 'job completed unexpectedly') | |
719 | self.assert_qmp(event, 'data/device', 'drive0') | |
720 | ready = True | |
721 | ||
722 | self.complete_and_wait(wait_ready=False) | |
ecc1c88e | 723 | self.assert_no_active_block_jobs() |
9dfa9f59 | 724 | |
866323f3 | 725 | class TestSetSpeed(iotests.QMPTestCase): |
44c7ca5e PB |
726 | image_len = 80 * 1024 * 1024 # MB |
727 | ||
728 | def setUp(self): | |
729 | qemu_img('create', backing_img, str(TestSetSpeed.image_len)) | |
b66ff2c2 EB |
730 | qemu_img('create', '-f', iotests.imgfmt, |
731 | '-o', 'backing_file=%s' % backing_img, '-F', 'raw', test_img) | |
44c7ca5e PB |
732 | self.vm = iotests.VM().add_drive(test_img) |
733 | self.vm.launch() | |
734 | ||
735 | def tearDown(self): | |
736 | self.vm.shutdown() | |
737 | os.remove(test_img) | |
738 | os.remove(backing_img) | |
739 | os.remove(target_img) | |
740 | ||
741 | def test_set_speed(self): | |
ecc1c88e | 742 | self.assert_no_active_block_jobs() |
44c7ca5e PB |
743 | |
744 | result = self.vm.qmp('drive-mirror', device='drive0', sync='full', | |
745 | target=target_img) | |
746 | self.assert_qmp(result, 'return', {}) | |
747 | ||
748 | # Default speed is 0 | |
749 | result = self.vm.qmp('query-block-jobs') | |
750 | self.assert_qmp(result, 'return[0]/device', 'drive0') | |
751 | self.assert_qmp(result, 'return[0]/speed', 0) | |
752 | ||
753 | result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024) | |
754 | self.assert_qmp(result, 'return', {}) | |
755 | ||
756 | # Ensure the speed we set was accepted | |
757 | result = self.vm.qmp('query-block-jobs') | |
758 | self.assert_qmp(result, 'return[0]/device', 'drive0') | |
759 | self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024) | |
760 | ||
2575fe16 | 761 | self.wait_ready_and_cancel() |
44c7ca5e PB |
762 | |
763 | # Check setting speed in drive-mirror works | |
764 | result = self.vm.qmp('drive-mirror', device='drive0', sync='full', | |
765 | target=target_img, speed=4*1024*1024) | |
766 | self.assert_qmp(result, 'return', {}) | |
767 | ||
768 | result = self.vm.qmp('query-block-jobs') | |
769 | self.assert_qmp(result, 'return[0]/device', 'drive0') | |
770 | self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024) | |
771 | ||
2575fe16 | 772 | self.wait_ready_and_cancel() |
44c7ca5e PB |
773 | |
774 | def test_set_speed_invalid(self): | |
ecc1c88e | 775 | self.assert_no_active_block_jobs() |
44c7ca5e PB |
776 | |
777 | result = self.vm.qmp('drive-mirror', device='drive0', sync='full', | |
778 | target=target_img, speed=-1) | |
779 | self.assert_qmp(result, 'error/class', 'GenericError') | |
780 | ||
ecc1c88e | 781 | self.assert_no_active_block_jobs() |
44c7ca5e PB |
782 | |
783 | result = self.vm.qmp('drive-mirror', device='drive0', sync='full', | |
784 | target=target_img) | |
785 | self.assert_qmp(result, 'return', {}) | |
786 | ||
787 | result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1) | |
788 | self.assert_qmp(result, 'error/class', 'GenericError') | |
789 | ||
2575fe16 | 790 | self.wait_ready_and_cancel() |
44c7ca5e | 791 | |
866323f3 | 792 | class TestUnbackedSource(iotests.QMPTestCase): |
c15badee HR |
793 | image_len = 2 * 1024 * 1024 # MB |
794 | ||
795 | def setUp(self): | |
796 | qemu_img('create', '-f', iotests.imgfmt, test_img, | |
797 | str(TestUnbackedSource.image_len)) | |
9463ee1f | 798 | self.vm = iotests.VM() |
c15badee | 799 | self.vm.launch() |
9463ee1f HR |
800 | result = self.vm.qmp('blockdev-add', node_name='drive0', |
801 | driver=iotests.imgfmt, | |
802 | file={ | |
803 | 'driver': 'file', | |
804 | 'filename': test_img, | |
805 | }) | |
806 | self.assert_qmp(result, 'return', {}) | |
c15badee HR |
807 | |
808 | def tearDown(self): | |
809 | self.vm.shutdown() | |
810 | os.remove(test_img) | |
811 | os.remove(target_img) | |
812 | ||
171d6431 HR |
813 | def test_absolute_paths_full(self): |
814 | self.assert_no_active_block_jobs() | |
9463ee1f | 815 | result = self.vm.qmp('drive-mirror', job_id='drive0', device='drive0', |
171d6431 HR |
816 | sync='full', target=target_img, |
817 | mode='absolute-paths') | |
818 | self.assert_qmp(result, 'return', {}) | |
819 | self.complete_and_wait() | |
820 | self.assert_no_active_block_jobs() | |
821 | ||
822 | def test_absolute_paths_top(self): | |
823 | self.assert_no_active_block_jobs() | |
9463ee1f | 824 | result = self.vm.qmp('drive-mirror', job_id='drive0', device='drive0', |
171d6431 HR |
825 | sync='top', target=target_img, |
826 | mode='absolute-paths') | |
827 | self.assert_qmp(result, 'return', {}) | |
828 | self.complete_and_wait() | |
c15badee HR |
829 | self.assert_no_active_block_jobs() |
830 | ||
171d6431 HR |
831 | def test_absolute_paths_none(self): |
832 | self.assert_no_active_block_jobs() | |
9463ee1f | 833 | result = self.vm.qmp('drive-mirror', job_id='drive0', device='drive0', |
171d6431 HR |
834 | sync='none', target=target_img, |
835 | mode='absolute-paths') | |
836 | self.assert_qmp(result, 'return', {}) | |
837 | self.complete_and_wait() | |
838 | self.assert_no_active_block_jobs() | |
c15badee | 839 | |
9463ee1f HR |
840 | def test_existing_full(self): |
841 | qemu_img('create', '-f', iotests.imgfmt, target_img, | |
842 | str(self.image_len)) | |
843 | qemu_io('-c', 'write -P 42 0 64k', target_img) | |
844 | ||
845 | self.assert_no_active_block_jobs() | |
846 | result = self.vm.qmp('drive-mirror', job_id='drive0', device='drive0', | |
847 | sync='full', target=target_img, mode='existing') | |
848 | self.assert_qmp(result, 'return', {}) | |
849 | self.complete_and_wait() | |
850 | self.assert_no_active_block_jobs() | |
851 | ||
852 | result = self.vm.qmp('blockdev-del', node_name='drive0') | |
853 | self.assert_qmp(result, 'return', {}) | |
854 | ||
855 | self.assertTrue(iotests.compare_images(test_img, target_img), | |
856 | 'target image does not match source after mirroring') | |
857 | ||
858 | def test_blockdev_full(self): | |
859 | qemu_img('create', '-f', iotests.imgfmt, target_img, | |
860 | str(self.image_len)) | |
861 | qemu_io('-c', 'write -P 42 0 64k', target_img) | |
862 | ||
863 | result = self.vm.qmp('blockdev-add', node_name='target', | |
864 | driver=iotests.imgfmt, | |
865 | file={ | |
866 | 'driver': 'file', | |
867 | 'filename': target_img, | |
868 | }) | |
869 | self.assert_qmp(result, 'return', {}) | |
870 | ||
871 | self.assert_no_active_block_jobs() | |
872 | result = self.vm.qmp('blockdev-mirror', job_id='drive0', device='drive0', | |
873 | sync='full', target='target') | |
874 | self.assert_qmp(result, 'return', {}) | |
875 | self.complete_and_wait() | |
876 | self.assert_no_active_block_jobs() | |
877 | ||
878 | result = self.vm.qmp('blockdev-del', node_name='drive0') | |
879 | self.assert_qmp(result, 'return', {}) | |
880 | ||
881 | result = self.vm.qmp('blockdev-del', node_name='target') | |
882 | self.assert_qmp(result, 'return', {}) | |
883 | ||
884 | self.assertTrue(iotests.compare_images(test_img, target_img), | |
885 | 'target image does not match source after mirroring') | |
886 | ||
ccee3d8f JS |
887 | class TestGranularity(iotests.QMPTestCase): |
888 | image_len = 10 * 1024 * 1024 # MB | |
889 | ||
890 | def setUp(self): | |
891 | qemu_img('create', '-f', iotests.imgfmt, test_img, | |
892 | str(TestGranularity.image_len)) | |
893 | qemu_io('-c', 'write 0 %d' % (self.image_len), | |
894 | test_img) | |
895 | self.vm = iotests.VM().add_drive(test_img) | |
896 | self.vm.launch() | |
897 | ||
898 | def tearDown(self): | |
899 | self.vm.shutdown() | |
900 | self.assertTrue(iotests.compare_images(test_img, target_img), | |
901 | 'target image does not match source after mirroring') | |
902 | os.remove(test_img) | |
903 | os.remove(target_img) | |
904 | ||
905 | def test_granularity(self): | |
906 | self.assert_no_active_block_jobs() | |
907 | result = self.vm.qmp('drive-mirror', device='drive0', | |
908 | sync='full', target=target_img, | |
909 | mode='absolute-paths', granularity=8192) | |
910 | self.assert_qmp(result, 'return', {}) | |
1dac83f1 | 911 | |
ccee3d8f | 912 | event = self.vm.get_qmp_event(wait=60.0) |
1dac83f1 KW |
913 | while event['event'] == 'JOB_STATUS_CHANGE': |
914 | self.assert_qmp(event, 'data/id', 'drive0') | |
915 | event = self.vm.get_qmp_event(wait=60.0) | |
916 | ||
ccee3d8f JS |
917 | # Failures will manifest as COMPLETED/ERROR. |
918 | self.assert_qmp(event, 'event', 'BLOCK_JOB_READY') | |
919 | self.complete_and_wait(drive='drive0', wait_ready=False) | |
920 | self.assert_no_active_block_jobs() | |
921 | ||
866323f3 | 922 | class TestRepairQuorum(iotests.QMPTestCase): |
d88964ae BC |
923 | """ This class test quorum file repair using drive-mirror. |
924 | It's mostly a fork of TestSingleDrive """ | |
925 | image_len = 1 * 1024 * 1024 # MB | |
926 | IMAGES = [ quorum_img1, quorum_img2, quorum_img3 ] | |
927 | ||
9442bebe | 928 | @iotests.skip_if_unsupported(['quorum']) |
d88964ae BC |
929 | def setUp(self): |
930 | self.vm = iotests.VM() | |
931 | ||
0ed82f7a HR |
932 | if iotests.qemu_default_machine == 'pc': |
933 | self.vm.add_drive(None, 'media=cdrom', 'ide') | |
934 | ||
d88964ae BC |
935 | # Add each individual quorum images |
936 | for i in self.IMAGES: | |
937 | qemu_img('create', '-f', iotests.imgfmt, i, | |
89e21945 | 938 | str(self.image_len)) |
d88964ae BC |
939 | # Assign a node name to each quorum image in order to manipulate |
940 | # them | |
941 | opts = "node-name=img%i" % self.IMAGES.index(i) | |
f718ca14 HR |
942 | opts += ',driver=%s' % iotests.imgfmt |
943 | opts += ',file.driver=file' | |
944 | opts += ',file.filename=%s' % i | |
945 | self.vm = self.vm.add_blockdev(opts) | |
d88964ae BC |
946 | |
947 | self.vm.launch() | |
948 | ||
949 | #assemble the quorum block device from the individual files | |
0153d2f5 KW |
950 | args = { "driver": "quorum", "node-name": "quorum0", |
951 | "vote-threshold": 2, "children": [ "img0", "img1", "img2" ] } | |
9442bebe TH |
952 | result = self.vm.qmp("blockdev-add", **args) |
953 | self.assert_qmp(result, 'return', {}) | |
d88964ae BC |
954 | |
955 | ||
956 | def tearDown(self): | |
957 | self.vm.shutdown() | |
a1da1878 HR |
958 | for i in self.IMAGES + [ quorum_repair_img, quorum_snapshot_file, |
959 | nbd_sock_path ]: | |
d88964ae BC |
960 | # Do a try/except because the test may have deleted some images |
961 | try: | |
962 | os.remove(i) | |
963 | except OSError: | |
964 | pass | |
965 | ||
966 | def test_complete(self): | |
476fb028 KW |
967 | result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', |
968 | sync='full', node_name="repair0", replaces="img1", | |
d88964ae BC |
969 | target=quorum_repair_img, format=iotests.imgfmt) |
970 | self.assert_qmp(result, 'return', {}) | |
971 | ||
476fb028 | 972 | self.complete_and_wait(drive="job0") |
e71fc0ba | 973 | self.assert_has_block_node("repair0", quorum_repair_img) |
c351afd6 | 974 | self.vm.assert_block_path('quorum0', '/children.1', 'repair0') |
d88964ae BC |
975 | self.vm.shutdown() |
976 | self.assertTrue(iotests.compare_images(quorum_img2, quorum_repair_img), | |
977 | 'target image does not match source after mirroring') | |
978 | ||
979 | def test_cancel(self): | |
476fb028 KW |
980 | result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', |
981 | sync='full', node_name="repair0", replaces="img1", | |
d88964ae BC |
982 | target=quorum_repair_img, format=iotests.imgfmt) |
983 | self.assert_qmp(result, 'return', {}) | |
984 | ||
476fb028 | 985 | self.cancel_and_wait(drive="job0", force=True) |
d88964ae BC |
986 | # here we check that the last registered quorum file has not been |
987 | # swapped out and unref | |
e71fc0ba | 988 | self.assert_has_block_node(None, quorum_img3) |
d88964ae BC |
989 | |
990 | def test_cancel_after_ready(self): | |
476fb028 KW |
991 | result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', |
992 | sync='full', node_name="repair0", replaces="img1", | |
d88964ae BC |
993 | target=quorum_repair_img, format=iotests.imgfmt) |
994 | self.assert_qmp(result, 'return', {}) | |
995 | ||
476fb028 | 996 | self.wait_ready_and_cancel(drive="job0") |
d88964ae BC |
997 | # here we check that the last registered quorum file has not been |
998 | # swapped out and unref | |
e71fc0ba | 999 | self.assert_has_block_node(None, quorum_img3) |
d88964ae BC |
1000 | self.vm.shutdown() |
1001 | self.assertTrue(iotests.compare_images(quorum_img2, quorum_repair_img), | |
1002 | 'target image does not match source after mirroring') | |
1003 | ||
1004 | def test_pause(self): | |
476fb028 KW |
1005 | result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', |
1006 | sync='full', node_name="repair0", replaces="img1", | |
d88964ae BC |
1007 | target=quorum_repair_img, format=iotests.imgfmt) |
1008 | self.assert_qmp(result, 'return', {}) | |
1009 | ||
2c93c5cb | 1010 | self.pause_job('job0') |
d88964ae | 1011 | |
d88964ae BC |
1012 | result = self.vm.qmp('query-block-jobs') |
1013 | offset = self.dictpath(result, 'return[0]/offset') | |
1014 | ||
2c93c5cb | 1015 | time.sleep(0.5) |
d88964ae BC |
1016 | result = self.vm.qmp('query-block-jobs') |
1017 | self.assert_qmp(result, 'return[0]/offset', offset) | |
1018 | ||
476fb028 | 1019 | result = self.vm.qmp('block-job-resume', device='job0') |
d88964ae BC |
1020 | self.assert_qmp(result, 'return', {}) |
1021 | ||
476fb028 | 1022 | self.complete_and_wait(drive="job0") |
d88964ae BC |
1023 | self.vm.shutdown() |
1024 | self.assertTrue(iotests.compare_images(quorum_img2, quorum_repair_img), | |
1025 | 'target image does not match source after mirroring') | |
1026 | ||
1027 | def test_medium_not_found(self): | |
d8683155 BT |
1028 | if iotests.qemu_default_machine != 'pc': |
1029 | return | |
1030 | ||
476fb028 | 1031 | result = self.vm.qmp('drive-mirror', job_id='job0', device='drive0', # CD-ROM |
0ed82f7a | 1032 | sync='full', |
d88964ae BC |
1033 | node_name='repair0', |
1034 | replaces='img1', | |
1035 | target=quorum_repair_img, format=iotests.imgfmt) | |
1036 | self.assert_qmp(result, 'error/class', 'GenericError') | |
1037 | ||
1038 | def test_image_not_found(self): | |
476fb028 KW |
1039 | result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', |
1040 | sync='full', node_name='repair0', replaces='img1', | |
1041 | mode='existing', target=quorum_repair_img, | |
1042 | format=iotests.imgfmt) | |
d88964ae BC |
1043 | self.assert_qmp(result, 'error/class', 'GenericError') |
1044 | ||
1045 | def test_device_not_found(self): | |
476fb028 KW |
1046 | result = self.vm.qmp('drive-mirror', job_id='job0', |
1047 | device='nonexistent', sync='full', | |
d88964ae BC |
1048 | node_name='repair0', |
1049 | replaces='img1', | |
1050 | target=quorum_repair_img, format=iotests.imgfmt) | |
0524e93a | 1051 | self.assert_qmp(result, 'error/class', 'GenericError') |
d88964ae BC |
1052 | |
1053 | def test_wrong_sync_mode(self): | |
476fb028 | 1054 | result = self.vm.qmp('drive-mirror', device='quorum0', job_id='job0', |
d88964ae BC |
1055 | node_name='repair0', |
1056 | replaces='img1', | |
1057 | target=quorum_repair_img, format=iotests.imgfmt) | |
1058 | self.assert_qmp(result, 'error/class', 'GenericError') | |
1059 | ||
1060 | def test_no_node_name(self): | |
476fb028 KW |
1061 | result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', |
1062 | sync='full', replaces='img1', | |
d88964ae BC |
1063 | target=quorum_repair_img, format=iotests.imgfmt) |
1064 | self.assert_qmp(result, 'error/class', 'GenericError') | |
1065 | ||
67cc32eb | 1066 | def test_nonexistent_replaces(self): |
476fb028 KW |
1067 | result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', |
1068 | sync='full', node_name='repair0', replaces='img77', | |
d88964ae BC |
1069 | target=quorum_repair_img, format=iotests.imgfmt) |
1070 | self.assert_qmp(result, 'error/class', 'GenericError') | |
1071 | ||
1072 | def test_after_a_quorum_snapshot(self): | |
1073 | result = self.vm.qmp('blockdev-snapshot-sync', node_name='img1', | |
1074 | snapshot_file=quorum_snapshot_file, | |
1075 | snapshot_node_name="snap1"); | |
1076 | ||
476fb028 KW |
1077 | result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', |
1078 | sync='full', node_name='repair0', replaces="img1", | |
d88964ae BC |
1079 | target=quorum_repair_img, format=iotests.imgfmt) |
1080 | self.assert_qmp(result, 'error/class', 'GenericError') | |
1081 | ||
476fb028 KW |
1082 | result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0', |
1083 | sync='full', node_name='repair0', replaces="snap1", | |
d88964ae BC |
1084 | target=quorum_repair_img, format=iotests.imgfmt) |
1085 | self.assert_qmp(result, 'return', {}) | |
1086 | ||
476fb028 | 1087 | self.complete_and_wait('job0') |
e71fc0ba | 1088 | self.assert_has_block_node("repair0", quorum_repair_img) |
c351afd6 | 1089 | self.vm.assert_block_path('quorum0', '/children.1', 'repair0') |
d88964ae | 1090 | |
a1da1878 HR |
1091 | def test_with_other_parent(self): |
1092 | """ | |
1093 | Check that we cannot replace a Quorum child when it has other | |
1094 | parents. | |
1095 | """ | |
1096 | result = self.vm.qmp('nbd-server-start', | |
1097 | addr={ | |
1098 | 'type': 'unix', | |
1099 | 'data': {'path': nbd_sock_path} | |
1100 | }) | |
1101 | self.assert_qmp(result, 'return', {}) | |
1102 | ||
1103 | result = self.vm.qmp('nbd-server-add', device='img1') | |
1104 | self.assert_qmp(result, 'return', {}) | |
1105 | ||
1106 | result = self.vm.qmp('drive-mirror', job_id='mirror', device='quorum0', | |
1107 | sync='full', node_name='repair0', replaces='img1', | |
1108 | target=quorum_repair_img, format=iotests.imgfmt) | |
1109 | self.assert_qmp(result, 'error/desc', | |
1110 | "Cannot replace 'img1' by a node mirrored from " | |
1111 | "'quorum0', because it cannot be guaranteed that doing " | |
1112 | "so would not lead to an abrupt change of visible data") | |
1113 | ||
1114 | def test_with_other_parents_after_mirror_start(self): | |
1115 | """ | |
1116 | The same as test_with_other_parent(), but add the NBD server | |
1117 | only when the mirror job is already running. | |
1118 | """ | |
1119 | result = self.vm.qmp('nbd-server-start', | |
1120 | addr={ | |
1121 | 'type': 'unix', | |
1122 | 'data': {'path': nbd_sock_path} | |
1123 | }) | |
1124 | self.assert_qmp(result, 'return', {}) | |
1125 | ||
1126 | result = self.vm.qmp('drive-mirror', job_id='mirror', device='quorum0', | |
1127 | sync='full', node_name='repair0', replaces='img1', | |
1128 | target=quorum_repair_img, format=iotests.imgfmt) | |
1129 | self.assert_qmp(result, 'return', {}) | |
1130 | ||
1131 | result = self.vm.qmp('nbd-server-add', device='img1') | |
1132 | self.assert_qmp(result, 'return', {}) | |
1133 | ||
1134 | # The full error message goes to stderr, we will check it later | |
1135 | self.complete_and_wait('mirror', | |
1136 | completion_error='Operation not permitted') | |
1137 | ||
1138 | # Should not have been replaced | |
1139 | self.vm.assert_block_path('quorum0', '/children.1', 'img1') | |
1140 | ||
1141 | # Check the full error message now | |
1142 | self.vm.shutdown() | |
1143 | log = self.vm.get_log() | |
1144 | log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log) | |
1145 | log = re.sub(r'^Formatting.*\n', '', log) | |
1146 | log = re.sub(r'\n\[I \+\d+\.\d+\] CLOSED\n?$', '', log) | |
1147 | log = re.sub(r'^%s: ' % os.path.basename(iotests.qemu_prog), '', log) | |
1148 | ||
1149 | self.assertEqual(log, | |
1150 | "Can no longer replace 'img1' by 'repair0', because " + | |
1151 | "it can no longer be guaranteed that doing so would " + | |
1152 | "not lead to an abrupt change of visible data") | |
1153 | ||
1154 | ||
5694923a HR |
1155 | # Test mirroring with a source that does not have any parents (not even a |
1156 | # BlockBackend) | |
1157 | class TestOrphanedSource(iotests.QMPTestCase): | |
1158 | def setUp(self): | |
1159 | blk0 = { 'node-name': 'src', | |
1160 | 'driver': 'null-co' } | |
1161 | ||
1162 | blk1 = { 'node-name': 'dest', | |
1163 | 'driver': 'null-co' } | |
1164 | ||
1165 | blk2 = { 'node-name': 'dest-ro', | |
1166 | 'driver': 'null-co', | |
1167 | 'read-only': 'on' } | |
1168 | ||
1169 | self.vm = iotests.VM() | |
62a94288 KW |
1170 | self.vm.add_blockdev(self.vm.qmp_to_opts(blk0)) |
1171 | self.vm.add_blockdev(self.vm.qmp_to_opts(blk1)) | |
1172 | self.vm.add_blockdev(self.vm.qmp_to_opts(blk2)) | |
5694923a HR |
1173 | self.vm.launch() |
1174 | ||
1175 | def tearDown(self): | |
1176 | self.vm.shutdown() | |
1177 | ||
1178 | def test_no_job_id(self): | |
1179 | self.assert_no_active_block_jobs() | |
1180 | ||
1181 | result = self.vm.qmp('blockdev-mirror', device='src', sync='full', | |
1182 | target='dest') | |
1183 | self.assert_qmp(result, 'error/class', 'GenericError') | |
1184 | ||
1185 | def test_success(self): | |
1186 | self.assert_no_active_block_jobs() | |
1187 | ||
1188 | result = self.vm.qmp('blockdev-mirror', job_id='job', device='src', | |
1189 | sync='full', target='dest') | |
1190 | self.assert_qmp(result, 'return', {}) | |
1191 | ||
1192 | self.complete_and_wait('job') | |
1193 | ||
1194 | def test_failing_permissions(self): | |
1195 | self.assert_no_active_block_jobs() | |
1196 | ||
1197 | result = self.vm.qmp('blockdev-mirror', device='src', sync='full', | |
1198 | target='dest-ro') | |
1199 | self.assert_qmp(result, 'error/class', 'GenericError') | |
1200 | ||
9592fe45 HR |
1201 | def test_failing_permission_in_complete(self): |
1202 | self.assert_no_active_block_jobs() | |
1203 | ||
1204 | # Unshare consistent-read on the target | |
1205 | # (The mirror job does not care) | |
1206 | result = self.vm.qmp('blockdev-add', | |
1207 | driver='blkdebug', | |
1208 | node_name='dest-perm', | |
1209 | image='dest', | |
1210 | unshare_child_perms=['consistent-read']) | |
1211 | self.assert_qmp(result, 'return', {}) | |
1212 | ||
1213 | result = self.vm.qmp('blockdev-mirror', job_id='job', device='src', | |
1214 | sync='full', target='dest', | |
1215 | filter_node_name='mirror-filter') | |
1216 | self.assert_qmp(result, 'return', {}) | |
1217 | ||
1218 | # Require consistent-read on the source | |
1219 | # (We can only add this node once the job has started, or it | |
1220 | # will complain that it does not want to run on non-root nodes) | |
1221 | result = self.vm.qmp('blockdev-add', | |
1222 | driver='blkdebug', | |
1223 | node_name='src-perm', | |
1224 | image='src', | |
1225 | take_child_perms=['consistent-read']) | |
1226 | self.assert_qmp(result, 'return', {}) | |
1227 | ||
1228 | # While completing, mirror will attempt to replace src by | |
1229 | # dest, which must fail because src-perm requires | |
1230 | # consistent-read but dest-perm does not share it; thus | |
1231 | # aborting the job when it is supposed to complete | |
1232 | self.complete_and_wait('job', | |
1233 | completion_error='Operation not permitted') | |
1234 | ||
1235 | # Assert that all of our nodes are still there (except for the | |
1236 | # mirror filter, which should be gone despite the failure) | |
1237 | nodes = self.vm.qmp('query-named-block-nodes')['return'] | |
1238 | nodes = [node['node-name'] for node in nodes] | |
1239 | ||
1240 | for expect in ('src', 'src-perm', 'dest', 'dest-perm'): | |
1241 | self.assertTrue(expect in nodes, '%s disappeared' % expect) | |
1242 | self.assertFalse('mirror-filter' in nodes, | |
1243 | 'Mirror filter node did not disappear') | |
1244 | ||
c45a88f4 HR |
1245 | # Test cases for @replaces that do not necessarily involve Quorum |
1246 | class TestReplaces(iotests.QMPTestCase): | |
1247 | # Each of these test cases needs their own block graph, so do not | |
1248 | # create any nodes here | |
1249 | def setUp(self): | |
1250 | self.vm = iotests.VM() | |
1251 | self.vm.launch() | |
1252 | ||
1253 | def tearDown(self): | |
1254 | self.vm.shutdown() | |
1255 | for img in (test_img, target_img): | |
1256 | try: | |
1257 | os.remove(img) | |
1258 | except OSError: | |
1259 | pass | |
1260 | ||
1261 | @iotests.skip_if_unsupported(['copy-on-read']) | |
1262 | def test_replace_filter(self): | |
1263 | """ | |
1264 | Check that we can replace filter nodes. | |
1265 | """ | |
1266 | result = self.vm.qmp('blockdev-add', **{ | |
1267 | 'driver': 'copy-on-read', | |
1268 | 'node-name': 'filter0', | |
1269 | 'file': { | |
1270 | 'driver': 'copy-on-read', | |
1271 | 'node-name': 'filter1', | |
1272 | 'file': { | |
1273 | 'driver': 'null-co' | |
1274 | } | |
1275 | } | |
1276 | }) | |
1277 | self.assert_qmp(result, 'return', {}) | |
1278 | ||
1279 | result = self.vm.qmp('blockdev-add', | |
1280 | node_name='target', driver='null-co') | |
1281 | self.assert_qmp(result, 'return', {}) | |
1282 | ||
1283 | result = self.vm.qmp('blockdev-mirror', job_id='mirror', device='filter0', | |
1284 | target='target', sync='full', replaces='filter1') | |
1285 | self.assert_qmp(result, 'return', {}) | |
1286 | ||
1287 | self.complete_and_wait('mirror') | |
1288 | ||
1289 | self.vm.assert_block_path('filter0', '/file', 'target') | |
1290 | ||
44c7ca5e | 1291 | if __name__ == '__main__': |
103cbc77 | 1292 | iotests.main(supported_fmts=['qcow2', 'qed'], |
877d18f2 TH |
1293 | supported_protocols=['file'], |
1294 | supported_platforms=['linux', 'freebsd', 'netbsd', 'openbsd']) |