xref: /openbmc/qemu/tests/qemu-iotests/iotests.py (revision ed5abf46)
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                if use_log:
628                    self.qmp_log('job-complete', id=job)
629                else:
630                    self.qmp('job-complete', id=job)
631            elif status == 'pending' and not auto_finalize:
632                if pre_finalize:
633                    pre_finalize()
634                if cancel and use_log:
635                    self.qmp_log('job-cancel', id=job)
636                elif cancel:
637                    self.qmp('job-cancel', id=job)
638                elif use_log:
639                    self.qmp_log('job-finalize', id=job)
640                else:
641                    self.qmp('job-finalize', id=job)
642            elif status == 'concluded' and not auto_dismiss:
643                if use_log:
644                    self.qmp_log('job-dismiss', id=job)
645                else:
646                    self.qmp('job-dismiss', id=job)
647            elif status == 'null':
648                return error
649
650    # Returns None on success, and an error string on failure
651    def blockdev_create(self, options, job_id='job0', filters=None):
652        if filters is None:
653            filters = [filter_qmp_testfiles]
654        result = self.qmp_log('blockdev-create', filters=filters,
655                              job_id=job_id, options=options)
656
657        if 'return' in result:
658            assert result['return'] == {}
659            job_result = self.run_job(job_id)
660        else:
661            job_result = result['error']
662
663        log("")
664        return job_result
665
666    def enable_migration_events(self, name):
667        log('Enabling migration QMP events on %s...' % name)
668        log(self.qmp('migrate-set-capabilities', capabilities=[
669            {
670                'capability': 'events',
671                'state': True
672            }
673        ]))
674
675    def wait_migration(self, expect_runstate):
676        while True:
677            event = self.event_wait('MIGRATION')
678            log(event, filters=[filter_qmp_event])
679            if event['data']['status'] == 'completed':
680                break
681        # The event may occur in finish-migrate, so wait for the expected
682        # post-migration runstate
683        while self.qmp('query-status')['return']['status'] != expect_runstate:
684            pass
685
686    def node_info(self, node_name):
687        nodes = self.qmp('query-named-block-nodes')
688        for x in nodes['return']:
689            if x['node-name'] == node_name:
690                return x
691        return None
692
693    def query_bitmaps(self):
694        res = self.qmp("query-named-block-nodes")
695        return {device['node-name']: device['dirty-bitmaps']
696                for device in res['return'] if 'dirty-bitmaps' in device}
697
698    def get_bitmap(self, node_name, bitmap_name, recording=None, bitmaps=None):
699        """
700        get a specific bitmap from the object returned by query_bitmaps.
701        :param recording: If specified, filter results by the specified value.
702        :param bitmaps: If specified, use it instead of call query_bitmaps()
703        """
704        if bitmaps is None:
705            bitmaps = self.query_bitmaps()
706
707        for bitmap in bitmaps[node_name]:
708            if bitmap.get('name', '') == bitmap_name:
709                if recording is None:
710                    return bitmap
711                elif bitmap.get('recording') == recording:
712                    return bitmap
713        return None
714
715    def check_bitmap_status(self, node_name, bitmap_name, fields):
716        ret = self.get_bitmap(node_name, bitmap_name)
717
718        return fields.items() <= ret.items()
719
720    def assert_block_path(self, root, path, expected_node, graph=None):
721        """
722        Check whether the node under the given path in the block graph
723        is @expected_node.
724
725        @root is the node name of the node where the @path is rooted.
726
727        @path is a string that consists of child names separated by
728        slashes.  It must begin with a slash.
729
730        Examples for @root + @path:
731          - root="qcow2-node", path="/backing/file"
732          - root="quorum-node", path="/children.2/file"
733
734        Hypothetically, @path could be empty, in which case it would
735        point to @root.  However, in practice this case is not useful
736        and hence not allowed.
737
738        @expected_node may be None.  (All elements of the path but the
739        leaf must still exist.)
740
741        @graph may be None or the result of an x-debug-query-block-graph
742        call that has already been performed.
743        """
744        if graph is None:
745            graph = self.qmp('x-debug-query-block-graph')['return']
746
747        iter_path = iter(path.split('/'))
748
749        # Must start with a /
750        assert next(iter_path) == ''
751
752        node = next((node for node in graph['nodes'] if node['name'] == root),
753                    None)
754
755        # An empty @path is not allowed, so the root node must be present
756        assert node is not None, 'Root node %s not found' % root
757
758        for child_name in iter_path:
759            assert node is not None, 'Cannot follow path %s%s' % (root, path)
760
761            try:
762                node_id = next(edge['child'] for edge in graph['edges'] \
763                                             if edge['parent'] == node['id'] and
764                                                edge['name'] == child_name)
765
766                node = next(node for node in graph['nodes'] \
767                                 if node['id'] == node_id)
768            except StopIteration:
769                node = None
770
771        if node is None:
772            assert expected_node is None, \
773                   'No node found under %s (but expected %s)' % \
774                   (path, expected_node)
775        else:
776            assert node['name'] == expected_node, \
777                   'Found node %s under %s (but expected %s)' % \
778                   (node['name'], path, expected_node)
779
780index_re = re.compile(r'([^\[]+)\[([^\]]+)\]')
781
782class QMPTestCase(unittest.TestCase):
783    '''Abstract base class for QMP test cases'''
784
785    def dictpath(self, d, path):
786        '''Traverse a path in a nested dict'''
787        for component in path.split('/'):
788            m = index_re.match(component)
789            if m:
790                component, idx = m.groups()
791                idx = int(idx)
792
793            if not isinstance(d, dict) or component not in d:
794                self.fail('failed path traversal for "%s" in "%s"' % (path, str(d)))
795            d = d[component]
796
797            if m:
798                if not isinstance(d, list):
799                    self.fail('path component "%s" in "%s" is not a list in "%s"' % (component, path, str(d)))
800                try:
801                    d = d[idx]
802                except IndexError:
803                    self.fail('invalid index "%s" in path "%s" in "%s"' % (idx, path, str(d)))
804        return d
805
806    def assert_qmp_absent(self, d, path):
807        try:
808            result = self.dictpath(d, path)
809        except AssertionError:
810            return
811        self.fail('path "%s" has value "%s"' % (path, str(result)))
812
813    def assert_qmp(self, d, path, value):
814        '''Assert that the value for a specific path in a QMP dict
815           matches.  When given a list of values, assert that any of
816           them matches.'''
817
818        result = self.dictpath(d, path)
819
820        # [] makes no sense as a list of valid values, so treat it as
821        # an actual single value.
822        if isinstance(value, list) and value != []:
823            for v in value:
824                if result == v:
825                    return
826            self.fail('no match for "%s" in %s' % (str(result), str(value)))
827        else:
828            self.assertEqual(result, value,
829                             '"%s" is "%s", expected "%s"'
830                                 % (path, str(result), str(value)))
831
832    def assert_no_active_block_jobs(self):
833        result = self.vm.qmp('query-block-jobs')
834        self.assert_qmp(result, 'return', [])
835
836    def assert_has_block_node(self, node_name=None, file_name=None):
837        """Issue a query-named-block-nodes and assert node_name and/or
838        file_name is present in the result"""
839        def check_equal_or_none(a, b):
840            return a == None or b == None or a == b
841        assert node_name or file_name
842        result = self.vm.qmp('query-named-block-nodes')
843        for x in result["return"]:
844            if check_equal_or_none(x.get("node-name"), node_name) and \
845                    check_equal_or_none(x.get("file"), file_name):
846                return
847        self.assertTrue(False, "Cannot find %s %s in result:\n%s" % \
848                (node_name, file_name, result))
849
850    def assert_json_filename_equal(self, json_filename, reference):
851        '''Asserts that the given filename is a json: filename and that its
852           content is equal to the given reference object'''
853        self.assertEqual(json_filename[:5], 'json:')
854        self.assertEqual(self.vm.flatten_qmp_object(json.loads(json_filename[5:])),
855                         self.vm.flatten_qmp_object(reference))
856
857    def cancel_and_wait(self, drive='drive0', force=False, resume=False, wait=60.0):
858        '''Cancel a block job and wait for it to finish, returning the event'''
859        result = self.vm.qmp('block-job-cancel', device=drive, force=force)
860        self.assert_qmp(result, 'return', {})
861
862        if resume:
863            self.vm.resume_drive(drive)
864
865        cancelled = False
866        result = None
867        while not cancelled:
868            for event in self.vm.get_qmp_events(wait=wait):
869                if event['event'] == 'BLOCK_JOB_COMPLETED' or \
870                   event['event'] == 'BLOCK_JOB_CANCELLED':
871                    self.assert_qmp(event, 'data/device', drive)
872                    result = event
873                    cancelled = True
874                elif event['event'] == 'JOB_STATUS_CHANGE':
875                    self.assert_qmp(event, 'data/id', drive)
876
877
878        self.assert_no_active_block_jobs()
879        return result
880
881    def wait_until_completed(self, drive='drive0', check_offset=True, wait=60.0,
882                             error=None):
883        '''Wait for a block job to finish, returning the event'''
884        while True:
885            for event in self.vm.get_qmp_events(wait=wait):
886                if event['event'] == 'BLOCK_JOB_COMPLETED':
887                    self.assert_qmp(event, 'data/device', drive)
888                    if error is None:
889                        self.assert_qmp_absent(event, 'data/error')
890                        if check_offset:
891                            self.assert_qmp(event, 'data/offset',
892                                            event['data']['len'])
893                    else:
894                        self.assert_qmp(event, 'data/error', error)
895                    self.assert_no_active_block_jobs()
896                    return event
897                elif event['event'] == 'JOB_STATUS_CHANGE':
898                    self.assert_qmp(event, 'data/id', drive)
899
900    def wait_ready(self, drive='drive0'):
901        '''Wait until a block job BLOCK_JOB_READY event'''
902        f = {'data': {'type': 'mirror', 'device': drive } }
903        event = self.vm.event_wait(name='BLOCK_JOB_READY', match=f)
904
905    def wait_ready_and_cancel(self, drive='drive0'):
906        self.wait_ready(drive=drive)
907        event = self.cancel_and_wait(drive=drive)
908        self.assertEqual(event['event'], 'BLOCK_JOB_COMPLETED')
909        self.assert_qmp(event, 'data/type', 'mirror')
910        self.assert_qmp(event, 'data/offset', event['data']['len'])
911
912    def complete_and_wait(self, drive='drive0', wait_ready=True,
913                          completion_error=None):
914        '''Complete a block job and wait for it to finish'''
915        if wait_ready:
916            self.wait_ready(drive=drive)
917
918        result = self.vm.qmp('block-job-complete', device=drive)
919        self.assert_qmp(result, 'return', {})
920
921        event = self.wait_until_completed(drive=drive, error=completion_error)
922        self.assert_qmp(event, 'data/type', 'mirror')
923
924    def pause_wait(self, job_id='job0'):
925        with Timeout(1, "Timeout waiting for job to pause"):
926            while True:
927                result = self.vm.qmp('query-block-jobs')
928                found = False
929                for job in result['return']:
930                    if job['device'] == job_id:
931                        found = True
932                        if job['paused'] == True and job['busy'] == False:
933                            return job
934                        break
935                assert found
936
937    def pause_job(self, job_id='job0', wait=True):
938        result = self.vm.qmp('block-job-pause', device=job_id)
939        self.assert_qmp(result, 'return', {})
940        if wait:
941            return self.pause_wait(job_id)
942        return result
943
944    def case_skip(self, reason):
945        '''Skip this test case'''
946        case_notrun(reason)
947        self.skipTest(reason)
948
949
950def notrun(reason):
951    '''Skip this test suite'''
952    # Each test in qemu-iotests has a number ("seq")
953    seq = os.path.basename(sys.argv[0])
954
955    open('%s/%s.notrun' % (output_dir, seq), 'w').write(reason + '\n')
956    print('%s not run: %s' % (seq, reason))
957    sys.exit(0)
958
959def case_notrun(reason):
960    '''Mark this test case as not having been run (without actually
961    skipping it, that is left to the caller).  See
962    QMPTestCase.case_skip() for a variant that actually skips the
963    current test case.'''
964
965    # Each test in qemu-iotests has a number ("seq")
966    seq = os.path.basename(sys.argv[0])
967
968    open('%s/%s.casenotrun' % (output_dir, seq), 'a').write(
969        '    [case not run] ' + reason + '\n')
970
971def verify_image_format(supported_fmts=[], unsupported_fmts=[]):
972    assert not (supported_fmts and unsupported_fmts)
973
974    if 'generic' in supported_fmts and \
975            os.environ.get('IMGFMT_GENERIC', 'true') == 'true':
976        # similar to
977        #   _supported_fmt generic
978        # for bash tests
979        return
980
981    not_sup = supported_fmts and (imgfmt not in supported_fmts)
982    if not_sup or (imgfmt in unsupported_fmts):
983        notrun('not suitable for this image format: %s' % imgfmt)
984
985def verify_protocol(supported=[], unsupported=[]):
986    assert not (supported and unsupported)
987
988    if 'generic' in supported:
989        return
990
991    not_sup = supported and (imgproto not in supported)
992    if not_sup or (imgproto in unsupported):
993        notrun('not suitable for this protocol: %s' % imgproto)
994
995def verify_platform(supported=None, unsupported=None):
996    if unsupported is not None:
997        if any((sys.platform.startswith(x) for x in unsupported)):
998            notrun('not suitable for this OS: %s' % sys.platform)
999
1000    if supported is not None:
1001        if not any((sys.platform.startswith(x) for x in supported)):
1002            notrun('not suitable for this OS: %s' % sys.platform)
1003
1004def verify_cache_mode(supported_cache_modes=[]):
1005    if supported_cache_modes and (cachemode not in supported_cache_modes):
1006        notrun('not suitable for this cache mode: %s' % cachemode)
1007
1008def verify_aio_mode(supported_aio_modes=[]):
1009    if supported_aio_modes and (aiomode not in supported_aio_modes):
1010        notrun('not suitable for this aio mode: %s' % aiomode)
1011
1012def supports_quorum():
1013    return 'quorum' in qemu_img_pipe('--help')
1014
1015def verify_quorum():
1016    '''Skip test suite if quorum support is not available'''
1017    if not supports_quorum():
1018        notrun('quorum support missing')
1019
1020def qemu_pipe(*args):
1021    '''Run qemu with an option to print something and exit (e.g. a help option),
1022    and return its output'''
1023    args = [qemu_prog] + qemu_opts + list(args)
1024    subp = subprocess.Popen(args, stdout=subprocess.PIPE,
1025                            stderr=subprocess.STDOUT,
1026                            universal_newlines=True)
1027    exitcode = subp.wait()
1028    if exitcode < 0:
1029        sys.stderr.write('qemu received signal %i: %s\n' % (-exitcode,
1030                         ' '.join(args)))
1031    return subp.communicate()[0]
1032
1033def supported_formats(read_only=False):
1034    '''Set 'read_only' to True to check ro-whitelist
1035       Otherwise, rw-whitelist is checked'''
1036
1037    if not hasattr(supported_formats, "formats"):
1038        supported_formats.formats = {}
1039
1040    if read_only not in supported_formats.formats:
1041        format_message = qemu_pipe("-drive", "format=help")
1042        line = 1 if read_only else 0
1043        supported_formats.formats[read_only] = \
1044            format_message.splitlines()[line].split(":")[1].split()
1045
1046    return supported_formats.formats[read_only]
1047
1048def skip_if_unsupported(required_formats=[], read_only=False):
1049    '''Skip Test Decorator
1050       Runs the test if all the required formats are whitelisted'''
1051    def skip_test_decorator(func):
1052        def func_wrapper(test_case: QMPTestCase, *args, **kwargs):
1053            if callable(required_formats):
1054                fmts = required_formats(test_case)
1055            else:
1056                fmts = required_formats
1057
1058            usf_list = list(set(fmts) - set(supported_formats(read_only)))
1059            if usf_list:
1060                test_case.case_skip('{}: formats {} are not whitelisted'.format(
1061                    test_case, usf_list))
1062            else:
1063                return func(test_case, *args, **kwargs)
1064        return func_wrapper
1065    return skip_test_decorator
1066
1067def skip_if_user_is_root(func):
1068    '''Skip Test Decorator
1069       Runs the test only without root permissions'''
1070    def func_wrapper(*args, **kwargs):
1071        if os.getuid() == 0:
1072            case_notrun('{}: cannot be run as root'.format(args[0]))
1073        else:
1074            return func(*args, **kwargs)
1075    return func_wrapper
1076
1077def execute_unittest(output, verbosity, debug):
1078    runner = unittest.TextTestRunner(stream=output, descriptions=True,
1079                                     verbosity=verbosity)
1080    try:
1081        # unittest.main() will use sys.exit(); so expect a SystemExit
1082        # exception
1083        unittest.main(testRunner=runner)
1084    finally:
1085        if not debug:
1086            out = output.getvalue()
1087            out = re.sub(r'Ran (\d+) tests? in [\d.]+s', r'Ran \1 tests', out)
1088
1089            # Hide skipped tests from the reference output
1090            out = re.sub(r'OK \(skipped=\d+\)', 'OK', out)
1091            out_first_line, out_rest = out.split('\n', 1)
1092            out = out_first_line.replace('s', '.') + '\n' + out_rest
1093
1094            sys.stderr.write(out)
1095
1096def execute_test(test_function=None,
1097                 supported_fmts=[],
1098                 supported_platforms=None,
1099                 supported_cache_modes=[], supported_aio_modes={},
1100                 unsupported_fmts=[], supported_protocols=[],
1101                 unsupported_protocols=[]):
1102    """Run either unittest or script-style tests."""
1103
1104    # We are using TEST_DIR and QEMU_DEFAULT_MACHINE as proxies to
1105    # indicate that we're not being run via "check". There may be
1106    # other things set up by "check" that individual test cases rely
1107    # on.
1108    if test_dir is None or qemu_default_machine is None:
1109        sys.stderr.write('Please run this test via the "check" script\n')
1110        sys.exit(os.EX_USAGE)
1111
1112    debug = '-d' in sys.argv
1113    verbosity = 1
1114    verify_image_format(supported_fmts, unsupported_fmts)
1115    verify_protocol(supported_protocols, unsupported_protocols)
1116    verify_platform(supported=supported_platforms)
1117    verify_cache_mode(supported_cache_modes)
1118    verify_aio_mode(supported_aio_modes)
1119
1120    if debug:
1121        output = sys.stdout
1122        verbosity = 2
1123        sys.argv.remove('-d')
1124    else:
1125        # We need to filter out the time taken from the output so that
1126        # qemu-iotest can reliably diff the results against master output.
1127        output = io.StringIO()
1128
1129    logging.basicConfig(level=(logging.DEBUG if debug else logging.WARN))
1130
1131    if not test_function:
1132        execute_unittest(output, verbosity, debug)
1133    else:
1134        test_function()
1135
1136def script_main(test_function, *args, **kwargs):
1137    """Run script-style tests outside of the unittest framework"""
1138    execute_test(test_function, *args, **kwargs)
1139
1140def main(*args, **kwargs):
1141    """Run tests using the unittest framework"""
1142    execute_test(None, *args, **kwargs)
1143