xref: /openbmc/qemu/tests/qemu-iotests/iotests.py (revision e4ec5ad4)
1from __future__ import print_function
2# Common utilities and Python wrappers for qemu-iotests
3#
4# Copyright (C) 2012 IBM Corp.
5#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program.  If not, see <http://www.gnu.org/licenses/>.
18#
19
20import errno
21import os
22import re
23import subprocess
24import string
25import unittest
26import sys
27import struct
28import json
29import signal
30import logging
31import atexit
32import io
33from collections import OrderedDict
34
35sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
36from qemu import qtest
37
38assert sys.version_info >= (3,6)
39
40# This will not work if arguments contain spaces but is necessary if we
41# want to support the override options that ./check supports.
42qemu_img_args = [os.environ.get('QEMU_IMG_PROG', 'qemu-img')]
43if os.environ.get('QEMU_IMG_OPTIONS'):
44    qemu_img_args += os.environ['QEMU_IMG_OPTIONS'].strip().split(' ')
45
46qemu_io_args = [os.environ.get('QEMU_IO_PROG', 'qemu-io')]
47if os.environ.get('QEMU_IO_OPTIONS'):
48    qemu_io_args += os.environ['QEMU_IO_OPTIONS'].strip().split(' ')
49
50qemu_nbd_args = [os.environ.get('QEMU_NBD_PROG', 'qemu-nbd')]
51if os.environ.get('QEMU_NBD_OPTIONS'):
52    qemu_nbd_args += os.environ['QEMU_NBD_OPTIONS'].strip().split(' ')
53
54qemu_prog = os.environ.get('QEMU_PROG', 'qemu')
55qemu_opts = os.environ.get('QEMU_OPTIONS', '').strip().split(' ')
56
57imgfmt = os.environ.get('IMGFMT', 'raw')
58imgproto = os.environ.get('IMGPROTO', 'file')
59test_dir = os.environ.get('TEST_DIR')
60output_dir = os.environ.get('OUTPUT_DIR', '.')
61cachemode = os.environ.get('CACHEMODE')
62qemu_default_machine = os.environ.get('QEMU_DEFAULT_MACHINE')
63
64socket_scm_helper = os.environ.get('SOCKET_SCM_HELPER', 'socket_scm_helper')
65
66luks_default_secret_object = 'secret,id=keysec0,data=' + \
67                             os.environ.get('IMGKEYSECRET', '')
68luks_default_key_secret_opt = 'key-secret=keysec0'
69
70
71def qemu_img(*args):
72    '''Run qemu-img and return the exit code'''
73    devnull = open('/dev/null', 'r+')
74    exitcode = subprocess.call(qemu_img_args + list(args), stdin=devnull, stdout=devnull)
75    if exitcode < 0:
76        sys.stderr.write('qemu-img received signal %i: %s\n' % (-exitcode, ' '.join(qemu_img_args + list(args))))
77    return exitcode
78
79def ordered_qmp(qmsg, conv_keys=True):
80    # Dictionaries are not ordered prior to 3.6, therefore:
81    if isinstance(qmsg, list):
82        return [ordered_qmp(atom) for atom in qmsg]
83    if isinstance(qmsg, dict):
84        od = OrderedDict()
85        for k, v in sorted(qmsg.items()):
86            if conv_keys:
87                k = k.replace('_', '-')
88            od[k] = ordered_qmp(v, conv_keys=False)
89        return od
90    return qmsg
91
92def qemu_img_create(*args):
93    args = list(args)
94
95    # default luks support
96    if '-f' in args and args[args.index('-f') + 1] == 'luks':
97        if '-o' in args:
98            i = args.index('-o')
99            if 'key-secret' not in args[i + 1]:
100                args[i + 1].append(luks_default_key_secret_opt)
101                args.insert(i + 2, '--object')
102                args.insert(i + 3, luks_default_secret_object)
103        else:
104            args = ['-o', luks_default_key_secret_opt,
105                    '--object', luks_default_secret_object] + args
106
107    args.insert(0, 'create')
108
109    return qemu_img(*args)
110
111def qemu_img_verbose(*args):
112    '''Run qemu-img without suppressing its output and return the exit code'''
113    exitcode = subprocess.call(qemu_img_args + list(args))
114    if exitcode < 0:
115        sys.stderr.write('qemu-img received signal %i: %s\n' % (-exitcode, ' '.join(qemu_img_args + list(args))))
116    return exitcode
117
118def qemu_img_pipe(*args):
119    '''Run qemu-img and return its output'''
120    subp = subprocess.Popen(qemu_img_args + list(args),
121                            stdout=subprocess.PIPE,
122                            stderr=subprocess.STDOUT,
123                            universal_newlines=True)
124    exitcode = subp.wait()
125    if exitcode < 0:
126        sys.stderr.write('qemu-img received signal %i: %s\n' % (-exitcode, ' '.join(qemu_img_args + list(args))))
127    return subp.communicate()[0]
128
129def qemu_img_log(*args):
130    result = qemu_img_pipe(*args)
131    log(result, filters=[filter_testfiles])
132    return result
133
134def img_info_log(filename, filter_path=None, imgopts=False, extra_args=[]):
135    args = [ 'info' ]
136    if imgopts:
137        args.append('--image-opts')
138    else:
139        args += [ '-f', imgfmt ]
140    args += extra_args
141    args.append(filename)
142
143    output = qemu_img_pipe(*args)
144    if not filter_path:
145        filter_path = filename
146    log(filter_img_info(output, filter_path))
147
148def qemu_io(*args):
149    '''Run qemu-io and return the stdout data'''
150    args = qemu_io_args + list(args)
151    subp = subprocess.Popen(args, stdout=subprocess.PIPE,
152                            stderr=subprocess.STDOUT,
153                            universal_newlines=True)
154    exitcode = subp.wait()
155    if exitcode < 0:
156        sys.stderr.write('qemu-io received signal %i: %s\n' % (-exitcode, ' '.join(args)))
157    return subp.communicate()[0]
158
159def qemu_io_silent(*args):
160    '''Run qemu-io and return the exit code, suppressing stdout'''
161    args = qemu_io_args + list(args)
162    exitcode = subprocess.call(args, stdout=open('/dev/null', 'w'))
163    if exitcode < 0:
164        sys.stderr.write('qemu-io received signal %i: %s\n' %
165                         (-exitcode, ' '.join(args)))
166    return exitcode
167
168def get_virtio_scsi_device():
169    if qemu_default_machine == 's390-ccw-virtio':
170        return 'virtio-scsi-ccw'
171    return 'virtio-scsi-pci'
172
173class QemuIoInteractive:
174    def __init__(self, *args):
175        self.args = qemu_io_args + list(args)
176        self._p = subprocess.Popen(self.args, stdin=subprocess.PIPE,
177                                   stdout=subprocess.PIPE,
178                                   stderr=subprocess.STDOUT,
179                                   universal_newlines=True)
180        assert self._p.stdout.read(9) == 'qemu-io> '
181
182    def close(self):
183        self._p.communicate('q\n')
184
185    def _read_output(self):
186        pattern = 'qemu-io> '
187        n = len(pattern)
188        pos = 0
189        s = []
190        while pos != n:
191            c = self._p.stdout.read(1)
192            # check unexpected EOF
193            assert c != ''
194            s.append(c)
195            if c == pattern[pos]:
196                pos += 1
197            else:
198                pos = 0
199
200        return ''.join(s[:-n])
201
202    def cmd(self, cmd):
203        # quit command is in close(), '\n' is added automatically
204        assert '\n' not in cmd
205        cmd = cmd.strip()
206        assert cmd != 'q' and cmd != 'quit'
207        self._p.stdin.write(cmd + '\n')
208        self._p.stdin.flush()
209        return self._read_output()
210
211
212def qemu_nbd(*args):
213    '''Run qemu-nbd in daemon mode and return the parent's exit code'''
214    return subprocess.call(qemu_nbd_args + ['--fork'] + list(args))
215
216def qemu_nbd_early_pipe(*args):
217    '''Run qemu-nbd in daemon mode and return both the parent's exit code
218       and its output in case of an error'''
219    subp = subprocess.Popen(qemu_nbd_args + ['--fork'] + list(args),
220                            stdout=subprocess.PIPE,
221                            stderr=subprocess.STDOUT,
222                            universal_newlines=True)
223    exitcode = subp.wait()
224    if exitcode < 0:
225        sys.stderr.write('qemu-nbd received signal %i: %s\n' %
226                         (-exitcode,
227                          ' '.join(qemu_nbd_args + ['--fork'] + list(args))))
228    if exitcode == 0:
229        return exitcode, ''
230    else:
231        return exitcode, subp.communicate()[0]
232
233def compare_images(img1, img2, fmt1=imgfmt, fmt2=imgfmt):
234    '''Return True if two image files are identical'''
235    return qemu_img('compare', '-f', fmt1,
236                    '-F', fmt2, img1, img2) == 0
237
238def create_image(name, size):
239    '''Create a fully-allocated raw image with sector markers'''
240    file = open(name, 'wb')
241    i = 0
242    while i < size:
243        sector = struct.pack('>l504xl', i // 512, i // 512)
244        file.write(sector)
245        i = i + 512
246    file.close()
247
248def image_size(img):
249    '''Return image's virtual size'''
250    r = qemu_img_pipe('info', '--output=json', '-f', imgfmt, img)
251    return json.loads(r)['virtual-size']
252
253def is_str(val):
254    return isinstance(val, str)
255
256test_dir_re = re.compile(r"%s" % test_dir)
257def filter_test_dir(msg):
258    return test_dir_re.sub("TEST_DIR", msg)
259
260win32_re = re.compile(r"\r")
261def filter_win32(msg):
262    return win32_re.sub("", msg)
263
264qemu_io_re = re.compile(r"[0-9]* ops; [0-9\/:. sec]* \([0-9\/.inf]* [EPTGMKiBbytes]*\/sec and [0-9\/.inf]* ops\/sec\)")
265def filter_qemu_io(msg):
266    msg = filter_win32(msg)
267    return qemu_io_re.sub("X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)", msg)
268
269chown_re = re.compile(r"chown [0-9]+:[0-9]+")
270def filter_chown(msg):
271    return chown_re.sub("chown UID:GID", msg)
272
273def filter_qmp_event(event):
274    '''Filter a QMP event dict'''
275    event = dict(event)
276    if 'timestamp' in event:
277        event['timestamp']['seconds'] = 'SECS'
278        event['timestamp']['microseconds'] = 'USECS'
279    return event
280
281def filter_qmp(qmsg, filter_fn):
282    '''Given a string filter, filter a QMP object's values.
283    filter_fn takes a (key, value) pair.'''
284    # Iterate through either lists or dicts;
285    if isinstance(qmsg, list):
286        items = enumerate(qmsg)
287    else:
288        items = qmsg.items()
289
290    for k, v in items:
291        if isinstance(v, list) or isinstance(v, dict):
292            qmsg[k] = filter_qmp(v, filter_fn)
293        else:
294            qmsg[k] = filter_fn(k, v)
295    return qmsg
296
297def filter_testfiles(msg):
298    prefix = os.path.join(test_dir, "%s-" % (os.getpid()))
299    return msg.replace(prefix, 'TEST_DIR/PID-')
300
301def filter_qmp_testfiles(qmsg):
302    def _filter(key, value):
303        if is_str(value):
304            return filter_testfiles(value)
305        return value
306    return filter_qmp(qmsg, _filter)
307
308def filter_generated_node_ids(msg):
309    return re.sub("#block[0-9]+", "NODE_NAME", msg)
310
311def filter_img_info(output, filename):
312    lines = []
313    for line in output.split('\n'):
314        if 'disk size' in line or 'actual-size' in line:
315            continue
316        line = line.replace(filename, 'TEST_IMG') \
317                   .replace(imgfmt, 'IMGFMT')
318        line = re.sub('iters: [0-9]+', 'iters: XXX', line)
319        line = re.sub('uuid: [-a-f0-9]+', 'uuid: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX', line)
320        line = re.sub('cid: [0-9]+', 'cid: XXXXXXXXXX', line)
321        lines.append(line)
322    return '\n'.join(lines)
323
324def filter_imgfmt(msg):
325    return msg.replace(imgfmt, 'IMGFMT')
326
327def filter_qmp_imgfmt(qmsg):
328    def _filter(key, value):
329        if is_str(value):
330            return filter_imgfmt(value)
331        return value
332    return filter_qmp(qmsg, _filter)
333
334def log(msg, filters=[], indent=None):
335    '''Logs either a string message or a JSON serializable message (like QMP).
336    If indent is provided, JSON serializable messages are pretty-printed.'''
337    for flt in filters:
338        msg = flt(msg)
339    if isinstance(msg, dict) or isinstance(msg, list):
340        # Python < 3.4 needs to know not to add whitespace when pretty-printing:
341        separators = (', ', ': ') if indent is None else (',', ': ')
342        # Don't sort if it's already sorted
343        do_sort = not isinstance(msg, OrderedDict)
344        print(json.dumps(msg, sort_keys=do_sort,
345                         indent=indent, separators=separators))
346    else:
347        print(msg)
348
349class Timeout:
350    def __init__(self, seconds, errmsg = "Timeout"):
351        self.seconds = seconds
352        self.errmsg = errmsg
353    def __enter__(self):
354        signal.signal(signal.SIGALRM, self.timeout)
355        signal.setitimer(signal.ITIMER_REAL, self.seconds)
356        return self
357    def __exit__(self, type, value, traceback):
358        signal.setitimer(signal.ITIMER_REAL, 0)
359        return False
360    def timeout(self, signum, frame):
361        raise Exception(self.errmsg)
362
363def file_pattern(name):
364    return "{0}-{1}".format(os.getpid(), name)
365
366class FilePaths(object):
367    """
368    FilePaths is an auto-generated filename that cleans itself up.
369
370    Use this context manager to generate filenames and ensure that the file
371    gets deleted::
372
373        with FilePaths(['test.img']) as img_path:
374            qemu_img('create', img_path, '1G')
375        # migration_sock_path is automatically deleted
376    """
377    def __init__(self, names):
378        self.paths = []
379        for name in names:
380            self.paths.append(os.path.join(test_dir, file_pattern(name)))
381
382    def __enter__(self):
383        return self.paths
384
385    def __exit__(self, exc_type, exc_val, exc_tb):
386        try:
387            for path in self.paths:
388                os.remove(path)
389        except OSError:
390            pass
391        return False
392
393class FilePath(FilePaths):
394    """
395    FilePath is a specialization of FilePaths that takes a single filename.
396    """
397    def __init__(self, name):
398        super(FilePath, self).__init__([name])
399
400    def __enter__(self):
401        return self.paths[0]
402
403def file_path_remover():
404    for path in reversed(file_path_remover.paths):
405        try:
406            os.remove(path)
407        except OSError:
408            pass
409
410
411def file_path(*names):
412    ''' Another way to get auto-generated filename that cleans itself up.
413
414    Use is as simple as:
415
416    img_a, img_b = file_path('a.img', 'b.img')
417    sock = file_path('socket')
418    '''
419
420    if not hasattr(file_path_remover, 'paths'):
421        file_path_remover.paths = []
422        atexit.register(file_path_remover)
423
424    paths = []
425    for name in names:
426        filename = file_pattern(name)
427        path = os.path.join(test_dir, filename)
428        file_path_remover.paths.append(path)
429        paths.append(path)
430
431    return paths[0] if len(paths) == 1 else paths
432
433def remote_filename(path):
434    if imgproto == 'file':
435        return path
436    elif imgproto == 'ssh':
437        return "ssh://%s@127.0.0.1:22%s" % (os.environ.get('USER'), path)
438    else:
439        raise Exception("Protocol %s not supported" % (imgproto))
440
441class VM(qtest.QEMUQtestMachine):
442    '''A QEMU VM'''
443
444    def __init__(self, path_suffix=''):
445        name = "qemu%s-%d" % (path_suffix, os.getpid())
446        super(VM, self).__init__(qemu_prog, qemu_opts, name=name,
447                                 test_dir=test_dir,
448                                 socket_scm_helper=socket_scm_helper)
449        self._num_drives = 0
450
451    def add_object(self, opts):
452        self._args.append('-object')
453        self._args.append(opts)
454        return self
455
456    def add_device(self, opts):
457        self._args.append('-device')
458        self._args.append(opts)
459        return self
460
461    def add_drive_raw(self, opts):
462        self._args.append('-drive')
463        self._args.append(opts)
464        return self
465
466    def add_drive(self, path, opts='', interface='virtio', format=imgfmt):
467        '''Add a virtio-blk drive to the VM'''
468        options = ['if=%s' % interface,
469                   'id=drive%d' % self._num_drives]
470
471        if path is not None:
472            options.append('file=%s' % path)
473            options.append('format=%s' % format)
474            options.append('cache=%s' % cachemode)
475
476        if opts:
477            options.append(opts)
478
479        if format == 'luks' and 'key-secret' not in opts:
480            # default luks support
481            if luks_default_secret_object not in self._args:
482                self.add_object(luks_default_secret_object)
483
484            options.append(luks_default_key_secret_opt)
485
486        self._args.append('-drive')
487        self._args.append(','.join(options))
488        self._num_drives += 1
489        return self
490
491    def add_blockdev(self, opts):
492        self._args.append('-blockdev')
493        if isinstance(opts, str):
494            self._args.append(opts)
495        else:
496            self._args.append(','.join(opts))
497        return self
498
499    def add_incoming(self, addr):
500        self._args.append('-incoming')
501        self._args.append(addr)
502        return self
503
504    def pause_drive(self, drive, event=None):
505        '''Pause drive r/w operations'''
506        if not event:
507            self.pause_drive(drive, "read_aio")
508            self.pause_drive(drive, "write_aio")
509            return
510        self.qmp('human-monitor-command',
511                    command_line='qemu-io %s "break %s bp_%s"' % (drive, event, drive))
512
513    def resume_drive(self, drive):
514        self.qmp('human-monitor-command',
515                    command_line='qemu-io %s "remove_break bp_%s"' % (drive, drive))
516
517    def hmp_qemu_io(self, drive, cmd):
518        '''Write to a given drive using an HMP command'''
519        return self.qmp('human-monitor-command',
520                        command_line='qemu-io %s "%s"' % (drive, cmd))
521
522    def flatten_qmp_object(self, obj, output=None, basestr=''):
523        if output is None:
524            output = dict()
525        if isinstance(obj, list):
526            for i in range(len(obj)):
527                self.flatten_qmp_object(obj[i], output, basestr + str(i) + '.')
528        elif isinstance(obj, dict):
529            for key in obj:
530                self.flatten_qmp_object(obj[key], output, basestr + key + '.')
531        else:
532            output[basestr[:-1]] = obj # Strip trailing '.'
533        return output
534
535    def qmp_to_opts(self, obj):
536        obj = self.flatten_qmp_object(obj)
537        output_list = list()
538        for key in obj:
539            output_list += [key + '=' + obj[key]]
540        return ','.join(output_list)
541
542    def get_qmp_events_filtered(self, wait=60.0):
543        result = []
544        for ev in self.get_qmp_events(wait=wait):
545            result.append(filter_qmp_event(ev))
546        return result
547
548    def qmp_log(self, cmd, filters=[], indent=None, **kwargs):
549        full_cmd = OrderedDict((
550            ("execute", cmd),
551            ("arguments", ordered_qmp(kwargs))
552        ))
553        log(full_cmd, filters, indent=indent)
554        result = self.qmp(cmd, **kwargs)
555        log(result, filters, indent=indent)
556        return result
557
558    # Returns None on success, and an error string on failure
559    def run_job(self, job, auto_finalize=True, auto_dismiss=False,
560                pre_finalize=None, cancel=False, use_log=True, wait=60.0):
561        """
562        run_job moves a job from creation through to dismissal.
563
564        :param job: String. ID of recently-launched job
565        :param auto_finalize: Bool. True if the job was launched with
566                              auto_finalize. Defaults to True.
567        :param auto_dismiss: Bool. True if the job was launched with
568                             auto_dismiss=True. Defaults to False.
569        :param pre_finalize: Callback. A callable that takes no arguments to be
570                             invoked prior to issuing job-finalize, if any.
571        :param cancel: Bool. When true, cancels the job after the pre_finalize
572                       callback.
573        :param use_log: Bool. When false, does not log QMP messages.
574        :param wait: Float. Timeout value specifying how long to wait for any
575                     event, in seconds. Defaults to 60.0.
576        """
577        match_device = {'data': {'device': job}}
578        match_id = {'data': {'id': job}}
579        events = [
580            ('BLOCK_JOB_COMPLETED', match_device),
581            ('BLOCK_JOB_CANCELLED', match_device),
582            ('BLOCK_JOB_ERROR', match_device),
583            ('BLOCK_JOB_READY', match_device),
584            ('BLOCK_JOB_PENDING', match_id),
585            ('JOB_STATUS_CHANGE', match_id)
586        ]
587        error = None
588        while True:
589            ev = filter_qmp_event(self.events_wait(events))
590            if ev['event'] != 'JOB_STATUS_CHANGE':
591                if use_log:
592                    log(ev)
593                continue
594            status = ev['data']['status']
595            if status == 'aborting':
596                result = self.qmp('query-jobs')
597                for j in result['return']:
598                    if j['id'] == job:
599                        error = j['error']
600                        if use_log:
601                            log('Job failed: %s' % (j['error']))
602            elif status == 'pending' and not auto_finalize:
603                if pre_finalize:
604                    pre_finalize()
605                if cancel and use_log:
606                    self.qmp_log('job-cancel', id=job)
607                elif cancel:
608                    self.qmp('job-cancel', id=job)
609                elif use_log:
610                    self.qmp_log('job-finalize', id=job)
611                else:
612                    self.qmp('job-finalize', id=job)
613            elif status == 'concluded' and not auto_dismiss:
614                if use_log:
615                    self.qmp_log('job-dismiss', id=job)
616                else:
617                    self.qmp('job-dismiss', id=job)
618            elif status == 'null':
619                return error
620
621    def enable_migration_events(self, name):
622        log('Enabling migration QMP events on %s...' % name)
623        log(self.qmp('migrate-set-capabilities', capabilities=[
624            {
625                'capability': 'events',
626                'state': True
627            }
628        ]))
629
630    def wait_migration(self):
631        while True:
632            event = self.event_wait('MIGRATION')
633            log(event, filters=[filter_qmp_event])
634            if event['data']['status'] == 'completed':
635                break
636
637    def node_info(self, node_name):
638        nodes = self.qmp('query-named-block-nodes')
639        for x in nodes['return']:
640            if x['node-name'] == node_name:
641                return x
642        return None
643
644    def query_bitmaps(self):
645        res = self.qmp("query-named-block-nodes")
646        return {device['node-name']: device['dirty-bitmaps']
647                for device in res['return'] if 'dirty-bitmaps' in device}
648
649    def get_bitmap(self, node_name, bitmap_name, recording=None, bitmaps=None):
650        """
651        get a specific bitmap from the object returned by query_bitmaps.
652        :param recording: If specified, filter results by the specified value.
653        :param bitmaps: If specified, use it instead of call query_bitmaps()
654        """
655        if bitmaps is None:
656            bitmaps = self.query_bitmaps()
657
658        for bitmap in bitmaps[node_name]:
659            if bitmap.get('name', '') == bitmap_name:
660                if recording is None:
661                    return bitmap
662                elif bitmap.get('recording') == recording:
663                    return bitmap
664        return None
665
666    def check_bitmap_status(self, node_name, bitmap_name, fields):
667        ret = self.get_bitmap(node_name, bitmap_name)
668
669        return fields.items() <= ret.items()
670
671
672index_re = re.compile(r'([^\[]+)\[([^\]]+)\]')
673
674class QMPTestCase(unittest.TestCase):
675    '''Abstract base class for QMP test cases'''
676
677    def dictpath(self, d, path):
678        '''Traverse a path in a nested dict'''
679        for component in path.split('/'):
680            m = index_re.match(component)
681            if m:
682                component, idx = m.groups()
683                idx = int(idx)
684
685            if not isinstance(d, dict) or component not in d:
686                self.fail('failed path traversal for "%s" in "%s"' % (path, str(d)))
687            d = d[component]
688
689            if m:
690                if not isinstance(d, list):
691                    self.fail('path component "%s" in "%s" is not a list in "%s"' % (component, path, str(d)))
692                try:
693                    d = d[idx]
694                except IndexError:
695                    self.fail('invalid index "%s" in path "%s" in "%s"' % (idx, path, str(d)))
696        return d
697
698    def assert_qmp_absent(self, d, path):
699        try:
700            result = self.dictpath(d, path)
701        except AssertionError:
702            return
703        self.fail('path "%s" has value "%s"' % (path, str(result)))
704
705    def assert_qmp(self, d, path, value):
706        '''Assert that the value for a specific path in a QMP dict
707           matches.  When given a list of values, assert that any of
708           them matches.'''
709
710        result = self.dictpath(d, path)
711
712        # [] makes no sense as a list of valid values, so treat it as
713        # an actual single value.
714        if isinstance(value, list) and value != []:
715            for v in value:
716                if result == v:
717                    return
718            self.fail('no match for "%s" in %s' % (str(result), str(value)))
719        else:
720            self.assertEqual(result, value,
721                             'values not equal "%s" and "%s"'
722                                 % (str(result), str(value)))
723
724    def assert_no_active_block_jobs(self):
725        result = self.vm.qmp('query-block-jobs')
726        self.assert_qmp(result, 'return', [])
727
728    def assert_has_block_node(self, node_name=None, file_name=None):
729        """Issue a query-named-block-nodes and assert node_name and/or
730        file_name is present in the result"""
731        def check_equal_or_none(a, b):
732            return a == None or b == None or a == b
733        assert node_name or file_name
734        result = self.vm.qmp('query-named-block-nodes')
735        for x in result["return"]:
736            if check_equal_or_none(x.get("node-name"), node_name) and \
737                    check_equal_or_none(x.get("file"), file_name):
738                return
739        self.assertTrue(False, "Cannot find %s %s in result:\n%s" % \
740                (node_name, file_name, result))
741
742    def assert_json_filename_equal(self, json_filename, reference):
743        '''Asserts that the given filename is a json: filename and that its
744           content is equal to the given reference object'''
745        self.assertEqual(json_filename[:5], 'json:')
746        self.assertEqual(self.vm.flatten_qmp_object(json.loads(json_filename[5:])),
747                         self.vm.flatten_qmp_object(reference))
748
749    def cancel_and_wait(self, drive='drive0', force=False, resume=False, wait=60.0):
750        '''Cancel a block job and wait for it to finish, returning the event'''
751        result = self.vm.qmp('block-job-cancel', device=drive, force=force)
752        self.assert_qmp(result, 'return', {})
753
754        if resume:
755            self.vm.resume_drive(drive)
756
757        cancelled = False
758        result = None
759        while not cancelled:
760            for event in self.vm.get_qmp_events(wait=wait):
761                if event['event'] == 'BLOCK_JOB_COMPLETED' or \
762                   event['event'] == 'BLOCK_JOB_CANCELLED':
763                    self.assert_qmp(event, 'data/device', drive)
764                    result = event
765                    cancelled = True
766                elif event['event'] == 'JOB_STATUS_CHANGE':
767                    self.assert_qmp(event, 'data/id', drive)
768
769
770        self.assert_no_active_block_jobs()
771        return result
772
773    def wait_until_completed(self, drive='drive0', check_offset=True, wait=60.0):
774        '''Wait for a block job to finish, returning the event'''
775        while True:
776            for event in self.vm.get_qmp_events(wait=wait):
777                if event['event'] == 'BLOCK_JOB_COMPLETED':
778                    self.assert_qmp(event, 'data/device', drive)
779                    self.assert_qmp_absent(event, 'data/error')
780                    if check_offset:
781                        self.assert_qmp(event, 'data/offset', event['data']['len'])
782                    self.assert_no_active_block_jobs()
783                    return event
784                elif event['event'] == 'JOB_STATUS_CHANGE':
785                    self.assert_qmp(event, 'data/id', drive)
786
787    def wait_ready(self, drive='drive0'):
788        '''Wait until a block job BLOCK_JOB_READY event'''
789        f = {'data': {'type': 'mirror', 'device': drive } }
790        event = self.vm.event_wait(name='BLOCK_JOB_READY', match=f)
791
792    def wait_ready_and_cancel(self, drive='drive0'):
793        self.wait_ready(drive=drive)
794        event = self.cancel_and_wait(drive=drive)
795        self.assertEqual(event['event'], 'BLOCK_JOB_COMPLETED')
796        self.assert_qmp(event, 'data/type', 'mirror')
797        self.assert_qmp(event, 'data/offset', event['data']['len'])
798
799    def complete_and_wait(self, drive='drive0', wait_ready=True):
800        '''Complete a block job and wait for it to finish'''
801        if wait_ready:
802            self.wait_ready(drive=drive)
803
804        result = self.vm.qmp('block-job-complete', device=drive)
805        self.assert_qmp(result, 'return', {})
806
807        event = self.wait_until_completed(drive=drive)
808        self.assert_qmp(event, 'data/type', 'mirror')
809
810    def pause_wait(self, job_id='job0'):
811        with Timeout(1, "Timeout waiting for job to pause"):
812            while True:
813                result = self.vm.qmp('query-block-jobs')
814                found = False
815                for job in result['return']:
816                    if job['device'] == job_id:
817                        found = True
818                        if job['paused'] == True and job['busy'] == False:
819                            return job
820                        break
821                assert found
822
823    def pause_job(self, job_id='job0', wait=True):
824        result = self.vm.qmp('block-job-pause', device=job_id)
825        self.assert_qmp(result, 'return', {})
826        if wait:
827            return self.pause_wait(job_id)
828        return result
829
830
831def notrun(reason):
832    '''Skip this test suite'''
833    # Each test in qemu-iotests has a number ("seq")
834    seq = os.path.basename(sys.argv[0])
835
836    open('%s/%s.notrun' % (output_dir, seq), 'w').write(reason + '\n')
837    print('%s not run: %s' % (seq, reason))
838    sys.exit(0)
839
840def case_notrun(reason):
841    '''Skip this test case'''
842    # Each test in qemu-iotests has a number ("seq")
843    seq = os.path.basename(sys.argv[0])
844
845    open('%s/%s.casenotrun' % (output_dir, seq), 'a').write(
846        '    [case not run] ' + reason + '\n')
847
848def verify_image_format(supported_fmts=[], unsupported_fmts=[]):
849    assert not (supported_fmts and unsupported_fmts)
850
851    if 'generic' in supported_fmts and \
852            os.environ.get('IMGFMT_GENERIC', 'true') == 'true':
853        # similar to
854        #   _supported_fmt generic
855        # for bash tests
856        return
857
858    not_sup = supported_fmts and (imgfmt not in supported_fmts)
859    if not_sup or (imgfmt in unsupported_fmts):
860        notrun('not suitable for this image format: %s' % imgfmt)
861
862def verify_protocol(supported=[], unsupported=[]):
863    assert not (supported and unsupported)
864
865    if 'generic' in supported:
866        return
867
868    not_sup = supported and (imgproto not in supported)
869    if not_sup or (imgproto in unsupported):
870        notrun('not suitable for this protocol: %s' % imgproto)
871
872def verify_platform(supported_oses=['linux']):
873    if True not in [sys.platform.startswith(x) for x in supported_oses]:
874        notrun('not suitable for this OS: %s' % sys.platform)
875
876def verify_cache_mode(supported_cache_modes=[]):
877    if supported_cache_modes and (cachemode not in supported_cache_modes):
878        notrun('not suitable for this cache mode: %s' % cachemode)
879
880def supports_quorum():
881    return 'quorum' in qemu_img_pipe('--help')
882
883def verify_quorum():
884    '''Skip test suite if quorum support is not available'''
885    if not supports_quorum():
886        notrun('quorum support missing')
887
888def qemu_pipe(*args):
889    '''Run qemu with an option to print something and exit (e.g. a help option),
890    and return its output'''
891    args = [qemu_prog] + qemu_opts + list(args)
892    subp = subprocess.Popen(args, stdout=subprocess.PIPE,
893                            stderr=subprocess.STDOUT,
894                            universal_newlines=True)
895    exitcode = subp.wait()
896    if exitcode < 0:
897        sys.stderr.write('qemu received signal %i: %s\n' % (-exitcode,
898                         ' '.join(args)))
899    return subp.communicate()[0]
900
901def supported_formats(read_only=False):
902    '''Set 'read_only' to True to check ro-whitelist
903       Otherwise, rw-whitelist is checked'''
904    format_message = qemu_pipe("-drive", "format=help")
905    line = 1 if read_only else 0
906    return format_message.splitlines()[line].split(":")[1].split()
907
908def skip_if_unsupported(required_formats=[], read_only=False):
909    '''Skip Test Decorator
910       Runs the test if all the required formats are whitelisted'''
911    def skip_test_decorator(func):
912        def func_wrapper(*args, **kwargs):
913            usf_list = list(set(required_formats) -
914                            set(supported_formats(read_only)))
915            if usf_list:
916                case_notrun('{}: formats {} are not whitelisted'.format(
917                    args[0], usf_list))
918            else:
919                return func(*args, **kwargs)
920        return func_wrapper
921    return skip_test_decorator
922
923def execute_unittest(output, verbosity, debug):
924    runner = unittest.TextTestRunner(stream=output, descriptions=True,
925                                     verbosity=verbosity)
926    try:
927        # unittest.main() will use sys.exit(); so expect a SystemExit
928        # exception
929        unittest.main(testRunner=runner)
930    finally:
931        if not debug:
932            sys.stderr.write(re.sub(r'Ran (\d+) tests? in [\d.]+s',
933                                    r'Ran \1 tests', output.getvalue()))
934
935def execute_test(test_function=None,
936                 supported_fmts=[], supported_oses=['linux'],
937                 supported_cache_modes=[], unsupported_fmts=[],
938                 supported_protocols=[], unsupported_protocols=[]):
939    """Run either unittest or script-style tests."""
940
941    # We are using TEST_DIR and QEMU_DEFAULT_MACHINE as proxies to
942    # indicate that we're not being run via "check". There may be
943    # other things set up by "check" that individual test cases rely
944    # on.
945    if test_dir is None or qemu_default_machine is None:
946        sys.stderr.write('Please run this test via the "check" script\n')
947        sys.exit(os.EX_USAGE)
948
949    debug = '-d' in sys.argv
950    verbosity = 1
951    verify_image_format(supported_fmts, unsupported_fmts)
952    verify_protocol(supported_protocols, unsupported_protocols)
953    verify_platform(supported_oses)
954    verify_cache_mode(supported_cache_modes)
955
956    if debug:
957        output = sys.stdout
958        verbosity = 2
959        sys.argv.remove('-d')
960    else:
961        # We need to filter out the time taken from the output so that
962        # qemu-iotest can reliably diff the results against master output.
963        output = io.StringIO()
964
965    logging.basicConfig(level=(logging.DEBUG if debug else logging.WARN))
966
967    if not test_function:
968        execute_unittest(output, verbosity, debug)
969    else:
970        test_function()
971
972def script_main(test_function, *args, **kwargs):
973    """Run script-style tests outside of the unittest framework"""
974    execute_test(test_function, *args, **kwargs)
975
976def main(*args, **kwargs):
977    """Run tests using the unittest framework"""
978    execute_test(None, *args, **kwargs)
979