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