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