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