Statistics
| Branch: | Revision:

root / tests / qemu-iotests / 030 @ ecc1c88e

History | View | Annotate | Download (20 kB)

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

    
21
import time
22
import os
23
import iotests
24
from iotests import qemu_img, qemu_io
25
import struct
26

    
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')
30

    
31
class ImageStreamingTestCase(iotests.QMPTestCase):
32
    '''Abstract base class for image streaming test cases'''
33

    
34
    def cancel_and_wait(self, drive='drive0'):
35
        '''Cancel a block job and wait for it to finish'''
36
        result = self.vm.qmp('block-job-cancel', device=drive)
37
        self.assert_qmp(result, 'return', {})
38

    
39
        cancelled = False
40
        while not cancelled:
41
            for event in self.vm.get_qmp_events(wait=True):
42
                if event['event'] == 'BLOCK_JOB_CANCELLED':
43
                    self.assert_qmp(event, 'data/type', 'stream')
44
                    self.assert_qmp(event, 'data/device', drive)
45
                    cancelled = True
46

    
47
        self.assert_no_active_block_jobs()
48

    
49
    def create_image(self, name, size):
50
        file = open(name, 'w')
51
        i = 0
52
        while i < size:
53
            sector = struct.pack('>l504xl', i / 512, i / 512)
54
            file.write(sector)
55
            i = i + 512
56
        file.close()
57

    
58

    
59
class TestSingleDrive(ImageStreamingTestCase):
60
    image_len = 1 * 1024 * 1024 # MB
61

    
62
    def setUp(self):
63
        self.create_image(backing_img, TestSingleDrive.image_len)
64
        qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, mid_img)
65
        qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % mid_img, test_img)
66
        self.vm = iotests.VM().add_drive(test_img)
67
        self.vm.launch()
68

    
69
    def tearDown(self):
70
        self.vm.shutdown()
71
        os.remove(test_img)
72
        os.remove(mid_img)
73
        os.remove(backing_img)
74

    
75
    def test_stream(self):
76
        self.assert_no_active_block_jobs()
77

    
78
        result = self.vm.qmp('block-stream', device='drive0')
79
        self.assert_qmp(result, 'return', {})
80

    
81
        completed = False
82
        while not completed:
83
            for event in self.vm.get_qmp_events(wait=True):
84
                if event['event'] == 'BLOCK_JOB_COMPLETED':
85
                    self.assert_qmp(event, 'data/type', 'stream')
86
                    self.assert_qmp(event, 'data/device', 'drive0')
87
                    self.assert_qmp(event, 'data/offset', self.image_len)
88
                    self.assert_qmp(event, 'data/len', self.image_len)
89
                    completed = True
90

    
91
        self.assert_no_active_block_jobs()
92
        self.vm.shutdown()
93

    
94
        self.assertEqual(qemu_io('-c', 'map', backing_img),
95
                         qemu_io('-c', 'map', test_img),
96
                         'image file map does not match backing file after streaming')
97

    
98
    def test_stream_pause(self):
99
        self.assert_no_active_block_jobs()
100

    
101
        result = self.vm.qmp('block-stream', device='drive0')
102
        self.assert_qmp(result, 'return', {})
103

    
104
        result = self.vm.qmp('block-job-pause', device='drive0')
105
        self.assert_qmp(result, 'return', {})
106

    
107
        time.sleep(1)
108
        result = self.vm.qmp('query-block-jobs')
109
        offset = self.dictpath(result, 'return[0]/offset')
110

    
111
        time.sleep(1)
112
        result = self.vm.qmp('query-block-jobs')
113
        self.assert_qmp(result, 'return[0]/offset', offset)
114

    
115
        result = self.vm.qmp('block-job-resume', device='drive0')
116
        self.assert_qmp(result, 'return', {})
117

    
118
        completed = False
119
        while not completed:
120
            for event in self.vm.get_qmp_events(wait=True):
121
                if event['event'] == 'BLOCK_JOB_COMPLETED':
122
                    self.assert_qmp(event, 'data/type', 'stream')
123
                    self.assert_qmp(event, 'data/device', 'drive0')
124
                    self.assert_qmp(event, 'data/offset', self.image_len)
