xref: /openbmc/qemu/tests/qemu-iotests/041 (revision 2bfe11c8)
1#!/usr/bin/env python
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
21import time
22import os
23import iotests
24from iotests import qemu_img, qemu_io
25
26backing_img = os.path.join(iotests.test_dir, 'backing.img')
27target_backing_img = os.path.join(iotests.test_dir, 'target-backing.img')
28test_img = os.path.join(iotests.test_dir, 'test.img')
29target_img = os.path.join(iotests.test_dir, 'target.img')
30
31quorum_img1 = os.path.join(iotests.test_dir, 'quorum1.img')
32quorum_img2 = os.path.join(iotests.test_dir, 'quorum2.img')
33quorum_img3 = os.path.join(iotests.test_dir, 'quorum3.img')
34quorum_repair_img = os.path.join(iotests.test_dir, 'quorum_repair.img')
35quorum_snapshot_file = os.path.join(iotests.test_dir, 'quorum_snapshot.img')
36
37class TestSingleDrive(iotests.QMPTestCase):
38    image_len = 1 * 1024 * 1024 # MB
39    qmp_cmd = 'drive-mirror'
40    qmp_target = target_img
41
42    def setUp(self):
43        iotests.create_image(backing_img, self.image_len)
44        qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
45        self.vm = iotests.VM().add_drive(test_img)
46        if iotests.qemu_default_machine == 'pc':
47            self.vm.add_drive(None, 'media=cdrom', 'ide')
48        self.vm.launch()
49
50    def tearDown(self):
51        self.vm.shutdown()
52        os.remove(test_img)
53        os.remove(backing_img)
54        try:
55            os.remove(target_img)
56        except OSError:
57            pass
58
59    def test_complete(self):
60        self.assert_no_active_block_jobs()
61
62        result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full',
63                             target=self.qmp_target)
64        self.assert_qmp(result, 'return', {})
65
66        self.complete_and_wait()
67        result = self.vm.qmp('query-block')
68        self.assert_qmp(result, 'return[0]/inserted/file', target_img)
69        self.vm.shutdown()
70        self.assertTrue(iotests.compare_images(test_img, target_img),
71                        'target image does not match source after mirroring')
72
73    def test_cancel(self):
74        self.assert_no_active_block_jobs()
75
76        result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full',
77                             target=self.qmp_target)
78        self.assert_qmp(result, 'return', {})
79
80        self.cancel_and_wait(force=True)
81        result = self.vm.qmp('query-block')
82        self.assert_qmp(result, 'return[0]/inserted/file', test_img)
83        self.vm.shutdown()
84
85    def test_cancel_after_ready(self):
86        self.assert_no_active_block_jobs()
87
88        result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full',
89                             target=self.qmp_target)
90        self.assert_qmp(result, 'return', {})
91
92        self.wait_ready_and_cancel()
93        result = self.vm.qmp('query-block')
94        self.assert_qmp(result, 'return[0]/inserted/file', test_img)
95        self.vm.shutdown()
96        self.assertTrue(iotests.compare_images(test_img, target_img),
97                        'target image does not match source after mirroring')
98
99    def test_pause(self):
100        self.assert_no_active_block_jobs()
101
102        result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full',
103                             target=self.qmp_target)
104        self.assert_qmp(result, 'return', {})
105
106        result = self.vm.qmp('block-job-pause', device='drive0')
107        self.assert_qmp(result, 'return', {})
108
109        time.sleep(1)
110        result = self.vm.qmp('query-block-jobs')
111        offset = self.dictpath(result, 'return[0]/offset')
112
113        time.sleep(1)
114        result = self.vm.qmp('query-block-jobs')
115        self.assert_qmp(result, 'return[0]/offset', offset)
116
117        result = self.vm.qmp('block-job-resume', device='drive0')
118        self.assert_qmp(result, 'return', {})
119
120        self.complete_and_wait()
121        self.vm.shutdown()
122        self.assertTrue(iotests.compare_images(test_img, target_img),
123                        'target image does not match source after mirroring')
124
125    def test_small_buffer(self):
126        self.assert_no_active_block_jobs()
127
128        # A small buffer is rounded up automatically
129        result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full',
130                             buf_size=4096, target=self.qmp_target)
131        self.assert_qmp(result, 'return', {})
132
133        self.complete_and_wait()
134        result = self.vm.qmp('query-block')
135        self.assert_qmp(result, 'return[0]/inserted/file', target_img)
136        self.vm.shutdown()
137        self.assertTrue(iotests.compare_images(test_img, target_img),
138                        'target image does not match source after mirroring')
139
140    def test_small_buffer2(self):
141        self.assert_no_active_block_jobs()
142
143        qemu_img('create', '-f', iotests.imgfmt, '-o', 'cluster_size=%d,size=%d'
144                        % (self.image_len, self.image_len), target_img)
145        result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full',
146                             buf_size=65536, mode='existing', target=self.qmp_target)
147        self.assert_qmp(result, 'return', {})
148
149        self.complete_and_wait()
150        result = self.vm.qmp('query-block')
151        self.assert_qmp(result, 'return[0]/inserted/file', target_img)
152        self.vm.shutdown()
153        self.assertTrue(iotests.compare_images(test_img, target_img),
154                        'target image does not match source after mirroring')
155
156    def test_large_cluster(self):
157        self.assert_no_active_block_jobs()
158
159        qemu_img('create', '-f', iotests.imgfmt, '-o', 'cluster_size=%d,backing_file=%s'
160                        % (self.image_len, backing_img), target_img)
161        result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full',
162                             mode='existing', target=self.qmp_target)
163        self.assert_qmp(result, 'return', {})
164
165        self.complete_and_wait()
166        result = self.vm.qmp('query-block')
167        self.assert_qmp(result, 'return[0]/inserted/file', target_img)
168        self.vm.shutdown()
169        self.assertTrue(iotests.compare_images(test_img, target_img),
170                        'target image does not match source after mirroring')
171
172    def test_medium_not_found(self):
173        if iotests.qemu_default_machine != 'pc':
174            return
175
176        result = self.vm.qmp(self.qmp_cmd, device='ide1-cd0', sync='full',
177                             target=self.qmp_target)
178        self.assert_qmp(result, 'error/class', 'GenericError')
179
180    def test_image_not_found(self):
181        result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full',
182                             mode='existing', target=self.qmp_target)
183        self.assert_qmp(result, 'error/class', 'GenericError')
184
185    def test_device_not_found(self):
186        result = self.vm.qmp(self.qmp_cmd, device='nonexistent', sync='full',
187                             target=self.qmp_target)
188        self.assert_qmp(result, 'error/class', 'GenericError')
189
190class TestSingleBlockdev(TestSingleDrive):
191    qmp_cmd = 'blockdev-mirror'
192    qmp_target = 'node1'
193
194    def setUp(self):
195        TestSingleDrive.setUp(self)
196        qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, target_img)
197        args = {'options':
198                    {'driver': iotests.imgfmt,
199                     'node-name': self.qmp_target,
200                     'file': { 'filename': target_img, 'driver': 'file' } } }
201        result = self.vm.qmp("blockdev-add", **args)
202        self.assert_qmp(result, 'return', {})
203
204    test_large_cluster = None
205    test_image_not_found = None
206    test_small_buffer2 = None
207
208class TestSingleDriveZeroLength(TestSingleDrive):
209    image_len = 0
210    test_small_buffer2 = None
211    test_large_cluster = None
212
213class TestSingleBlockdevZeroLength(TestSingleBlockdev):
214    image_len = 0
215
216class TestSingleDriveUnalignedLength(TestSingleDrive):
217    image_len = 1025 * 1024
218    test_small_buffer2 = None
219    test_large_cluster = None
220
221class TestSingleBlockdevUnalignedLength(TestSingleBlockdev):
222    image_len = 1025 * 1024
223
224class TestMirrorNoBacking(iotests.QMPTestCase):
225    image_len = 2 * 1024 * 1024 # MB
226
227    def setUp(self):
228        iotests.create_image(backing_img, TestMirrorNoBacking.image_len)
229        qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
230        self.vm = iotests.VM().add_drive(test_img)
231        self.vm.launch()
232
233    def tearDown(self):
234        self.vm.shutdown()
235        os.remove(test_img)
236        os.remove(backing_img)
237        try:
238            os.remove(target_backing_img)
239        except:
240            pass
241        os.remove(target_img)
242
243    def test_complete(self):
244        self.assert_no_active_block_jobs()
245
246        qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, target_img)
247        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
248                             mode='existing', target=target_img)
249        self.assert_qmp(result, 'return', {})
250
251        self.complete_and_wait()
252        result = self.vm.qmp('query-block')
253        self.assert_qmp(result, 'return[0]/inserted/file', target_img)
254        self.vm.shutdown()
255        self.assertTrue(iotests.compare_images(test_img, target_img),
256                        'target image does not match source after mirroring')
257
258    def test_cancel(self):
259        self.assert_no_active_block_jobs()
260
261        qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, target_img)
262        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
263                             mode='existing', target=target_img)
264        self.assert_qmp(result, 'return', {})
265
266        self.wait_ready_and_cancel()
267        result = self.vm.qmp('query-block')
268        self.assert_qmp(result, 'return[0]/inserted/file', test_img)
269        self.vm.shutdown()
270        self.assertTrue(iotests.compare_images(test_img, target_img),
271                        'target image does not match source after mirroring')
272
273    def test_large_cluster(self):
274        self.assert_no_active_block_jobs()
275
276        # qemu-img create fails if the image is not there
277        qemu_img('create', '-f', iotests.imgfmt, '-o', 'size=%d'
278                        %(TestMirrorNoBacking.image_len), target_backing_img)
279        qemu_img('create', '-f', iotests.imgfmt, '-o', 'cluster_size=%d,backing_file=%s'
280                        % (TestMirrorNoBacking.image_len, target_backing_img), target_img)
281
282        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
283                             mode='existing', target=target_img)
284        self.assert_qmp(result, 'return', {})
285
286        self.complete_and_wait()
287        result = self.vm.qmp('query-block')
288        self.assert_qmp(result, 'return[0]/inserted/file', target_img)
289        self.vm.shutdown()
290        self.assertTrue(iotests.compare_images(test_img, target_img),
291                        'target image does not match source after mirroring')
292
293class TestMirrorResized(iotests.QMPTestCase):
294    backing_len = 1 * 1024 * 1024 # MB
295    image_len = 2 * 1024 * 1024 # MB
296
297    def setUp(self):
298        iotests.create_image(backing_img, TestMirrorResized.backing_len)
299        qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
300        qemu_img('resize', test_img, '2M')
301        self.vm = iotests.VM().add_drive(test_img)
302        self.vm.launch()
303
304    def tearDown(self):
305        self.vm.shutdown()
306        os.remove(test_img)
307        os.remove(backing_img)
308        try:
309            os.remove(target_img)
310        except OSError:
311            pass
312
313    def test_complete_top(self):
314        self.assert_no_active_block_jobs()
315
316        result = self.vm.qmp('drive-mirror', device='drive0', sync='top',
317                             target=target_img)
318        self.assert_qmp(result, 'return', {})
319
320        self.complete_and_wait()
321        result = self.vm.qmp('query-block')
322        self.assert_qmp(result, 'return[0]/inserted/file', target_img)
323        self.vm.shutdown()
324        self.assertTrue(iotests.compare_images(test_img, target_img),
325                        'target image does not match source after mirroring')
326
327    def test_complete_full(self):
328        self.assert_no_active_block_jobs()
329
330        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
331                             target=target_img)
332        self.assert_qmp(result, 'return', {})
333
334        self.complete_and_wait()
335        result = self.vm.qmp('query-block')
336        self.assert_qmp(result, 'return[0]/inserted/file', target_img)
337        self.vm.shutdown()
338        self.assertTrue(iotests.compare_images(test_img, target_img),
339                        'target image does not match source after mirroring')
340
341class TestReadErrors(iotests.QMPTestCase):
342    image_len = 2 * 1024 * 1024 # MB
343
344    # this should be a multiple of twice the default granularity
345    # so that we hit this offset first in state 1
346    MIRROR_GRANULARITY = 1024 * 1024
347
348    def create_blkdebug_file(self, name, event, errno):
349        file = open(name, 'w')
350        file.write('''
351[inject-error]
352state = "1"
353event = "%s"
354errno = "%d"
355immediately = "off"
356once = "on"
357sector = "%d"
358
359[set-state]
360state = "1"
361event = "%s"
362new_state = "2"
363
364[set-state]
365state = "2"
366event = "%s"
367new_state = "1"
368''' % (event, errno, self.MIRROR_GRANULARITY / 512, event, event))
369        file.close()
370
371    def setUp(self):
372        self.blkdebug_file = backing_img + ".blkdebug"
373        iotests.create_image(backing_img, TestReadErrors.image_len)
374        self.create_blkdebug_file(self.blkdebug_file, "read_aio", 5)
375        qemu_img('create', '-f', iotests.imgfmt,
376                 '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw'
377                       % (self.blkdebug_file, backing_img),
378                 test_img)
379        # Write something for tests that use sync='top'
380        qemu_io('-c', 'write %d 512' % (self.MIRROR_GRANULARITY + 65536),
381                        test_img)
382        self.vm = iotests.VM().add_drive(test_img)
383        self.vm.launch()
384
385    def tearDown(self):
386        self.vm.shutdown()
387        os.remove(test_img)
388        os.remove(backing_img)
389        os.remove(self.blkdebug_file)
390
391    def test_report_read(self):
392        self.assert_no_active_block_jobs()
393
394        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
395                             target=target_img)
396        self.assert_qmp(result, 'return', {})
397
398        completed = False
399        error = False
400        while not completed:
401            for event in self.vm.get_qmp_events(wait=True):
402                if event['event'] == 'BLOCK_JOB_ERROR':
403                    self.assert_qmp(event, 'data/device', 'drive0')
404                    self.assert_qmp(event, 'data/operation', 'read')
405                    error = True
406                elif event['event'] == 'BLOCK_JOB_READY':
407                    self.assertTrue(False, 'job completed unexpectedly')
408                elif event['event'] == 'BLOCK_JOB_COMPLETED':
409                    self.assertTrue(error, 'job completed unexpectedly')
410                    self.assert_qmp(event, 'data/type', 'mirror')
411                    self.assert_qmp(event, 'data/device', 'drive0')
412                    self.assert_qmp(event, 'data/error', 'Input/output error')
413                    completed = True
414
415        self.assert_no_active_block_jobs()
416        self.vm.shutdown()
417
418    def test_ignore_read(self):
419        self.assert_no_active_block_jobs()
420
421        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
422                             target=target_img, on_source_error='ignore')
423        self.assert_qmp(result, 'return', {})
424
425        event = self.vm.get_qmp_event(wait=True)
426        self.assertEquals(event['event'], 'BLOCK_JOB_ERROR')
427        self.assert_qmp(event, 'data/device', 'drive0')
428        self.assert_qmp(event, 'data/operation', 'read')
429        result = self.vm.qmp('query-block-jobs')
430        self.assert_qmp(result, 'return[0]/paused', False)
431        self.complete_and_wait()
432        self.vm.shutdown()
433
434    def test_large_cluster(self):
435        self.assert_no_active_block_jobs()
436
437        # Test COW into the target image.  The first half of the
438        # cluster at MIRROR_GRANULARITY has to be copied from
439        # backing_img, even though sync='top'.
440        qemu_img('create', '-f', iotests.imgfmt, '-ocluster_size=131072,backing_file=%s' %(backing_img), target_img)
441        result = self.vm.qmp('drive-mirror', device='drive0', sync='top',
442                             on_source_error='ignore',
443                             mode='existing', target=target_img)
444        self.assert_qmp(result, 'return', {})
445
446        event = self.vm.get_qmp_event(wait=True)
447        self.assertEquals(event['event'], 'BLOCK_JOB_ERROR')
448        self.assert_qmp(event, 'data/device', 'drive0')
449        self.assert_qmp(event, 'data/operation', 'read')
450        result = self.vm.qmp('query-block-jobs')
451        self.assert_qmp(result, 'return[0]/paused', False)
452        self.complete_and_wait()
453        self.vm.shutdown()
454
455        # Detach blkdebug to compare images successfully
456        qemu_img('rebase', '-f', iotests.imgfmt, '-u', '-b', backing_img, test_img)
457        self.assertTrue(iotests.compare_images(test_img, target_img),
458                        'target image does not match source after mirroring')
459
460    def test_stop_read(self):
461        self.assert_no_active_block_jobs()
462
463        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
464                             target=target_img, on_source_error='stop')
465        self.assert_qmp(result, 'return', {})
466
467        error = False
468        ready = False
469        while not ready:
470            for event in self.vm.get_qmp_events(wait=True):
471                if event['event'] == 'BLOCK_JOB_ERROR':
472                    self.assert_qmp(event, 'data/device', 'drive0')
473                    self.assert_qmp(event, 'data/operation', 'read')
474
475                    result = self.vm.qmp('query-block-jobs')
476                    self.assert_qmp(result, 'return[0]/paused', True)
477                    self.assert_qmp(result, 'return[0]/io-status', 'failed')
478
479                    result = self.vm.qmp('block-job-resume', device='drive0')
480                    self.assert_qmp(result, 'return', {})
481                    error = True
482                elif event['event'] == 'BLOCK_JOB_READY':
483                    self.assertTrue(error, 'job completed unexpectedly')
484                    self.assert_qmp(event, 'data/device', 'drive0')
485                    ready = True
486
487        result = self.vm.qmp('query-block-jobs')
488        self.assert_qmp(result, 'return[0]/paused', False)
489        self.assert_qmp(result, 'return[0]/io-status', 'ok')
490
491        self.complete_and_wait(wait_ready=False)
492        self.assert_no_active_block_jobs()
493        self.vm.shutdown()
494
495class TestWriteErrors(iotests.QMPTestCase):
496    image_len = 2 * 1024 * 1024 # MB
497
498    # this should be a multiple of twice the default granularity
499    # so that we hit this offset first in state 1
500    MIRROR_GRANULARITY = 1024 * 1024
501
502    def create_blkdebug_file(self, name, event, errno):
503        file = open(name, 'w')
504        file.write('''
505[inject-error]
506state = "1"
507event = "%s"
508errno = "%d"
509immediately = "off"
510once = "on"
511sector = "%d"
512
513[set-state]
514state = "1"
515event = "%s"
516new_state = "2"
517
518[set-state]
519state = "2"
520event = "%s"
521new_state = "1"
522''' % (event, errno, self.MIRROR_GRANULARITY / 512, event, event))
523        file.close()
524
525    def setUp(self):
526        self.blkdebug_file = target_img + ".blkdebug"
527        iotests.create_image(backing_img, TestWriteErrors.image_len)
528        self.create_blkdebug_file(self.blkdebug_file, "write_aio", 5)
529        qemu_img('create', '-f', iotests.imgfmt, '-obacking_file=%s' %(backing_img), test_img)
530        self.vm = iotests.VM().add_drive(test_img)
531        self.target_img = 'blkdebug:%s:%s' % (self.blkdebug_file, target_img)
532        qemu_img('create', '-f', iotests.imgfmt, '-osize=%d' %(TestWriteErrors.image_len), target_img)
533        self.vm.launch()
534
535    def tearDown(self):
536        self.vm.shutdown()
537        os.remove(test_img)
538        os.remove(backing_img)
539        os.remove(self.blkdebug_file)
540
541    def test_report_write(self):
542        self.assert_no_active_block_jobs()
543
544        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
545                             mode='existing', target=self.target_img)
546        self.assert_qmp(result, 'return', {})
547
548        completed = False
549        error = False
550        while not completed:
551            for event in self.vm.get_qmp_events(wait=True):
552                if event['event'] == 'BLOCK_JOB_ERROR':
553                    self.assert_qmp(event, 'data/device', 'drive0')
554                    self.assert_qmp(event, 'data/operation', 'write')
555                    error = True
556                elif event['event'] == 'BLOCK_JOB_READY':
557                    self.assertTrue(False, 'job completed unexpectedly')
558                elif event['event'] == 'BLOCK_JOB_COMPLETED':
559                    self.assertTrue(error, 'job completed unexpectedly')
560                    self.assert_qmp(event, 'data/type', 'mirror')
561                    self.assert_qmp(event, 'data/device', 'drive0')
562                    self.assert_qmp(event, 'data/error', 'Input/output error')
563                    completed = True
564
565        self.assert_no_active_block_jobs()
566        self.vm.shutdown()
567
568    def test_ignore_write(self):
569        self.assert_no_active_block_jobs()
570
571        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
572                             mode='existing', target=self.target_img,
573                             on_target_error='ignore')
574        self.assert_qmp(result, 'return', {})
575
576        event = self.vm.get_qmp_event(wait=True)
577        self.assertEquals(event['event'], 'BLOCK_JOB_ERROR')
578        self.assert_qmp(event, 'data/device', 'drive0')
579        self.assert_qmp(event, 'data/operation', 'write')
580        result = self.vm.qmp('query-block-jobs')
581        self.assert_qmp(result, 'return[0]/paused', False)
582        self.complete_and_wait()
583        self.vm.shutdown()
584
585    def test_stop_write(self):
586        self.assert_no_active_block_jobs()
587
588        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
589                             mode='existing', target=self.target_img,
590                             on_target_error='stop')
591        self.assert_qmp(result, 'return', {})
592
593        error = False
594        ready = False
595        while not ready:
596            for event in self.vm.get_qmp_events(wait=True):
597                if event['event'] == 'BLOCK_JOB_ERROR':
598                    self.assert_qmp(event, 'data/device', 'drive0')
599                    self.assert_qmp(event, 'data/operation', 'write')
600
601                    result = self.vm.qmp('query-block-jobs')
602                    self.assert_qmp(result, 'return[0]/paused', True)
603                    self.assert_qmp(result, 'return[0]/io-status', 'failed')
604
605                    result = self.vm.qmp('block-job-resume', device='drive0')
606                    self.assert_qmp(result, 'return', {})
607
608                    result = self.vm.qmp('query-block-jobs')
609                    self.assert_qmp(result, 'return[0]/paused', False)
610                    self.assert_qmp(result, 'return[0]/io-status', 'ok')
611                    error = True
612                elif event['event'] == 'BLOCK_JOB_READY':
613                    self.assertTrue(error, 'job completed unexpectedly')
614                    self.assert_qmp(event, 'data/device', 'drive0')
615                    ready = True
616
617        self.complete_and_wait(wait_ready=False)
618        self.assert_no_active_block_jobs()
619        self.vm.shutdown()
620
621class TestSetSpeed(iotests.QMPTestCase):
622    image_len = 80 * 1024 * 1024 # MB
623
624    def setUp(self):
625        qemu_img('create', backing_img, str(TestSetSpeed.image_len))
626        qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
627        self.vm = iotests.VM().add_drive(test_img)
628        self.vm.launch()
629
630    def tearDown(self):
631        self.vm.shutdown()
632        os.remove(test_img)
633        os.remove(backing_img)
634        os.remove(target_img)
635
636    def test_set_speed(self):
637        self.assert_no_active_block_jobs()
638
639        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
640                             target=target_img)
641        self.assert_qmp(result, 'return', {})
642
643        # Default speed is 0
644        result = self.vm.qmp('query-block-jobs')
645        self.assert_qmp(result, 'return[0]/device', 'drive0')
646        self.assert_qmp(result, 'return[0]/speed', 0)
647
648        result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024)
649        self.assert_qmp(result, 'return', {})
650
651        # Ensure the speed we set was accepted
652        result = self.vm.qmp('query-block-jobs')
653        self.assert_qmp(result, 'return[0]/device', 'drive0')
654        self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024)
655
656        self.wait_ready_and_cancel()
657
658        # Check setting speed in drive-mirror works
659        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
660                             target=target_img, speed=4*1024*1024)
661        self.assert_qmp(result, 'return', {})
662
663        result = self.vm.qmp('query-block-jobs')
664        self.assert_qmp(result, 'return[0]/device', 'drive0')
665        self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024)
666
667        self.wait_ready_and_cancel()
668
669    def test_set_speed_invalid(self):
670        self.assert_no_active_block_jobs()
671
672        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
673                             target=target_img, speed=-1)
674        self.assert_qmp(result, 'error/class', 'GenericError')
675
676        self.assert_no_active_block_jobs()
677
678        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
679                             target=target_img)
680        self.assert_qmp(result, 'return', {})
681
682        result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1)
683        self.assert_qmp(result, 'error/class', 'GenericError')
684
685        self.wait_ready_and_cancel()
686
687class TestUnbackedSource(iotests.QMPTestCase):
688    image_len = 2 * 1024 * 1024 # MB
689
690    def setUp(self):
691        qemu_img('create', '-f', iotests.imgfmt, test_img,
692                 str(TestUnbackedSource.image_len))
693        self.vm = iotests.VM().add_drive(test_img)
694        self.vm.launch()
695
696    def tearDown(self):
697        self.vm.shutdown()
698        os.remove(test_img)
699        os.remove(target_img)
700
701    def test_absolute_paths_full(self):
702        self.assert_no_active_block_jobs()
703        result = self.vm.qmp('drive-mirror', device='drive0',
704                             sync='full', target=target_img,
705                             mode='absolute-paths')
706        self.assert_qmp(result, 'return', {})
707        self.complete_and_wait()
708        self.assert_no_active_block_jobs()
709
710    def test_absolute_paths_top(self):
711        self.assert_no_active_block_jobs()
712        result = self.vm.qmp('drive-mirror', device='drive0',
713                             sync='top', target=target_img,
714                             mode='absolute-paths')
715        self.assert_qmp(result, 'return', {})
716        self.complete_and_wait()
717        self.assert_no_active_block_jobs()
718
719    def test_absolute_paths_none(self):
720        self.assert_no_active_block_jobs()
721        result = self.vm.qmp('drive-mirror', device='drive0',
722                             sync='none', target=target_img,
723                             mode='absolute-paths')
724        self.assert_qmp(result, 'return', {})
725        self.complete_and_wait()
726        self.assert_no_active_block_jobs()
727
728class TestGranularity(iotests.QMPTestCase):
729    image_len = 10 * 1024 * 1024 # MB
730
731    def setUp(self):
732        qemu_img('create', '-f', iotests.imgfmt, test_img,
733                 str(TestGranularity.image_len))
734        qemu_io('-c', 'write 0 %d' % (self.image_len),
735                test_img)
736        self.vm = iotests.VM().add_drive(test_img)
737        self.vm.launch()
738
739    def tearDown(self):
740        self.vm.shutdown()
741        self.assertTrue(iotests.compare_images(test_img, target_img),
742                        'target image does not match source after mirroring')
743        os.remove(test_img)
744        os.remove(target_img)
745
746    def test_granularity(self):
747        self.assert_no_active_block_jobs()
748        result = self.vm.qmp('drive-mirror', device='drive0',
749                             sync='full', target=target_img,
750                             mode='absolute-paths', granularity=8192)
751        self.assert_qmp(result, 'return', {})
752        event = self.vm.get_qmp_event(wait=60.0)
753        # Failures will manifest as COMPLETED/ERROR.
754        self.assert_qmp(event, 'event', 'BLOCK_JOB_READY')
755        self.complete_and_wait(drive='drive0', wait_ready=False)
756        self.assert_no_active_block_jobs()
757
758class TestRepairQuorum(iotests.QMPTestCase):
759    """ This class test quorum file repair using drive-mirror.
760        It's mostly a fork of TestSingleDrive """
761    image_len = 1 * 1024 * 1024 # MB
762    IMAGES = [ quorum_img1, quorum_img2, quorum_img3 ]
763
764    def has_quorum(self):
765        return 'quorum' in iotests.qemu_img_pipe('--help')
766
767    def setUp(self):
768        self.vm = iotests.VM()
769
770        if iotests.qemu_default_machine == 'pc':
771            self.vm.add_drive(None, 'media=cdrom', 'ide')
772
773        # Add each individual quorum images
774        for i in self.IMAGES:
775            qemu_img('create', '-f', iotests.imgfmt, i,
776                     str(TestSingleDrive.image_len))
777            # Assign a node name to each quorum image in order to manipulate
778            # them
779            opts = "node-name=img%i" % self.IMAGES.index(i)
780            self.vm = self.vm.add_drive(i, opts)
781
782        self.vm.launch()
783
784        #assemble the quorum block device from the individual files
785        args = { "options" : { "driver": "quorum", "node-name": "quorum0",
786                 "vote-threshold": 2, "children": [ "img0", "img1", "img2" ] } }
787        if self.has_quorum():
788            result = self.vm.qmp("blockdev-add", **args)
789            self.assert_qmp(result, 'return', {})
790
791
792    def tearDown(self):
793        self.vm.shutdown()
794        for i in self.IMAGES + [ quorum_repair_img ]:
795            # Do a try/except because the test may have deleted some images
796            try:
797                os.remove(i)
798            except OSError:
799                pass
800
801    def test_complete(self):
802        if not self.has_quorum():
803            return
804
805        self.assert_no_active_block_jobs()
806
807        result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0',
808                             sync='full', node_name="repair0", replaces="img1",
809                             target=quorum_repair_img, format=iotests.imgfmt)
810        self.assert_qmp(result, 'return', {})
811
812        self.complete_and_wait(drive="job0")
813        self.assert_has_block_node("repair0", quorum_repair_img)
814        # TODO: a better test requiring some QEMU infrastructure will be added
815        #       to check that this file is really driven by quorum
816        self.vm.shutdown()
817        self.assertTrue(iotests.compare_images(quorum_img2, quorum_repair_img),
818                        'target image does not match source after mirroring')
819
820    def test_cancel(self):
821        if not self.has_quorum():
822            return
823
824        self.assert_no_active_block_jobs()
825
826        result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0',
827                             sync='full', node_name="repair0", replaces="img1",
828                             target=quorum_repair_img, format=iotests.imgfmt)
829        self.assert_qmp(result, 'return', {})
830
831        self.cancel_and_wait(drive="job0", force=True)
832        # here we check that the last registered quorum file has not been
833        # swapped out and unref
834        self.assert_has_block_node(None, quorum_img3)
835        self.vm.shutdown()
836
837    def test_cancel_after_ready(self):
838        if not self.has_quorum():
839            return
840
841        self.assert_no_active_block_jobs()
842
843        result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0',
844                             sync='full', node_name="repair0", replaces="img1",
845                             target=quorum_repair_img, format=iotests.imgfmt)
846        self.assert_qmp(result, 'return', {})
847
848        self.wait_ready_and_cancel(drive="job0")
849        # here we check that the last registered quorum file has not been
850        # swapped out and unref
851        self.assert_has_block_node(None, quorum_img3)
852        self.vm.shutdown()
853        self.assertTrue(iotests.compare_images(quorum_img2, quorum_repair_img),
854                        'target image does not match source after mirroring')
855
856    def test_pause(self):
857        if not self.has_quorum():
858            return
859
860        self.assert_no_active_block_jobs()
861
862        result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0',
863                             sync='full', node_name="repair0", replaces="img1",
864                             target=quorum_repair_img, format=iotests.imgfmt)
865        self.assert_qmp(result, 'return', {})
866
867        result = self.vm.qmp('block-job-pause', device='job0')
868        self.assert_qmp(result, 'return', {})
869
870        time.sleep(1)
871        result = self.vm.qmp('query-block-jobs')
872        offset = self.dictpath(result, 'return[0]/offset')
873
874        time.sleep(1)
875        result = self.vm.qmp('query-block-jobs')
876        self.assert_qmp(result, 'return[0]/offset', offset)
877
878        result = self.vm.qmp('block-job-resume', device='job0')
879        self.assert_qmp(result, 'return', {})
880
881        self.complete_and_wait(drive="job0")
882        self.vm.shutdown()
883        self.assertTrue(iotests.compare_images(quorum_img2, quorum_repair_img),
884                        'target image does not match source after mirroring')
885
886    def test_medium_not_found(self):
887        if not self.has_quorum():
888            return
889
890        if iotests.qemu_default_machine != 'pc':
891            return
892
893        result = self.vm.qmp('drive-mirror', job_id='job0', device='drive0', # CD-ROM
894                             sync='full',
895                             node_name='repair0',
896                             replaces='img1',
897                             target=quorum_repair_img, format=iotests.imgfmt)
898        self.assert_qmp(result, 'error/class', 'GenericError')
899
900    def test_image_not_found(self):
901        if not self.has_quorum():
902            return
903
904        result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0',
905                             sync='full', node_name='repair0', replaces='img1',
906                             mode='existing', target=quorum_repair_img,
907                             format=iotests.imgfmt)
908        self.assert_qmp(result, 'error/class', 'GenericError')
909
910    def test_device_not_found(self):
911        if not self.has_quorum():
912            return
913
914        result = self.vm.qmp('drive-mirror', job_id='job0',
915                             device='nonexistent', sync='full',
916                             node_name='repair0',
917                             replaces='img1',
918                             target=quorum_repair_img, format=iotests.imgfmt)
919        self.assert_qmp(result, 'error/class', 'GenericError')
920
921    def test_wrong_sync_mode(self):
922        if not self.has_quorum():
923            return
924
925        result = self.vm.qmp('drive-mirror', device='quorum0', job_id='job0',
926                             node_name='repair0',
927                             replaces='img1',
928                             target=quorum_repair_img, format=iotests.imgfmt)
929        self.assert_qmp(result, 'error/class', 'GenericError')
930
931    def test_no_node_name(self):
932        if not self.has_quorum():
933            return
934
935        result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0',
936                             sync='full', replaces='img1',
937                             target=quorum_repair_img, format=iotests.imgfmt)
938        self.assert_qmp(result, 'error/class', 'GenericError')
939
940    def test_nonexistent_replaces(self):
941        if not self.has_quorum():
942            return
943
944        result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0',
945                             sync='full', node_name='repair0', replaces='img77',
946                             target=quorum_repair_img, format=iotests.imgfmt)
947        self.assert_qmp(result, 'error/class', 'GenericError')
948
949    def test_after_a_quorum_snapshot(self):
950        if not self.has_quorum():
951            return
952
953        result = self.vm.qmp('blockdev-snapshot-sync', node_name='img1',
954                             snapshot_file=quorum_snapshot_file,
955                             snapshot_node_name="snap1");
956
957        result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0',
958                             sync='full', node_name='repair0', replaces="img1",
959                             target=quorum_repair_img, format=iotests.imgfmt)
960        self.assert_qmp(result, 'error/class', 'GenericError')
961
962        result = self.vm.qmp('drive-mirror', job_id='job0', device='quorum0',
963                             sync='full', node_name='repair0', replaces="snap1",
964                             target=quorum_repair_img, format=iotests.imgfmt)
965        self.assert_qmp(result, 'return', {})
966
967        self.complete_and_wait('job0')
968        self.assert_has_block_node("repair0", quorum_repair_img)
969        # TODO: a better test requiring some QEMU infrastructure will be added
970        #       to check that this file is really driven by quorum
971        self.vm.shutdown()
972
973if __name__ == '__main__':
974    iotests.main(supported_fmts=['qcow2', 'qed'])
975