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