]> git.proxmox.com Git - mirror_qemu.git/blame - tests/qemu-iotests/iotests.py
iotests: touch up log function signature
[mirror_qemu.git] / tests / qemu-iotests / iotests.py
CommitLineData
f345cfd0
SH
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
b404b13b
JS
19import atexit
20from collections import OrderedDict
21import faulthandler
22import io
23import json
24import logging
f345cfd0
SH
25import os
26import re
b404b13b
JS
27import signal
28import struct
f345cfd0 29import subprocess
ed338bb0 30import sys
1cd0dbfc 31from typing import (Any, Callable, Dict, Iterable, List, Optional, TypeVar)
b404b13b 32import unittest
f345cfd0 33
368e0620 34# pylint: disable=import-error, wrong-import-position
8f8fd9ed
CR
35sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
36from qemu import qtest
02f3a911 37
6a96d87c 38assert sys.version_info >= (3, 6)
f345cfd0 39
aa1cbeb8
KW
40faulthandler.enable()
41
934659c4 42# This will not work if arguments contain spaces but is necessary if we
f345cfd0 43# want to support the override options that ./check supports.
934659c4
HR
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
a4d925f8
AS
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
bec87774
HR
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
4c44b4a4 61qemu_prog = os.environ.get('QEMU_PROG', 'qemu')
66613974 62qemu_opts = os.environ.get('QEMU_OPTIONS', '').strip().split(' ')
f345cfd0
SH
63
64imgfmt = os.environ.get('IMGFMT', 'raw')
65imgproto = os.environ.get('IMGPROTO', 'file')
5a8fabf3 66test_dir = os.environ.get('TEST_DIR')
32558ce7 67sock_dir = os.environ.get('SOCK_DIR')
e8f8624d 68output_dir = os.environ.get('OUTPUT_DIR', '.')
58cc2ae1 69cachemode = os.environ.get('CACHEMODE')
7156ca48 70aiomode = os.environ.get('AIOMODE')
e166b414 71qemu_default_machine = os.environ.get('QEMU_DEFAULT_MACHINE')
f345cfd0 72
30b005d9
WX
73socket_scm_helper = os.environ.get('SOCKET_SCM_HELPER', 'socket_scm_helper')
74
85a353a0 75luks_default_secret_object = 'secret,id=keysec0,data=' + \
58ebcb65 76 os.environ.get('IMGKEYSECRET', '')
85a353a0
VSO
77luks_default_key_secret_opt = 'key-secret=keysec0'
78
79
f345cfd0
SH
80def qemu_img(*args):
81 '''Run qemu-img and return the exit code'''
82 devnull = open('/dev/null', 'r+')
2ef6093c
HR
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
f345cfd0 87
8a57a4be 88def ordered_qmp(qmsg, conv_keys=True):
039be85c
JS
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()):
8a57a4be
HR
95 if conv_keys:
96 k = k.replace('_', '-')
97 od[k] = ordered_qmp(v, conv_keys=False)
039be85c
JS
98 return od
99 return qmsg
0706e87d 100
85a353a0
VSO
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
d2ef210c 120def qemu_img_verbose(*args):
993d46ce 121 '''Run qemu-img without suppressing its output and return the exit code'''
2ef6093c
HR
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
d2ef210c 126
3677e6f6
HR
127def qemu_img_pipe(*args):
128 '''Run qemu-img and return its output'''
491e5e85
DB
129 subp = subprocess.Popen(qemu_img_args + list(args),
130 stdout=subprocess.PIPE,
8eb5e674
HR
131 stderr=subprocess.STDOUT,
132 universal_newlines=True)
2ef6093c
HR
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]
3677e6f6 137
ac6fb43e
KW
138def qemu_img_log(*args):
139 result = qemu_img_pipe(*args)
140 log(result, filters=[filter_testfiles])
141 return result
142
4eabe051 143def img_info_log(filename, filter_path=None, imgopts=False, extra_args=()):
6a96d87c 144 args = ['info']
5ba141dc
KW
145 if imgopts:
146 args.append('--image-opts')
147 else:
6a96d87c 148 args += ['-f', imgfmt]
5ba141dc
KW
149 args += extra_args
150 args.append(filename)
151
152 output = qemu_img_pipe(*args)
6b605ade
KW
153 if not filter_path:
154 filter_path = filename
155 log(filter_img_info(output, filter_path))
156
f345cfd0
SH
157def qemu_io(*args):
158 '''Run qemu-io and return the stdout data'''
159 args = qemu_io_args + list(args)
491e5e85 160 subp = subprocess.Popen(args, stdout=subprocess.PIPE,
8eb5e674
HR
161 stderr=subprocess.STDOUT,
162 universal_newlines=True)
2ef6093c
HR
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]
f345cfd0 167
a96f0350
KW
168def qemu_io_log(*args):
169 result = qemu_io(*args)
170 log(result, filters=[filter_testfiles, filter_qemu_io])
171 return result
172
745f2bf4
HR
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
23ee0ec2
VSO
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
f357576f
JS
189def get_virtio_scsi_device():
190 if qemu_default_machine == 's390-ccw-virtio':
191 return 'virtio-scsi-ccw'
192 return 'virtio-scsi-pci'
9fa90eec
VSO
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,
8eb5e674
HR
199 stderr=subprocess.STDOUT,
200 universal_newlines=True)
9fa90eec
VSO
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()
6a96d87c 227 assert cmd not in ('q', 'quit')
9fa90eec 228 self._p.stdin.write(cmd + '\n')
f544adf7 229 self._p.stdin.flush()
9fa90eec
VSO
230 return self._read_output()
231
232
bec87774
HR
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
6177b584 237def qemu_nbd_early_pipe(*args):
e1e6eccd 238 '''Run qemu-nbd in daemon mode and return both the parent's exit code
6177b584 239 and its output in case of an error'''
e1e6eccd
HR
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))))
6a96d87c
JS
249
250 return exitcode, subp.communicate()[0] if exitcode else ''
e1e6eccd 251
23ee0ec2
VSO
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
e1b5c51f 256def compare_images(img1, img2, fmt1=imgfmt, fmt2=imgfmt):
3a3918c3 257 '''Return True if two image files are identical'''
e1b5c51f
PB
258 return qemu_img('compare', '-f', fmt1,
259 '-F', fmt2, img1, img2) == 0
3a3918c3 260
2499a096
SH
261def create_image(name, size):
262 '''Create a fully-allocated raw image with sector markers'''
8eb5e674 263 file = open(name, 'wb')
2499a096
SH
264 i = 0
265 while i < size:
9a3a9a63 266 sector = struct.pack('>l504xl', i // 512, i // 512)
2499a096
SH
267 file.write(sector)
268 i = i + 512
269 file.close()
270
74f69050
FZ
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
011a5761 276def is_str(val):
7e693a05 277 return isinstance(val, str)
011a5761 278
a2d1c8fd
DB
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
12314f2d
SH
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
08fcd611
JS
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:
6a96d87c 314 if isinstance(v, (dict, list)):
08fcd611
JS
315 qmsg[k] = filter_qmp(v, filter_fn)
316 else:
317 qmsg[k] = filter_fn(k, v)
318 return qmsg
319
e234398a
KW
320def filter_testfiles(msg):
321 prefix = os.path.join(test_dir, "%s-" % (os.getpid()))
322 return msg.replace(prefix, 'TEST_DIR/PID-')
323
08fcd611 324def filter_qmp_testfiles(qmsg):
6a96d87c 325 def _filter(_key, value):
56a6e5d0 326 if is_str(value):
08fcd611
JS
327 return filter_testfiles(value)
328 return value
329 return filter_qmp(qmsg, _filter)
330
fa1151f8
JS
331def filter_generated_node_ids(msg):
332 return re.sub("#block[0-9]+", "NODE_NAME", msg)
333
6b605ade
KW
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
fd586ce8
KW
339 line = line.replace(filename, 'TEST_IMG')
340 line = filter_testfiles(line)
341 line = line.replace(imgfmt, 'IMGFMT')
6b605ade
KW
342 line = re.sub('iters: [0-9]+', 'iters: XXX', line)
343 line = re.sub('uuid: [-a-f0-9]+', 'uuid: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX', line)
bab4feb2 344 line = re.sub('cid: [0-9]+', 'cid: XXXXXXXXXX', line)
6b605ade
KW
345 lines.append(line)
346 return '\n'.join(lines)
347
f2ea0b20
HR
348def filter_imgfmt(msg):
349 return msg.replace(imgfmt, 'IMGFMT')
350
351def filter_qmp_imgfmt(qmsg):
6a96d87c 352 def _filter(_key, value):
f2ea0b20
HR
353 if is_str(value):
354 return filter_imgfmt(value)
355 return value
356 return filter_qmp(qmsg, _filter)
357
1cd0dbfc
JS
358
359Msg = TypeVar('Msg', Dict[str, Any], List[Any], str)
360
361def log(msg: Msg,
362 filters: Iterable[Callable[[Msg], Msg]] = (),
363 indent: Optional[int] = None) -> None:
364 """
365 Logs either a string message or a JSON serializable message (like QMP).
366 If indent is provided, JSON serializable messages are pretty-printed.
367 """
a2d1c8fd
DB
368 for flt in filters:
369 msg = flt(msg)
6a96d87c 370 if isinstance(msg, (dict, list)):
0706e87d
JS
371 # Don't sort if it's already sorted
372 do_sort = not isinstance(msg, OrderedDict)
229fc074 373 print(json.dumps(msg, sort_keys=do_sort, indent=indent))
e21b5f34
HR
374 else:
375 print(msg)
a2d1c8fd 376
2c93c5cb 377class Timeout:
6a96d87c 378 def __init__(self, seconds, errmsg="Timeout"):
2c93c5cb
KW
379 self.seconds = seconds
380 self.errmsg = errmsg
381 def __enter__(self):
382 signal.signal(signal.SIGALRM, self.timeout)
383 signal.setitimer(signal.ITIMER_REAL, self.seconds)
384 return self
6a96d87c 385 def __exit__(self, exc_type, value, traceback):
2c93c5cb
KW
386 signal.setitimer(signal.ITIMER_REAL, 0)
387 return False
388 def timeout(self, signum, frame):
389 raise Exception(self.errmsg)
390
de263986
JS
391def file_pattern(name):
392 return "{0}-{1}".format(os.getpid(), name)
f4844ac0 393
6a96d87c 394class FilePaths:
de263986
JS
395 """
396 FilePaths is an auto-generated filename that cleans itself up.
f4844ac0
SH
397
398 Use this context manager to generate filenames and ensure that the file
399 gets deleted::
400
de263986 401 with FilePaths(['test.img']) as img_path:
f4844ac0
SH
402 qemu_img('create', img_path, '1G')
403 # migration_sock_path is automatically deleted
de263986 404 """
93b78ea5 405 def __init__(self, names, base_dir=test_dir):
de263986
JS
406 self.paths = []
407 for name in names:
93b78ea5 408 self.paths.append(os.path.join(base_dir, file_pattern(name)))
f4844ac0
SH
409
410 def __enter__(self):
de263986 411 return self.paths
f4844ac0
SH
412
413 def __exit__(self, exc_type, exc_val, exc_tb):
414 try:
de263986
JS
415 for path in self.paths:
416 os.remove(path)
f4844ac0
SH
417 except OSError:
418 pass
419 return False
420
de263986
JS
421class FilePath(FilePaths):
422 """
423 FilePath is a specialization of FilePaths that takes a single filename.
424 """
93b78ea5
HR
425 def __init__(self, name, base_dir=test_dir):
426 super(FilePath, self).__init__([name], base_dir)
de263986
JS
427
428 def __enter__(self):
429 return self.paths[0]
f4844ac0 430
ef6e9228
VSO
431def file_path_remover():
432 for path in reversed(file_path_remover.paths):
433 try:
434 os.remove(path)
435 except OSError:
436 pass
437
438
93b78ea5 439def file_path(*names, base_dir=test_dir):
ef6e9228
VSO
440 ''' Another way to get auto-generated filename that cleans itself up.
441
442 Use is as simple as:
443
444 img_a, img_b = file_path('a.img', 'b.img')
445 sock = file_path('socket')
446 '''
447
448 if not hasattr(file_path_remover, 'paths'):
449 file_path_remover.paths = []
450 atexit.register(file_path_remover)
451
452 paths = []
453 for name in names:
de263986 454 filename = file_pattern(name)
93b78ea5 455 path = os.path.join(base_dir, filename)
ef6e9228
VSO
456 file_path_remover.paths.append(path)
457 paths.append(path)
458
459 return paths[0] if len(paths) == 1 else paths
460
5a259e86
KW
461def remote_filename(path):
462 if imgproto == 'file':
463 return path
464 elif imgproto == 'ssh':
b8c1f901 465 return "ssh://%s@127.0.0.1:22%s" % (os.environ.get('USER'), path)
5a259e86
KW
466 else:
467 raise Exception("Protocol %s not supported" % (imgproto))
ef6e9228 468
4c44b4a4 469class VM(qtest.QEMUQtestMachine):
f345cfd0
SH
470 '''A QEMU VM'''
471
5fcbdf50
HR
472 def __init__(self, path_suffix=''):
473 name = "qemu%s-%d" % (path_suffix, os.getpid())
474 super(VM, self).__init__(qemu_prog, qemu_opts, name=name,
475 test_dir=test_dir,
32558ce7
HR
476 socket_scm_helper=socket_scm_helper,
477 sock_dir=sock_dir)
f345cfd0 478 self._num_drives = 0
30b005d9 479
ccc15f7d
SH
480 def add_object(self, opts):
481 self._args.append('-object')
482 self._args.append(opts)
483 return self
484
486b88bd
KW
485 def add_device(self, opts):
486 self._args.append('-device')
487 self._args.append(opts)
488 return self
489
78b666f4
FZ
490 def add_drive_raw(self, opts):
491 self._args.append('-drive')
492 self._args.append(opts)
493 return self
494
1d3d4b63 495 def add_drive(self, path, opts='', interface='virtio', img_format=imgfmt):
f345cfd0 496 '''Add a virtio-blk drive to the VM'''
8e492253 497 options = ['if=%s' % interface,
f345cfd0 498 'id=drive%d' % self._num_drives]
8e492253
HR
499
500 if path is not None:
501 options.append('file=%s' % path)
1d3d4b63 502 options.append('format=%s' % img_format)
fc17c259 503 options.append('cache=%s' % cachemode)
7156ca48 504 options.append('aio=%s' % aiomode)
8e492253 505
f345cfd0
SH
506 if opts:
507 options.append(opts)
508
1d3d4b63 509 if img_format == 'luks' and 'key-secret' not in opts:
85a353a0
VSO
510 # default luks support
511 if luks_default_secret_object not in self._args:
512 self.add_object(luks_default_secret_object)
513
514 options.append(luks_default_key_secret_opt)
515
f345cfd0
SH
516 self._args.append('-drive')
517 self._args.append(','.join(options))
518 self._num_drives += 1
519 return self
520
5694923a
HR
521 def add_blockdev(self, opts):
522 self._args.append('-blockdev')
523 if isinstance(opts, str):
524 self._args.append(opts)
525 else:
526 self._args.append(','.join(opts))
527 return self
528
12314f2d
SH
529 def add_incoming(self, addr):
530 self._args.append('-incoming')
531 self._args.append(addr)
532 return self
533
3cf53c77
FZ
534 def pause_drive(self, drive, event=None):
535 '''Pause drive r/w operations'''
536 if not event:
537 self.pause_drive(drive, "read_aio")
538 self.pause_drive(drive, "write_aio")
539 return
540 self.qmp('human-monitor-command',
6a96d87c 541 command_line='qemu-io %s "break %s bp_%s"' % (drive, event, drive))
3cf53c77
FZ
542
543 def resume_drive(self, drive):
544 self.qmp('human-monitor-command',
6a96d87c 545 command_line='qemu-io %s "remove_break bp_%s"' % (drive, drive))
3cf53c77 546
e3409362
IM
547 def hmp_qemu_io(self, drive, cmd):
548 '''Write to a given drive using an HMP command'''
549 return self.qmp('human-monitor-command',
550 command_line='qemu-io %s "%s"' % (drive, cmd))
551
62a94288
KW
552 def flatten_qmp_object(self, obj, output=None, basestr=''):
553 if output is None:
554 output = dict()
555 if isinstance(obj, list):
6a96d87c
JS
556 for i, item in enumerate(obj):
557 self.flatten_qmp_object(item, output, basestr + str(i) + '.')
62a94288
KW
558 elif isinstance(obj, dict):
559 for key in obj:
560 self.flatten_qmp_object(obj[key], output, basestr + key + '.')
561 else:
562 output[basestr[:-1]] = obj # Strip trailing '.'
563 return output
564
565 def qmp_to_opts(self, obj):
566 obj = self.flatten_qmp_object(obj)
567 output_list = list()
568 for key in obj:
569 output_list += [key + '=' + obj[key]]
570 return ','.join(output_list)
571
8b6f5f8b 572 def get_qmp_events_filtered(self, wait=60.0):
5ad1dbf7
KW
573 result = []
574 for ev in self.get_qmp_events(wait=wait):
575 result.append(filter_qmp_event(ev))
576 return result
62a94288 577
4eabe051 578 def qmp_log(self, cmd, filters=(), indent=None, **kwargs):
0706e87d
JS
579 full_cmd = OrderedDict((
580 ("execute", cmd),
039be85c 581 ("arguments", ordered_qmp(kwargs))
0706e87d 582 ))
55cd64ea 583 log(full_cmd, filters, indent=indent)
e234398a 584 result = self.qmp(cmd, **kwargs)
55cd64ea 585 log(result, filters, indent=indent)
e234398a
KW
586 return result
587
6a4e88e1 588 # Returns None on success, and an error string on failure
ac6fb43e 589 def run_job(self, job, auto_finalize=True, auto_dismiss=False,
d443b74b
JS
590 pre_finalize=None, cancel=False, use_log=True, wait=60.0):
591 """
592 run_job moves a job from creation through to dismissal.
593
594 :param job: String. ID of recently-launched job
595 :param auto_finalize: Bool. True if the job was launched with
596 auto_finalize. Defaults to True.
597 :param auto_dismiss: Bool. True if the job was launched with
598 auto_dismiss=True. Defaults to False.
599 :param pre_finalize: Callback. A callable that takes no arguments to be
600 invoked prior to issuing job-finalize, if any.
601 :param cancel: Bool. When true, cancels the job after the pre_finalize
602 callback.
603 :param use_log: Bool. When false, does not log QMP messages.
604 :param wait: Float. Timeout value specifying how long to wait for any
605 event, in seconds. Defaults to 60.0.
606 """
d6a79af0
JS
607 match_device = {'data': {'device': job}}
608 match_id = {'data': {'id': job}}
609 events = [
610 ('BLOCK_JOB_COMPLETED', match_device),
611 ('BLOCK_JOB_CANCELLED', match_device),
612 ('BLOCK_JOB_ERROR', match_device),
613 ('BLOCK_JOB_READY', match_device),
614 ('BLOCK_JOB_PENDING', match_id),
615 ('JOB_STATUS_CHANGE', match_id)
616 ]
6a4e88e1 617 error = None
fc47d851 618 while True:
55824e09 619 ev = filter_qmp_event(self.events_wait(events, timeout=wait))
d6a79af0 620 if ev['event'] != 'JOB_STATUS_CHANGE':
15427f63
HR
621 if use_log:
622 log(ev)
d6a79af0
JS
623 continue
624 status = ev['data']['status']
625 if status == 'aborting':
626 result = self.qmp('query-jobs')
627 for j in result['return']:
628 if j['id'] == job:
629 error = j['error']
15427f63
HR
630 if use_log:
631 log('Job failed: %s' % (j['error']))
4688c4e3 632 elif status == 'ready':
b31b5321
KW
633 if use_log:
634 self.qmp_log('job-complete', id=job)
635 else:
636 self.qmp('job-complete', id=job)
d6a79af0
JS
637 elif status == 'pending' and not auto_finalize:
638 if pre_finalize:
639 pre_finalize()
d443b74b
JS
640 if cancel and use_log:
641 self.qmp_log('job-cancel', id=job)
642 elif cancel:
643 self.qmp('job-cancel', id=job)
644 elif use_log:
15427f63
HR
645 self.qmp_log('job-finalize', id=job)
646 else:
647 self.qmp('job-finalize', id=job)
d6a79af0 648 elif status == 'concluded' and not auto_dismiss:
15427f63
HR
649 if use_log:
650 self.qmp_log('job-dismiss', id=job)
651 else:
652 self.qmp('job-dismiss', id=job)
d6a79af0
JS
653 elif status == 'null':
654 return error
fc47d851 655
e9dbd1ca
KW
656 # Returns None on success, and an error string on failure
657 def blockdev_create(self, options, job_id='job0', filters=None):
658 if filters is None:
659 filters = [filter_qmp_testfiles]
660 result = self.qmp_log('blockdev-create', filters=filters,
661 job_id=job_id, options=options)
662
663 if 'return' in result:
664 assert result['return'] == {}
665 job_result = self.run_job(job_id)
666 else:
667 job_result = result['error']
668
669 log("")
670 return job_result
671
980448f1
KW
672 def enable_migration_events(self, name):
673 log('Enabling migration QMP events on %s...' % name)
674 log(self.qmp('migrate-set-capabilities', capabilities=[
675 {
676 'capability': 'events',
677 'state': True
678 }
679 ]))
680
8da7969b 681 def wait_migration(self, expect_runstate):
980448f1
KW
682 while True:
683 event = self.event_wait('MIGRATION')
684 log(event, filters=[filter_qmp_event])
685 if event['data']['status'] == 'completed':
686 break
8da7969b
HR
687 # The event may occur in finish-migrate, so wait for the expected
688 # post-migration runstate
689 while self.qmp('query-status')['return']['status'] != expect_runstate:
690 pass
980448f1 691
ef7afd63
HR
692 def node_info(self, node_name):
693 nodes = self.qmp('query-named-block-nodes')
694 for x in nodes['return']:
695 if x['node-name'] == node_name:
696 return x
697 return None
698
5c4343b8
VSO
699 def query_bitmaps(self):
700 res = self.qmp("query-named-block-nodes")
701 return {device['node-name']: device['dirty-bitmaps']
702 for device in res['return'] if 'dirty-bitmaps' in device}
703
704 def get_bitmap(self, node_name, bitmap_name, recording=None, bitmaps=None):
705 """
706 get a specific bitmap from the object returned by query_bitmaps.
707 :param recording: If specified, filter results by the specified value.
708 :param bitmaps: If specified, use it instead of call query_bitmaps()
709 """
710 if bitmaps is None:
711 bitmaps = self.query_bitmaps()
712
713 for bitmap in bitmaps[node_name]:
714 if bitmap.get('name', '') == bitmap_name:
6a96d87c 715 if recording is None or bitmap.get('recording') == recording:
5c4343b8
VSO
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
6a3d0f1e
HR
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:
6a96d87c
JS
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)
6a3d0f1e 772
6a3d0f1e
HR
773 except StopIteration:
774 node = None
775
776 if node is None:
777 assert expected_node is None, \
778 'No node found under %s (but expected %s)' % \
779 (path, expected_node)
780 else:
781 assert node['name'] == expected_node, \
782 'Found node %s under %s (but expected %s)' % \
783 (node['name'], path, expected_node)
7898f74e 784
f345cfd0
SH
785index_re = re.compile(r'([^\[]+)\[([^\]]+)\]')
786
787class QMPTestCase(unittest.TestCase):
788 '''Abstract base class for QMP test cases'''
789
6a96d87c
JS
790 def __init__(self, *args, **kwargs):
791 super().__init__(*args, **kwargs)
792 # Many users of this class set a VM property we rely on heavily
793 # in the methods below.
794 self.vm = None
795
f345cfd0
SH
796 def dictpath(self, d, path):
797 '''Traverse a path in a nested dict'''
798 for component in path.split('/'):
799 m = index_re.match(component)
800 if m:
801 component, idx = m.groups()
802 idx = int(idx)
803
804 if not isinstance(d, dict) or component not in d:
805 self.fail('failed path traversal for "%s" in "%s"' % (path, str(d)))
806 d = d[component]
807
808 if m:
809 if not isinstance(d, list):
810 self.fail('path component "%s" in "%s" is not a list in "%s"' % (component, path, str(d)))
811 try:
812 d = d[idx]
813 except IndexError:
814 self.fail('invalid index "%s" in path "%s" in "%s"' % (idx, path, str(d)))
815 return d
816
90f0b711
PB
817 def assert_qmp_absent(self, d, path):
818 try:
819 result = self.dictpath(d, path)
820 except AssertionError:
821 return
822 self.fail('path "%s" has value "%s"' % (path, str(result)))
823
f345cfd0 824 def assert_qmp(self, d, path, value):
a93a42bd
HR
825 '''Assert that the value for a specific path in a QMP dict
826 matches. When given a list of values, assert that any of
827 them matches.'''
828
f345cfd0 829 result = self.dictpath(d, path)
a93a42bd
HR
830
831 # [] makes no sense as a list of valid values, so treat it as
832 # an actual single value.
833 if isinstance(value, list) and value != []:
834 for v in value:
835 if result == v:
836 return
837 self.fail('no match for "%s" in %s' % (str(result), str(value)))
838 else:
839 self.assertEqual(result, value,
dbf231d7 840 '"%s" is "%s", expected "%s"'
6a96d87c 841 % (path, str(result), str(value)))
f345cfd0 842
ecc1c88e
SH
843 def assert_no_active_block_jobs(self):
844 result = self.vm.qmp('query-block-jobs')
845 self.assert_qmp(result, 'return', [])
846
e71fc0ba
FZ
847 def assert_has_block_node(self, node_name=None, file_name=None):
848 """Issue a query-named-block-nodes and assert node_name and/or
849 file_name is present in the result"""
850 def check_equal_or_none(a, b):
6a96d87c 851 return a is None or b is None or a == b
e71fc0ba
FZ
852 assert node_name or file_name
853 result = self.vm.qmp('query-named-block-nodes')
854 for x in result["return"]:
855 if check_equal_or_none(x.get("node-name"), node_name) and \
856 check_equal_or_none(x.get("file"), file_name):
857 return
6a96d87c
JS
858 self.fail("Cannot find %s %s in result:\n%s" %
859 (node_name, file_name, result))
e71fc0ba 860
e07375f5
HR
861 def assert_json_filename_equal(self, json_filename, reference):
862 '''Asserts that the given filename is a json: filename and that its
863 content is equal to the given reference object'''
864 self.assertEqual(json_filename[:5], 'json:')
62a94288
KW
865 self.assertEqual(self.vm.flatten_qmp_object(json.loads(json_filename[5:])),
866 self.vm.flatten_qmp_object(reference))
e07375f5 867
8b6f5f8b 868 def cancel_and_wait(self, drive='drive0', force=False, resume=False, wait=60.0):
2575fe16
SH
869 '''Cancel a block job and wait for it to finish, returning the event'''
870 result = self.vm.qmp('block-job-cancel', device=drive, force=force)
871 self.assert_qmp(result, 'return', {})
872
3cf53c77
FZ
873 if resume:
874 self.vm.resume_drive(drive)
875
2575fe16
SH
876 cancelled = False
877 result = None
878 while not cancelled:
8b6f5f8b 879 for event in self.vm.get_qmp_events(wait=wait):
2575fe16
SH
880 if event['event'] == 'BLOCK_JOB_COMPLETED' or \
881 event['event'] == 'BLOCK_JOB_CANCELLED':
882 self.assert_qmp(event, 'data/device', drive)
883 result = event
884 cancelled = True
1dac83f1
KW
885 elif event['event'] == 'JOB_STATUS_CHANGE':
886 self.assert_qmp(event, 'data/id', drive)
887
2575fe16
SH
888
889 self.assert_no_active_block_jobs()
890 return result
891
216656f5
HR
892 def wait_until_completed(self, drive='drive0', check_offset=True, wait=60.0,
893 error=None):
0dbe8a1b 894 '''Wait for a block job to finish, returning the event'''
c3988519 895 while True:
8b6f5f8b 896 for event in self.vm.get_qmp_events(wait=wait):
0dbe8a1b
SH
897 if event['event'] == 'BLOCK_JOB_COMPLETED':
898 self.assert_qmp(event, 'data/device', drive)
216656f5
HR
899 if error is None:
900 self.assert_qmp_absent(event, 'data/error')
901 if check_offset:
902 self.assert_qmp(event, 'data/offset',
903 event['data']['len'])
904 else:
905 self.assert_qmp(event, 'data/error', error)
c3988519
PX
906 self.assert_no_active_block_jobs()
907 return event
6a96d87c 908 if event['event'] == 'JOB_STATUS_CHANGE':
1dac83f1 909 self.assert_qmp(event, 'data/id', drive)
0dbe8a1b 910
866323f3 911 def wait_ready(self, drive='drive0'):
6a96d87c
JS
912 """Wait until a BLOCK_JOB_READY event, and return the event."""
913 f = {'data': {'type': 'mirror', 'device': drive}}
914 return self.vm.event_wait(name='BLOCK_JOB_READY', match=f)
866323f3
FZ
915
916 def wait_ready_and_cancel(self, drive='drive0'):
917 self.wait_ready(drive=drive)
918 event = self.cancel_and_wait(drive=drive)
fa1cfb40 919 self.assertEqual(event['event'], 'BLOCK_JOB_COMPLETED')
866323f3
FZ
920 self.assert_qmp(event, 'data/type', 'mirror')
921 self.assert_qmp(event, 'data/offset', event['data']['len'])
922
216656f5
HR
923 def complete_and_wait(self, drive='drive0', wait_ready=True,
924 completion_error=None):
866323f3
FZ
925 '''Complete a block job and wait for it to finish'''
926 if wait_ready:
927 self.wait_ready(drive=drive)
928
929 result = self.vm.qmp('block-job-complete', device=drive)
930 self.assert_qmp(result, 'return', {})
931
216656f5 932 event = self.wait_until_completed(drive=drive, error=completion_error)
866323f3
FZ
933 self.assert_qmp(event, 'data/type', 'mirror')
934
f03d9d24 935 def pause_wait(self, job_id='job0'):
e1df89bb 936 with Timeout(3, "Timeout waiting for job to pause"):
2c93c5cb
KW
937 while True:
938 result = self.vm.qmp('query-block-jobs')
c1bac161 939 found = False
2c93c5cb 940 for job in result['return']:
c1bac161
VSO
941 if job['device'] == job_id:
942 found = True
6a96d87c 943 if job['paused'] and not job['busy']:
c1bac161
VSO
944 return job
945 break
946 assert found
2c93c5cb 947
f03d9d24
JS
948 def pause_job(self, job_id='job0', wait=True):
949 result = self.vm.qmp('block-job-pause', device=job_id)
950 self.assert_qmp(result, 'return', {})
951 if wait:
952 return self.pause_wait(job_id)
953 return result
954
6be01225
HR
955 def case_skip(self, reason):
956 '''Skip this test case'''
957 case_notrun(reason)
958 self.skipTest(reason)
959
2c93c5cb 960
f345cfd0
SH
961def notrun(reason):
962 '''Skip this test suite'''
963 # Each test in qemu-iotests has a number ("seq")
964 seq = os.path.basename(sys.argv[0])
965
ce090f65 966 open('%s/%s.notrun' % (output_dir, seq), 'w').write(reason + '\n')
f03868bd 967 print('%s not run: %s' % (seq, reason))
f345cfd0
SH
968 sys.exit(0)
969
57ed557f 970def case_notrun(reason):
6be01225
HR
971 '''Mark this test case as not having been run (without actually
972 skipping it, that is left to the caller). See
973 QMPTestCase.case_skip() for a variant that actually skips the
974 current test case.'''
975
57ed557f
AS
976 # Each test in qemu-iotests has a number ("seq")
977 seq = os.path.basename(sys.argv[0])
978
979 open('%s/%s.casenotrun' % (output_dir, seq), 'a').write(
980 ' [case not run] ' + reason + '\n')
981
4eabe051 982def verify_image_format(supported_fmts=(), unsupported_fmts=()):
f48351d2
VSO
983 assert not (supported_fmts and unsupported_fmts)
984
985 if 'generic' in supported_fmts and \
986 os.environ.get('IMGFMT_GENERIC', 'true') == 'true':
987 # similar to
988 # _supported_fmt generic
989 # for bash tests
990 return
991
992 not_sup = supported_fmts and (imgfmt not in supported_fmts)
993 if not_sup or (imgfmt in unsupported_fmts):
3f5c4076 994 notrun('not suitable for this image format: %s' % imgfmt)
f345cfd0 995
4eabe051 996def verify_protocol(supported=(), unsupported=()):
5a259e86
KW
997 assert not (supported and unsupported)
998
999 if 'generic' in supported:
1000 return
1001
1002 not_sup = supported and (imgproto not in supported)
1003 if not_sup or (imgproto in unsupported):
1004 notrun('not suitable for this protocol: %s' % imgproto)
1005
72b29030
JS
1006def verify_platform(supported=None, unsupported=None):
1007 if unsupported is not None:
1008 if any((sys.platform.startswith(x) for x in unsupported)):
1009 notrun('not suitable for this OS: %s' % sys.platform)
1010
1011 if supported is not None:
1012 if not any((sys.platform.startswith(x) for x in supported)):
1013 notrun('not suitable for this OS: %s' % sys.platform)
bc521696 1014
4eabe051 1015def verify_cache_mode(supported_cache_modes=()):
ac8bd439
VSO
1016 if supported_cache_modes and (cachemode not in supported_cache_modes):
1017 notrun('not suitable for this cache mode: %s' % cachemode)
1018
4eabe051 1019def verify_aio_mode(supported_aio_modes=()):
7156ca48
AM
1020 if supported_aio_modes and (aiomode not in supported_aio_modes):
1021 notrun('not suitable for this aio mode: %s' % aiomode)
1022
b0f90495
AG
1023def supports_quorum():
1024 return 'quorum' in qemu_img_pipe('--help')
1025
3f647b51
SS
1026def verify_quorum():
1027 '''Skip test suite if quorum support is not available'''
b0f90495 1028 if not supports_quorum():
3f647b51
SS
1029 notrun('quorum support missing')
1030
57ed557f
AS
1031def qemu_pipe(*args):
1032 '''Run qemu with an option to print something and exit (e.g. a help option),
1033 and return its output'''
1034 args = [qemu_prog] + qemu_opts + list(args)
1035 subp = subprocess.Popen(args, stdout=subprocess.PIPE,
1036 stderr=subprocess.STDOUT,
1037 universal_newlines=True)
1038 exitcode = subp.wait()
1039 if exitcode < 0:
6a96d87c
JS
1040 sys.stderr.write('qemu received signal %i: %s\n' %
1041 (-exitcode, ' '.join(args)))
57ed557f
AS
1042 return subp.communicate()[0]
1043
1044def supported_formats(read_only=False):
1045 '''Set 'read_only' to True to check ro-whitelist
1046 Otherwise, rw-whitelist is checked'''
767de537
HR
1047
1048 if not hasattr(supported_formats, "formats"):
1049 supported_formats.formats = {}
1050
1051 if read_only not in supported_formats.formats:
1052 format_message = qemu_pipe("-drive", "format=help")
1053 line = 1 if read_only else 0
1054 supported_formats.formats[read_only] = \
1055 format_message.splitlines()[line].split(":")[1].split()
1056
1057 return supported_formats.formats[read_only]
57ed557f 1058
4eabe051 1059def skip_if_unsupported(required_formats=(), read_only=False):
57ed557f
AS
1060 '''Skip Test Decorator
1061 Runs the test if all the required formats are whitelisted'''
1062 def skip_test_decorator(func):
e6067a95 1063 def func_wrapper(test_case: QMPTestCase, *args, **kwargs):
7448be83
HR
1064 if callable(required_formats):
1065 fmts = required_formats(test_case)
1066 else:
1067 fmts = required_formats
1068
1069 usf_list = list(set(fmts) - set(supported_formats(read_only)))
57ed557f 1070 if usf_list:
e6067a95
HR
1071 test_case.case_skip('{}: formats {} are not whitelisted'.format(
1072 test_case, usf_list))
6a96d87c 1073 return None
57ed557f 1074 else:
e6067a95 1075 return func(test_case, *args, **kwargs)
57ed557f
AS
1076 return func_wrapper
1077 return skip_test_decorator
1078
d926f4dd
KW
1079def skip_if_user_is_root(func):
1080 '''Skip Test Decorator
1081 Runs the test only without root permissions'''
1082 def func_wrapper(*args, **kwargs):
1083 if os.getuid() == 0:
1084 case_notrun('{}: cannot be run as root'.format(args[0]))
6a96d87c 1085 return None
d926f4dd
KW
1086 else:
1087 return func(*args, **kwargs)
1088 return func_wrapper
1089
456a2d5a
JS
1090def execute_unittest(output, verbosity, debug):
1091 runner = unittest.TextTestRunner(stream=output, descriptions=True,
1092 verbosity=verbosity)
1093 try:
1094 # unittest.main() will use sys.exit(); so expect a SystemExit
1095 # exception
1096 unittest.main(testRunner=runner)
1097 finally:
1098 if not debug:
6be01225
HR
1099 out = output.getvalue()
1100 out = re.sub(r'Ran (\d+) tests? in [\d.]+s', r'Ran \1 tests', out)
1101
1102 # Hide skipped tests from the reference output
1103 out = re.sub(r'OK \(skipped=\d+\)', 'OK', out)
1104 out_first_line, out_rest = out.split('\n', 1)
1105 out = out_first_line.replace('s', '.') + '\n' + out_rest
1106
1107 sys.stderr.write(out)
c6a92369 1108
456a2d5a 1109def execute_test(test_function=None,
4eabe051 1110 supported_fmts=(),
72b29030 1111 supported_platforms=None,
4eabe051
JS
1112 supported_cache_modes=(), supported_aio_modes=(),
1113 unsupported_fmts=(), supported_protocols=(),
1114 unsupported_protocols=()):
456a2d5a 1115 """Run either unittest or script-style tests."""
c0088d79 1116
5a8fabf3
SS
1117 # We are using TEST_DIR and QEMU_DEFAULT_MACHINE as proxies to
1118 # indicate that we're not being run via "check". There may be
1119 # other things set up by "check" that individual test cases rely
1120 # on.
1121 if test_dir is None or qemu_default_machine is None:
1122 sys.stderr.write('Please run this test via the "check" script\n')
1123 sys.exit(os.EX_USAGE)
1124
c6a92369
DB
1125 debug = '-d' in sys.argv
1126 verbosity = 1
febc8c86 1127 verify_image_format(supported_fmts, unsupported_fmts)
88d2aa53 1128 verify_protocol(supported_protocols, unsupported_protocols)
72b29030 1129 verify_platform(supported=supported_platforms)
ac8bd439 1130 verify_cache_mode(supported_cache_modes)
7156ca48 1131 verify_aio_mode(supported_aio_modes)
c6a92369 1132
aa4f592a
FZ
1133 if debug:
1134 output = sys.stdout
1135 verbosity = 2
1136 sys.argv.remove('-d')
1137 else:
2d894bee
HR
1138 # We need to filter out the time taken from the output so that
1139 # qemu-iotest can reliably diff the results against master output.
7e693a05 1140 output = io.StringIO()
f345cfd0 1141
43851b5b
EH
1142 logging.basicConfig(level=(logging.DEBUG if debug else logging.WARN))
1143
456a2d5a
JS
1144 if not test_function:
1145 execute_unittest(output, verbosity, debug)
1146 else:
1147 test_function()
1148
1149def script_main(test_function, *args, **kwargs):
1150 """Run script-style tests outside of the unittest framework"""
1151 execute_test(test_function, *args, **kwargs)
f345cfd0 1152
456a2d5a
JS
1153def main(*args, **kwargs):
1154 """Run tests using the unittest framework"""
1155 execute_test(None, *args, **kwargs)