]> Git Repo - qemu.git/blame - tests/qemu-iotests/iotests.py
iotests: Add qemu_io_log()
[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://%[email protected]: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:
d6a79af0
JS
612 ev = filter_qmp_event(self.events_wait(events))
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']))
d6a79af0
JS
625 elif status == 'pending' and not auto_finalize:
626 if pre_finalize:
627 pre_finalize()
d443b74b
JS
628 if cancel and use_log:
629 self.qmp_log('job-cancel', id=job)
630 elif cancel:
631 self.qmp('job-cancel', id=job)
632 elif use_log:
15427f63
HR
633 self.qmp_log('job-finalize', id=job)
634 else:
635 self.qmp('job-finalize', id=job)
d6a79af0 636 elif status == 'concluded' and not auto_dismiss:
15427f63
HR
637 if use_log:
638 self.qmp_log('job-dismiss', id=job)
639 else:
640 self.qmp('job-dismiss', id=job)
d6a79af0
JS
641 elif status == 'null':
642 return error
fc47d851 643
980448f1
KW
644 def enable_migration_events(self, name):
645 log('Enabling migration QMP events on %s...' % name)
646 log(self.qmp('migrate-set-capabilities', capabilities=[
647 {
648 'capability': 'events',
649 'state': True
650 }
651 ]))
652
653 def wait_migration(self):
654 while True:
655 event = self.event_wait('MIGRATION')
656 log(event, filters=[filter_qmp_event])
657 if event['data']['status'] == 'completed':
658 break
659
ef7afd63
HR
660 def node_info(self, node_name):
661 nodes = self.qmp('query-named-block-nodes')
662 for x in nodes['return']:
663 if x['node-name'] == node_name:
664 return x
665 return None
666
5c4343b8
VSO
667 def query_bitmaps(self):
668 res = self.qmp("query-named-block-nodes")
669 return {device['node-name']: device['dirty-bitmaps']
670 for device in res['return'] if 'dirty-bitmaps' in device}
671
672 def get_bitmap(self, node_name, bitmap_name, recording=None, bitmaps=None):
673 """
674 get a specific bitmap from the object returned by query_bitmaps.
675 :param recording: If specified, filter results by the specified value.
676 :param bitmaps: If specified, use it instead of call query_bitmaps()
677 """
678 if bitmaps is None:
679 bitmaps = self.query_bitmaps()
680
681 for bitmap in bitmaps[node_name]:
682 if bitmap.get('name', '') == bitmap_name:
683 if recording is None:
684 return bitmap
685 elif bitmap.get('recording') == recording:
686 return bitmap
687 return None
688
689 def check_bitmap_status(self, node_name, bitmap_name, fields):
690 ret = self.get_bitmap(node_name, bitmap_name)
691
692 return fields.items() <= ret.items()
693
7898f74e 694
f345cfd0
SH
695index_re = re.compile(r'([^\[]+)\[([^\]]+)\]')
696
697class QMPTestCase(unittest.TestCase):
698 '''Abstract base class for QMP test cases'''
699
700 def dictpath(self, d, path):
701 '''Traverse a path in a nested dict'''
702 for component in path.split('/'):
703 m = index_re.match(component)
704 if m:
705 component, idx = m.groups()
706 idx = int(idx)
707
708 if not isinstance(d, dict) or component not in d:
709 self.fail('failed path traversal for "%s" in "%s"' % (path, str(d)))
710 d = d[component]
711
712 if m:
713 if not isinstance(d, list):
714 self.fail('path component "%s" in "%s" is not a list in "%s"' % (component, path, str(d)))
715 try:
716 d = d[idx]
717 except IndexError:
718 self.fail('invalid index "%s" in path "%s" in "%s"' % (idx, path, str(d)))
719 return d
720
90f0b711
PB
721 def assert_qmp_absent(self, d, path):
722 try:
723 result = self.dictpath(d, path)
724 except AssertionError:
725 return
726 self.fail('path "%s" has value "%s"' % (path, str(result)))
727
f345cfd0 728 def assert_qmp(self, d, path, value):
a93a42bd
HR
729 '''Assert that the value for a specific path in a QMP dict
730 matches. When given a list of values, assert that any of
731 them matches.'''
732
f345cfd0 733 result = self.dictpath(d, path)
a93a42bd
HR
734
735 # [] makes no sense as a list of valid values, so treat it as
736 # an actual single value.
737 if isinstance(value, list) and value != []:
738 for v in value:
739 if result == v:
740 return
741 self.fail('no match for "%s" in %s' % (str(result), str(value)))
742 else:
743 self.assertEqual(result, value,
dbf231d7
VSO
744 '"%s" is "%s", expected "%s"'
745 % (path, str(result), str(value)))
f345cfd0 746
ecc1c88e
SH
747 def assert_no_active_block_jobs(self):
748 result = self.vm.qmp('query-block-jobs')
749 self.assert_qmp(result, 'return', [])
750
e71fc0ba
FZ
751 def assert_has_block_node(self, node_name=None, file_name=None):
752 """Issue a query-named-block-nodes and assert node_name and/or
753 file_name is present in the result"""
754 def check_equal_or_none(a, b):
755 return a == None or b == None or a == b
756 assert node_name or file_name
757 result = self.vm.qmp('query-named-block-nodes')
758 for x in result["return"]:
759 if check_equal_or_none(x.get("node-name"), node_name) and \
760 check_equal_or_none(x.get("file"), file_name):
761 return
762 self.assertTrue(False, "Cannot find %s %s in result:\n%s" % \
763 (node_name, file_name, result))
764
e07375f5
HR
765 def assert_json_filename_equal(self, json_filename, reference):
766 '''Asserts that the given filename is a json: filename and that its
767 content is equal to the given reference object'''
768 self.assertEqual(json_filename[:5], 'json:')
62a94288
KW
769 self.assertEqual(self.vm.flatten_qmp_object(json.loads(json_filename[5:])),
770 self.vm.flatten_qmp_object(reference))
e07375f5 771
8b6f5f8b 772 def cancel_and_wait(self, drive='drive0', force=False, resume=False, wait=60.0):
2575fe16
SH
773 '''Cancel a block job and wait for it to finish, returning the event'''
774 result = self.vm.qmp('block-job-cancel', device=drive, force=force)
775 self.assert_qmp(result, 'return', {})
776
3cf53c77
FZ
777 if resume:
778 self.vm.resume_drive(drive)
779
2575fe16
SH
780 cancelled = False
781 result = None
782 while not cancelled:
8b6f5f8b 783 for event in self.vm.get_qmp_events(wait=wait):
2575fe16
SH
784 if event['event'] == 'BLOCK_JOB_COMPLETED' or \
785 event['event'] == 'BLOCK_JOB_CANCELLED':
786 self.assert_qmp(event, 'data/device', drive)
787 result = event
788 cancelled = True
1dac83f1
KW
789 elif event['event'] == 'JOB_STATUS_CHANGE':
790 self.assert_qmp(event, 'data/id', drive)
791
2575fe16
SH
792
793 self.assert_no_active_block_jobs()
794 return result
795
8b6f5f8b 796 def wait_until_completed(self, drive='drive0', check_offset=True, wait=60.0):
0dbe8a1b 797 '''Wait for a block job to finish, returning the event'''
c3988519 798 while True:
8b6f5f8b 799 for event in self.vm.get_qmp_events(wait=wait):
0dbe8a1b
SH
800 if event['event'] == 'BLOCK_JOB_COMPLETED':
801 self.assert_qmp(event, 'data/device', drive)
802 self.assert_qmp_absent(event, 'data/error')
9974ad40 803 if check_offset:
1d3ba15a 804 self.assert_qmp(event, 'data/offset', event['data']['len'])
c3988519
PX
805 self.assert_no_active_block_jobs()
806 return event
1dac83f1
KW
807 elif event['event'] == 'JOB_STATUS_CHANGE':
808 self.assert_qmp(event, 'data/id', drive)
0dbe8a1b 809
866323f3
FZ
810 def wait_ready(self, drive='drive0'):
811 '''Wait until a block job BLOCK_JOB_READY event'''
d7b25297
FZ
812 f = {'data': {'type': 'mirror', 'device': drive } }
813 event = self.vm.event_wait(name='BLOCK_JOB_READY', match=f)
866323f3
FZ
814
815 def wait_ready_and_cancel(self, drive='drive0'):
816 self.wait_ready(drive=drive)
817 event = self.cancel_and_wait(drive=drive)
fa1cfb40 818 self.assertEqual(event['event'], 'BLOCK_JOB_COMPLETED')
866323f3
FZ
819 self.assert_qmp(event, 'data/type', 'mirror')
820 self.assert_qmp(event, 'data/offset', event['data']['len'])
821
822 def complete_and_wait(self, drive='drive0', wait_ready=True):
823 '''Complete a block job and wait for it to finish'''
824 if wait_ready:
825 self.wait_ready(drive=drive)
826
827 result = self.vm.qmp('block-job-complete', device=drive)
828 self.assert_qmp(result, 'return', {})
829
830 event = self.wait_until_completed(drive=drive)
831 self.assert_qmp(event, 'data/type', 'mirror')
832
f03d9d24 833 def pause_wait(self, job_id='job0'):
2c93c5cb
KW
834 with Timeout(1, "Timeout waiting for job to pause"):
835 while True:
836 result = self.vm.qmp('query-block-jobs')
c1bac161 837 found = False
2c93c5cb 838 for job in result['return']:
c1bac161
VSO
839 if job['device'] == job_id:
840 found = True
841 if job['paused'] == True and job['busy'] == False:
842 return job
843 break
844 assert found
2c93c5cb 845
f03d9d24
JS
846 def pause_job(self, job_id='job0', wait=True):
847 result = self.vm.qmp('block-job-pause', device=job_id)
848 self.assert_qmp(result, 'return', {})
849 if wait:
850 return self.pause_wait(job_id)
851 return result
852
6be01225
HR
853 def case_skip(self, reason):
854 '''Skip this test case'''
855 case_notrun(reason)
856 self.skipTest(reason)
857
2c93c5cb 858
f345cfd0
SH
859def notrun(reason):
860 '''Skip this test suite'''
861 # Each test in qemu-iotests has a number ("seq")
862 seq = os.path.basename(sys.argv[0])
863
ce090f65 864 open('%s/%s.notrun' % (output_dir, seq), 'w').write(reason + '\n')
f03868bd 865 print('%s not run: %s' % (seq, reason))
f345cfd0
SH
866 sys.exit(0)
867
57ed557f 868def case_notrun(reason):
6be01225
HR
869 '''Mark this test case as not having been run (without actually
870 skipping it, that is left to the caller). See
871 QMPTestCase.case_skip() for a variant that actually skips the
872 current test case.'''
873
57ed557f
AS
874 # Each test in qemu-iotests has a number ("seq")
875 seq = os.path.basename(sys.argv[0])
876
877 open('%s/%s.casenotrun' % (output_dir, seq), 'a').write(
878 ' [case not run] ' + reason + '\n')
879
3f5c4076 880def verify_image_format(supported_fmts=[], unsupported_fmts=[]):
f48351d2
VSO
881 assert not (supported_fmts and unsupported_fmts)
882
883 if 'generic' in supported_fmts and \
884 os.environ.get('IMGFMT_GENERIC', 'true') == 'true':
885 # similar to
886 # _supported_fmt generic
887 # for bash tests
888 return
889
890 not_sup = supported_fmts and (imgfmt not in supported_fmts)
891 if not_sup or (imgfmt in unsupported_fmts):
3f5c4076 892 notrun('not suitable for this image format: %s' % imgfmt)
f345cfd0 893
5a259e86
KW
894def verify_protocol(supported=[], unsupported=[]):
895 assert not (supported and unsupported)
896
897 if 'generic' in supported:
898 return
899
900 not_sup = supported and (imgproto not in supported)
901 if not_sup or (imgproto in unsupported):
902 notrun('not suitable for this protocol: %s' % imgproto)
903
c6a92369 904def verify_platform(supported_oses=['linux']):
79e7a019 905 if True not in [sys.platform.startswith(x) for x in supported_oses]:
bc521696
FZ
906 notrun('not suitable for this OS: %s' % sys.platform)
907
ac8bd439
VSO
908def verify_cache_mode(supported_cache_modes=[]):
909 if supported_cache_modes and (cachemode not in supported_cache_modes):
910 notrun('not suitable for this cache mode: %s' % cachemode)
911
b0f90495
AG
912def supports_quorum():
913 return 'quorum' in qemu_img_pipe('--help')
914
3f647b51
SS
915def verify_quorum():
916 '''Skip test suite if quorum support is not available'''
b0f90495 917 if not supports_quorum():
3f647b51
SS
918 notrun('quorum support missing')
919
57ed557f
AS
920def qemu_pipe(*args):
921 '''Run qemu with an option to print something and exit (e.g. a help option),
922 and return its output'''
923 args = [qemu_prog] + qemu_opts + list(args)
924 subp = subprocess.Popen(args, stdout=subprocess.PIPE,
925 stderr=subprocess.STDOUT,
926 universal_newlines=True)
927 exitcode = subp.wait()
928 if exitcode < 0:
929 sys.stderr.write('qemu received signal %i: %s\n' % (-exitcode,
930 ' '.join(args)))
931 return subp.communicate()[0]
932
933def supported_formats(read_only=False):
934 '''Set 'read_only' to True to check ro-whitelist
935 Otherwise, rw-whitelist is checked'''
767de537
HR
936
937 if not hasattr(supported_formats, "formats"):
938 supported_formats.formats = {}
939
940 if read_only not in supported_formats.formats:
941 format_message = qemu_pipe("-drive", "format=help")
942 line = 1 if read_only else 0
943 supported_formats.formats[read_only] = \
944 format_message.splitlines()[line].split(":")[1].split()
945
946 return supported_formats.formats[read_only]
57ed557f
AS
947
948def skip_if_unsupported(required_formats=[], read_only=False):
949 '''Skip Test Decorator
950 Runs the test if all the required formats are whitelisted'''
951 def skip_test_decorator(func):
e6067a95 952 def func_wrapper(test_case: QMPTestCase, *args, **kwargs):
7448be83
HR
953 if callable(required_formats):
954 fmts = required_formats(test_case)
955 else:
956 fmts = required_formats
957
958 usf_list = list(set(fmts) - set(supported_formats(read_only)))
57ed557f 959 if usf_list:
e6067a95
HR
960 test_case.case_skip('{}: formats {} are not whitelisted'.format(
961 test_case, usf_list))
57ed557f 962 else:
e6067a95 963 return func(test_case, *args, **kwargs)
57ed557f
AS
964 return func_wrapper
965 return skip_test_decorator
966
d926f4dd
KW
967def skip_if_user_is_root(func):
968 '''Skip Test Decorator
969 Runs the test only without root permissions'''
970 def func_wrapper(*args, **kwargs):
971 if os.getuid() == 0:
972 case_notrun('{}: cannot be run as root'.format(args[0]))
973 else:
974 return func(*args, **kwargs)
975 return func_wrapper
976
456a2d5a
JS
977def execute_unittest(output, verbosity, debug):
978 runner = unittest.TextTestRunner(stream=output, descriptions=True,
979 verbosity=verbosity)
980 try:
981 # unittest.main() will use sys.exit(); so expect a SystemExit
982 # exception
983 unittest.main(testRunner=runner)
984 finally:
985 if not debug:
6be01225
HR
986 out = output.getvalue()
987 out = re.sub(r'Ran (\d+) tests? in [\d.]+s', r'Ran \1 tests', out)
988
989 # Hide skipped tests from the reference output
990 out = re.sub(r'OK \(skipped=\d+\)', 'OK', out)
991 out_first_line, out_rest = out.split('\n', 1)
992 out = out_first_line.replace('s', '.') + '\n' + out_rest
993
994 sys.stderr.write(out)
c6a92369 995
456a2d5a
JS
996def execute_test(test_function=None,
997 supported_fmts=[], supported_oses=['linux'],
88d2aa53
HR
998 supported_cache_modes=[], unsupported_fmts=[],
999 supported_protocols=[], unsupported_protocols=[]):
456a2d5a 1000 """Run either unittest or script-style tests."""
c0088d79 1001
5a8fabf3
SS
1002 # We are using TEST_DIR and QEMU_DEFAULT_MACHINE as proxies to
1003 # indicate that we're not being run via "check". There may be
1004 # other things set up by "check" that individual test cases rely
1005 # on.
1006 if test_dir is None or qemu_default_machine is None:
1007 sys.stderr.write('Please run this test via the "check" script\n')
1008 sys.exit(os.EX_USAGE)
1009
c6a92369
DB
1010 debug = '-d' in sys.argv
1011 verbosity = 1
febc8c86 1012 verify_image_format(supported_fmts, unsupported_fmts)
88d2aa53 1013 verify_protocol(supported_protocols, unsupported_protocols)
c6a92369 1014 verify_platform(supported_oses)
ac8bd439 1015 verify_cache_mode(supported_cache_modes)
c6a92369 1016
aa4f592a
FZ
1017 if debug:
1018 output = sys.stdout
1019 verbosity = 2
1020 sys.argv.remove('-d')
1021 else:
2d894bee
HR
1022 # We need to filter out the time taken from the output so that
1023 # qemu-iotest can reliably diff the results against master output.
7e693a05 1024 output = io.StringIO()
f345cfd0 1025
43851b5b
EH
1026 logging.basicConfig(level=(logging.DEBUG if debug else logging.WARN))
1027
456a2d5a
JS
1028 if not test_function:
1029 execute_unittest(output, verbosity, debug)
1030 else:
1031 test_function()
1032
1033def script_main(test_function, *args, **kwargs):
1034 """Run script-style tests outside of the unittest framework"""
1035 execute_test(test_function, *args, **kwargs)
f345cfd0 1036
456a2d5a
JS
1037def main(*args, **kwargs):
1038 """Run tests using the unittest framework"""
1039 execute_test(None, *args, **kwargs)
This page took 0.741322 seconds and 4 git commands to generate.