xref: /openbmc/qemu/tests/qemu-iotests/iotests.py (revision 64552b6b)
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 node_info(self, node_name):
587        nodes = self.qmp('query-named-block-nodes')
588        for x in nodes['return']:
589            if x['node-name'] == node_name:
590                return x
591        return None
592
593
594index_re = re.compile(r'([^\[]+)\[([^\]]+)\]')
595
596class QMPTestCase(unittest.TestCase):
597    '''Abstract base class for QMP test cases'''
598
599    def dictpath(self, d, path):
600        '''Traverse a path in a nested dict'''
601        for component in path.split('/'):
602            m = index_re.match(component)
603            if m:
604                component, idx = m.groups()
605                idx = int(idx)
606
607            if not isinstance(d, dict) or component not in d:
608                self.fail('failed path traversal for "%s" in "%s"' % (path, str(d)))
609            d = d[component]
610
611            if m:
612                if not isinstance(d, list):
613                    self.fail('path component "%s" in "%s" is not a list in "%s"' % (component, path, str(d)))
614                try:
615                    d = d[idx]
616                except IndexError:
617                    self.fail('invalid index "%s" in path "%s" in "%s"' % (idx, path, str(d)))
618        return d
619
620    def assert_qmp_absent(self, d, path):
621        try:
622            result = self.dictpath(d, path)
623        except AssertionError:
624            return
625        self.fail('path "%s" has value "%s"' % (path, str(result)))
626
627    def assert_qmp(self, d, path, value):
628        '''Assert that the value for a specific path in a QMP dict
629           matches.  When given a list of values, assert that any of
630           them matches.'''
631
632        result = self.dictpath(d, path)
633
634        # [] makes no sense as a list of valid values, so treat it as
635        # an actual single value.
636        if isinstance(value, list) and value != []:
637            for v in value:
638                if result == v:
639                    return
640            self.fail('no match for "%s" in %s' % (str(result), str(value)))
641        else:
642            self.assertEqual(result, value,
643                             'values not equal "%s" and "%s"'
644                                 % (str(result), str(value)))
645
646    def assert_no_active_block_jobs(self):
647        result = self.vm.qmp('query-block-jobs')
648        self.assert_qmp(result, 'return', [])
649
650    def assert_has_block_node(self, node_name=None, file_name=None):
651        """Issue a query-named-block-nodes and assert node_name and/or
652        file_name is present in the result"""
653        def check_equal_or_none(a, b):
654            return a == None or b == None or a == b
655        assert node_name or file_name
656        result = self.vm.qmp('query-named-block-nodes')
657        for x in result["return"]:
658            if check_equal_or_none(x.get("node-name"), node_name) and \
659                    check_equal_or_none(x.get("file"), file_name):
660                return
661        self.assertTrue(False, "Cannot find %s %s in result:\n%s" % \
662                (node_name, file_name, result))
663
664    def assert_json_filename_equal(self, json_filename, reference):
665        '''Asserts that the given filename is a json: filename and that its
666           content is equal to the given reference object'''
667        self.assertEqual(json_filename[:5], 'json:')
668        self.assertEqual(self.vm.flatten_qmp_object(json.loads(json_filename[5:])),
669                         self.vm.flatten_qmp_object(reference))
670
671    def cancel_and_wait(self, drive='drive0', force=False, resume=False, wait=60.0):
672        '''Cancel a block job and wait for it to finish, returning the event'''
673        result = self.vm.qmp('block-job-cancel', device=drive, force=force)
674        self.assert_qmp(result, 'return', {})
675
676        if resume:
677            self.vm.resume_drive(drive)
678
679        cancelled = False
680        result = None
681        while not cancelled:
682            for event in self.vm.get_qmp_events(wait=wait):
683                if event['event'] == 'BLOCK_JOB_COMPLETED' or \
684                   event['event'] == 'BLOCK_JOB_CANCELLED':
685                    self.assert_qmp(event, 'data/device', drive)
686                    result = event
687                    cancelled = True
688                elif event['event'] == 'JOB_STATUS_CHANGE':
689                    self.assert_qmp(event, 'data/id', drive)
690
691
692        self.assert_no_active_block_jobs()
693        return result
694
695    def wait_until_completed(self, drive='drive0', check_offset=True, wait=60.0):
696        '''Wait for a block job to finish, returning the event'''
697        while True:
698            for event in self.vm.get_qmp_events(wait=wait):
699                if event['event'] == 'BLOCK_JOB_COMPLETED':
700                    self.assert_qmp(event, 'data/device', drive)
701                    self.assert_qmp_absent(event, 'data/error')
702                    if check_offset:
703                        self.assert_qmp(event, 'data/offset', event['data']['len'])
704                    self.assert_no_active_block_jobs()
705                    return event
706                elif event['event'] == 'JOB_STATUS_CHANGE':
707                    self.assert_qmp(event, 'data/id', drive)
708
709    def wait_ready(self, drive='drive0'):
710        '''Wait until a block job BLOCK_JOB_READY event'''
711        f = {'data': {'type': 'mirror', 'device': drive } }
712        event = self.vm.event_wait(name='BLOCK_JOB_READY', match=f)
713
714    def wait_ready_and_cancel(self, drive='drive0'):
715        self.wait_ready(drive=drive)
716        event = self.cancel_and_wait(drive=drive)
717        self.assertEqual(event['event'], 'BLOCK_JOB_COMPLETED')
718        self.assert_qmp(event, 'data/type', 'mirror')
719        self.assert_qmp(event, 'data/offset', event['data']['len'])
720
721    def complete_and_wait(self, drive='drive0', wait_ready=True):
722        '''Complete a block job and wait for it to finish'''
723        if wait_ready:
724            self.wait_ready(drive=drive)
725
726        result = self.vm.qmp('block-job-complete', device=drive)
727        self.assert_qmp(result, 'return', {})
728
729        event = self.wait_until_completed(drive=drive)
730        self.assert_qmp(event, 'data/type', 'mirror')
731
732    def pause_wait(self, job_id='job0'):
733        with Timeout(1, "Timeout waiting for job to pause"):
734            while True:
735                result = self.vm.qmp('query-block-jobs')
736                found = False
737                for job in result['return']:
738                    if job['device'] == job_id:
739                        found = True
740                        if job['paused'] == True and job['busy'] == False:
741                            return job
742                        break
743                assert found
744
745    def pause_job(self, job_id='job0', wait=True):
746        result = self.vm.qmp('block-job-pause', device=job_id)
747        self.assert_qmp(result, 'return', {})
748        if wait:
749            return self.pause_wait(job_id)
750        return result
751
752
753def notrun(reason):
754    '''Skip this test suite'''
755    # Each test in qemu-iotests has a number ("seq")
756    seq = os.path.basename(sys.argv[0])
757
758    open('%s/%s.notrun' % (output_dir, seq), 'w').write(reason + '\n')
759    print('%s not run: %s' % (seq, reason))
760    sys.exit(0)
761
762def case_notrun(reason):
763    '''Skip this test case'''
764    # Each test in qemu-iotests has a number ("seq")
765    seq = os.path.basename(sys.argv[0])
766
767    open('%s/%s.casenotrun' % (output_dir, seq), 'a').write(
768        '    [case not run] ' + reason + '\n')
769
770def verify_image_format(supported_fmts=[], unsupported_fmts=[]):
771    assert not (supported_fmts and unsupported_fmts)
772
773    if 'generic' in supported_fmts and \
774            os.environ.get('IMGFMT_GENERIC', 'true') == 'true':
775        # similar to
776        #   _supported_fmt generic
777        # for bash tests
778        return
779
780    not_sup = supported_fmts and (imgfmt not in supported_fmts)
781    if not_sup or (imgfmt in unsupported_fmts):
782        notrun('not suitable for this image format: %s' % imgfmt)
783
784def verify_protocol(supported=[], unsupported=[]):
785    assert not (supported and unsupported)
786
787    if 'generic' in supported:
788        return
789
790    not_sup = supported and (imgproto not in supported)
791    if not_sup or (imgproto in unsupported):
792        notrun('not suitable for this protocol: %s' % imgproto)
793
794def verify_platform(supported_oses=['linux']):
795    if True not in [sys.platform.startswith(x) for x in supported_oses]:
796        notrun('not suitable for this OS: %s' % sys.platform)
797
798def verify_cache_mode(supported_cache_modes=[]):
799    if supported_cache_modes and (cachemode not in supported_cache_modes):
800        notrun('not suitable for this cache mode: %s' % cachemode)
801
802def supports_quorum():
803    return 'quorum' in qemu_img_pipe('--help')
804
805def verify_quorum():
806    '''Skip test suite if quorum support is not available'''
807    if not supports_quorum():
808        notrun('quorum support missing')
809
810def qemu_pipe(*args):
811    '''Run qemu with an option to print something and exit (e.g. a help option),
812    and return its output'''
813    args = [qemu_prog] + qemu_opts + list(args)
814    subp = subprocess.Popen(args, stdout=subprocess.PIPE,
815                            stderr=subprocess.STDOUT,
816                            universal_newlines=True)
817    exitcode = subp.wait()
818    if exitcode < 0:
819        sys.stderr.write('qemu received signal %i: %s\n' % (-exitcode,
820                         ' '.join(args)))
821    return subp.communicate()[0]
822
823def supported_formats(read_only=False):
824    '''Set 'read_only' to True to check ro-whitelist
825       Otherwise, rw-whitelist is checked'''
826    format_message = qemu_pipe("-drive", "format=help")
827    line = 1 if read_only else 0
828    return format_message.splitlines()[line].split(":")[1].split()
829
830def skip_if_unsupported(required_formats=[], read_only=False):
831    '''Skip Test Decorator
832       Runs the test if all the required formats are whitelisted'''
833    def skip_test_decorator(func):
834        def func_wrapper(*args, **kwargs):
835            usf_list = list(set(required_formats) -
836                            set(supported_formats(read_only)))
837            if usf_list:
838                case_notrun('{}: formats {} are not whitelisted'.format(
839                    args[0], usf_list))
840            else:
841                return func(*args, **kwargs)
842        return func_wrapper
843    return skip_test_decorator
844
845def main(supported_fmts=[], supported_oses=['linux'], supported_cache_modes=[],
846         unsupported_fmts=[]):
847    '''Run tests'''
848
849    global debug
850
851    # We are using TEST_DIR and QEMU_DEFAULT_MACHINE as proxies to
852    # indicate that we're not being run via "check". There may be
853    # other things set up by "check" that individual test cases rely
854    # on.
855    if test_dir is None or qemu_default_machine is None:
856        sys.stderr.write('Please run this test via the "check" script\n')
857        sys.exit(os.EX_USAGE)
858
859    debug = '-d' in sys.argv
860    verbosity = 1
861    verify_image_format(supported_fmts, unsupported_fmts)
862    verify_platform(supported_oses)
863    verify_cache_mode(supported_cache_modes)
864
865    if debug:
866        output = sys.stdout
867        verbosity = 2
868        sys.argv.remove('-d')
869    else:
870        # We need to filter out the time taken from the output so that
871        # qemu-iotest can reliably diff the results against master output.
872        if sys.version_info.major >= 3:
873            output = io.StringIO()
874        else:
875            # io.StringIO is for unicode strings, which is not what
876            # 2.x's test runner emits.
877            output = io.BytesIO()
878
879    logging.basicConfig(level=(logging.DEBUG if debug else logging.WARN))
880
881    class MyTestRunner(unittest.TextTestRunner):
882        def __init__(self, stream=output, descriptions=True, verbosity=verbosity):
883            unittest.TextTestRunner.__init__(self, stream, descriptions, verbosity)
884
885    # unittest.main() will use sys.exit() so expect a SystemExit exception
886    try:
887        unittest.main(testRunner=MyTestRunner)
888    finally:
889        if not debug:
890            sys.stderr.write(re.sub(r'Ran (\d+) tests? in [\d.]+s', r'Ran \1 tests', output.getvalue()))
891