xref: /openbmc/qemu/tests/qemu-iotests/041 (revision 3bc2f570)
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
25import struct
26
27backing_img = os.path.join(iotests.test_dir, 'backing.img')
28target_backing_img = os.path.join(iotests.test_dir, 'target-backing.img')
29test_img = os.path.join(iotests.test_dir, 'test.img')
30target_img = os.path.join(iotests.test_dir, 'target.img')
31
32class ImageMirroringTestCase(iotests.QMPTestCase):
33    '''Abstract base class for image mirroring test cases'''
34
35    def assert_no_active_mirrors(self):
36        result = self.vm.qmp('query-block-jobs')
37        self.assert_qmp(result, 'return', [])
38
39    def cancel_and_wait(self, drive='drive0', wait_ready=True):
40        '''Cancel a block job and wait for it to finish'''
41        if wait_ready:
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        result = self.vm.qmp('block-job-cancel', device=drive,
51                             force=not wait_ready)
52        self.assert_qmp(result, 'return', {})
53
54        cancelled = False
55        while not cancelled:
56            for event in self.vm.get_qmp_events(wait=True):
57                if event['event'] == 'BLOCK_JOB_COMPLETED' or \
58                   event['event'] == 'BLOCK_JOB_CANCELLED':
59                    self.assert_qmp(event, 'data/type', 'mirror')
60                    self.assert_qmp(event, 'data/device', drive)
61                    if wait_ready:
62                        self.assertEquals(event['event'], 'BLOCK_JOB_COMPLETED')
63                        self.assert_qmp(event, 'data/offset', self.image_len)
64                        self.assert_qmp(event, 'data/len', self.image_len)
65                    cancelled = True
66
67        self.assert_no_active_mirrors()
68
69    def complete_and_wait(self, drive='drive0', wait_ready=True):
70        '''Complete a block job and wait for it to finish'''
71        if wait_ready:
72            ready = False
73            while not ready:
74                for event in self.vm.get_qmp_events(wait=True):
75                    if event['event'] == 'BLOCK_JOB_READY':
76                        self.assert_qmp(event, 'data/type', 'mirror')
77                        self.assert_qmp(event, 'data/device', drive)
78                        ready = True
79
80        result = self.vm.qmp('block-job-complete', device=drive)
81        self.assert_qmp(result, 'return', {})
82
83        completed = False
84        while not completed:
85            for event in self.vm.get_qmp_events(wait=True):
86                if event['event'] == 'BLOCK_JOB_COMPLETED':
87                    self.assert_qmp(event, 'data/type', 'mirror')
88                    self.assert_qmp(event, 'data/device', drive)
89                    self.assert_qmp_absent(event, 'data/error')
90                    self.assert_qmp(event, 'data/offset', self.image_len)
91                    self.assert_qmp(event, 'data/len', self.image_len)
92                    completed = True
93
94        self.assert_no_active_mirrors()
95
96    def create_image(self, name, size):
97        file = open(name, 'w')
98        i = 0
99        while i < size:
100            sector = struct.pack('>l504xl', i / 512, i / 512)
101            file.write(sector)
102            i = i + 512
103        file.close()
104
105    def compare_images(self, img1, img2):
106        try:
107            qemu_img('convert', '-f', iotests.imgfmt, '-O', 'raw', img1, img1 + '.raw')
108            qemu_img('convert', '-f', iotests.imgfmt, '-O', 'raw', img2, img2 + '.raw')
109            file1 = open(img1 + '.raw', 'r')
110            file2 = open(img2 + '.raw', 'r')
111            return file1.read() == file2.read()
112        finally:
113            if file1 is not None:
114                file1.close()
115            if file2 is not None:
116                file2.close()
117            try:
118                os.remove(img1 + '.raw')
119            except OSError:
120                pass
121            try:
122                os.remove(img2 + '.raw')
123            except OSError:
124                pass
125
126class TestSingleDrive(ImageMirroringTestCase):
127    image_len = 1 * 1024 * 1024 # MB
128
129    def setUp(self):
130        self.create_image(backing_img, TestSingleDrive.image_len)
131        qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
132        self.vm = iotests.VM().add_drive(test_img)
133        self.vm.launch()
134
135    def tearDown(self):
136        self.vm.shutdown()
137        os.remove(test_img)
138        os.remove(backing_img)
139        try:
140            os.remove(target_img)
141        except OSError:
142            pass
143
144    def test_complete(self):
145        self.assert_no_active_mirrors()
146
147        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
148                             target=target_img)
149        self.assert_qmp(result, 'return', {})
150
151        self.complete_and_wait()
152        result = self.vm.qmp('query-block')
153        self.assert_qmp(result, 'return[0]/inserted/file', target_img)
154        self.vm.shutdown()
155        self.assertTrue(self.compare_images(test_img, target_img),
156                        'target image does not match source after mirroring')
157
158    def test_cancel(self):
159        self.assert_no_active_mirrors()
160
161        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
162                             target=target_img)
163        self.assert_qmp(result, 'return', {})
164
165        self.cancel_and_wait(wait_ready=False)
166        result = self.vm.qmp('query-block')
167        self.assert_qmp(result, 'return[0]/inserted/file', test_img)
168        self.vm.shutdown()
169
170    def test_cancel_after_ready(self):
171        self.assert_no_active_mirrors()
172
173        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
174                             target=target_img)
175        self.assert_qmp(result, 'return', {})
176
177        self.cancel_and_wait()
178        result = self.vm.qmp('query-block')
179        self.assert_qmp(result, 'return[0]/inserted/file', test_img)
180        self.vm.shutdown()
181        self.assertTrue(self.compare_images(test_img, target_img),
182                        'target image does not match source after mirroring')
183
184    def test_pause(self):
185        self.assert_no_active_mirrors()
186
187        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
188                             target=target_img)
189        self.assert_qmp(result, 'return', {})
190
191        result = self.vm.qmp('block-job-pause', device='drive0')
192        self.assert_qmp(result, 'return', {})
193
194        time.sleep(1)
195        result = self.vm.qmp('query-block-jobs')
196        offset = self.dictpath(result, 'return[0]/offset')
197
198        time.sleep(1)
199        result = self.vm.qmp('query-block-jobs')
200        self.assert_qmp(result, 'return[0]/offset', offset)
201
202        result = self.vm.qmp('block-job-resume', device='drive0')
203        self.assert_qmp(result, 'return', {})
204
205        self.complete_and_wait()
206        self.vm.shutdown()
207        self.assertTrue(self.compare_images(test_img, target_img),
208                        'target image does not match source after mirroring')
209
210    def test_large_cluster(self):
211        self.assert_no_active_mirrors()
212
213        qemu_img('create', '-f', iotests.imgfmt, '-o', 'cluster_size=%d,backing_file=%s'
214                        % (TestSingleDrive.image_len, backing_img), target_img)
215        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
216                             mode='existing', target=target_img)
217        self.assert_qmp(result, 'return', {})
218
219        self.complete_and_wait()
220        result = self.vm.qmp('query-block')
221        self.assert_qmp(result, 'return[0]/inserted/file', target_img)
222        self.vm.shutdown()
223        self.assertTrue(self.compare_images(test_img, target_img),
224                        'target image does not match source after mirroring')
225
226    def test_medium_not_found(self):
227        result = self.vm.qmp('drive-mirror', device='ide1-cd0', sync='full',
228                             target=target_img)
229        self.assert_qmp(result, 'error/class', 'GenericError')
230
231    def test_image_not_found(self):
232        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
233                             mode='existing', target=target_img)
234        self.assert_qmp(result, 'error/class', 'GenericError')
235
236    def test_device_not_found(self):
237        result = self.vm.qmp('drive-mirror', device='nonexistent', sync='full',
238                             target=target_img)
239        self.assert_qmp(result, 'error/class', 'DeviceNotFound')
240
241class TestMirrorNoBacking(ImageMirroringTestCase):
242    image_len = 2 * 1024 * 1024 # MB
243
244    def complete_and_wait(self, drive='drive0', wait_ready=True):
245        self.create_image(target_backing_img, TestMirrorNoBacking.image_len)
246        return ImageMirroringTestCase.complete_and_wait(self, drive, wait_ready)
247
248    def compare_images(self, img1, img2):
249        self.create_image(target_backing_img, TestMirrorNoBacking.image_len)
250        return ImageMirroringTestCase.compare_images(self, img1, img2)
251
252    def setUp(self):
253        self.create_image(backing_img, TestMirrorNoBacking.image_len)
254        qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
255        self.vm = iotests.VM().add_drive(test_img)
256        self.vm.launch()
257
258    def tearDown(self):
259        self.vm.shutdown()
260        os.remove(test_img)
261        os.remove(backing_img)
262        os.remove(target_backing_img)
263        os.remove(target_img)
264
265    def test_complete(self):
266        self.assert_no_active_mirrors()
267
268        qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, target_img)
269        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
270                             mode='existing', target=target_img)
271        self.assert_qmp(result, 'return', {})
272
273        self.complete_and_wait()
274        result = self.vm.qmp('query-block')
275        self.assert_qmp(result, 'return[0]/inserted/file', target_img)
276        self.vm.shutdown()
277        self.assertTrue(self.compare_images(test_img, target_img),
278                        'target image does not match source after mirroring')
279
280    def test_cancel(self):
281        self.assert_no_active_mirrors()
282
283        qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, target_img)
284        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
285                             mode='existing', target=target_img)
286        self.assert_qmp(result, 'return', {})
287
288        self.cancel_and_wait()
289        result = self.vm.qmp('query-block')
290        self.assert_qmp(result, 'return[0]/inserted/file', test_img)
291        self.vm.shutdown()
292        self.assertTrue(self.compare_images(test_img, target_img),
293                        'target image does not match source after mirroring')
294
295class TestReadErrors(ImageMirroringTestCase):
296    image_len = 2 * 1024 * 1024 # MB
297
298    # this should be a multiple of twice the default granularity
299    # so that we hit this offset first in state 1
300    MIRROR_GRANULARITY = 1024 * 1024
301
302    def create_blkdebug_file(self, name, event, errno):
303        file = open(name, 'w')
304        file.write('''
305[inject-error]
306state = "1"
307event = "%s"
308errno = "%d"
309immediately = "off"
310once = "on"
311sector = "%d"
312
313[set-state]
314state = "1"
315event = "%s"
316new_state = "2"
317
318[set-state]
319state = "2"
320event = "%s"
321new_state = "1"
322''' % (event, errno, self.MIRROR_GRANULARITY / 512, event, event))
323        file.close()
324
325    def setUp(self):
326        self.blkdebug_file = backing_img + ".blkdebug"
327        self.create_image(backing_img, TestReadErrors.image_len)
328        self.create_blkdebug_file(self.blkdebug_file, "read_aio", 5)
329        qemu_img('create', '-f', iotests.imgfmt,
330                 '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw'
331                       % (self.blkdebug_file, backing_img),
332                 test_img)
333        self.vm = iotests.VM().add_drive(test_img)
334        self.vm.launch()
335
336    def tearDown(self):
337        self.vm.shutdown()
338        os.remove(test_img)
339        os.remove(backing_img)
340        os.remove(self.blkdebug_file)
341
342    def test_report_read(self):
343        self.assert_no_active_mirrors()
344
345        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
346                             target=target_img)
347        self.assert_qmp(result, 'return', {})
348
349        completed = False
350        error = False
351        while not completed:
352            for event in self.vm.get_qmp_events(wait=True):
353                if event['event'] == 'BLOCK_JOB_ERROR':
354                    self.assert_qmp(event, 'data/device', 'drive0')
355                    self.assert_qmp(event, 'data/operation', 'read')
356                    error = True
357                elif event['event'] == 'BLOCK_JOB_READY':
358                    self.assertTrue(False, 'job completed unexpectedly')
359                elif event['event'] == 'BLOCK_JOB_COMPLETED':
360                    self.assertTrue(error, 'job completed unexpectedly')
361                    self.assert_qmp(event, 'data/type', 'mirror')
362                    self.assert_qmp(event, 'data/device', 'drive0')
363                    self.assert_qmp(event, 'data/error', 'Input/output error')
364                    self.assert_qmp(event, 'data/len', self.image_len)
365                    completed = True
366
367        self.assert_no_active_mirrors()
368        self.vm.shutdown()
369
370    def test_ignore_read(self):
371        self.assert_no_active_mirrors()
372
373        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
374                             target=target_img, on_source_error='ignore')
375        self.assert_qmp(result, 'return', {})
376
377        event = self.vm.get_qmp_event(wait=True)
378        self.assertEquals(event['event'], 'BLOCK_JOB_ERROR')
379        self.assert_qmp(event, 'data/device', 'drive0')
380        self.assert_qmp(event, 'data/operation', 'read')
381        result = self.vm.qmp('query-block-jobs')
382        self.assert_qmp(result, 'return[0]/paused', False)
383        self.complete_and_wait()
384        self.vm.shutdown()
385
386    def test_stop_read(self):
387        self.assert_no_active_mirrors()
388
389        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
390                             target=target_img, on_source_error='stop')
391        self.assert_qmp(result, 'return', {})
392
393        error = False
394        ready = False
395        while not ready:
396            for event in self.vm.get_qmp_events(wait=True):
397                if event['event'] == 'BLOCK_JOB_ERROR':
398                    self.assert_qmp(event, 'data/device', 'drive0')
399                    self.assert_qmp(event, 'data/operation', 'read')
400
401                    result = self.vm.qmp('query-block-jobs')
402                    self.assert_qmp(result, 'return[0]/paused', True)
403                    self.assert_qmp(result, 'return[0]/io-status', 'failed')
404
405                    result = self.vm.qmp('block-job-resume', device='drive0')
406                    self.assert_qmp(result, 'return', {})
407                    error = True
408                elif event['event'] == 'BLOCK_JOB_READY':
409                    self.assertTrue(error, 'job completed unexpectedly')
410                    self.assert_qmp(event, 'data/device', 'drive0')
411                    ready = True
412
413        result = self.vm.qmp('query-block-jobs')
414        self.assert_qmp(result, 'return[0]/paused', False)
415        self.assert_qmp(result, 'return[0]/io-status', 'ok')
416
417        self.complete_and_wait(wait_ready=False)
418        self.assert_no_active_mirrors()
419        self.vm.shutdown()
420
421class TestWriteErrors(ImageMirroringTestCase):
422    image_len = 2 * 1024 * 1024 # MB
423
424    # this should be a multiple of twice the default granularity
425    # so that we hit this offset first in state 1
426    MIRROR_GRANULARITY = 1024 * 1024
427
428    def create_blkdebug_file(self, name, event, errno):
429        file = open(name, 'w')
430        file.write('''
431[inject-error]
432state = "1"
433event = "%s"
434errno = "%d"
435immediately = "off"
436once = "on"
437sector = "%d"
438
439[set-state]
440state = "1"
441event = "%s"
442new_state = "2"
443
444[set-state]
445state = "2"
446event = "%s"
447new_state = "1"
448''' % (event, errno, self.MIRROR_GRANULARITY / 512, event, event))
449        file.close()
450
451    def setUp(self):
452        self.blkdebug_file = target_img + ".blkdebug"
453        self.create_image(backing_img, TestWriteErrors.image_len)
454        self.create_blkdebug_file(self.blkdebug_file, "write_aio", 5)
455        qemu_img('create', '-f', iotests.imgfmt, '-obacking_file=%s' %(backing_img), test_img)
456        self.vm = iotests.VM().add_drive(test_img)
457        self.target_img = 'blkdebug:%s:%s' % (self.blkdebug_file, target_img)
458        qemu_img('create', '-f', iotests.imgfmt, '-osize=%d' %(TestWriteErrors.image_len), target_img)
459        self.vm.launch()
460
461    def tearDown(self):
462        self.vm.shutdown()
463        os.remove(test_img)
464        os.remove(backing_img)
465        os.remove(self.blkdebug_file)
466
467    def test_report_write(self):
468        self.assert_no_active_mirrors()
469
470        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
471                             mode='existing', target=self.target_img)
472        self.assert_qmp(result, 'return', {})
473
474        completed = False
475        error = False
476        while not completed:
477            for event in self.vm.get_qmp_events(wait=True):
478                if event['event'] == 'BLOCK_JOB_ERROR':
479                    self.assert_qmp(event, 'data/device', 'drive0')
480                    self.assert_qmp(event, 'data/operation', 'write')
481                    error = True
482                elif event['event'] == 'BLOCK_JOB_READY':
483                    self.assertTrue(False, 'job completed unexpectedly')
484                elif event['event'] == 'BLOCK_JOB_COMPLETED':
485                    self.assertTrue(error, 'job completed unexpectedly')
486                    self.assert_qmp(event, 'data/type', 'mirror')
487                    self.assert_qmp(event, 'data/device', 'drive0')
488                    self.assert_qmp(event, 'data/error', 'Input/output error')
489                    self.assert_qmp(event, 'data/len', self.image_len)
490                    completed = True
491
492        self.assert_no_active_mirrors()
493        self.vm.shutdown()
494
495    def test_ignore_write(self):
496        self.assert_no_active_mirrors()
497
498        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
499                             mode='existing', target=self.target_img,
500                             on_target_error='ignore')
501        self.assert_qmp(result, 'return', {})
502
503        event = self.vm.get_qmp_event(wait=True)
504        self.assertEquals(event['event'], 'BLOCK_JOB_ERROR')
505        self.assert_qmp(event, 'data/device', 'drive0')
506        self.assert_qmp(event, 'data/operation', 'write')
507        result = self.vm.qmp('query-block-jobs')
508        self.assert_qmp(result, 'return[0]/paused', False)
509        self.complete_and_wait()
510        self.vm.shutdown()
511
512    def test_stop_write(self):
513        self.assert_no_active_mirrors()
514
515        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
516                             mode='existing', target=self.target_img,
517                             on_target_error='stop')
518        self.assert_qmp(result, 'return', {})
519
520        error = False
521        ready = False
522        while not ready:
523            for event in self.vm.get_qmp_events(wait=True):
524                if event['event'] == 'BLOCK_JOB_ERROR':
525                    self.assert_qmp(event, 'data/device', 'drive0')
526                    self.assert_qmp(event, 'data/operation', 'write')
527
528                    result = self.vm.qmp('query-block-jobs')
529                    self.assert_qmp(result, 'return[0]/paused', True)
530                    self.assert_qmp(result, 'return[0]/io-status', 'failed')
531
532                    result = self.vm.qmp('block-job-resume', device='drive0')
533                    self.assert_qmp(result, 'return', {})
534
535                    result = self.vm.qmp('query-block-jobs')
536                    self.assert_qmp(result, 'return[0]/paused', False)
537                    self.assert_qmp(result, 'return[0]/io-status', 'ok')
538                    error = True
539                elif event['event'] == 'BLOCK_JOB_READY':
540                    self.assertTrue(error, 'job completed unexpectedly')
541                    self.assert_qmp(event, 'data/device', 'drive0')
542                    ready = True
543
544        self.complete_and_wait(wait_ready=False)
545        self.assert_no_active_mirrors()
546        self.vm.shutdown()
547
548class TestSetSpeed(ImageMirroringTestCase):
549    image_len = 80 * 1024 * 1024 # MB
550
551    def setUp(self):
552        qemu_img('create', backing_img, str(TestSetSpeed.image_len))
553        qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
554        self.vm = iotests.VM().add_drive(test_img)
555        self.vm.launch()
556
557    def tearDown(self):
558        self.vm.shutdown()
559        os.remove(test_img)
560        os.remove(backing_img)
561        os.remove(target_img)
562
563    def test_set_speed(self):
564        self.assert_no_active_mirrors()
565
566        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
567                             target=target_img)
568        self.assert_qmp(result, 'return', {})
569
570        # Default speed is 0
571        result = self.vm.qmp('query-block-jobs')
572        self.assert_qmp(result, 'return[0]/device', 'drive0')
573        self.assert_qmp(result, 'return[0]/speed', 0)
574
575        result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024)
576        self.assert_qmp(result, 'return', {})
577
578        # Ensure the speed we set was accepted
579        result = self.vm.qmp('query-block-jobs')
580        self.assert_qmp(result, 'return[0]/device', 'drive0')
581        self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024)
582
583        self.cancel_and_wait()
584
585        # Check setting speed in drive-mirror works
586        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
587                             target=target_img, speed=4*1024*1024)
588        self.assert_qmp(result, 'return', {})
589
590        result = self.vm.qmp('query-block-jobs')
591        self.assert_qmp(result, 'return[0]/device', 'drive0')
592        self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024)
593
594        self.cancel_and_wait()
595
596    def test_set_speed_invalid(self):
597        self.assert_no_active_mirrors()
598
599        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
600                             target=target_img, speed=-1)
601        self.assert_qmp(result, 'error/class', 'GenericError')
602
603        self.assert_no_active_mirrors()
604
605        result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
606                             target=target_img)
607        self.assert_qmp(result, 'return', {})
608
609        result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1)
610        self.assert_qmp(result, 'error/class', 'GenericError')
611
612        self.cancel_and_wait()
613
614if __name__ == '__main__':
615    iotests.main(supported_fmts=['qcow2', 'qed'])
616