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