125
                    self.assert_qmp(event, 'data/len', self.image_len)
126
                    completed = True
127

    
128
        self.assert_no_active_block_jobs()
129
        self.vm.shutdown()
130

    
131
        self.assertEqual(qemu_io('-c', 'map', backing_img),
132
                         qemu_io('-c', 'map', test_img),
133
                         'image file map does not match backing file after streaming')
134

    
135
    def test_stream_partial(self):
136
        self.assert_no_active_block_jobs()
137

    
138
        result = self.vm.qmp('block-stream', device='drive0', base=mid_img)
139
        self.assert_qmp(result, 'return', {})
140

    
141
        completed = False
142
        while not completed:
143
            for event in self.vm.get_qmp_events(wait=True):
144
                if event['event'] == 'BLOCK_JOB_COMPLETED':
145
                    self.assert_qmp(event, 'data/type', 'stream')
146
                    self.assert_qmp(event, 'data/device', 'drive0')
147
                    self.assert_qmp(event, 'data/offset', self.image_len)
148
                    self.assert_qmp(event, 'data/len', self.image_len)
149
                    completed = True
150

    
151
        self.assert_no_active_block_jobs()
152
        self.vm.shutdown()
153

    
154
        self.assertEqual(qemu_io('-c', 'map', mid_img),
155
                         qemu_io('-c', 'map', test_img),
156
                         'image file map does not match backing file after streaming')
157

    
158
    def test_device_not_found(self):
159
        result = self.vm.qmp('block-stream', device='nonexistent')
160
        self.assert_qmp(result, 'error/class', 'DeviceNotFound')
161

    
162

    
163
class TestSmallerBackingFile(ImageStreamingTestCase):
164
    backing_len = 1 * 1024 * 1024 # MB
165
    image_len = 2 * backing_len
166

    
167
    def setUp(self):
168
        self.create_image(backing_img, self.backing_len)
169
        qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img, str(self.image_len))
170
        self.vm = iotests.VM().add_drive(test_img)
171
        self.vm.launch()
172

    
173
    # If this hangs, then you are missing a fix to complete streaming when the
174
    # end of the backing file is reached.
175
    def test_stream(self):
176
        self.assert_no_active_block_jobs()
177

    
178
        result = self.vm.qmp('block-stream', device='drive0')
179
        self.assert_qmp(result, 'return', {})
180

    
181
        completed = False
182
        while not completed:
183
            for event in self.vm.get_qmp_events(wait=True):
184
                if event['event'] == 'BLOCK_JOB_COMPLETED':
185
                    self.assert_qmp(event, 'data/type', 'stream')
186
                    self.assert_qmp(event, 'data/device', 'drive0')
187
                    self.assert_qmp(event, 'data/offset', self.image_len)
188
                    self.assert_qmp(event, 'data/len', self.image_len)
189
                    completed = True
190

    
191
        self.assert_no_active_block_jobs()
192
        self.vm.shutdown()
193

    
194
class TestErrors(ImageStreamingTestCase):
195
    image_len = 2 * 1024 * 1024 # MB
196

    
197
    # this should match STREAM_BUFFER_SIZE/512 in block/stream.c
198
    STREAM_BUFFER_SIZE = 512 * 1024
199

    
200
    def create_blkdebug_file(self, name, event, errno):
201
        file = open(name, 'w')
202
        file.write('''
203
[inject-error]
204
state = "1"
205
event = "%s"
206
errno = "%d"
207
immediately = "off"
208
once = "on"
209
sector = "%d"
210

    
211
[set-state]
212
state = "1"
213
event = "%s"
214
new_state = "2"
215

    
216
[set-state]
217
state = "2"
218
event = "%s"
219
new_state = "1"
220
''' % (event, errno, self.STREAM_BUFFER_SIZE / 512, event, event))
221
        file.close()
222

    
223
class TestEIO(TestErrors):
224
    def setUp(self):
225
        self.blkdebug_file = backing_img + ".blkdebug"
226
        self.create_image(backing_img, TestErrors.image_len)
227
        self.create_blkdebug_file(self.blkdebug_file, "read_aio", 5)
