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