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