228
        qemu_img('create', '-f', iotests.imgfmt,
229
                 '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw'
230
                       % (self.blkdebug_file, backing_img),
231
                 test_img)
232
        self.vm = iotests.VM().add_drive(test_img)
233
        self.vm.launch()
234

    
235
    def tearDown(self):
236
        self.vm.shutdown()
237
        os.remove(test_img)
238
        os.remove(backing_img)
239
        os.remove(self.blkdebug_file)
240

    
241
    def test_report(self):
242
        self.assert_no_active_block_jobs()
243

    
244
        result = self.vm.qmp('block-stream', device='drive0')
245
        self.assert_qmp(result, 'return', {})
246

    
247
        completed = False
248
        error = 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
                    error = True
255
                elif event['event'] == 'BLOCK_JOB_COMPLETED':
256
                    self.assertTrue(error, 'job completed unexpectedly')
257
                    self.assert_qmp(event, 'data/type', 'stream')
258
                    self.assert_qmp(event, 'data/device', 'drive0')
259
                    self.assert_qmp(event, 'data/error', 'Input/output error')
260
                    self.assert_qmp(event, 'data/offset', self.STREAM_BUFFER_SIZE)
261
                    self.assert_qmp(event, 'data/len', self.image_len)
262
                    completed = True
263

    
264
        self.assert_no_active_block_jobs()
265
        self.vm.shutdown()
266

    
267
    def test_ignore(self):
268
        self.assert_no_active_block_jobs()
269

    
270
        result = self.vm.qmp('block-stream', device='drive0', on_error='ignore')
271
        self.assert_qmp(result, 'return', {})
272

    
273
        error = False
274
        completed = False
275
        while not completed:
276
            for event in self.vm.get_qmp_events(wait=True):
277
                if event['event'] == 'BLOCK_JOB_ERROR':
278
                    self.assert_qmp(event, 'data/device', 'drive0')
279
                    self.assert_qmp(event, 'data/operation', 'read')
280
                    result = self.vm.qmp('query-block-jobs')
281
                    self.assert_qmp(result, 'return[0]/paused', False)
282
                    error = True
283
                elif event['event'] == 'BLOCK_JOB_COMPLETED':
284
                    self.assertTrue(error, 'job completed unexpectedly')
285
                    self.assert_qmp(event, 'data/type', 'stream')
286
                    self.assert_qmp(event, 'data/device', 'drive0')
287
                    self.assert_qmp(event, 'data/error', 'Input/output error')
288
                    self.assert_qmp(event, 'data/offset', self.image_len)
289
                    self.assert_qmp(event, 'data/len', self.image_len)
290
                    completed = True
291

    
292
        self.assert_no_active_block_jobs()
293
        self.vm.shutdown()
294

    
295
    def test_stop(self):
296
        self.assert_no_active_block_jobs()
297

    
298
        result = self.vm.qmp('block-stream', device='drive0', on_error='stop')
299
        self.assert_qmp(result, 'return', {})
300

    
301
        error = False
302
        completed = False
303
        while not completed:
304
            for event in self.vm.get_qmp_events(wait=True):
305
                if event['event'] == 'BLOCK_JOB_ERROR':
306
                    self.assert_qmp(event, 'data/device', 'drive0')
307
                    self.assert_qmp(event, 'data/operation', 'read')
308

    
309
                    result = self.vm.qmp('query-block-jobs')
310
                    self.assert_qmp(result, 'return[0]/paused', True)
311
                    self.assert_qmp(result, 'return[0]/offset', self.STREAM_BUFFER_SIZE)
312
                    self.assert_qmp(result, 'return[0]/io-status', 'failed')
313

    
314
                    result = self.vm.qmp('block-job-resume', device='drive0')
315
                    self.assert_qmp(result, 'return', {})
316

    
317
                    result = self.vm.qmp('query-block-jobs')
318
                    self.assert_qmp(result, 'return[0]/paused', False)
319
                    self.assert_qmp(result, 'return[0]/io-status', 'ok')
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_absent(event, 'data/error')
326
                    self.assert_qmp(event, 'data/offset', self.image_len)
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

    
333
    def test_enospc(self):
