3 # Tests for image streaming.
5 # Copyright (C) 2012 IBM Corp.
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.
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.
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/>.
24 from iotests
import qemu_img
, qemu_io
27 backing_img
= os
.path
.join(iotests
.test_dir
, 'backing.img')
28 mid_img
= os
.path
.join(iotests
.test_dir
, 'mid.img')
29 test_img
= os
.path
.join(iotests
.test_dir
, 'test.img')
31 class ImageStreamingTestCase(iotests
.QMPTestCase
):
32 '''Abstract base class for image streaming test cases'''
34 def assert_no_active_streams(self
):
35 result
= self
.vm
.qmp('query-block-jobs')
36 self
.assert_qmp(result
, 'return', [])
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', {})
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
)
51 self
.assert_no_active_streams()
53 def create_image(self
, name
, size
):
54 file = open(name
, 'w')
57 sector
= struct
.pack('>l504xl', i
/ 512, i
/ 512)
63 class TestSingleDrive(ImageStreamingTestCase
):
64 image_len
= 1 * 1024 * 1024 # MB
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
)
77 os
.remove(backing_img
)
79 def test_stream(self
):
80 self
.assert_no_active_streams()
82 result
= self
.vm
.qmp('block-stream', device
='drive0')
83 self
.assert_qmp(result
, 'return', {})
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
)
95 self
.assert_no_active_streams()
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')
102 def test_stream_pause(self
):
103 self
.assert_no_active_streams()
105 result
= self
.vm
.qmp('block-stream', device
='drive0')
106 self
.assert_qmp(result
, 'return', {})
108 result
= self
.vm
.qmp('block-job-pause', device
='drive0')
109 self
.assert_qmp(result
, 'return', {})
112 result
= self
.vm
.qmp('query-block-jobs')
113 offset
= self
.dictpath(result
, 'return[0]/offset')
116 result
= self
.vm
.qmp('query-block-jobs')
117 self
.assert_qmp(result
, 'return[0]/offset', offset
)
119 result
= self
.vm
.qmp('block-job-resume', device
='drive0')
120 self
.assert_qmp(result
, 'return', {})
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
)
132 self
.assert_no_active_streams()
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')
139 def test_stream_partial(self
):
140 self
.assert_no_active_streams()
142 result
= self
.vm
.qmp('block-stream', device
='drive0', base
=mid_img
)
143 self
.assert_qmp(result
, 'return', {})
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
)
155 self
.assert_no_active_streams()
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')
162 def test_device_not_found(self
):
163 result
= self
.vm
.qmp('block-stream', device
='nonexistent')
164 self
.assert_qmp(result
, 'error/class', 'DeviceNotFound')
167 class TestSmallerBackingFile(ImageStreamingTestCase
):
168 backing_len
= 1 * 1024 * 1024 # MB
169 image_len
= 2 * backing_len
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
)
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()
182 result
= self
.vm
.qmp('block-stream', device
='drive0')
183 self
.assert_qmp(result
, 'return', {})
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
)
195 self
.assert_no_active_streams()
198 class TestErrors(ImageStreamingTestCase
):
199 image_len
= 2 * 1024 * 1024 # MB
201 # this should match STREAM_BUFFER_SIZE/512 in block/stream.c
202 STREAM_BUFFER_SIZE
= 512 * 1024
204 def create_blkdebug_file(self
, name
, event
, errno
):
205 file = open(name
, 'w')
224 ''' % (event
, errno
, self
.STREAM_BUFFER_SIZE
/ 512, event
, event
))
227 class TestEIO(TestErrors
):
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
),
236 self
.vm
= iotests
.VM().add_drive(test_img
)
242 os
.remove(backing_img
)
243 os
.remove(self
.blkdebug_file
)
245 def test_report(self
):
246 self
.assert_no_active_streams()
248 result
= self
.vm
.qmp('block-stream', device
='drive0')
249 self
.assert_qmp(result
, 'return', {})
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')
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
)
268 self
.assert_no_active_streams()
271 def test_ignore(self
):
272 self
.assert_no_active_streams()
274 result
= self
.vm
.qmp('block-stream', device
='drive0', on_error
='ignore')
275 self
.assert_qmp(result
, 'return', {})
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)
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
)
296 self
.assert_no_active_streams()
300 self
.assert_no_active_streams()
302 result
= self
.vm
.qmp('block-stream', device
='drive0', on_error
='stop')
303 self
.assert_qmp(result
, 'return', {})
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')
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')
318 result
= self
.vm
.qmp('block-job-resume', device
='drive0')
319 self
.assert_qmp(result
, 'return', {})
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')
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
)
334 self
.assert_no_active_streams()
337 def test_enospc(self
):
338 self
.assert_no_active_streams()
340 result
= self
.vm
.qmp('block-stream', device
='drive0', on_error
='enospc')
341 self
.assert_qmp(result
, 'return', {})
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')
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
)
360 self
.assert_no_active_streams()
363 class TestENOSPC(TestErrors
):
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
),
372 self
.vm
= iotests
.VM().add_drive(test_img
)
378 os
.remove(backing_img
)
379 os
.remove(self
.blkdebug_file
)
381 def test_enospc(self
):
382 self
.assert_no_active_streams()
384 result
= self
.vm
.qmp('block-stream', device
='drive0', on_error
='enospc')
385 self
.assert_qmp(result
, 'return', {})
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')
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')
400 result
= self
.vm
.qmp('block-job-resume', device
='drive0')
401 self
.assert_qmp(result
, 'return', {})
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')
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
)
416 self
.assert_no_active_streams()
419 class TestStreamStop(ImageStreamingTestCase
):
420 image_len
= 8 * 1024 * 1024 * 1024 # GB
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
)
431 os
.remove(backing_img
)
433 def test_stream_stop(self
):
434 self
.assert_no_active_streams()
436 result
= self
.vm
.qmp('block-stream', device
='drive0')
437 self
.assert_qmp(result
, 'return', {})
440 events
= self
.vm
.get_qmp_events(wait
=False)
441 self
.assertEqual(events
, [], 'unexpected QMP event: %s' % events
)
443 self
.cancel_and_wait()
445 class TestSetSpeed(ImageStreamingTestCase
):
446 image_len
= 80 * 1024 * 1024 # MB
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
)
457 os
.remove(backing_img
)
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()
464 result
= self
.vm
.qmp('block-stream', device
='drive0')
465 self
.assert_qmp(result
, 'return', {})
467 result
= self
.vm
.qmp('block-job-set-speed', device
='drive0', speed
=8 * 1024 * 1024)
468 self
.assert_qmp(result
, 'return', {})
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
)
480 self
.assert_no_active_streams()
482 def test_set_speed(self
):
483 self
.assert_no_active_streams()
485 result
= self
.vm
.qmp('block-stream', device
='drive0')
486 self
.assert_qmp(result
, 'return', {})
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)
493 result
= self
.vm
.qmp('block-job-set-speed', device
='drive0', speed
=8 * 1024 * 1024)
494 self
.assert_qmp(result
, 'return', {})
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)
501 self
.cancel_and_wait()
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', {})
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)
511 self
.cancel_and_wait()
513 def test_set_speed_invalid(self
):
514 self
.assert_no_active_streams()
516 result
= self
.vm
.qmp('block-stream', device
='drive0', speed
=-1)
517 self
.assert_qmp(result
, 'error/class', 'GenericError')
519 self
.assert_no_active_streams()
521 result
= self
.vm
.qmp('block-stream', device
='drive0')
522 self
.assert_qmp(result
, 'return', {})
524 result
= self
.vm
.qmp('block-job-set-speed', device
='drive0', speed
=-1)
525 self
.assert_qmp(result
, 'error/class', 'GenericError')
527 self
.cancel_and_wait()
529 if __name__
== '__main__':
530 iotests
.main(supported_fmts
=['qcow2', 'qed'])