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