334
        self.assert_no_active_block_jobs()
335

    
336
        result = self.vm.qmp('block-stream', device='drive0', on_error='enospc')
337
        self.assert_qmp(result, 'return', {})
338

    
339
        completed = False
340
        error = False
341
        while not completed:
342
            for event in self.vm.get_qmp_events(wait=True):
343
                if event['event'] == 'BLOCK_JOB_ERROR':
344
                    self.assert_qmp(event, 'data/device', 'drive0')
345
                    self.assert_qmp(event, 'data/operation', 'read')
346
                    error = True
347
                elif event['event'] == 'BLOCK_JOB_COMPLETED':
348
                    self.assertTrue(error, 'job completed unexpectedly')
349
                    self.assert_qmp(event, 'data/type', 'stream')
350
                    self.assert_qmp(event, 'data/device', 'drive0')
351
                    self.assert_qmp(event, 'data/error', 'Input/output error')
352
                    self.assert_qmp(event, 'data/offset', self.STREAM_BUFFER_SIZE)
353
                    self.assert_qmp(event, 'data/len', self.image_len)
354
                    completed = True
355

    
356
        self.assert_no_active_block_jobs()
357
        self.vm.shutdown()
358

    
359
class TestENOSPC(TestErrors):
360
    def setUp(self):
361
        self.blkdebug_file = backing_img + ".blkdebug"
362
        self.create_image(backing_img, TestErrors.image_len)
363
        self.create_blkdebug_file(self.blkdebug_file, "read_aio", 28)
364
        qemu_img('create', '-f', iotests.imgfmt,
365
                 '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw'
366
                       % (self.blkdebug_file, backing_img),
367
                 test_img)
368
        self.vm = iotests.VM().add_drive(test_img)
369
        self.vm.launch()
370

    
371
    def tearDown(self):
372
        self.vm.shutdown()
373
        os.remove(test_img)
374
        os.remove(backing_img)
375
        os.remove(self.blkdebug_file)
376

    
377
    def test_enospc(self):
378
        self.assert_no_active_block_jobs()
379

    
380
        result = self.vm.qmp('block-stream', device='drive0', on_error='enospc')
381
        self.assert_qmp(result, 'return', {})
382

    
383
        error = False
384
        completed = False
385
        while not completed:
386
            for event in self.vm.get_qmp_events(wait=True):
387
                if event['event'] == 'BLOCK_JOB_ERROR':
388
                    self.assert_qmp(event, 'data/device', 'drive0')
389
                    self.assert_qmp(event, 'data/operation', 'read')
390

    
391
                    result = self.vm.qmp('query-block-jobs')
392
                    self.assert_qmp(result, 'return[0]/paused', True)
393
                    self.assert_qmp(result, 'return[0]/offset', self.STREAM_BUFFER_SIZE)
394
                    self.assert_qmp(result, 'return[0]/io-status', 'nospace')
395

    
396
                    result = self.vm.qmp('block-job-resume', device='drive0')
397
                    self.assert_qmp(result, 'return', {})
398

    
399
                    result = self.vm.qmp('query-block-jobs')
400
                    self.assert_qmp(result, 'return[0]/paused', False)
401
                    self.assert_qmp(result, 'return[0]/io-status', 'ok')
402
                    error = True
403
                elif event['event'] == 'BLOCK_JOB_COMPLETED':
404
                    self.assertTrue(error, 'job completed unexpectedly')
405
                    self.assert_qmp(event, 'data/type', 'stream')
406
                    self.assert_qmp(event, 'data/device', 'drive0')
407
                    self.assert_qmp_absent(event, 'data/error')
408
                    self.assert_qmp(event, 'data/offset', self.image_len)
409
                    self.assert_qmp(event, 'data/len', self.image_len)
410
                    completed = True
411

    
412
        self.assert_no_active_block_jobs()
413
        self.vm.shutdown()
414

    
415
class TestStreamStop(ImageStreamingTestCase):
416
    image_len = 8 * 1024 * 1024 * 1024 # GB
417

    
418
    def setUp(self):
419
        qemu_img('create', backing_img, str(TestStreamStop.image_len))
420
        qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
