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