xref: /openbmc/qemu/tests/qemu-iotests/030 (revision 2c9b15ca)
1#!/usr/bin/env python
2#
3# Tests for image streaming.
4#
5# Copyright (C) 2012 IBM Corp.
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')
27mid_img = os.path.join(iotests.test_dir, 'mid.img')
28test_img = os.path.join(iotests.test_dir, 'test.img')
29
30class TestSingleDrive(iotests.QMPTestCase):
31    image_len = 1 * 1024 * 1024 # MB
32
33    def setUp(self):
34        iotests.create_image(backing_img, TestSingleDrive.image_len)
35        qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, mid_img)
36        qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % mid_img, test_img)
37        self.vm = iotests.VM().add_drive(test_img)
38        self.vm.launch()
39
40    def tearDown(self):
41        self.vm.shutdown()
42        os.remove(test_img)
43        os.remove(mid_img)
44        os.remove(backing_img)
45
46    def test_stream(self):
47        self.assert_no_active_block_jobs()
48
49        result = self.vm.qmp('block-stream', device='drive0')
50        self.assert_qmp(result, 'return', {})
51
52        completed = False
53        while not completed:
54            for event in self.vm.get_qmp_events(wait=True):
55                if event['event'] == 'BLOCK_JOB_COMPLETED':
56                    self.assert_qmp(event, 'data/type', 'stream')
57                    self.assert_qmp(event, 'data/device', 'drive0')
58                    self.assert_qmp(event, 'data/offset', self.image_len)
59                    self.assert_qmp(event, 'data/len', self.image_len)
60                    completed = True
61
62        self.assert_no_active_block_jobs()
63        self.vm.shutdown()
64
65        self.assertEqual(qemu_io('-c', 'map', backing_img),
66                         qemu_io('-c', 'map', test_img),
67                         'image file map does not match backing file after streaming')
68
69    def test_stream_pause(self):
70        self.assert_no_active_block_jobs()
71
72        result = self.vm.qmp('block-stream', device='drive0')
73        self.assert_qmp(result, 'return', {})
74
75        result = self.vm.qmp('block-job-pause', device='drive0')
76        self.assert_qmp(result, 'return', {})
77
78        time.sleep(1)
79        result = self.vm.qmp('query-block-jobs')
80        offset = self.dictpath(result, 'return[0]/offset')
81
82        time.sleep(1)
83        result = self.vm.qmp('query-block-jobs')
84        self.assert_qmp(result, 'return[0]/offset', offset)
85
86        result = self.vm.qmp('block-job-resume', device='drive0')
87        self.assert_qmp(result, 'return', {})
88
89        completed = False
90        while not completed:
91            for event in self.vm.get_qmp_events(wait=True):
92                if event['event'] == 'BLOCK_JOB_COMPLETED':
93                    self.assert_qmp(event, 'data/type', 'stream')
94                    self.assert_qmp(event, 'data/device', 'drive0')
95                    self.assert_qmp(event, 'data/offset', self.image_len)
96                    self.assert_qmp(event, 'data/len', self.image_len)
97                    completed = True
98
99        self.assert_no_active_block_jobs()
100        self.vm.shutdown()
101
102        self.assertEqual(qemu_io('-c', 'map', backing_img),
103                         qemu_io('-c', 'map', test_img),
104                         'image file map does not match backing file after streaming')
105
106    def test_stream_partial(self):
107        self.assert_no_active_block_jobs()
108
109        result = self.vm.qmp('block-stream', device='drive0', base=mid_img)
110        self.assert_qmp(result, 'return', {})
111
112        completed = False
113        while not completed:
114            for event in self.vm.get_qmp_events(wait=True):
115                if event['event'] == 'BLOCK_JOB_COMPLETED':
116                    self.assert_qmp(event, 'data/type', 'stream')
117                    self.assert_qmp(event, 'data/device', 'drive0')
118                    self.assert_qmp(event, 'data/offset', self.image_len)
119                    self.assert_qmp(event, 'data/len', self.image_len)
120                    completed = True
121
122        self.assert_no_active_block_jobs()
123        self.vm.shutdown()
124
125        self.assertEqual(qemu_io('-c', 'map', mid_img),
126                         qemu_io('-c', 'map', test_img),
127                         'image file map does not match backing file after streaming')
128
129    def test_device_not_found(self):
130        result = self.vm.qmp('block-stream', device='nonexistent')
131        self.assert_qmp(result, 'error/class', 'DeviceNotFound')
132
133
134class TestSmallerBackingFile(iotests.QMPTestCase):
135    backing_len = 1 * 1024 * 1024 # MB
136    image_len = 2 * backing_len
137
138    def setUp(self):
139        iotests.create_image(backing_img, self.backing_len)
140        qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img, str(self.image_len))
141        self.vm = iotests.VM().add_drive(test_img)
142        self.vm.launch()
143
144    # If this hangs, then you are missing a fix to complete streaming when the
145    # end of the backing file is reached.
146    def test_stream(self):
147        self.assert_no_active_block_jobs()
148
149        result = self.vm.qmp('block-stream', device='drive0')
150        self.assert_qmp(result, 'return', {})
151
152        completed = False
153        while not completed:
154            for event in self.vm.get_qmp_events(wait=True):
155                if event['event'] == 'BLOCK_JOB_COMPLETED':
156                    self.assert_qmp(event, 'data/type', 'stream')
157                    self.assert_qmp(event, 'data/device', 'drive0')
158                    self.assert_qmp(event, 'data/offset', self.image_len)
159                    self.assert_qmp(event, 'data/len', self.image_len)
160                    completed = True
161
162        self.assert_no_active_block_jobs()
163        self.vm.shutdown()
164
165class TestErrors(iotests.QMPTestCase):
166    image_len = 2 * 1024 * 1024 # MB
167
168    # this should match STREAM_BUFFER_SIZE/512 in block/stream.c
169    STREAM_BUFFER_SIZE = 512 * 1024
170
171    def create_blkdebug_file(self, name, event, errno):
172        file = open(name, 'w')
173        file.write('''
174[inject-error]
175state = "1"
176event = "%s"
177errno = "%d"
178immediately = "off"
179once = "on"
180sector = "%d"
181
182[set-state]
183state = "1"
184event = "%s"
185new_state = "2"
186
187[set-state]
188state = "2"
189event = "%s"
190new_state = "1"
191''' % (event, errno, self.STREAM_BUFFER_SIZE / 512, event, event))
192        file.close()
193
194class TestEIO(TestErrors):
195    def setUp(self):
196        self.blkdebug_file = backing_img + ".blkdebug"
197        iotests.create_image(backing_img, TestErrors.image_len)
198        self.create_blkdebug_file(self.blkdebug_file, "read_aio", 5)
199        qemu_img('create', '-f', iotests.imgfmt,
200                 '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw'
201                       % (self.blkdebug_file, backing_img),
202                 test_img)
203        self.vm = iotests.VM().add_drive(test_img)
204        self.vm.launch()
205
206    def tearDown(self):
207        self.vm.shutdown()
208        os.remove(test_img)
209        os.remove(backing_img)
210        os.remove(self.blkdebug_file)
211
212    def test_report(self):
213        self.assert_no_active_block_jobs()
214
215        result = self.vm.qmp('block-stream', device='drive0')
216        self.assert_qmp(result, 'return', {})
217
218        completed = False
219        error = False
220        while not completed:
221            for event in self.vm.get_qmp_events(wait=True):
222                if event['event'] == 'BLOCK_JOB_ERROR':
223                    self.assert_qmp(event, 'data/device', 'drive0')
224                    self.assert_qmp(event, 'data/operation', 'read')
225                    error = True
226                elif event['event'] == 'BLOCK_JOB_COMPLETED':
227                    self.assertTrue(error, 'job completed unexpectedly')
228                    self.assert_qmp(event, 'data/type', 'stream')
229                    self.assert_qmp(event, 'data/device', 'drive0')
230                    self.assert_qmp(event, 'data/error', 'Input/output error')
231                    self.assert_qmp(event, 'data/offset', self.STREAM_BUFFER_SIZE)
232                    self.assert_qmp(event, 'data/len', self.image_len)
233                    completed = True
234
235        self.assert_no_active_block_jobs()
236        self.vm.shutdown()
237
238    def test_ignore(self):
239        self.assert_no_active_block_jobs()
240
241        result = self.vm.qmp('block-stream', device='drive0', on_error='ignore')
242        self.assert_qmp(result, 'return', {})
243
244        error = False
245        completed = False
246        while not completed:
247            for event in self.vm.get_qmp_events(wait=True):
248                if event['event'] == 'BLOCK_JOB_ERROR':
249                    self.assert_qmp(event, 'data/device', 'drive0')
250                    self.assert_qmp(event, 'data/operation', 'read')
251                    result = self.vm.qmp('query-block-jobs')
252                    self.assert_qmp(result, 'return[0]/paused', False)
253                    error = True
254                elif event['event'] == 'BLOCK_JOB_COMPLETED':
255                    self.assertTrue(error, 'job completed unexpectedly')
256                    self.assert_qmp(event, 'data/type', 'stream')
257                    self.assert_qmp(event, 'data/device', 'drive0')
258                    self.assert_qmp(event, 'data/error', 'Input/output error')
259                    self.assert_qmp(event, 'data/offset', self.image_len)
260                    self.assert_qmp(event, 'data/len', self.image_len)
261                    completed = True
262
263        self.assert_no_active_block_jobs()
264        self.vm.shutdown()
265
266    def test_stop(self):
267        self.assert_no_active_block_jobs()
268
269        result = self.vm.qmp('block-stream', device='drive0', on_error='stop')
270        self.assert_qmp(result, 'return', {})
271
272        error = False
273        completed = False
274        while not completed:
275            for event in self.vm.get_qmp_events(wait=True):
276                if event['event'] == 'BLOCK_JOB_ERROR':
277                    self.assert_qmp(event, 'data/device', 'drive0')
278                    self.assert_qmp(event, 'data/operation', 'read')
279
280                    result = self.vm.qmp('query-block-jobs')
281                    self.assert_qmp(result, 'return[0]/paused', True)
282                    self.assert_qmp(result, 'return[0]/offset', self.STREAM_BUFFER_SIZE)
283                    self.assert_qmp(result, 'return[0]/io-status', 'failed')
284
285                    result = self.vm.qmp('block-job-resume', device='drive0')
286                    self.assert_qmp(result, 'return', {})
287
288                    result = self.vm.qmp('query-block-jobs')
289                    self.assert_qmp(result, 'return[0]/paused', False)
290                    self.assert_qmp(result, 'return[0]/io-status', 'ok')
291                    error = True
292                elif event['event'] == 'BLOCK_JOB_COMPLETED':
293                    self.assertTrue(error, 'job completed unexpectedly')
294                    self.assert_qmp(event, 'data/type', 'stream')
295                    self.assert_qmp(event, 'data/device', 'drive0')
296                    self.assert_qmp_absent(event, 'data/error')
297                    self.assert_qmp(event, 'data/offset', self.image_len)
298                    self.assert_qmp(event, 'data/len', self.image_len)
299                    completed = True
300
301        self.assert_no_active_block_jobs()
302        self.vm.shutdown()
303
304    def test_enospc(self):
305        self.assert_no_active_block_jobs()
306
307        result = self.vm.qmp('block-stream', device='drive0', on_error='enospc')
308        self.assert_qmp(result, 'return', {})
309
310        completed = False
311        error = False
312        while not completed:
313            for event in self.vm.get_qmp_events(wait=True):
314                if event['event'] == 'BLOCK_JOB_ERROR':
315                    self.assert_qmp(event, 'data/device', 'drive0')
316                    self.assert_qmp(event, 'data/operation', 'read')
317                    error = True
318                elif event['event'] == 'BLOCK_JOB_COMPLETED':
319                    self.assertTrue(error, 'job completed unexpectedly')
320                    self.assert_qmp(event, 'data/type', 'stream')
321                    self.assert_qmp(event, 'data/device', 'drive0')
322                    self.assert_qmp(event, 'data/error', 'Input/output error')
323                    self.assert_qmp(event, 'data/offset', self.STREAM_BUFFER_SIZE)
324                    self.assert_qmp(event, 'data/len', self.image_len)
325                    completed = True
326
327        self.assert_no_active_block_jobs()
328        self.vm.shutdown()
329
330class TestENOSPC(TestErrors):
331    def setUp(self):
332        self.blkdebug_file = backing_img + ".blkdebug"
333        iotests.create_image(backing_img, TestErrors.image_len)
334        self.create_blkdebug_file(self.blkdebug_file, "read_aio", 28)
335        qemu_img('create', '-f', iotests.imgfmt,
336                 '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw'
337                       % (self.blkdebug_file, backing_img),
338                 test_img)
339        self.vm = iotests.VM().add_drive(test_img)
340        self.vm.launch()
341
342    def tearDown(self):
343        self.vm.shutdown()
344        os.remove(test_img)
345        os.remove(backing_img)
346        os.remove(self.blkdebug_file)
347
348    def test_enospc(self):
349        self.assert_no_active_block_jobs()
350
351        result = self.vm.qmp('block-stream', device='drive0', on_error='enospc')
352        self.assert_qmp(result, 'return', {})
353
354        error = False
355        completed = False
356        while not completed:
357            for event in self.vm.get_qmp_events(wait=True):
358                if event['event'] == 'BLOCK_JOB_ERROR':
359                    self.assert_qmp(event, 'data/device', 'drive0')
360                    self.assert_qmp(event, 'data/operation', 'read')
361
362                    result = self.vm.qmp('query-block-jobs')
363                    self.assert_qmp(result, 'return[0]/paused', True)
364                    self.assert_qmp(result, 'return[0]/offset', self.STREAM_BUFFER_SIZE)
365                    self.assert_qmp(result, 'return[0]/io-status', 'nospace')
366
367                    result = self.vm.qmp('block-job-resume', device='drive0')
368                    self.assert_qmp(result, 'return', {})
369
370                    result = self.vm.qmp('query-block-jobs')
371                    self.assert_qmp(result, 'return[0]/paused', False)
372                    self.assert_qmp(result, 'return[0]/io-status', 'ok')
373                    error = True
374                elif event['event'] == 'BLOCK_JOB_COMPLETED':
375                    self.assertTrue(error, 'job completed unexpectedly')
376                    self.assert_qmp(event, 'data/type', 'stream')
377                    self.assert_qmp(event, 'data/device', 'drive0')
378                    self.assert_qmp_absent(event, 'data/error')
379                    self.assert_qmp(event, 'data/offset', self.image_len)
380                    self.assert_qmp(event, 'data/len', self.image_len)
381                    completed = True
382
383        self.assert_no_active_block_jobs()
384        self.vm.shutdown()
385
386class TestStreamStop(iotests.QMPTestCase):
387    image_len = 8 * 1024 * 1024 * 1024 # GB
388
389    def setUp(self):
390        qemu_img('create', backing_img, str(TestStreamStop.image_len))
391        qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
392        self.vm = iotests.VM().add_drive(test_img)
393        self.vm.launch()
394
395    def tearDown(self):
396        self.vm.shutdown()
397        os.remove(test_img)
398        os.remove(backing_img)
399
400    def test_stream_stop(self):
401        self.assert_no_active_block_jobs()
402
403        result = self.vm.qmp('block-stream', device='drive0')
404        self.assert_qmp(result, 'return', {})
405
406        time.sleep(0.1)
407        events = self.vm.get_qmp_events(wait=False)
408        self.assertEqual(events, [], 'unexpected QMP event: %s' % events)
409
410        self.cancel_and_wait()
411
412class TestSetSpeed(iotests.QMPTestCase):
413    image_len = 80 * 1024 * 1024 # MB
414
415    def setUp(self):
416        qemu_img('create', backing_img, str(TestSetSpeed.image_len))
417        qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
418        self.vm = iotests.VM().add_drive(test_img)
419        self.vm.launch()
420
421    def tearDown(self):
422        self.vm.shutdown()
423        os.remove(test_img)
424        os.remove(backing_img)
425
426    # This is a short performance test which is not run by default.
427    # Invoke "IMGFMT=qed ./030 TestSetSpeed.perf_test_throughput"
428    def perf_test_throughput(self):
429        self.assert_no_active_block_jobs()
430
431        result = self.vm.qmp('block-stream', device='drive0')
432        self.assert_qmp(result, 'return', {})
433
434        result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024)
435        self.assert_qmp(result, 'return', {})
436
437        completed = False
438        while not completed:
439            for event in self.vm.get_qmp_events(wait=True):
440                if event['event'] == 'BLOCK_JOB_COMPLETED':
441                    self.assert_qmp(event, 'data/type', 'stream')
442                    self.assert_qmp(event, 'data/device', 'drive0')
443                    self.assert_qmp(event, 'data/offset', self.image_len)
444                    self.assert_qmp(event, 'data/len', self.image_len)
445                    completed = True
446
447        self.assert_no_active_block_jobs()
448
449    def test_set_speed(self):
450        self.assert_no_active_block_jobs()
451
452        result = self.vm.qmp('block-stream', device='drive0')
453        self.assert_qmp(result, 'return', {})
454
455        # Default speed is 0
456        result = self.vm.qmp('query-block-jobs')
457        self.assert_qmp(result, 'return[0]/device', 'drive0')
458        self.assert_qmp(result, 'return[0]/speed', 0)
459
460        result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024)
461        self.assert_qmp(result, 'return', {})
462
463        # Ensure the speed we set was accepted
464        result = self.vm.qmp('query-block-jobs')
465        self.assert_qmp(result, 'return[0]/device', 'drive0')
466        self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024)
467
468        self.cancel_and_wait()
469
470        # Check setting speed in block-stream works
471        result = self.vm.qmp('block-stream', device='drive0', speed=4 * 1024 * 1024)
472        self.assert_qmp(result, 'return', {})
473
474        result = self.vm.qmp('query-block-jobs')
475        self.assert_qmp(result, 'return[0]/device', 'drive0')
476        self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024)
477
478        self.cancel_and_wait()
479
480    def test_set_speed_invalid(self):
481        self.assert_no_active_block_jobs()
482
483        result = self.vm.qmp('block-stream', device='drive0', speed=-1)
484        self.assert_qmp(result, 'error/class', 'GenericError')
485
486        self.assert_no_active_block_jobs()
487
488        result = self.vm.qmp('block-stream', device='drive0')
489        self.assert_qmp(result, 'return', {})
490
491        result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1)
492        self.assert_qmp(result, 'error/class', 'GenericError')
493
494        self.cancel_and_wait()
495
496if __name__ == '__main__':
497    iotests.main(supported_fmts=['qcow2', 'qed'])
498