]> git.proxmox.com Git - mirror_qemu.git/blame - tests/qemu-iotests/iotests.py
qcow2: Move error check of local_err near its assignment
[mirror_qemu.git] / tests / qemu-iotests / iotests.py
CommitLineData
f03868bd 1from __future__ import print_function
f345cfd0
SH
2# Common utilities and Python wrappers for qemu-iotests
3#
4# Copyright (C) 2012 IBM Corp.
5#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
19
c1c71e49 20import errno
f345cfd0
SH
21import os
22import re
23import subprocess
4f450568 24import string
f345cfd0 25import unittest
ed338bb0 26import sys
2499a096 27import struct
74f69050 28import json
2c93c5cb 29import signal
43851b5b 30import logging
ef6e9228 31import atexit
2d894bee 32import io
0706e87d 33from collections import OrderedDict
f345cfd0 34
8f8fd9ed
CR
35sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
36from qemu import qtest
02f3a911 37
7e693a05 38assert sys.version_info >= (3,6)
f345cfd0 39
934659c4 40# This will not work if arguments contain spaces but is necessary if we
f345cfd0 41# want to support the override options that ./check supports.
934659c4
HR
42qemu_img_args = [os.environ.get('QEMU_IMG_PROG', 'qemu-img')]
43if os.environ.get('QEMU_IMG_OPTIONS'):
44 qemu_img_args += os.environ['QEMU_IMG_OPTIONS'].strip().split(' ')
45
46qemu_io_args = [os.environ.get('QEMU_IO_PROG', 'qemu-io')]
47if os.environ.get('QEMU_IO_OPTIONS'):
48 qemu_io_args += os.environ['QEMU_IO_OPTIONS'].strip().split(' ')
49
a4d925f8
AS
50qemu_io_args_no_fmt = [os.environ.get('QEMU_IO_PROG', 'qemu-io')]
51if os.environ.get('QEMU_IO_OPTIONS_NO_FMT'):
52 qemu_io_args_no_fmt += \
53 os.environ['QEMU_IO_OPTIONS_NO_FMT'].strip().split(' ')
54
bec87774
HR
55qemu_nbd_args = [os.environ.get('QEMU_NBD_PROG', 'qemu-nbd')]
56if os.environ.get('QEMU_NBD_OPTIONS'):
57 qemu_nbd_args += os.environ['QEMU_NBD_OPTIONS'].strip().split(' ')
58
4c44b4a4 59qemu_prog = os.environ.get('QEMU_PROG', 'qemu')
66613974 60qemu_opts = os.environ.get('QEMU_OPTIONS', '').strip().split(' ')
f345cfd0
SH
61
62imgfmt = os.environ.get('IMGFMT', 'raw')
63imgproto = os.environ.get('IMGPROTO', 'file')
5a8fabf3 64test_dir = os.environ.get('TEST_DIR')
32558ce7 65sock_dir = os.environ.get('SOCK_DIR')
e8f8624d 66output_dir = os.environ.get('OUTPUT_DIR', '.')
58cc2ae1 67cachemode = os.environ.get('CACHEMODE')
e166b414 68qemu_default_machine = os.environ.get('QEMU_DEFAULT_MACHINE')
f345cfd0 69
30b005d9
WX
70socket_scm_helper = os.environ.get('SOCKET_SCM_HELPER', 'socket_scm_helper')
71
85a353a0 72luks_default_secret_object = 'secret,id=keysec0,data=' + \
58ebcb65 73 os.environ.get('IMGKEYSECRET', '')
85a353a0
VSO
74luks_default_key_secret_opt = 'key-secret=keysec0'
75
76
f345cfd0
SH
77def qemu_img(*args):
78 '''Run qemu-img and return the exit code'''
79 devnull = open('/dev/null', 'r+')
2ef6093c
HR
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
f345cfd0 84
8a57a4be 85def ordered_qmp(qmsg, conv_keys=True):
039be85c
JS
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()):
8a57a4be
HR
92 if conv_keys:
93 k = k.replace('_', '-')
94 od[k] = ordered_qmp(v, conv_keys=False)
039be85c
JS
95 return od
96 return qmsg
0706e87d 97
85a353a0
VSO
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
d2ef210c 117def qemu_img_verbose(*args):
993d46ce 118 '''Run qemu-img without suppressing its output and return the exit code'''
2ef6093c
HR
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
d2ef210c 123
3677e6f6
HR
124def qemu_img_pipe(*args):
125 '''Run qemu-img and return its output'''
491e5e85
DB
126 subp = subprocess.Popen(qemu_img_args + list(args),
127 stdout=subprocess.PIPE,
8eb5e674
HR
128 stderr=subprocess.STDOUT,
129 universal_newlines=True)
2ef6093c
HR
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]
3677e6f6 134
ac6fb43e
KW
135def qemu_img_log(*args):
136 result = qemu_img_pipe(*args)
137 log(result, filters=[filter_testfiles])
138 return result
139
5ba141dc
KW
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)
6b605ade
KW
150 if not filter_path:
151 filter_path = filename
152 log(filter_img_info(output, filter_path))
153
f345cfd0
SH
154def qemu_io(*args):
155 '''Run qemu-io and return the stdout data'''
156 args = qemu_io_args + list(args)
491e5e85 157 subp = subprocess.Popen(args, stdout=subprocess.PIPE,
8eb5e674
HR
158 stderr=subprocess.STDOUT,
159 universal_newlines=True)
2ef6093c
HR
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]
f345cfd0 164
a96f0350
KW
165def qemu_io_log(*args):
166 result = qemu_io(*args)
167 log(result, filters=[filter_testfiles, filter_qemu_io])
168 return result
169
745f2bf4
HR
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
23ee0ec2
VSO
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
f357576f
JS
186def get_virtio_scsi_device():
187 if qemu_default_machine == 's390-ccw-virtio':
188 return 'virtio-scsi-ccw'
189 return 'virtio-scsi-pci'
9fa90eec
VSO
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,
8eb5e674
HR
196 stderr=subprocess.STDOUT,
197 universal_newlines=True)
9fa90eec
VSO
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')
f544adf7 226 self._p.stdin.flush()
9fa90eec
VSO
227 return self._read_output()
228
229
bec87774
HR
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
6177b584 234def qemu_nbd_early_pipe(*args):
e1e6eccd 235 '''Run qemu-nbd in daemon mode and return both the parent's exit code
6177b584 236 and its output in case of an error'''
e1e6eccd
HR
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))))
6177b584
HR
246 if exitcode == 0:
247 return exitcode, ''
248 else:
249 return exitcode, subp.communicate()[0]
e1e6eccd 250
23ee0ec2
VSO
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
e1b5c51f 255def compare_images(img1, img2, fmt1=imgfmt, fmt2=imgfmt):
3a3918c3 256 '''Return True if two image files are identical'''
e1b5c51f
PB
257 return qemu_img('compare', '-f', fmt1,
258 '-F', fmt2, img1, img2) == 0
3a3918c3 259
2499a096
SH
260def create_image(name, size):
261 '''Create a fully-allocated raw image with sector markers'''
8eb5e674 262 file = open(name, 'wb')
2499a096
SH
263 i = 0
264 while i < size:
9a3a9a63 265 sector = struct.pack('>l504xl', i // 512, i // 512)
2499a096
SH
266 file.write(sector)
267 i = i + 512
268 file.close()
269
74f69050
FZ
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
011a5761 275def is_str(val):
7e693a05 276 return isinstance(val, str)
011a5761 277
a2d1c8fd
DB
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
12314f2d
SH
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
08fcd611
JS
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
e234398a
KW
319def filter_testfiles(msg):
320 prefix = os.path.join(test_dir, "%s-" % (os.getpid()))
321 return msg.replace(prefix, 'TEST_DIR/PID-')
322
08fcd611
JS
323def filter_qmp_testfiles(qmsg):
324 def _filter(key, value):
56a6e5d0 325 if is_str(value):
08fcd611
JS
326 return filter_testfiles(value)
327 return value
328 return filter_qmp(qmsg, _filter)
329
fa1151f8
JS
330def filter_generated_node_ids(msg):
331 return re.sub("#block[0-9]+", "NODE_NAME", msg)
332
6b605ade
KW
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)
bab4feb2 342 line = re.sub('cid: [0-9]+', 'cid: XXXXXXXXXX', line)
6b605ade
KW
343 lines.append(line)
344 return '\n'.join(lines)
345
f2ea0b20
HR
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
55cd64ea
JS
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.'''
a2d1c8fd
DB
359 for flt in filters:
360 msg = flt(msg)
0706e87d 361 if isinstance(msg, dict) or isinstance(msg, list):
55cd64ea
JS
362 # Python < 3.4 needs to know not to add whitespace when pretty-printing:
363 separators = (', ', ': ') if indent is None else (',', ': ')
0706e87d
JS
364 # Don't sort if it's already sorted
365 do_sort = not isinstance(msg, OrderedDict)
55cd64ea
JS
366 print(json.dumps(msg, sort_keys=do_sort,
367 indent=indent, separators=separators))
e21b5f34
HR
368 else:
369 print(msg)
a2d1c8fd 370
2c93c5cb
KW
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
de263986
JS
385def file_pattern(name):
386 return "{0}-{1}".format(os.getpid(), name)
f4844ac0 387
de263986
JS
388class FilePaths(object):
389 """
390 FilePaths is an auto-generated filename that cleans itself up.
f4844ac0
SH
391
392 Use this context manager to generate filenames and ensure that the file
393 gets deleted::
394
de263986 395 with FilePaths(['test.img']) as img_path:
f4844ac0
SH
396 qemu_img('create', img_path, '1G')
397 # migration_sock_path is automatically deleted
de263986 398 """
93b78ea5 399 def __init__(self, names, base_dir=test_dir):
de263986
JS
400 self.paths = []
401 for name in names:
93b78ea5 402 self.paths.append(os.path.join(base_dir, file_pattern(name)))
f4844ac0
SH
403
404 def __enter__(self):
de263986 405 return self.paths
f4844ac0
SH
406
407 def __exit__(self, exc_type, exc_val, exc_tb):
408 try:
de263986
JS
409 for path in self.paths:
410 os.remove(path)
f4844ac0
SH
411 except OSError:
412 pass
413 return False
414
de263986
JS
415class FilePath(FilePaths):
416 """
417 FilePath is a specialization of FilePaths that takes a single filename.
418 """
93b78ea5
HR
419 def __init__(self, name, base_dir=test_dir):
420 super(FilePath, self).__init__([name], base_dir)
de263986
JS
421
422 def __enter__(self):
423 return self.paths[0]
f4844ac0 424
ef6e9228
VSO
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
93b78ea5 433def file_path(*names, base_dir=test_dir):
ef6e9228
VSO
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:
de263986 448 filename = file_pattern(name)
93b78ea5 449 path = os.path.join(base_dir, filename)
ef6e9228
VSO
450 file_path_remover.paths.append(path)
451 paths.append(path)
452
453 return paths[0] if len(paths) == 1 else paths
454
5a259e86
KW
455def remote_filename(path):
456 if imgproto == 'file':
457 return path
458 elif imgproto == 'ssh':
b8c1f901 459 return "ssh://%s@127.0.0.1:22%s" % (os.environ.get('USER'), path)
5a259e86
KW
460 else:
461 raise Exception("Protocol %s not supported" % (imgproto))
ef6e9228 462
4c44b4a4 463class VM(qtest.QEMUQtestMachine):
f345cfd0
SH
464 '''A QEMU VM'''
465
5fcbdf50
HR
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,
32558ce7
HR
470 socket_scm_helper=socket_scm_helper,
471 sock_dir=sock_dir)
f345cfd0 472 self._num_drives = 0
30b005d9 473
ccc15f7d
SH
474 def add_object(self, opts):
475 self._args.append('-object')
476 self._args.append(opts)
477 return self
478
486b88bd
KW
479 def add_device(self, opts):
480 self._args.append('-device')
481 self._args.append(opts)
482 return self
483
78b666f4
FZ
484 def add_drive_raw(self, opts):
485 self._args.append('-drive')
486 self._args.append(opts)
487 return self
488
e1b5c51f 489 def add_drive(self, path, opts='', interface='virtio', format=imgfmt):
f345cfd0 490 '''Add a virtio-blk drive to the VM'''
8e492253 491 options = ['if=%s' % interface,
f345cfd0 492 'id=drive%d' % self._num_drives]
8e492253
HR
493
494 if path is not None:
495 options.append('file=%s' % path)
e1b5c51f 496 options.append('format=%s' % format)
fc17c259 497 options.append('cache=%s' % cachemode)
8e492253 498
f345cfd0
SH
499 if opts:
500 options.append(opts)
501
85a353a0
VSO
502 if format == 'luks' and 'key-secret' not in opts:
503 # default luks support
504 if luks_default_secret_object not in self._args:
505 self.add_object(luks_default_secret_object)
506
507 options.append(luks_default_key_secret_opt)
508
f345cfd0
SH
509 self._args.append('-drive')
510 self._args.append(','.join(options))
511 self._num_drives += 1
512 return self
513
5694923a
HR
514 def add_blockdev(self, opts):
515 self._args.append('-blockdev')
516 if isinstance(opts, str):
517 self._args.append(opts)
518 else:
519 self._args.append(','.join(opts))
520 return self
521
12314f2d
SH
522 def add_incoming(self, addr):
523 self._args.append('-incoming')
524 self._args.append(addr)
525 return self
526
3cf53c77
FZ
527 def pause_drive(self, drive, event=None):
528 '''Pause drive r/w operations'''
529 if not event:
530 self.pause_drive(drive, "read_aio")
531 self.pause_drive(drive, "write_aio")
532 return
533 self.qmp('human-monitor-command',
534 command_line='qemu-io %s "break %s bp_%s"' % (drive, event, drive))
535
536 def resume_drive(self, drive):
537 self.qmp('human-monitor-command',
538 command_line='qemu-io %s "remove_break bp_%s"' % (drive, drive))
539
e3409362
IM
540 def hmp_qemu_io(self, drive, cmd):
541 '''Write to a given drive using an HMP command'''
542 return self.qmp('human-monitor-command',
543 command_line='qemu-io %s "%s"' % (drive, cmd))
544
62a94288
KW
545 def flatten_qmp_object(self, obj, output=None, basestr=''):
546 if output is None:
547 output = dict()
548 if isinstance(obj, list):
549 for i in range(len(obj)):
550 self.flatten_qmp_object(obj[i], output, basestr + str(i) + '.')
551 elif isinstance(obj, dict):
552 for key in obj:
553 self.flatten_qmp_object(obj[key], output, basestr + key + '.')
554 else:
555 output[basestr[:-1]] = obj # Strip trailing '.'
556 return output
557
558 def qmp_to_opts(self, obj):
559 obj = self.flatten_qmp_object(obj)
560 output_list = list()
561 for key in obj:
562 output_list += [key + '=' + obj[key]]
563 return ','.join(output_list)
564
8b6f5f8b 565 def get_qmp_events_filtered(self, wait=60.0):
5ad1dbf7
KW
566 result = []
567 for ev in self.get_qmp_events(wait=wait):
568 result.append(filter_qmp_event(ev))
569 return result
62a94288 570
55cd64ea 571 def qmp_log(self, cmd, filters=[], indent=None, **kwargs):
0706e87d
JS
572 full_cmd = OrderedDict((
573 ("execute", cmd),
039be85c 574 ("arguments", ordered_qmp(kwargs))
0706e87d 575 ))
55cd64ea 576 log(full_cmd, filters, indent=indent)
e234398a 577 result = self.qmp(cmd, **kwargs)
55cd64ea 578 log(result, filters, indent=indent)
e234398a
KW
579 return result
580
6a4e88e1 581 # Returns None on success, and an error string on failure
ac6fb43e 582 def run_job(self, job, auto_finalize=True, auto_dismiss=False,
d443b74b
JS
583 pre_finalize=None, cancel=False, use_log=True, wait=60.0):
584 """
585 run_job moves a job from creation through to dismissal.
586
587 :param job: String. ID of recently-launched job
588 :param auto_finalize: Bool. True if the job was launched with
589 auto_finalize. Defaults to True.
590 :param auto_dismiss: Bool. True if the job was launched with
591 auto_dismiss=True. Defaults to False.
592 :param pre_finalize: Callback. A callable that takes no arguments to be
593 invoked prior to issuing job-finalize, if any.
594 :param cancel: Bool. When true, cancels the job after the pre_finalize
595 callback.
596 :param use_log: Bool. When false, does not log QMP messages.
597 :param wait: Float. Timeout value specifying how long to wait for any
598 event, in seconds. Defaults to 60.0.
599 """
d6a79af0
JS
600 match_device = {'data': {'device': job}}
601 match_id = {'data': {'id': job}}
602 events = [
603 ('BLOCK_JOB_COMPLETED', match_device),
604 ('BLOCK_JOB_CANCELLED', match_device),
605 ('BLOCK_JOB_ERROR', match_device),
606 ('BLOCK_JOB_READY', match_device),
607 ('BLOCK_JOB_PENDING', match_id),
608 ('JOB_STATUS_CHANGE', match_id)
609 ]
6a4e88e1 610 error = None
fc47d851 611 while True:
55824e09 612 ev = filter_qmp_event(self.events_wait(events, timeout=wait))
d6a79af0 613 if ev['event'] != 'JOB_STATUS_CHANGE':
15427f63
HR
614 if use_log:
615 log(ev)
d6a79af0
JS
616 continue
617 status = ev['data']['status']
618 if status == 'aborting':
619 result = self.qmp('query-jobs')
620 for j in result['return']:
621 if j['id'] == job:
622 error = j['error']
15427f63
HR
623 if use_log:
624 log('Job failed: %s' % (j['error']))
4688c4e3
KW
625 elif status == 'ready':
626 self.qmp_log('job-complete', id=job)
d6a79af0
JS
627 elif status == 'pending' and not auto_finalize:
628 if pre_finalize:
629 pre_finalize()
d443b74b
JS
630 if cancel and use_log:
631 self.qmp_log('job-cancel', id=job)
632 elif cancel:
633 self.qmp('job-cancel', id=job)
634 elif use_log:
15427f63
HR
635 self.qmp_log('job-finalize', id=job)
636 else:
637 self.qmp('job-finalize', id=job)
d6a79af0 638 elif status == 'concluded' and not auto_dismiss:
15427f63
HR
639 if use_log:
640 self.qmp_log('job-dismiss', id=job)
641 else:
642 self.qmp('job-dismiss', id=job)
d6a79af0
JS
643 elif status == 'null':
644 return error
fc47d851 645
980448f1
KW
646 def enable_migration_events(self, name):
647 log('Enabling migration QMP events on %s...' % name)
648 log(self.qmp('migrate-set-capabilities', capabilities=[
649 {
650 'capability': 'events',
651 'state': True
652 }
653 ]))
654
655 def wait_migration(self):
656 while True:
657 event = self.event_wait('MIGRATION')
658 log(event, filters=[filter_qmp_event])
659 if event['data']['status'] == 'completed':
660 break
661
ef7afd63
HR
662 def node_info(self, node_name):
663 nodes = self.qmp('query-named-block-nodes')
664 for x in nodes['return']:
665 if x['node-name'] == node_name:
666 return x
667 return None
668
5c4343b8
VSO
669 def query_bitmaps(self):
670 res = self.qmp("query-named-block-nodes")
671 return {device['node-name']: device['dirty-bitmaps']
672 for device in res['return'] if 'dirty-bitmaps' in device}
673
674 def get_bitmap(self, node_name, bitmap_name, recording=None, bitmaps=None):
675 """
676 get a specific bitmap from the object returned by query_bitmaps.
677 :param recording: If specified, filter results by the specified value.
678 :param bitmaps: If specified, use it instead of call query_bitmaps()
679 """
680 if bitmaps is None:
681 bitmaps = self.query_bitmaps()
682
683 for bitmap in bitmaps[node_name]:
684 if bitmap.get('name', '') == bitmap_name:
685 if recording is None:
686 return bitmap
687 elif bitmap.get('recording') == recording:
688 return bitmap
689 return None
690
691 def check_bitmap_status(self, node_name, bitmap_name, fields):
692 ret = self.get_bitmap(node_name, bitmap_name)
693
694 return fields.items() <= ret.items()
695
7898f74e 696
f345cfd0
SH
697index_re = re.compile(r'([^\[]+)\[([^\]]+)\]')
698
699class QMPTestCase(unittest.TestCase):
700 '''Abstract base class for QMP test cases'''
701
702 def dictpath(self, d, path):
703 '''Traverse a path in a nested dict'''
704 for component in path.split('/'):
705 m = index_re.match(component)
706 if m:
707 component, idx = m.groups()
708 idx = int(idx)
709
710 if not isinstance(d, dict) or component not in d:
711 self.fail('failed path traversal for "%s" in "%s"' % (path, str(d)))
712 d = d[component]
713
714 if m:
715 if not isinstance(d, list):
716 self.fail('path component "%s" in "%s" is not a list in "%s"' % (component, path, str(d)))
717 try:
718 d = d[idx]
719 except IndexError:
720 self.fail('invalid index "%s" in path "%s" in "%s"' % (idx, path, str(d)))
721 return d
722
90f0b711
PB
723 def assert_qmp_absent(self, d, path):
724 try:
725 result = self.dictpath(d, path)
726 except AssertionError:
727 return
728 self.fail('path "%s" has value "%s"' % (path, str(result)))
729
f345cfd0 730 def assert_qmp(self, d, path, value):
a93a42bd
HR
731 '''Assert that the value for a specific path in a QMP dict
732 matches. When given a list of values, assert that any of
733 them matches.'''
734
f345cfd0 735 result = self.dictpath(d, path)
a93a42bd
HR
736
737 # [] makes no sense as a list of valid values, so treat it as
738 # an actual single value.
739 if isinstance(value, list) and value != []:
740 for v in value:
741 if result == v:
742 return
743 self.fail('no match for "%s" in %s' % (str(result), str(value)))
744 else:
745 self.assertEqual(result, value,
dbf231d7
VSO
746 '"%s" is "%s", expected "%s"'
747 % (path, str(result), str(value)))
f345cfd0 748
ecc1c88e
SH
749 def assert_no_active_block_jobs(self):
750 result = self.vm.qmp('query-block-jobs')
751 self.assert_qmp(result, 'return', [])
752
e71fc0ba
FZ
753 def assert_has_block_node(self, node_name=None, file_name=None):
754 """Issue a query-named-block-nodes and assert node_name and/or
755 file_name is present in the result"""
756 def check_equal_or_none(a, b):
757 return a == None or b == None or a == b
758 assert node_name or file_name
759 result = self.vm.qmp('query-named-block-nodes')
760 for x in result["return"]:
761 if check_equal_or_none(x.get("node-name"), node_name) and \
762 check_equal_or_none(x.get("file"), file_name):
763 return
764 self.assertTrue(False, "Cannot find %s %s in result:\n%s" % \
765 (node_name, file_name, result))
766
e07375f5
HR
767 def assert_json_filename_equal(self, json_filename, reference):
768 '''Asserts that the given filename is a json: filename and that its
769 content is equal to the given reference object'''
770 self.assertEqual(json_filename[:5], 'json:')
62a94288
KW
771 self.assertEqual(self.vm.flatten_qmp_object(json.loads(json_filename[5:])),
772 self.vm.flatten_qmp_object(reference))
e07375f5 773
8b6f5f8b 774 def cancel_and_wait(self, drive='drive0', force=False, resume=False, wait=60.0):
2575fe16
SH
775 '''Cancel a block job and wait for it to finish, returning the event'''
776 result = self.vm.qmp('block-job-cancel', device=drive, force=force)
777 self.assert_qmp(result, 'return', {})
778
3cf53c77
FZ
779 if resume:
780 self.vm.resume_drive(drive)
781
2575fe16
SH
782 cancelled = False
783 result = None
784 while not cancelled:
8b6f5f8b 785 for event in self.vm.get_qmp_events(wait=wait):
2575fe16
SH
786 if event['event'] == 'BLOCK_JOB_COMPLETED' or \
787 event['event'] == 'BLOCK_JOB_CANCELLED':
788 self.assert_qmp(event, 'data/device', drive)
789 result = event
790 cancelled = True
1dac83f1
KW
791 elif event['event'] == 'JOB_STATUS_CHANGE':
792 self.assert_qmp(event, 'data/id', drive)
793
2575fe16
SH
794
795 self.assert_no_active_block_jobs()
796 return result
797
8b6f5f8b 798 def wait_until_completed(self, drive='drive0', check_offset=True, wait=60.0):
0dbe8a1b 799 '''Wait for a block job to finish, returning the event'''
c3988519 800 while True:
8b6f5f8b 801 for event in self.vm.get_qmp_events(wait=wait):
0dbe8a1b
SH
802 if event['event'] == 'BLOCK_JOB_COMPLETED':
803 self.assert_qmp(event, 'data/device', drive)
804 self.assert_qmp_absent(event, 'data/error')
9974ad40 805 if check_offset:
1d3ba15a 806 self.assert_qmp(event, 'data/offset', event['data']['len'])
c3988519
PX
807 self.assert_no_active_block_jobs()
808 return event
1dac83f1
KW
809 elif event['event'] == 'JOB_STATUS_CHANGE':
810 self.assert_qmp(event, 'data/id', drive)
0dbe8a1b 811
866323f3
FZ
812 def wait_ready(self, drive='drive0'):
813 '''Wait until a block job BLOCK_JOB_READY event'''
d7b25297
FZ
814 f = {'data': {'type': 'mirror', 'device': drive } }
815 event = self.vm.event_wait(name='BLOCK_JOB_READY', match=f)
866323f3
FZ
816
817 def wait_ready_and_cancel(self, drive='drive0'):
818 self.wait_ready(drive=drive)
819 event = self.cancel_and_wait(drive=drive)
fa1cfb40 820 self.assertEqual(event['event'], 'BLOCK_JOB_COMPLETED')
866323f3
FZ
821 self.assert_qmp(event, 'data/type', 'mirror')
822 self.assert_qmp(event, 'data/offset', event['data']['len'])
823
824 def complete_and_wait(self, drive='drive0', wait_ready=True):
825 '''Complete a block job and wait for it to finish'''
826 if wait_ready:
827 self.wait_ready(drive=drive)
828
829 result = self.vm.qmp('block-job-complete', device=drive)
830 self.assert_qmp(result, 'return', {})
831
832 event = self.wait_until_completed(drive=drive)
833 self.assert_qmp(event, 'data/type', 'mirror')
834
f03d9d24 835 def pause_wait(self, job_id='job0'):
2c93c5cb
KW
836 with Timeout(1, "Timeout waiting for job to pause"):
837 while True:
838 result = self.vm.qmp('query-block-jobs')
c1bac161 839 found = False
2c93c5cb 840 for job in result['return']:
c1bac161
VSO
841 if job['device'] == job_id:
842 found = True
843 if job['paused'] == True and job['busy'] == False:
844 return job
845 break
846 assert found
2c93c5cb 847
f03d9d24
JS
848 def pause_job(self, job_id='job0', wait=True):
849 result = self.vm.qmp('block-job-pause', device=job_id)
850 self.assert_qmp(result, 'return', {})
851 if wait:
852 return self.pause_wait(job_id)
853 return result
854
6be01225
HR
855 def case_skip(self, reason):
856 '''Skip this test case'''
857 case_notrun(reason)
858 self.skipTest(reason)
859
2c93c5cb 860
f345cfd0
SH
861def notrun(reason):
862 '''Skip this test suite'''
863 # Each test in qemu-iotests has a number ("seq")
864 seq = os.path.basename(sys.argv[0])
865
ce090f65 866 open('%s/%s.notrun' % (output_dir, seq), 'w').write(reason + '\n')
f03868bd 867 print('%s not run: %s' % (seq, reason))
f345cfd0
SH
868 sys.exit(0)
869
57ed557f 870def case_notrun(reason):
6be01225
HR
871 '''Mark this test case as not having been run (without actually
872 skipping it, that is left to the caller). See
873 QMPTestCase.case_skip() for a variant that actually skips the
874 current test case.'''
875
57ed557f
AS
876 # Each test in qemu-iotests has a number ("seq")
877 seq = os.path.basename(sys.argv[0])
878
879 open('%s/%s.casenotrun' % (output_dir, seq), 'a').write(
880 ' [case not run] ' + reason + '\n')
881
3f5c4076 882def verify_image_format(supported_fmts=[], unsupported_fmts=[]):
f48351d2
VSO
883 assert not (supported_fmts and unsupported_fmts)
884
885 if 'generic' in supported_fmts and \
886 os.environ.get('IMGFMT_GENERIC', 'true') == 'true':
887 # similar to
888 # _supported_fmt generic
889 # for bash tests
890 return
891
892 not_sup = supported_fmts and (imgfmt not in supported_fmts)
893 if not_sup or (imgfmt in unsupported_fmts):
3f5c4076 894 notrun('not suitable for this image format: %s' % imgfmt)
f345cfd0 895
5a259e86
KW
896def verify_protocol(supported=[], unsupported=[]):
897 assert not (supported and unsupported)
898
899 if 'generic' in supported:
900 return
901
902 not_sup = supported and (imgproto not in supported)
903 if not_sup or (imgproto in unsupported):
904 notrun('not suitable for this protocol: %s' % imgproto)
905
c6a92369 906def verify_platform(supported_oses=['linux']):
79e7a019 907 if True not in [sys.platform.startswith(x) for x in supported_oses]:
bc521696
FZ
908 notrun('not suitable for this OS: %s' % sys.platform)
909
ac8bd439
VSO
910def verify_cache_mode(supported_cache_modes=[]):
911 if supported_cache_modes and (cachemode not in supported_cache_modes):
912 notrun('not suitable for this cache mode: %s' % cachemode)
913
b0f90495
AG
914def supports_quorum():
915 return 'quorum' in qemu_img_pipe('--help')
916
3f647b51
SS
917def verify_quorum():
918 '''Skip test suite if quorum support is not available'''
b0f90495 919 if not supports_quorum():
3f647b51
SS
920 notrun('quorum support missing')
921
57ed557f
AS
922def qemu_pipe(*args):
923 '''Run qemu with an option to print something and exit (e.g. a help option),
924 and return its output'''
925 args = [qemu_prog] + qemu_opts + list(args)
926 subp = subprocess.Popen(args, stdout=subprocess.PIPE,
927 stderr=subprocess.STDOUT,
928 universal_newlines=True)
929 exitcode = subp.wait()
930 if exitcode < 0:
931 sys.stderr.write('qemu received signal %i: %s\n' % (-exitcode,
932 ' '.join(args)))
933 return subp.communicate()[0]
934
935def supported_formats(read_only=False):
936 '''Set 'read_only' to True to check ro-whitelist
937 Otherwise, rw-whitelist is checked'''
767de537
HR
938
939 if not hasattr(supported_formats, "formats"):
940 supported_formats.formats = {}
941
942 if read_only not in supported_formats.formats:
943 format_message = qemu_pipe("-drive", "format=help")
944 line = 1 if read_only else 0
945 supported_formats.formats[read_only] = \
946 format_message.splitlines()[line].split(":")[1].split()
947
948 return supported_formats.formats[read_only]
57ed557f
AS
949
950def skip_if_unsupported(required_formats=[], read_only=False):
951 '''Skip Test Decorator
952 Runs the test if all the required formats are whitelisted'''
953 def skip_test_decorator(func):
e6067a95 954 def func_wrapper(test_case: QMPTestCase, *args, **kwargs):
7448be83
HR
955 if callable(required_formats):
956 fmts = required_formats(test_case)
957 else:
958 fmts = required_formats
959
960 usf_list = list(set(fmts) - set(supported_formats(read_only)))
57ed557f 961 if usf_list:
e6067a95
HR
962 test_case.case_skip('{}: formats {} are not whitelisted'.format(
963 test_case, usf_list))
57ed557f 964 else:
e6067a95 965 return func(test_case, *args, **kwargs)
57ed557f
AS
966 return func_wrapper
967 return skip_test_decorator
968
d926f4dd
KW
969def skip_if_user_is_root(func):
970 '''Skip Test Decorator
971 Runs the test only without root permissions'''
972 def func_wrapper(*args, **kwargs):
973 if os.getuid() == 0:
974 case_notrun('{}: cannot be run as root'.format(args[0]))
975 else:
976 return func(*args, **kwargs)
977 return func_wrapper
978
456a2d5a
JS
979def execute_unittest(output, verbosity, debug):
980 runner = unittest.TextTestRunner(stream=output, descriptions=True,
981 verbosity=verbosity)
982 try:
983 # unittest.main() will use sys.exit(); so expect a SystemExit
984 # exception
985 unittest.main(testRunner=runner)
986 finally:
987 if not debug:
6be01225
HR
988 out = output.getvalue()
989 out = re.sub(r'Ran (\d+) tests? in [\d.]+s', r'Ran \1 tests', out)
990
991 # Hide skipped tests from the reference output
992 out = re.sub(r'OK \(skipped=\d+\)', 'OK', out)
993 out_first_line, out_rest = out.split('\n', 1)
994 out = out_first_line.replace('s', '.') + '\n' + out_rest
995
996 sys.stderr.write(out)
c6a92369 997
456a2d5a
JS
998def execute_test(test_function=None,
999 supported_fmts=[], supported_oses=['linux'],
88d2aa53
HR
1000 supported_cache_modes=[], unsupported_fmts=[],
1001 supported_protocols=[], unsupported_protocols=[]):
456a2d5a 1002 """Run either unittest or script-style tests."""
c0088d79 1003
5a8fabf3
SS
1004 # We are using TEST_DIR and QEMU_DEFAULT_MACHINE as proxies to
1005 # indicate that we're not being run via "check". There may be
1006 # other things set up by "check" that individual test cases rely
1007 # on.
1008 if test_dir is None or qemu_default_machine is None:
1009 sys.stderr.write('Please run this test via the "check" script\n')
1010 sys.exit(os.EX_USAGE)
1011
c6a92369
DB
1012 debug = '-d' in sys.argv
1013 verbosity = 1
febc8c86 1014 verify_image_format(supported_fmts, unsupported_fmts)
88d2aa53 1015 verify_protocol(supported_protocols, unsupported_protocols)
c6a92369 1016 verify_platform(supported_oses)
ac8bd439 1017 verify_cache_mode(supported_cache_modes)
c6a92369 1018
aa4f592a
FZ
1019 if debug:
1020 output = sys.stdout
1021 verbosity = 2
1022 sys.argv.remove('-d')
1023 else:
2d894bee
HR
1024 # We need to filter out the time taken from the output so that
1025 # qemu-iotest can reliably diff the results against master output.
7e693a05 1026 output = io.StringIO()
f345cfd0 1027
43851b5b
EH
1028 logging.basicConfig(level=(logging.DEBUG if debug else logging.WARN))
1029
456a2d5a
JS
1030 if not test_function:
1031 execute_unittest(output, verbosity, debug)
1032 else:
1033 test_function()
1034
1035def script_main(test_function, *args, **kwargs):
1036 """Run script-style tests outside of the unittest framework"""
1037 execute_test(test_function, *args, **kwargs)
f345cfd0 1038
456a2d5a
JS
1039def main(*args, **kwargs):
1040 """Run tests using the unittest framework"""
1041 execute_test(None, *args, **kwargs)