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