421
        self.vm = iotests.VM().add_drive(test_img)
422
        self.vm.launch()
423

    
424
    def tearDown(self):
425
        self.vm.shutdown()
426
        os.remove(test_img)
427
        os.remove(backing_img)
428

    
429
    def test_stream_stop(self):
430
        self.assert_no_active_block_jobs()
431

    
432
        result = self.vm.qmp('block-stream', device='drive0')
433
        self.assert_qmp(result, 'return', {})
434

    
435
        time.sleep(0.1)
436
        events = self.vm.get_qmp_events(wait=False)
437
        self.assertEqual(events, [], 'unexpected QMP event: %s' % events)
438

    
439
        self.cancel_and_wait()
440

    
441
class TestSetSpeed(ImageStreamingTestCase):
442
    image_len = 80 * 1024 * 1024 # MB
443

    
444
    def setUp(self):
445
        qemu_img('create', backing_img, str(TestSetSpeed.image_len))
446
        qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
447
        self.vm = iotests.VM().add_drive(test_img)
448
        self.vm.launch()
449

    
450
    def tearDown(self):
451
        self.vm.shutdown()
452
        os.remove(test_img)
453
        os.remove(backing_img)
454

    
455
    # This is a short performance test which is not run by default.
456
    # Invoke "IMGFMT=qed ./030 TestSetSpeed.perf_test_throughput"
457
    def perf_test_throughput(self):
458
        self.assert_no_active_block_jobs()
459

    
460
        result = self.vm.qmp('block-stream', device='drive0')
461
        self.assert_qmp(result, 'return', {})
462

    
463
        result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024)
464
        self.assert_qmp(result, 'return', {})
465

    
466
        completed = False
467
        while not completed:
468
            for event in self.vm.get_qmp_events(wait=True):
469
                if event['event'] == 'BLOCK_JOB_COMPLETED':
470
                    self.assert_qmp(event, 'data/type', 'stream')
471
                    self.assert_qmp(event, 'data/device', 'drive0')
472
                    self.assert_qmp(event, 'data/offset', self.image_len)
473
                    self.assert_qmp(event, 'data/len', self.image_len)
474
                    completed = True
475

    
476
        self.assert_no_active_block_jobs()
477

    
478
    def test_set_speed(self):
479
        self.assert_no_active_block_jobs()
480

    
481
        result = self.vm.qmp('block-stream', device='drive0')
482
        self.assert_qmp(result, 'return', {})
483

    
484
        # Default speed is 0
485
        result = self.vm.qmp('query-block-jobs')
486
        self.assert_qmp(result, 'return[0]/device', 'drive0')
487
        self.assert_qmp(result, 'return[0]/speed', 0)
488

    
489
        result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024)
490
        self.assert_qmp(result, 'return', {})
491

    
492
        # Ensure the speed we set was accepted
493
        result = self.vm.qmp('query-block-jobs')
494
        self.assert_qmp(result, 'return[0]/device', 'drive0')
495
        self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024)
496

    
497
        self.cancel_and_wait()
498

    
499
        # Check setting speed in block-stream works
500
        result = self.vm.qmp('block-stream', device='drive0', speed=4 * 1024 * 1024)
501
        self.assert_qmp(result, 'return', {})
502

    
503
        result = self.vm.qmp('query-block-jobs')
504
        self.assert_qmp(result, 'return[0]/device', 'drive0')
505
        self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024)
506

    
507
        self.cancel_and_wait()
508

    
509
    def test_set_speed_invalid(self):
510
        self.assert_no_active_block_jobs()
511

    
512
        result = self.vm.qmp('block-stream', device='drive0', speed=-1)
513
        self.assert_qmp(result, 'error/class', 'GenericError')
514

    
515
        self.assert_no_active_block_jobs()
516

    
517
        result = self.vm.qmp('block-stream', device='drive0')
518
        self.assert_qmp(result, 'return', {})
519

    
520
        result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1)
521
        self.assert_qmp(result, 'error/class', 'GenericError')
522

    
523
        self.cancel_and_wait()
524

    
525
if __name__ == '__main__':
526
    iotests.main(supported_fmts=['qcow2', 'qed'])