xref: /openbmc/qemu/tests/qemu-iotests/030 (revision 11938d78)
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_io('-c', 'write -P 0x1 0 32M', backing_img)
392        qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
393        qemu_io('-c', 'write -P 0x1 32M 32M', test_img)
394        self.vm = iotests.VM().add_drive(test_img)
395        self.vm.launch()
396
397    def tearDown(self):
398        self.vm.shutdown()
399        os.remove(test_img)
400        os.remove(backing_img)
401
402    def test_stream_stop(self):
403        self.assert_no_active_block_jobs()
404
405        result = self.vm.qmp('block-stream', device='drive0')
406        self.assert_qmp(result, 'return', {})
407
408        time.sleep(0.1)
409        events = self.vm.get_qmp_events(wait=False)
410        self.assertEqual(events, [], 'unexpected QMP event: %s' % events)
411
412        self.cancel_and_wait()
413
414class TestSetSpeed(iotests.QMPTestCase):
415    image_len = 80 * 1024 * 1024 # MB
416
417    def setUp(self):
418        qemu_img('create', backing_img, str(TestSetSpeed.image_len))
419        qemu_io('-c', 'write -P 0x1 0 32M', backing_img)
420        qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
421        qemu_io('-c', 'write -P 0x1 32M 32M', test_img)
422        self.vm = iotests.VM().add_drive(test_img)
423        self.vm.launch()
424
425    def tearDown(self):
426        self.vm.shutdown()
427        os.remove(test_img)
428        os.remove(backing_img)
429
430    # This is a short performance test which is not run by default.
431    # Invoke "IMGFMT=qed ./030 TestSetSpeed.perf_test_throughput"
432    def perf_test_throughput(self):
433        self.assert_no_active_block_jobs()
434
435        result = self.vm.qmp('block-stream', device='drive0')
436        self.assert_qmp(result, 'return', {})
437
438        result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024)
439        self.assert_qmp(result, 'return', {})
440
441        completed = False
442        while not completed:
443            for event in self.vm.get_qmp_events(wait=True):
444                if event['event'] == 'BLOCK_JOB_COMPLETED':
445                    self.assert_qmp(event, 'data/type', 'stream')
446                    self.assert_qmp(event, 'data/device', 'drive0')
447                    self.assert_qmp(event, 'data/offset', self.image_len)
448                    self.assert_qmp(event, 'data/len', self.image_len)
449                    completed = True
450
451        self.assert_no_active_block_jobs()
452
453    def test_set_speed(self):
454        self.assert_no_active_block_jobs()
455
456        result = self.vm.qmp('block-stream', device='drive0')
457        self.assert_qmp(result, 'return', {})
458
459        # Default speed is 0
460        result = self.vm.qmp('query-block-jobs')
461        self.assert_qmp(result, 'return[0]/device', 'drive0')
462        self.assert_qmp(result, 'return[0]/speed', 0)
463
464        result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024)
465        self.assert_qmp(result, 'return', {})
466
467        # Ensure the speed we set was accepted
468        result = self.vm.qmp('query-block-jobs')
469        self.assert_qmp(result, 'return[0]/device', 'drive0')
470        self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024)
471
472        self.cancel_and_wait()
473
474        # Check setting speed in block-stream works
475        result = self.vm.qmp('block-stream', device='drive0', speed=4 * 1024 * 1024)
476        self.assert_qmp(result, 'return', {})
477
478        result = self.vm.qmp('query-block-jobs')
479        self.assert_qmp(result, 'return[0]/device', 'drive0')
480        self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024)
481
482        self.cancel_and_wait()
483
484    def test_set_speed_invalid(self):
485        self.assert_no_active_block_jobs()
486
487        result = self.vm.qmp('block-stream', device='drive0', speed=-1)
488        self.assert_qmp(result, 'error/class', 'GenericError')
489
490        self.assert_no_active_block_jobs()
491
492        result = self.vm.qmp('block-stream', device='drive0')
493        self.assert_qmp(result, 'return', {})
494
495        result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1)
496        self.assert_qmp(result, 'error/class', 'GenericError')
497
498        self.cancel_and_wait()
499
500if __name__ == '__main__':
501    iotests.main(supported_fmts=['qcow2', 'qed'])
502