]> Git Repo - qemu.git/blob - tests/qemu-iotests/iotests.py
Merge remote-tracking branch 'remotes/kraxel/tags/vga-20180524-pull-request' into...
[qemu.git] / tests / qemu-iotests / iotests.py
1 # Common utilities and Python wrappers for qemu-iotests
2 #
3 # Copyright (C) 2012 IBM Corp.
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 #
18
19 import errno
20 import os
21 import re
22 import subprocess
23 import string
24 import unittest
25 import sys
26 import struct
27 import json
28 import signal
29 import logging
30 import atexit
31
32 sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'scripts'))
33 import qtest
34
35
36 # This will not work if arguments contain spaces but is necessary if we
37 # want to support the override options that ./check supports.
38 qemu_img_args = [os.environ.get('QEMU_IMG_PROG', 'qemu-img')]
39 if os.environ.get('QEMU_IMG_OPTIONS'):
40     qemu_img_args += os.environ['QEMU_IMG_OPTIONS'].strip().split(' ')
41
42 qemu_io_args = [os.environ.get('QEMU_IO_PROG', 'qemu-io')]
43 if os.environ.get('QEMU_IO_OPTIONS'):
44     qemu_io_args += os.environ['QEMU_IO_OPTIONS'].strip().split(' ')
45
46 qemu_nbd_args = [os.environ.get('QEMU_NBD_PROG', 'qemu-nbd')]
47 if os.environ.get('QEMU_NBD_OPTIONS'):
48     qemu_nbd_args += os.environ['QEMU_NBD_OPTIONS'].strip().split(' ')
49
50 qemu_prog = os.environ.get('QEMU_PROG', 'qemu')
51 qemu_opts = os.environ.get('QEMU_OPTIONS', '').strip().split(' ')
52
53 imgfmt = os.environ.get('IMGFMT', 'raw')
54 imgproto = os.environ.get('IMGPROTO', 'file')
55 test_dir = os.environ.get('TEST_DIR')
56 output_dir = os.environ.get('OUTPUT_DIR', '.')
57 cachemode = os.environ.get('CACHEMODE')
58 qemu_default_machine = os.environ.get('QEMU_DEFAULT_MACHINE')
59
60 socket_scm_helper = os.environ.get('SOCKET_SCM_HELPER', 'socket_scm_helper')
61 debug = False
62
63 luks_default_secret_object = 'secret,id=keysec0,data=' + \
64                              os.environ['IMGKEYSECRET']
65 luks_default_key_secret_opt = 'key-secret=keysec0'
66
67
68 def qemu_img(*args):
69     '''Run qemu-img and return the exit code'''
70     devnull = open('/dev/null', 'r+')
71     exitcode = subprocess.call(qemu_img_args + list(args), stdin=devnull, stdout=devnull)
72     if exitcode < 0:
73         sys.stderr.write('qemu-img received signal %i: %s\n' % (-exitcode, ' '.join(qemu_img_args + list(args))))
74     return exitcode
75
76 def qemu_img_create(*args):
77     args = list(args)
78
79     # default luks support
80     if '-f' in args and args[args.index('-f') + 1] == 'luks':
81         if '-o' in args:
82             i = args.index('-o')
83             if 'key-secret' not in args[i + 1]:
84                 args[i + 1].append(luks_default_key_secret_opt)
85                 args.insert(i + 2, '--object')
86                 args.insert(i + 3, luks_default_secret_object)
87         else:
88             args = ['-o', luks_default_key_secret_opt,
89                     '--object', luks_default_secret_object] + args
90
91     args.insert(0, 'create')
92
93     return qemu_img(*args)
94
95 def qemu_img_verbose(*args):
96     '''Run qemu-img without suppressing its output and return the exit code'''
97     exitcode = subprocess.call(qemu_img_args + list(args))
98     if exitcode < 0:
99         sys.stderr.write('qemu-img received signal %i: %s\n' % (-exitcode, ' '.join(qemu_img_args + list(args))))
100     return exitcode
101
102 def qemu_img_pipe(*args):
103     '''Run qemu-img and return its output'''
104     subp = subprocess.Popen(qemu_img_args + list(args),
105                             stdout=subprocess.PIPE,
106                             stderr=subprocess.STDOUT)
107     exitcode = subp.wait()
108     if exitcode < 0:
109         sys.stderr.write('qemu-img received signal %i: %s\n' % (-exitcode, ' '.join(qemu_img_args + list(args))))
110     return subp.communicate()[0]
111
112 def qemu_io(*args):
113     '''Run qemu-io and return the stdout data'''
114     args = qemu_io_args + list(args)
115     subp = subprocess.Popen(args, stdout=subprocess.PIPE,
116                             stderr=subprocess.STDOUT)
117     exitcode = subp.wait()
118     if exitcode < 0:
119         sys.stderr.write('qemu-io received signal %i: %s\n' % (-exitcode, ' '.join(args)))
120     return subp.communicate()[0]
121
122
123 class QemuIoInteractive:
124     def __init__(self, *args):
125         self.args = qemu_io_args + list(args)
126         self._p = subprocess.Popen(self.args, stdin=subprocess.PIPE,
127                                    stdout=subprocess.PIPE,
128                                    stderr=subprocess.STDOUT)
129         assert self._p.stdout.read(9) == 'qemu-io> '
130
131     def close(self):
132         self._p.communicate('q\n')
133
134     def _read_output(self):
135         pattern = 'qemu-io> '
136         n = len(pattern)
137         pos = 0
138         s = []
139         while pos != n:
140             c = self._p.stdout.read(1)
141             # check unexpected EOF
142             assert c != ''
143             s.append(c)
144             if c == pattern[pos]:
145                 pos += 1
146             else:
147                 pos = 0
148
149         return ''.join(s[:-n])
150
151     def cmd(self, cmd):
152         # quit command is in close(), '\n' is added automatically
153         assert '\n' not in cmd
154         cmd = cmd.strip()
155         assert cmd != 'q' and cmd != 'quit'
156         self._p.stdin.write(cmd + '\n')
157         return self._read_output()
158
159
160 def qemu_nbd(*args):
161     '''Run qemu-nbd in daemon mode and return the parent's exit code'''
162     return subprocess.call(qemu_nbd_args + ['--fork'] + list(args))
163
164 def compare_images(img1, img2, fmt1=imgfmt, fmt2=imgfmt):
165     '''Return True if two image files are identical'''
166     return qemu_img('compare', '-f', fmt1,
167                     '-F', fmt2, img1, img2) == 0
168
169 def create_image(name, size):
170     '''Create a fully-allocated raw image with sector markers'''
171     file = open(name, 'w')
172     i = 0
173     while i < size:
174         sector = struct.pack('>l504xl', i / 512, i / 512)
175         file.write(sector)
176         i = i + 512
177     file.close()
178
179 def image_size(img):
180     '''Return image's virtual size'''
181     r = qemu_img_pipe('info', '--output=json', '-f', imgfmt, img)
182     return json.loads(r)['virtual-size']
183
184 test_dir_re = re.compile(r"%s" % test_dir)
185 def filter_test_dir(msg):
186     return test_dir_re.sub("TEST_DIR", msg)
187
188 win32_re = re.compile(r"\r")
189 def filter_win32(msg):
190     return win32_re.sub("", msg)
191
192 qemu_io_re = re.compile(r"[0-9]* ops; [0-9\/:. sec]* \([0-9\/.inf]* [EPTGMKiBbytes]*\/sec and [0-9\/.inf]* ops\/sec\)")
193 def filter_qemu_io(msg):
194     msg = filter_win32(msg)
195     return qemu_io_re.sub("X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)", msg)
196
197 chown_re = re.compile(r"chown [0-9]+:[0-9]+")
198 def filter_chown(msg):
199     return chown_re.sub("chown UID:GID", msg)
200
201 def filter_qmp_event(event):
202     '''Filter a QMP event dict'''
203     event = dict(event)
204     if 'timestamp' in event:
205         event['timestamp']['seconds'] = 'SECS'
206         event['timestamp']['microseconds'] = 'USECS'
207     return event
208
209 def log(msg, filters=[]):
210     for flt in filters:
211         msg = flt(msg)
212     print msg
213
214 class Timeout:
215     def __init__(self, seconds, errmsg = "Timeout"):
216         self.seconds = seconds
217         self.errmsg = errmsg
218     def __enter__(self):
219         signal.signal(signal.SIGALRM, self.timeout)
220         signal.setitimer(signal.ITIMER_REAL, self.seconds)
221         return self
222     def __exit__(self, type, value, traceback):
223         signal.setitimer(signal.ITIMER_REAL, 0)
224         return False
225     def timeout(self, signum, frame):
226         raise Exception(self.errmsg)
227
228
229 class FilePath(object):
230     '''An auto-generated filename that cleans itself up.
231
232     Use this context manager to generate filenames and ensure that the file
233     gets deleted::
234
235         with TestFilePath('test.img') as img_path:
236             qemu_img('create', img_path, '1G')
237         # migration_sock_path is automatically deleted
238     '''
239     def __init__(self, name):
240         filename = '{0}-{1}'.format(os.getpid(), name)
241         self.path = os.path.join(test_dir, filename)
242
243     def __enter__(self):
244         return self.path
245
246     def __exit__(self, exc_type, exc_val, exc_tb):
247         try:
248             os.remove(self.path)
249         except OSError:
250             pass
251         return False
252
253
254 def file_path_remover():
255     for path in reversed(file_path_remover.paths):
256         try:
257             os.remove(path)
258         except OSError:
259             pass
260
261
262 def file_path(*names):
263     ''' Another way to get auto-generated filename that cleans itself up.
264
265     Use is as simple as:
266
267     img_a, img_b = file_path('a.img', 'b.img')
268     sock = file_path('socket')
269     '''
270
271     if not hasattr(file_path_remover, 'paths'):
272         file_path_remover.paths = []
273         atexit.register(file_path_remover)
274
275     paths = []
276     for name in names:
277         filename = '{0}-{1}'.format(os.getpid(), name)
278         path = os.path.join(test_dir, filename)
279         file_path_remover.paths.append(path)
280         paths.append(path)
281
282     return paths[0] if len(paths) == 1 else paths
283
284
285 class VM(qtest.QEMUQtestMachine):
286     '''A QEMU VM'''
287
288     def __init__(self, path_suffix=''):
289         name = "qemu%s-%d" % (path_suffix, os.getpid())
290         super(VM, self).__init__(qemu_prog, qemu_opts, name=name,
291                                  test_dir=test_dir,
292                                  socket_scm_helper=socket_scm_helper)
293         self._num_drives = 0
294
295     def add_object(self, opts):
296         self._args.append('-object')
297         self._args.append(opts)
298         return self
299
300     def add_device(self, opts):
301         self._args.append('-device')
302         self._args.append(opts)
303         return self
304
305     def add_drive_raw(self, opts):
306         self._args.append('-drive')
307         self._args.append(opts)
308         return self
309
310     def add_drive(self, path, opts='', interface='virtio', format=imgfmt):
311         '''Add a virtio-blk drive to the VM'''
312         options = ['if=%s' % interface,
313                    'id=drive%d' % self._num_drives]
314
315         if path is not None:
316             options.append('file=%s' % path)
317             options.append('format=%s' % format)
318             options.append('cache=%s' % cachemode)
319
320         if opts:
321             options.append(opts)
322
323         if format == 'luks' and 'key-secret' not in opts:
324             # default luks support
325             if luks_default_secret_object not in self._args:
326                 self.add_object(luks_default_secret_object)
327
328             options.append(luks_default_key_secret_opt)
329
330         self._args.append('-drive')
331         self._args.append(','.join(options))
332         self._num_drives += 1
333         return self
334
335     def add_blockdev(self, opts):
336         self._args.append('-blockdev')
337         if isinstance(opts, str):
338             self._args.append(opts)
339         else:
340             self._args.append(','.join(opts))
341         return self
342
343     def add_incoming(self, addr):
344         self._args.append('-incoming')
345         self._args.append(addr)
346         return self
347
348     def pause_drive(self, drive, event=None):
349         '''Pause drive r/w operations'''
350         if not event:
351             self.pause_drive(drive, "read_aio")
352             self.pause_drive(drive, "write_aio")
353             return
354         self.qmp('human-monitor-command',
355                     command_line='qemu-io %s "break %s bp_%s"' % (drive, event, drive))
356
357     def resume_drive(self, drive):
358         self.qmp('human-monitor-command',
359                     command_line='qemu-io %s "remove_break bp_%s"' % (drive, drive))
360
361     def hmp_qemu_io(self, drive, cmd):
362         '''Write to a given drive using an HMP command'''
363         return self.qmp('human-monitor-command',
364                         command_line='qemu-io %s "%s"' % (drive, cmd))
365
366     def flatten_qmp_object(self, obj, output=None, basestr=''):
367         if output is None:
368             output = dict()
369         if isinstance(obj, list):
370             for i in range(len(obj)):
371                 self.flatten_qmp_object(obj[i], output, basestr + str(i) + '.')
372         elif isinstance(obj, dict):
373             for key in obj:
374                 self.flatten_qmp_object(obj[key], output, basestr + key + '.')
375         else:
376             output[basestr[:-1]] = obj # Strip trailing '.'
377         return output
378
379     def qmp_to_opts(self, obj):
380         obj = self.flatten_qmp_object(obj)
381         output_list = list()
382         for key in obj:
383             output_list += [key + '=' + obj[key]]
384         return ','.join(output_list)
385
386
387
388 index_re = re.compile(r'([^\[]+)\[([^\]]+)\]')
389
390 class QMPTestCase(unittest.TestCase):
391     '''Abstract base class for QMP test cases'''
392
393     def dictpath(self, d, path):
394         '''Traverse a path in a nested dict'''
395         for component in path.split('/'):
396             m = index_re.match(component)
397             if m:
398                 component, idx = m.groups()
399                 idx = int(idx)
400
401             if not isinstance(d, dict) or component not in d:
402                 self.fail('failed path traversal for "%s" in "%s"' % (path, str(d)))
403             d = d[component]
404
405             if m:
406                 if not isinstance(d, list):
407                     self.fail('path component "%s" in "%s" is not a list in "%s"' % (component, path, str(d)))
408                 try:
409                     d = d[idx]
410                 except IndexError:
411                     self.fail('invalid index "%s" in path "%s" in "%s"' % (idx, path, str(d)))
412         return d
413
414     def assert_qmp_absent(self, d, path):
415         try:
416             result = self.dictpath(d, path)
417         except AssertionError:
418             return
419         self.fail('path "%s" has value "%s"' % (path, str(result)))
420
421     def assert_qmp(self, d, path, value):
422         '''Assert that the value for a specific path in a QMP dict matches'''
423         result = self.dictpath(d, path)
424         self.assertEqual(result, value, 'values not equal "%s" and "%s"' % (str(result), str(value)))
425
426     def assert_no_active_block_jobs(self):
427         result = self.vm.qmp('query-block-jobs')
428         self.assert_qmp(result, 'return', [])
429
430     def assert_has_block_node(self, node_name=None, file_name=None):
431         """Issue a query-named-block-nodes and assert node_name and/or
432         file_name is present in the result"""
433         def check_equal_or_none(a, b):
434             return a == None or b == None or a == b
435         assert node_name or file_name
436         result = self.vm.qmp('query-named-block-nodes')
437         for x in result["return"]:
438             if check_equal_or_none(x.get("node-name"), node_name) and \
439                     check_equal_or_none(x.get("file"), file_name):
440                 return
441         self.assertTrue(False, "Cannot find %s %s in result:\n%s" % \
442                 (node_name, file_name, result))
443
444     def assert_json_filename_equal(self, json_filename, reference):
445         '''Asserts that the given filename is a json: filename and that its
446            content is equal to the given reference object'''
447         self.assertEqual(json_filename[:5], 'json:')
448         self.assertEqual(self.vm.flatten_qmp_object(json.loads(json_filename[5:])),
449                          self.vm.flatten_qmp_object(reference))
450
451     def cancel_and_wait(self, drive='drive0', force=False, resume=False):
452         '''Cancel a block job and wait for it to finish, returning the event'''
453         result = self.vm.qmp('block-job-cancel', device=drive, force=force)
454         self.assert_qmp(result, 'return', {})
455
456         if resume:
457             self.vm.resume_drive(drive)
458
459         cancelled = False
460         result = None
461         while not cancelled:
462             for event in self.vm.get_qmp_events(wait=True):
463                 if event['event'] == 'BLOCK_JOB_COMPLETED' or \
464                    event['event'] == 'BLOCK_JOB_CANCELLED':
465                     self.assert_qmp(event, 'data/device', drive)
466                     result = event
467                     cancelled = True
468                 elif event['event'] == 'JOB_STATUS_CHANGE':
469                     self.assert_qmp(event, 'data/id', drive)
470
471
472         self.assert_no_active_block_jobs()
473         return result
474
475     def wait_until_completed(self, drive='drive0', check_offset=True):
476         '''Wait for a block job to finish, returning the event'''
477         while True:
478             for event in self.vm.get_qmp_events(wait=True):
479                 if event['event'] == 'BLOCK_JOB_COMPLETED':
480                     self.assert_qmp(event, 'data/device', drive)
481                     self.assert_qmp_absent(event, 'data/error')
482                     if check_offset:
483                         self.assert_qmp(event, 'data/offset', event['data']['len'])
484                     self.assert_no_active_block_jobs()
485                     return event
486                 elif event['event'] == 'JOB_STATUS_CHANGE':
487                     self.assert_qmp(event, 'data/id', drive)
488
489     def wait_ready(self, drive='drive0'):
490         '''Wait until a block job BLOCK_JOB_READY event'''
491         f = {'data': {'type': 'mirror', 'device': drive } }
492         event = self.vm.event_wait(name='BLOCK_JOB_READY', match=f)
493
494     def wait_ready_and_cancel(self, drive='drive0'):
495         self.wait_ready(drive=drive)
496         event = self.cancel_and_wait(drive=drive)
497         self.assertEquals(event['event'], 'BLOCK_JOB_COMPLETED')
498         self.assert_qmp(event, 'data/type', 'mirror')
499         self.assert_qmp(event, 'data/offset', event['data']['len'])
500
501     def complete_and_wait(self, drive='drive0', wait_ready=True):
502         '''Complete a block job and wait for it to finish'''
503         if wait_ready:
504             self.wait_ready(drive=drive)
505
506         result = self.vm.qmp('block-job-complete', device=drive)
507         self.assert_qmp(result, 'return', {})
508
509         event = self.wait_until_completed(drive=drive)
510         self.assert_qmp(event, 'data/type', 'mirror')
511
512     def pause_wait(self, job_id='job0'):
513         with Timeout(1, "Timeout waiting for job to pause"):
514             while True:
515                 result = self.vm.qmp('query-block-jobs')
516                 for job in result['return']:
517                     if job['device'] == job_id and job['paused'] == True and job['busy'] == False:
518                         return job
519
520     def pause_job(self, job_id='job0', wait=True):
521         result = self.vm.qmp('block-job-pause', device=job_id)
522         self.assert_qmp(result, 'return', {})
523         if wait:
524             return self.pause_wait(job_id)
525         return result
526
527
528 def notrun(reason):
529     '''Skip this test suite'''
530     # Each test in qemu-iotests has a number ("seq")
531     seq = os.path.basename(sys.argv[0])
532
533     open('%s/%s.notrun' % (output_dir, seq), 'wb').write(reason + '\n')
534     print '%s not run: %s' % (seq, reason)
535     sys.exit(0)
536
537 def verify_image_format(supported_fmts=[], unsupported_fmts=[]):
538     assert not (supported_fmts and unsupported_fmts)
539
540     if 'generic' in supported_fmts and \
541             os.environ.get('IMGFMT_GENERIC', 'true') == 'true':
542         # similar to
543         #   _supported_fmt generic
544         # for bash tests
545         return
546
547     not_sup = supported_fmts and (imgfmt not in supported_fmts)
548     if not_sup or (imgfmt in unsupported_fmts):
549         notrun('not suitable for this image format: %s' % imgfmt)
550
551 def verify_platform(supported_oses=['linux']):
552     if True not in [sys.platform.startswith(x) for x in supported_oses]:
553         notrun('not suitable for this OS: %s' % sys.platform)
554
555 def verify_cache_mode(supported_cache_modes=[]):
556     if supported_cache_modes and (cachemode not in supported_cache_modes):
557         notrun('not suitable for this cache mode: %s' % cachemode)
558
559 def supports_quorum():
560     return 'quorum' in qemu_img_pipe('--help')
561
562 def verify_quorum():
563     '''Skip test suite if quorum support is not available'''
564     if not supports_quorum():
565         notrun('quorum support missing')
566
567 def main(supported_fmts=[], supported_oses=['linux'], supported_cache_modes=[],
568          unsupported_fmts=[]):
569     '''Run tests'''
570
571     global debug
572
573     # We are using TEST_DIR and QEMU_DEFAULT_MACHINE as proxies to
574     # indicate that we're not being run via "check". There may be
575     # other things set up by "check" that individual test cases rely
576     # on.
577     if test_dir is None or qemu_default_machine is None:
578         sys.stderr.write('Please run this test via the "check" script\n')
579         sys.exit(os.EX_USAGE)
580
581     debug = '-d' in sys.argv
582     verbosity = 1
583     verify_image_format(supported_fmts, unsupported_fmts)
584     verify_platform(supported_oses)
585     verify_cache_mode(supported_cache_modes)
586
587     # We need to filter out the time taken from the output so that qemu-iotest
588     # can reliably diff the results against master output.
589     import StringIO
590     if debug:
591         output = sys.stdout
592         verbosity = 2
593         sys.argv.remove('-d')
594     else:
595         output = StringIO.StringIO()
596
597     logging.basicConfig(level=(logging.DEBUG if debug else logging.WARN))
598
599     class MyTestRunner(unittest.TextTestRunner):
600         def __init__(self, stream=output, descriptions=True, verbosity=verbosity):
601             unittest.TextTestRunner.__init__(self, stream, descriptions, verbosity)
602
603     # unittest.main() will use sys.exit() so expect a SystemExit exception
604     try:
605         unittest.main(testRunner=MyTestRunner)
606     finally:
607         if not debug:
608             sys.stderr.write(re.sub(r'Ran (\d+) tests? in [\d.]+s', r'Ran \1 tests', output.getvalue()))
This page took 0.061641 seconds and 4 git commands to generate.