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