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