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