1# Common utilities and Python wrappers for qemu-iotests 2# 3# Copyright (C) 2012 IBM Corp. 4# 5# This program is free software; you can redistribute it and/or modify 6# it under the terms of the GNU General Public License as published by 7# the Free Software Foundation; either version 2 of the License, or 8# (at your option) any later version. 9# 10# This program is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with this program. If not, see <http://www.gnu.org/licenses/>. 17# 18 19import errno 20import os 21import re 22import subprocess 23import string 24import unittest 25import sys 26import struct 27import json 28import signal 29import logging 30import atexit 31import io 32from collections import OrderedDict 33import faulthandler 34 35sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python')) 36from qemu import qtest 37 38assert sys.version_info >= (3,6) 39 40faulthandler.enable() 41 42# This will not work if arguments contain spaces but is necessary if we 43# want to support the override options that ./check supports. 44qemu_img_args = [os.environ.get('QEMU_IMG_PROG', 'qemu-img')] 45if os.environ.get('QEMU_IMG_OPTIONS'): 46 qemu_img_args += os.environ['QEMU_IMG_OPTIONS'].strip().split(' ') 47 48qemu_io_args = [os.environ.get('QEMU_IO_PROG', 'qemu-io')] 49if os.environ.get('QEMU_IO_OPTIONS'): 50 qemu_io_args += os.environ['QEMU_IO_OPTIONS'].strip().split(' ') 51 52qemu_io_args_no_fmt = [os.environ.get('QEMU_IO_PROG', 'qemu-io')] 53if os.environ.get('QEMU_IO_OPTIONS_NO_FMT'): 54 qemu_io_args_no_fmt += \ 55 os.environ['QEMU_IO_OPTIONS_NO_FMT'].strip().split(' ') 56 57qemu_nbd_args = [os.environ.get('QEMU_NBD_PROG', 'qemu-nbd')] 58if os.environ.get('QEMU_NBD_OPTIONS'): 59 qemu_nbd_args += os.environ['QEMU_NBD_OPTIONS'].strip().split(' ') 60 61qemu_prog = os.environ.get('QEMU_PROG', 'qemu') 62qemu_opts = os.environ.get('QEMU_OPTIONS', '').strip().split(' ') 63 64imgfmt = os.environ.get('IMGFMT', 'raw') 65imgproto = os.environ.get('IMGPROTO', 'file') 66test_dir = os.environ.get('TEST_DIR') 67sock_dir = os.environ.get('SOCK_DIR') 68output_dir = os.environ.get('OUTPUT_DIR', '.') 69cachemode = os.environ.get('CACHEMODE') 70aiomode = os.environ.get('AIOMODE') 71qemu_default_machine = os.environ.get('QEMU_DEFAULT_MACHINE') 72 73socket_scm_helper = os.environ.get('SOCKET_SCM_HELPER', 'socket_scm_helper') 74 75luks_default_secret_object = 'secret,id=keysec0,data=' + \ 76 os.environ.get('IMGKEYSECRET', '') 77luks_default_key_secret_opt = 'key-secret=keysec0' 78 79 80def qemu_img(*args): 81 '''Run qemu-img and return the exit code''' 82 devnull = open('/dev/null', 'r+') 83 exitcode = subprocess.call(qemu_img_args + list(args), stdin=devnull, stdout=devnull) 84 if exitcode < 0: 85 sys.stderr.write('qemu-img received signal %i: %s\n' % (-exitcode, ' '.join(qemu_img_args + list(args)))) 86 return exitcode 87 88def ordered_qmp(qmsg, conv_keys=True): 89 # Dictionaries are not ordered prior to 3.6, therefore: 90 if isinstance(qmsg, list): 91 return [ordered_qmp(atom) for atom in qmsg] 92 if isinstance(qmsg, dict): 93 od = OrderedDict() 94 for k, v in sorted(qmsg.items()): 95 if conv_keys: 96 k = k.replace('_', '-') 97 od[k] = ordered_qmp(v, conv_keys=False) 98 return od 99 return qmsg 100 101def qemu_img_create(*args): 102 args = list(args) 103 104 # default luks support 105 if '-f' in args and args[args.index('-f') + 1] == 'luks': 106 if '-o' in args: 107 i = args.index('-o') 108 if 'key-secret' not in args[i + 1]: 109 args[i + 1].append(luks_default_key_secret_opt) 110 args.insert(i + 2, '--object') 111 args.insert(i + 3, luks_default_secret_object) 112 else: 113 args = ['-o', luks_default_key_secret_opt, 114 '--object', luks_default_secret_object] + args 115 116 args.insert(0, 'create') 117 118 return qemu_img(*args) 119 120def qemu_img_verbose(*args): 121 '''Run qemu-img without suppressing its output and return the exit code''' 122 exitcode = subprocess.call(qemu_img_args + list(args)) 123 if exitcode < 0: 124 sys.stderr.write('qemu-img received signal %i: %s\n' % (-exitcode, ' '.join(qemu_img_args + list(args)))) 125 return exitcode 126 127def qemu_img_pipe(*args): 128 '''Run qemu-img and return its output''' 129 subp = subprocess.Popen(qemu_img_args + list(args), 130 stdout=subprocess.PIPE, 131 stderr=subprocess.STDOUT, 132 universal_newlines=True) 133 exitcode = subp.wait() 134 if exitcode < 0: 135 sys.stderr.write('qemu-img received signal %i: %s\n' % (-exitcode, ' '.join(qemu_img_args + list(args)))) 136 return subp.communicate()[0] 137 138def qemu_img_log(*args): 139 result = qemu_img_pipe(*args) 140 log(result, filters=[filter_testfiles]) 141 return result 142 143def img_info_log(filename, filter_path=None, imgopts=False, extra_args=[]): 144 args = [ 'info' ] 145 if imgopts: 146 args.append('--image-opts') 147 else: 148 args += [ '-f', imgfmt ] 149 args += extra_args 150 args.append(filename) 151 152 output = qemu_img_pipe(*args) 153 if not filter_path: 154 filter_path = filename 155 log(filter_img_info(output, filter_path)) 156 157def qemu_io(*args): 158 '''Run qemu-io and return the stdout data''' 159 args = qemu_io_args + list(args) 160 subp = subprocess.Popen(args, stdout=subprocess.PIPE, 161 stderr=subprocess.STDOUT, 162 universal_newlines=True) 163 exitcode = subp.wait() 164 if exitcode < 0: 165 sys.stderr.write('qemu-io received signal %i: %s\n' % (-exitcode, ' '.join(args))) 166 return subp.communicate()[0] 167 168def qemu_io_log(*args): 169 result = qemu_io(*args) 170 log(result, filters=[filter_testfiles, filter_qemu_io]) 171 return result 172 173def qemu_io_silent(*args): 174 '''Run qemu-io and return the exit code, suppressing stdout''' 175 args = qemu_io_args + list(args) 176 exitcode = subprocess.call(args, stdout=open('/dev/null', 'w')) 177 if exitcode < 0: 178 sys.stderr.write('qemu-io received signal %i: %s\n' % 179 (-exitcode, ' '.join(args))) 180 return exitcode 181 182def qemu_io_silent_check(*args): 183 '''Run qemu-io and return the true if subprocess returned 0''' 184 args = qemu_io_args + list(args) 185 exitcode = subprocess.call(args, stdout=open('/dev/null', 'w'), 186 stderr=subprocess.STDOUT) 187 return exitcode == 0 188 189def get_virtio_scsi_device(): 190 if qemu_default_machine == 's390-ccw-virtio': 191 return 'virtio-scsi-ccw' 192 return 'virtio-scsi-pci' 193 194class QemuIoInteractive: 195 def __init__(self, *args): 196 self.args = qemu_io_args + list(args) 197 self._p = subprocess.Popen(self.args, stdin=subprocess.PIPE, 198 stdout=subprocess.PIPE, 199 stderr=subprocess.STDOUT, 200 universal_newlines=True) 201 assert self._p.stdout.read(9) == 'qemu-io> ' 202 203 def close(self): 204 self._p.communicate('q\n') 205 206 def _read_output(self): 207 pattern = 'qemu-io> ' 208 n = len(pattern) 209 pos = 0 210 s = [] 211 while pos != n: 212 c = self._p.stdout.read(1) 213 # check unexpected EOF 214 assert c != '' 215 s.append(c) 216 if c == pattern[pos]: 217 pos += 1 218 else: 219 pos = 0 220 221 return ''.join(s[:-n]) 222 223 def cmd(self, cmd): 224 # quit command is in close(), '\n' is added automatically 225 assert '\n' not in cmd 226 cmd = cmd.strip() 227 assert cmd != 'q' and cmd != 'quit' 228 self._p.stdin.write(cmd + '\n') 229 self._p.stdin.flush() 230 return self._read_output() 231 232 233def qemu_nbd(*args): 234 '''Run qemu-nbd in daemon mode and return the parent's exit code''' 235 return subprocess.call(qemu_nbd_args + ['--fork'] + list(args)) 236 237def qemu_nbd_early_pipe(*args): 238 '''Run qemu-nbd in daemon mode and return both the parent's exit code 239 and its output in case of an error''' 240 subp = subprocess.Popen(qemu_nbd_args + ['--fork'] + list(args), 241 stdout=subprocess.PIPE, 242 stderr=subprocess.STDOUT, 243 universal_newlines=True) 244 exitcode = subp.wait() 245 if exitcode < 0: 246 sys.stderr.write('qemu-nbd received signal %i: %s\n' % 247 (-exitcode, 248 ' '.join(qemu_nbd_args + ['--fork'] + list(args)))) 249 if exitcode == 0: 250 return exitcode, '' 251 else: 252 return exitcode, subp.communicate()[0] 253 254def qemu_nbd_popen(*args): 255 '''Run qemu-nbd in daemon mode and return the parent's exit code''' 256 return subprocess.Popen(qemu_nbd_args + ['--persistent'] + list(args)) 257 258def compare_images(img1, img2, fmt1=imgfmt, fmt2=imgfmt): 259 '''Return True if two image files are identical''' 260 return qemu_img('compare', '-f', fmt1, 261 '-F', fmt2, img1, img2) == 0 262 263def create_image(name, size): 264 '''Create a fully-allocated raw image with sector markers''' 265 file = open(name, 'wb') 266 i = 0 267 while i < size: 268 sector = struct.pack('>l504xl', i // 512, i // 512) 269 file.write(sector) 270 i = i + 512 271 file.close() 272 273def image_size(img): 274 '''Return image's virtual size''' 275 r = qemu_img_pipe('info', '--output=json', '-f', imgfmt, img) 276 return json.loads(r)['virtual-size'] 277 278def is_str(val): 279 return isinstance(val, str) 280 281test_dir_re = re.compile(r"%s" % test_dir) 282def filter_test_dir(msg): 283 return test_dir_re.sub("TEST_DIR", msg) 284 285win32_re = re.compile(r"\r") 286def filter_win32(msg): 287 return win32_re.sub("", msg) 288 289qemu_io_re = re.compile(r"[0-9]* ops; [0-9\/:. sec]* \([0-9\/.inf]* [EPTGMKiBbytes]*\/sec and [0-9\/.inf]* ops\/sec\)") 290def filter_qemu_io(msg): 291 msg = filter_win32(msg) 292 return qemu_io_re.sub("X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)", msg) 293 294chown_re = re.compile(r"chown [0-9]+:[0-9]+") 295def filter_chown(msg): 296 return chown_re.sub("chown UID:GID", msg) 297 298def filter_qmp_event(event): 299 '''Filter a QMP event dict''' 300 event = dict(event) 301 if 'timestamp' in event: 302 event['timestamp']['seconds'] = 'SECS' 303 event['timestamp']['microseconds'] = 'USECS' 304 return event 305 306def filter_qmp(qmsg, filter_fn): 307 '''Given a string filter, filter a QMP object's values. 308 filter_fn takes a (key, value) pair.''' 309 # Iterate through either lists or dicts; 310 if isinstance(qmsg, list): 311 items = enumerate(qmsg) 312 else: 313 items = qmsg.items() 314 315 for k, v in items: 316 if isinstance(v, list) or isinstance(v, dict): 317 qmsg[k] = filter_qmp(v, filter_fn) 318 else: 319 qmsg[k] = filter_fn(k, v) 320 return qmsg 321 322def filter_testfiles(msg): 323 prefix = os.path.join(test_dir, "%s-" % (os.getpid())) 324 return msg.replace(prefix, 'TEST_DIR/PID-') 325 326def filter_qmp_testfiles(qmsg): 327 def _filter(key, value): 328 if is_str(value): 329 return filter_testfiles(value) 330 return value 331 return filter_qmp(qmsg, _filter) 332 333def filter_generated_node_ids(msg): 334 return re.sub("#block[0-9]+", "NODE_NAME", msg) 335 336def filter_img_info(output, filename): 337 lines = [] 338 for line in output.split('\n'): 339 if 'disk size' in line or 'actual-size' in line: 340 continue 341 line = line.replace(filename, 'TEST_IMG') 342 line = filter_testfiles(line) 343 line = line.replace(imgfmt, 'IMGFMT') 344 line = re.sub('iters: [0-9]+', 'iters: XXX', line) 345 line = re.sub('uuid: [-a-f0-9]+', 'uuid: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX', line) 346 line = re.sub('cid: [0-9]+', 'cid: XXXXXXXXXX', line) 347 lines.append(line) 348 return '\n'.join(lines) 349 350def filter_imgfmt(msg): 351 return msg.replace(imgfmt, 'IMGFMT') 352 353def filter_qmp_imgfmt(qmsg): 354 def _filter(key, value): 355 if is_str(value): 356 return filter_imgfmt(value) 357 return value 358 return filter_qmp(qmsg, _filter) 359 360def log(msg, filters=[], indent=None): 361 '''Logs either a string message or a JSON serializable message (like QMP). 362 If indent is provided, JSON serializable messages are pretty-printed.''' 363 for flt in filters: 364 msg = flt(msg) 365 if isinstance(msg, dict) or isinstance(msg, list): 366 # Python < 3.4 needs to know not to add whitespace when pretty-printing: 367 separators = (', ', ': ') if indent is None else (',', ': ') 368 # Don't sort if it's already sorted 369 do_sort = not isinstance(msg, OrderedDict) 370 print(json.dumps(msg, sort_keys=do_sort, 371 indent=indent, separators=separators)) 372 else: 373 print(msg) 374 375class Timeout: 376 def __init__(self, seconds, errmsg = "Timeout"): 377 self.seconds = seconds 378 self.errmsg = errmsg 379 def __enter__(self): 380 signal.signal(signal.SIGALRM, self.timeout) 381 signal.setitimer(signal.ITIMER_REAL, self.seconds) 382 return self 383 def __exit__(self, type, value, traceback): 384 signal.setitimer(signal.ITIMER_REAL, 0) 385 return False 386 def timeout(self, signum, frame): 387 raise Exception(self.errmsg) 388 389def file_pattern(name): 390 return "{0}-{1}".format(os.getpid(), name) 391 392class FilePaths(object): 393 """ 394 FilePaths is an auto-generated filename that cleans itself up. 395 396 Use this context manager to generate filenames and ensure that the file 397 gets deleted:: 398 399 with FilePaths(['test.img']) as img_path: 400 qemu_img('create', img_path, '1G') 401 # migration_sock_path is automatically deleted 402 """ 403 def __init__(self, names, base_dir=test_dir): 404 self.paths = [] 405 for name in names: 406 self.paths.append(os.path.join(base_dir, file_pattern(name))) 407 408 def __enter__(self): 409 return self.paths 410 411 def __exit__(self, exc_type, exc_val, exc_tb): 412 try: 413 for path in self.paths: 414 os.remove(path) 415 except OSError: 416 pass 417 return False 418 419class FilePath(FilePaths): 420 """ 421 FilePath is a specialization of FilePaths that takes a single filename. 422 """ 423 def __init__(self, name, base_dir=test_dir): 424 super(FilePath, self).__init__([name], base_dir) 425 426 def __enter__(self): 427 return self.paths[0] 428 429def file_path_remover(): 430 for path in reversed(file_path_remover.paths): 431 try: 432 os.remove(path) 433 except OSError: 434 pass 435 436 437def file_path(*names, base_dir=test_dir): 438 ''' Another way to get auto-generated filename that cleans itself up. 439 440 Use is as simple as: 441 442 img_a, img_b = file_path('a.img', 'b.img') 443 sock = file_path('socket') 444 ''' 445 446 if not hasattr(file_path_remover, 'paths'): 447 file_path_remover.paths = [] 448 atexit.register(file_path_remover) 449 450 paths = [] 451 for name in names: 452 filename = file_pattern(name) 453 path = os.path.join(base_dir, filename) 454 file_path_remover.paths.append(path) 455 paths.append(path) 456 457 return paths[0] if len(paths) == 1 else paths 458 459def remote_filename(path): 460 if imgproto == 'file': 461 return path 462 elif imgproto == 'ssh': 463 return "ssh://%s@127.0.0.1:22%s" % (os.environ.get('USER'), path) 464 else: 465 raise Exception("Protocol %s not supported" % (imgproto)) 466 467class VM(qtest.QEMUQtestMachine): 468 '''A QEMU VM''' 469 470 def __init__(self, path_suffix=''): 471 name = "qemu%s-%d" % (path_suffix, os.getpid()) 472 super(VM, self).__init__(qemu_prog, qemu_opts, name=name, 473 test_dir=test_dir, 474 socket_scm_helper=socket_scm_helper, 475 sock_dir=sock_dir) 476 self._num_drives = 0 477 478 def add_object(self, opts): 479 self._args.append('-object') 480 self._args.append(opts) 481 return self 482 483 def add_device(self, opts): 484 self._args.append('-device') 485 self._args.append(opts) 486 return self 487 488 def add_drive_raw(self, opts): 489 self._args.append('-drive') 490 self._args.append(opts) 491 return self 492 493 def add_drive(self, path, opts='', interface='virtio', format=imgfmt): 494 '''Add a virtio-blk drive to the VM''' 495 options = ['if=%s' % interface, 496 'id=drive%d' % self._num_drives] 497 498 if path is not None: 499 options.append('file=%s' % path) 500 options.append('format=%s' % format) 501 options.append('cache=%s' % cachemode) 502 options.append('aio=%s' % aiomode) 503 504 if opts: 505 options.append(opts) 506 507 if format == 'luks' and 'key-secret' not in opts: 508 # default luks support 509 if luks_default_secret_object not in self._args: 510 self.add_object(luks_default_secret_object) 511 512 options.append(luks_default_key_secret_opt) 513 514 self._args.append('-drive') 515 self._args.append(','.join(options)) 516 self._num_drives += 1 517 return self 518 519 def add_blockdev(self, opts): 520 self._args.append('-blockdev') 521 if isinstance(opts, str): 522 self._args.append(opts) 523 else: 524 self._args.append(','.join(opts)) 525 return self 526 527 def add_incoming(self, addr): 528 self._args.append('-incoming') 529 self._args.append(addr) 530 return self 531 532 def pause_drive(self, drive, event=None): 533 '''Pause drive r/w operations''' 534 if not event: 535 self.pause_drive(drive, "read_aio") 536 self.pause_drive(drive, "write_aio") 537 return 538 self.qmp('human-monitor-command', 539 command_line='qemu-io %s "break %s bp_%s"' % (drive, event, drive)) 540 541 def resume_drive(self, drive): 542 self.qmp('human-monitor-command', 543 command_line='qemu-io %s "remove_break bp_%s"' % (drive, drive)) 544 545 def hmp_qemu_io(self, drive, cmd): 546 '''Write to a given drive using an HMP command''' 547 return self.qmp('human-monitor-command', 548 command_line='qemu-io %s "%s"' % (drive, cmd)) 549 550 def flatten_qmp_object(self, obj, output=None, basestr=''): 551 if output is None: 552 output = dict() 553 if isinstance(obj, list): 554 for i in range(len(obj)): 555 self.flatten_qmp_object(obj[i], output, basestr + str(i) + '.') 556 elif isinstance(obj, dict): 557 for key in obj: 558 self.flatten_qmp_object(obj[key], output, basestr + key + '.') 559 else: 560 output[basestr[:-1]] = obj # Strip trailing '.' 561 return output 562 563 def qmp_to_opts(self, obj): 564 obj = self.flatten_qmp_object(obj) 565 output_list = list() 566 for key in obj: 567 output_list += [key + '=' + obj[key]] 568 return ','.join(output_list) 569 570 def get_qmp_events_filtered(self, wait=60.0): 571 result = [] 572 for ev in self.get_qmp_events(wait=wait): 573 result.append(filter_qmp_event(ev)) 574 return result 575 576 def qmp_log(self, cmd, filters=[], indent=None, **kwargs): 577 full_cmd = OrderedDict(( 578 ("execute", cmd), 579 ("arguments", ordered_qmp(kwargs)) 580 )) 581 log(full_cmd, filters, indent=indent) 582 result = self.qmp(cmd, **kwargs) 583 log(result, filters, indent=indent) 584 return result 585 586 # Returns None on success, and an error string on failure 587 def run_job(self, job, auto_finalize=True, auto_dismiss=False, 588 pre_finalize=None, cancel=False, use_log=True, wait=60.0): 589 """ 590 run_job moves a job from creation through to dismissal. 591 592 :param job: String. ID of recently-launched job 593 :param auto_finalize: Bool. True if the job was launched with 594 auto_finalize. Defaults to True. 595 :param auto_dismiss: Bool. True if the job was launched with 596 auto_dismiss=True. Defaults to False. 597 :param pre_finalize: Callback. A callable that takes no arguments to be 598 invoked prior to issuing job-finalize, if any. 599 :param cancel: Bool. When true, cancels the job after the pre_finalize 600 callback. 601 :param use_log: Bool. When false, does not log QMP messages. 602 :param wait: Float. Timeout value specifying how long to wait for any 603 event, in seconds. Defaults to 60.0. 604 """ 605 match_device = {'data': {'device': job}} 606 match_id = {'data': {'id': job}} 607 events = [ 608 ('BLOCK_JOB_COMPLETED', match_device), 609 ('BLOCK_JOB_CANCELLED', match_device), 610 ('BLOCK_JOB_ERROR', match_device), 611 ('BLOCK_JOB_READY', match_device), 612 ('BLOCK_JOB_PENDING', match_id), 613 ('JOB_STATUS_CHANGE', match_id) 614 ] 615 error = None 616 while True: 617 ev = filter_qmp_event(self.events_wait(events, timeout=wait)) 618 if ev['event'] != 'JOB_STATUS_CHANGE': 619 if use_log: 620 log(ev) 621 continue 622 status = ev['data']['status'] 623 if status == 'aborting': 624 result = self.qmp('query-jobs') 625 for j in result['return']: 626 if j['id'] == job: 627 error = j['error'] 628 if use_log: 629 log('Job failed: %s' % (j['error'])) 630 elif status == 'ready': 631 if use_log: 632 self.qmp_log('job-complete', id=job) 633 else: 634 self.qmp('job-complete', id=job) 635 elif status == 'pending' and not auto_finalize: 636 if pre_finalize: 637 pre_finalize() 638 if cancel and use_log: 639 self.qmp_log('job-cancel', id=job) 640 elif cancel: 641 self.qmp('job-cancel', id=job) 642 elif use_log: 643 self.qmp_log('job-finalize', id=job) 644 else: 645 self.qmp('job-finalize', id=job) 646 elif status == 'concluded' and not auto_dismiss: 647 if use_log: 648 self.qmp_log('job-dismiss', id=job) 649 else: 650 self.qmp('job-dismiss', id=job) 651 elif status == 'null': 652 return error 653 654 # Returns None on success, and an error string on failure 655 def blockdev_create(self, options, job_id='job0', filters=None): 656 if filters is None: 657 filters = [filter_qmp_testfiles] 658 result = self.qmp_log('blockdev-create', filters=filters, 659 job_id=job_id, options=options) 660 661 if 'return' in result: 662 assert result['return'] == {} 663 job_result = self.run_job(job_id) 664 else: 665 job_result = result['error'] 666 667 log("") 668 return job_result 669 670 def enable_migration_events(self, name): 671 log('Enabling migration QMP events on %s...' % name) 672 log(self.qmp('migrate-set-capabilities', capabilities=[ 673 { 674 'capability': 'events', 675 'state': True 676 } 677 ])) 678 679 def wait_migration(self, expect_runstate): 680 while True: 681 event = self.event_wait('MIGRATION') 682 log(event, filters=[filter_qmp_event]) 683 if event['data']['status'] == 'completed': 684 break 685 # The event may occur in finish-migrate, so wait for the expected 686 # post-migration runstate 687 while self.qmp('query-status')['return']['status'] != expect_runstate: 688 pass 689 690 def node_info(self, node_name): 691 nodes = self.qmp('query-named-block-nodes') 692 for x in nodes['return']: 693 if x['node-name'] == node_name: 694 return x 695 return None 696 697 def query_bitmaps(self): 698 res = self.qmp("query-named-block-nodes") 699 return {device['node-name']: device['dirty-bitmaps'] 700 for device in res['return'] if 'dirty-bitmaps' in device} 701 702 def get_bitmap(self, node_name, bitmap_name, recording=None, bitmaps=None): 703 """ 704 get a specific bitmap from the object returned by query_bitmaps. 705 :param recording: If specified, filter results by the specified value. 706 :param bitmaps: If specified, use it instead of call query_bitmaps() 707 """ 708 if bitmaps is None: 709 bitmaps = self.query_bitmaps() 710 711 for bitmap in bitmaps[node_name]: 712 if bitmap.get('name', '') == bitmap_name: 713 if recording is None: 714 return bitmap 715 elif bitmap.get('recording') == recording: 716 return bitmap 717 return None 718 719 def check_bitmap_status(self, node_name, bitmap_name, fields): 720 ret = self.get_bitmap(node_name, bitmap_name) 721 722 return fields.items() <= ret.items() 723 724 def assert_block_path(self, root, path, expected_node, graph=None): 725 """ 726 Check whether the node under the given path in the block graph 727 is @expected_node. 728 729 @root is the node name of the node where the @path is rooted. 730 731 @path is a string that consists of child names separated by 732 slashes. It must begin with a slash. 733 734 Examples for @root + @path: 735 - root="qcow2-node", path="/backing/file" 736 - root="quorum-node", path="/children.2/file" 737 738 Hypothetically, @path could be empty, in which case it would 739 point to @root. However, in practice this case is not useful 740 and hence not allowed. 741 742 @expected_node may be None. (All elements of the path but the 743 leaf must still exist.) 744 745 @graph may be None or the result of an x-debug-query-block-graph 746 call that has already been performed. 747 """ 748 if graph is None: 749 graph = self.qmp('x-debug-query-block-graph')['return'] 750 751 iter_path = iter(path.split('/')) 752 753 # Must start with a / 754 assert next(iter_path) == '' 755 756 node = next((node for node in graph['nodes'] if node['name'] == root), 757 None) 758 759 # An empty @path is not allowed, so the root node must be present 760 assert node is not None, 'Root node %s not found' % root 761 762 for child_name in iter_path: 763 assert node is not None, 'Cannot follow path %s%s' % (root, path) 764 765 try: 766 node_id = next(edge['child'] for edge in graph['edges'] \ 767 if edge['parent'] == node['id'] and 768 edge['name'] == child_name) 769 770 node = next(node for node in graph['nodes'] \ 771 if node['id'] == node_id) 772 except StopIteration: 773 node = None 774 775 if node is None: 776 assert expected_node is None, \ 777 'No node found under %s (but expected %s)' % \ 778 (path, expected_node) 779 else: 780 assert node['name'] == expected_node, \ 781 'Found node %s under %s (but expected %s)' % \ 782 (node['name'], path, expected_node) 783 784index_re = re.compile(r'([^\[]+)\[([^\]]+)\]') 785 786class QMPTestCase(unittest.TestCase): 787 '''Abstract base class for QMP test cases''' 788 789 def dictpath(self, d, path): 790 '''Traverse a path in a nested dict''' 791 for component in path.split('/'): 792 m = index_re.match(component) 793 if m: 794 component, idx = m.groups() 795 idx = int(idx) 796 797 if not isinstance(d, dict) or component not in d: 798 self.fail('failed path traversal for "%s" in "%s"' % (path, str(d))) 799 d = d[component] 800 801 if m: 802 if not isinstance(d, list): 803 self.fail('path component "%s" in "%s" is not a list in "%s"' % (component, path, str(d))) 804 try: 805 d = d[idx] 806 except IndexError: 807 self.fail('invalid index "%s" in path "%s" in "%s"' % (idx, path, str(d))) 808 return d 809 810 def assert_qmp_absent(self, d, path): 811 try: 812 result = self.dictpath(d, path) 813 except AssertionError: 814 return 815 self.fail('path "%s" has value "%s"' % (path, str(result))) 816 817 def assert_qmp(self, d, path, value): 818 '''Assert that the value for a specific path in a QMP dict 819 matches. When given a list of values, assert that any of 820 them matches.''' 821 822 result = self.dictpath(d, path) 823 824 # [] makes no sense as a list of valid values, so treat it as 825 # an actual single value. 826 if isinstance(value, list) and value != []: 827 for v in value: 828 if result == v: 829 return 830 self.fail('no match for "%s" in %s' % (str(result), str(value))) 831 else: 832 self.assertEqual(result, value, 833 '"%s" is "%s", expected "%s"' 834 % (path, str(result), str(value))) 835 836 def assert_no_active_block_jobs(self): 837 result = self.vm.qmp('query-block-jobs') 838 self.assert_qmp(result, 'return', []) 839 840 def assert_has_block_node(self, node_name=None, file_name=None): 841 """Issue a query-named-block-nodes and assert node_name and/or 842 file_name is present in the result""" 843 def check_equal_or_none(a, b): 844 return a == None or b == None or a == b 845 assert node_name or file_name 846 result = self.vm.qmp('query-named-block-nodes') 847 for x in result["return"]: 848 if check_equal_or_none(x.get("node-name"), node_name) and \ 849 check_equal_or_none(x.get("file"), file_name): 850 return 851 self.assertTrue(False, "Cannot find %s %s in result:\n%s" % \ 852 (node_name, file_name, result)) 853 854 def assert_json_filename_equal(self, json_filename, reference): 855 '''Asserts that the given filename is a json: filename and that its 856 content is equal to the given reference object''' 857 self.assertEqual(json_filename[:5], 'json:') 858 self.assertEqual(self.vm.flatten_qmp_object(json.loads(json_filename[5:])), 859 self.vm.flatten_qmp_object(reference)) 860 861 def cancel_and_wait(self, drive='drive0', force=False, resume=False, wait=60.0): 862 '''Cancel a block job and wait for it to finish, returning the event''' 863 result = self.vm.qmp('block-job-cancel', device=drive, force=force) 864 self.assert_qmp(result, 'return', {}) 865 866 if resume: 867 self.vm.resume_drive(drive) 868 869 cancelled = False 870 result = None 871 while not cancelled: 872 for event in self.vm.get_qmp_events(wait=wait): 873 if event['event'] == 'BLOCK_JOB_COMPLETED' or \ 874 event['event'] == 'BLOCK_JOB_CANCELLED': 875 self.assert_qmp(event, 'data/device', drive) 876 result = event 877 cancelled = True 878 elif event['event'] == 'JOB_STATUS_CHANGE': 879 self.assert_qmp(event, 'data/id', drive) 880 881 882 self.assert_no_active_block_jobs() 883 return result 884 885 def wait_until_completed(self, drive='drive0', check_offset=True, wait=60.0, 886 error=None): 887 '''Wait for a block job to finish, returning the event''' 888 while True: 889 for event in self.vm.get_qmp_events(wait=wait): 890 if event['event'] == 'BLOCK_JOB_COMPLETED': 891 self.assert_qmp(event, 'data/device', drive) 892 if error is None: 893 self.assert_qmp_absent(event, 'data/error') 894 if check_offset: 895 self.assert_qmp(event, 'data/offset', 896 event['data']['len']) 897 else: 898 self.assert_qmp(event, 'data/error', error) 899 self.assert_no_active_block_jobs() 900 return event 901 elif event['event'] == 'JOB_STATUS_CHANGE': 902 self.assert_qmp(event, 'data/id', drive) 903 904 def wait_ready(self, drive='drive0'): 905 '''Wait until a block job BLOCK_JOB_READY event''' 906 f = {'data': {'type': 'mirror', 'device': drive } } 907 event = self.vm.event_wait(name='BLOCK_JOB_READY', match=f) 908 909 def wait_ready_and_cancel(self, drive='drive0'): 910 self.wait_ready(drive=drive) 911 event = self.cancel_and_wait(drive=drive) 912 self.assertEqual(event['event'], 'BLOCK_JOB_COMPLETED') 913 self.assert_qmp(event, 'data/type', 'mirror') 914 self.assert_qmp(event, 'data/offset', event['data']['len']) 915 916 def complete_and_wait(self, drive='drive0', wait_ready=True, 917 completion_error=None): 918 '''Complete a block job and wait for it to finish''' 919 if wait_ready: 920 self.wait_ready(drive=drive) 921 922 result = self.vm.qmp('block-job-complete', device=drive) 923 self.assert_qmp(result, 'return', {}) 924 925 event = self.wait_until_completed(drive=drive, error=completion_error) 926 self.assert_qmp(event, 'data/type', 'mirror') 927 928 def pause_wait(self, job_id='job0'): 929 with Timeout(3, "Timeout waiting for job to pause"): 930 while True: 931 result = self.vm.qmp('query-block-jobs') 932 found = False 933 for job in result['return']: 934 if job['device'] == job_id: 935 found = True 936 if job['paused'] == True and job['busy'] == False: 937 return job 938 break 939 assert found 940 941 def pause_job(self, job_id='job0', wait=True): 942 result = self.vm.qmp('block-job-pause', device=job_id) 943 self.assert_qmp(result, 'return', {}) 944 if wait: 945 return self.pause_wait(job_id) 946 return result 947 948 def case_skip(self, reason): 949 '''Skip this test case''' 950 case_notrun(reason) 951 self.skipTest(reason) 952 953 954def notrun(reason): 955 '''Skip this test suite''' 956 # Each test in qemu-iotests has a number ("seq") 957 seq = os.path.basename(sys.argv[0]) 958 959 open('%s/%s.notrun' % (output_dir, seq), 'w').write(reason + '\n') 960 print('%s not run: %s' % (seq, reason)) 961 sys.exit(0) 962 963def case_notrun(reason): 964 '''Mark this test case as not having been run (without actually 965 skipping it, that is left to the caller). See 966 QMPTestCase.case_skip() for a variant that actually skips the 967 current test case.''' 968 969 # Each test in qemu-iotests has a number ("seq") 970 seq = os.path.basename(sys.argv[0]) 971 972 open('%s/%s.casenotrun' % (output_dir, seq), 'a').write( 973 ' [case not run] ' + reason + '\n') 974 975def verify_image_format(supported_fmts=[], unsupported_fmts=[]): 976 assert not (supported_fmts and unsupported_fmts) 977 978 if 'generic' in supported_fmts and \ 979 os.environ.get('IMGFMT_GENERIC', 'true') == 'true': 980 # similar to 981 # _supported_fmt generic 982 # for bash tests 983 return 984 985 not_sup = supported_fmts and (imgfmt not in supported_fmts) 986 if not_sup or (imgfmt in unsupported_fmts): 987 notrun('not suitable for this image format: %s' % imgfmt) 988 989def verify_protocol(supported=[], unsupported=[]): 990 assert not (supported and unsupported) 991 992 if 'generic' in supported: 993 return 994 995 not_sup = supported and (imgproto not in supported) 996 if not_sup or (imgproto in unsupported): 997 notrun('not suitable for this protocol: %s' % imgproto) 998 999def verify_platform(supported=None, unsupported=None): 1000 if unsupported is not None: 1001 if any((sys.platform.startswith(x) for x in unsupported)): 1002 notrun('not suitable for this OS: %s' % sys.platform) 1003 1004 if supported is not None: 1005 if not any((sys.platform.startswith(x) for x in supported)): 1006 notrun('not suitable for this OS: %s' % sys.platform) 1007 1008def verify_cache_mode(supported_cache_modes=[]): 1009 if supported_cache_modes and (cachemode not in supported_cache_modes): 1010 notrun('not suitable for this cache mode: %s' % cachemode) 1011 1012def verify_aio_mode(supported_aio_modes=[]): 1013 if supported_aio_modes and (aiomode not in supported_aio_modes): 1014 notrun('not suitable for this aio mode: %s' % aiomode) 1015 1016def supports_quorum(): 1017 return 'quorum' in qemu_img_pipe('--help') 1018 1019def verify_quorum(): 1020 '''Skip test suite if quorum support is not available''' 1021 if not supports_quorum(): 1022 notrun('quorum support missing') 1023 1024def qemu_pipe(*args): 1025 '''Run qemu with an option to print something and exit (e.g. a help option), 1026 and return its output''' 1027 args = [qemu_prog] + qemu_opts + list(args) 1028 subp = subprocess.Popen(args, stdout=subprocess.PIPE, 1029 stderr=subprocess.STDOUT, 1030 universal_newlines=True) 1031 exitcode = subp.wait() 1032 if exitcode < 0: 1033 sys.stderr.write('qemu received signal %i: %s\n' % (-exitcode, 1034 ' '.join(args))) 1035 return subp.communicate()[0] 1036 1037def supported_formats(read_only=False): 1038 '''Set 'read_only' to True to check ro-whitelist 1039 Otherwise, rw-whitelist is checked''' 1040 1041 if not hasattr(supported_formats, "formats"): 1042 supported_formats.formats = {} 1043 1044 if read_only not in supported_formats.formats: 1045 format_message = qemu_pipe("-drive", "format=help") 1046 line = 1 if read_only else 0 1047 supported_formats.formats[read_only] = \ 1048 format_message.splitlines()[line].split(":")[1].split() 1049 1050 return supported_formats.formats[read_only] 1051 1052def skip_if_unsupported(required_formats=[], read_only=False): 1053 '''Skip Test Decorator 1054 Runs the test if all the required formats are whitelisted''' 1055 def skip_test_decorator(func): 1056 def func_wrapper(test_case: QMPTestCase, *args, **kwargs): 1057 if callable(required_formats): 1058 fmts = required_formats(test_case) 1059 else: 1060 fmts = required_formats 1061 1062 usf_list = list(set(fmts) - set(supported_formats(read_only))) 1063 if usf_list: 1064 test_case.case_skip('{}: formats {} are not whitelisted'.format( 1065 test_case, usf_list)) 1066 else: 1067 return func(test_case, *args, **kwargs) 1068 return func_wrapper 1069 return skip_test_decorator 1070 1071def skip_if_user_is_root(func): 1072 '''Skip Test Decorator 1073 Runs the test only without root permissions''' 1074 def func_wrapper(*args, **kwargs): 1075 if os.getuid() == 0: 1076 case_notrun('{}: cannot be run as root'.format(args[0])) 1077 else: 1078 return func(*args, **kwargs) 1079 return func_wrapper 1080 1081def execute_unittest(output, verbosity, debug): 1082 runner = unittest.TextTestRunner(stream=output, descriptions=True, 1083 verbosity=verbosity) 1084 try: 1085 # unittest.main() will use sys.exit(); so expect a SystemExit 1086 # exception 1087 unittest.main(testRunner=runner) 1088 finally: 1089 if not debug: 1090 out = output.getvalue() 1091 out = re.sub(r'Ran (\d+) tests? in [\d.]+s', r'Ran \1 tests', out) 1092 1093 # Hide skipped tests from the reference output 1094 out = re.sub(r'OK \(skipped=\d+\)', 'OK', out) 1095 out_first_line, out_rest = out.split('\n', 1) 1096 out = out_first_line.replace('s', '.') + '\n' + out_rest 1097 1098 sys.stderr.write(out) 1099 1100def execute_test(test_function=None, 1101 supported_fmts=[], 1102 supported_platforms=None, 1103 supported_cache_modes=[], supported_aio_modes={}, 1104 unsupported_fmts=[], supported_protocols=[], 1105 unsupported_protocols=[]): 1106 """Run either unittest or script-style tests.""" 1107 1108 # We are using TEST_DIR and QEMU_DEFAULT_MACHINE as proxies to 1109 # indicate that we're not being run via "check". There may be 1110 # other things set up by "check" that individual test cases rely 1111 # on. 1112 if test_dir is None or qemu_default_machine is None: 1113 sys.stderr.write('Please run this test via the "check" script\n') 1114 sys.exit(os.EX_USAGE) 1115 1116 debug = '-d' in sys.argv 1117 verbosity = 1 1118 verify_image_format(supported_fmts, unsupported_fmts) 1119 verify_protocol(supported_protocols, unsupported_protocols) 1120 verify_platform(supported=supported_platforms) 1121 verify_cache_mode(supported_cache_modes) 1122 verify_aio_mode(supported_aio_modes) 1123 1124 if debug: 1125 output = sys.stdout 1126 verbosity = 2 1127 sys.argv.remove('-d') 1128 else: 1129 # We need to filter out the time taken from the output so that 1130 # qemu-iotest can reliably diff the results against master output. 1131 output = io.StringIO() 1132 1133 logging.basicConfig(level=(logging.DEBUG if debug else logging.WARN)) 1134 1135 if not test_function: 1136 execute_unittest(output, verbosity, debug) 1137 else: 1138 test_function() 1139 1140def script_main(test_function, *args, **kwargs): 1141 """Run script-style tests outside of the unittest framework""" 1142 execute_test(test_function, *args, **kwargs) 1143 1144def main(*args, **kwargs): 1145 """Run tests using the unittest framework""" 1146 execute_test(None, *args, **kwargs) 1147