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