1from __future__ import print_function 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 20import errno 21import os 22import re 23import subprocess 24import string 25import unittest 26import sys 27import struct 28import json 29import signal 30import logging 31import atexit 32import io 33from collections import OrderedDict 34 35sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python')) 36from qemu import qtest 37 38 39# This will not work if arguments contain spaces but is necessary if we 40# want to support the override options that ./check supports. 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 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 53qemu_prog = os.environ.get('QEMU_PROG', 'qemu') 54qemu_opts = os.environ.get('QEMU_OPTIONS', '').strip().split(' ') 55 56imgfmt = os.environ.get('IMGFMT', 'raw') 57imgproto = os.environ.get('IMGPROTO', 'file') 58test_dir = os.environ.get('TEST_DIR') 59output_dir = os.environ.get('OUTPUT_DIR', '.') 60cachemode = os.environ.get('CACHEMODE') 61qemu_default_machine = os.environ.get('QEMU_DEFAULT_MACHINE') 62 63socket_scm_helper = os.environ.get('SOCKET_SCM_HELPER', 'socket_scm_helper') 64 65luks_default_secret_object = 'secret,id=keysec0,data=' + \ 66 os.environ.get('IMGKEYSECRET', '') 67luks_default_key_secret_opt = 'key-secret=keysec0' 68 69 70def qemu_img(*args): 71 '''Run qemu-img and return the exit code''' 72 devnull = open('/dev/null', 'r+') 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 77 78def ordered_qmp(qmsg, conv_keys=True): 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()): 85 if conv_keys: 86 k = k.replace('_', '-') 87 od[k] = ordered_qmp(v, conv_keys=False) 88 return od 89 return qmsg 90 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 110def qemu_img_verbose(*args): 111 '''Run qemu-img without suppressing its output and return the exit code''' 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 116 117def qemu_img_pipe(*args): 118 '''Run qemu-img and return its output''' 119 subp = subprocess.Popen(qemu_img_args + list(args), 120 stdout=subprocess.PIPE, 121 stderr=subprocess.STDOUT, 122 universal_newlines=True) 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] 127 128def qemu_img_log(*args): 129 result = qemu_img_pipe(*args) 130 log(result, filters=[filter_testfiles]) 131 return result 132 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) 143 if not filter_path: 144 filter_path = filename 145 log(filter_img_info(output, filter_path)) 146 147def qemu_io(*args): 148 '''Run qemu-io and return the stdout data''' 149 args = qemu_io_args + list(args) 150 subp = subprocess.Popen(args, stdout=subprocess.PIPE, 151 stderr=subprocess.STDOUT, 152 universal_newlines=True) 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] 157 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 167def get_virtio_scsi_device(): 168 if qemu_default_machine == 's390-ccw-virtio': 169 return 'virtio-scsi-ccw' 170 return 'virtio-scsi-pci' 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, 177 stderr=subprocess.STDOUT, 178 universal_newlines=True) 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') 207 self._p.stdin.flush() 208 return self._read_output() 209 210 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 215def qemu_nbd_early_pipe(*args): 216 '''Run qemu-nbd in daemon mode and return both the parent's exit code 217 and its output in case of an error''' 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)))) 227 if exitcode == 0: 228 return exitcode, '' 229 else: 230 return exitcode, subp.communicate()[0] 231 232def compare_images(img1, img2, fmt1=imgfmt, fmt2=imgfmt): 233 '''Return True if two image files are identical''' 234 return qemu_img('compare', '-f', fmt1, 235 '-F', fmt2, img1, img2) == 0 236 237def create_image(name, size): 238 '''Create a fully-allocated raw image with sector markers''' 239 file = open(name, 'wb') 240 i = 0 241 while i < size: 242 sector = struct.pack('>l504xl', i // 512, i // 512) 243 file.write(sector) 244 i = i + 512 245 file.close() 246 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 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 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 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 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 299def filter_testfiles(msg): 300 prefix = os.path.join(test_dir, "%s-" % (os.getpid())) 301 return msg.replace(prefix, 'TEST_DIR/PID-') 302 303def filter_qmp_testfiles(qmsg): 304 def _filter(key, value): 305 if is_str(value): 306 return filter_testfiles(value) 307 return value 308 return filter_qmp(qmsg, _filter) 309 310def filter_generated_node_ids(msg): 311 return re.sub("#block[0-9]+", "NODE_NAME", msg) 312 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) 322 line = re.sub('cid: [0-9]+', 'cid: XXXXXXXXXX', line) 323 lines.append(line) 324 return '\n'.join(lines) 325 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 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.''' 339 for flt in filters: 340 msg = flt(msg) 341 if isinstance(msg, dict) or isinstance(msg, list): 342 # Python < 3.4 needs to know not to add whitespace when pretty-printing: 343 separators = (', ', ': ') if indent is None else (',', ': ') 344 # Don't sort if it's already sorted 345 do_sort = not isinstance(msg, OrderedDict) 346 print(json.dumps(msg, sort_keys=do_sort, 347 indent=indent, separators=separators)) 348 else: 349 print(msg) 350 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 365def file_pattern(name): 366 return "{0}-{1}".format(os.getpid(), name) 367 368class FilePaths(object): 369 """ 370 FilePaths is an auto-generated filename that cleans itself up. 371 372 Use this context manager to generate filenames and ensure that the file 373 gets deleted:: 374 375 with FilePaths(['test.img']) as img_path: 376 qemu_img('create', img_path, '1G') 377 # migration_sock_path is automatically deleted 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))) 383 384 def __enter__(self): 385 return self.paths 386 387 def __exit__(self, exc_type, exc_val, exc_tb): 388 try: 389 for path in self.paths: 390 os.remove(path) 391 except OSError: 392 pass 393 return False 394 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] 404 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: 428 filename = file_pattern(name) 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 435def remote_filename(path): 436 if imgproto == 'file': 437 return path 438 elif imgproto == 'ssh': 439 return "ssh://%s@127.0.0.1:22%s" % (os.environ.get('USER'), path) 440 else: 441 raise Exception("Protocol %s not supported" % (imgproto)) 442 443class VM(qtest.QEMUQtestMachine): 444 '''A QEMU VM''' 445 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, 450 socket_scm_helper=socket_scm_helper) 451 self._num_drives = 0 452 453 def add_object(self, opts): 454 self._args.append('-object') 455 self._args.append(opts) 456 return self 457 458 def add_device(self, opts): 459 self._args.append('-device') 460 self._args.append(opts) 461 return self 462 463 def add_drive_raw(self, opts): 464 self._args.append('-drive') 465 self._args.append(opts) 466 return self 467 468 def add_drive(self, path, opts='', interface='virtio', format=imgfmt): 469 '''Add a virtio-blk drive to the VM''' 470 options = ['if=%s' % interface, 471 'id=drive%d' % self._num_drives] 472 473 if path is not None: 474 options.append('file=%s' % path) 475 options.append('format=%s' % format) 476 options.append('cache=%s' % cachemode) 477 478 if opts: 479 options.append(opts) 480 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 488 self._args.append('-drive') 489 self._args.append(','.join(options)) 490 self._num_drives += 1 491 return self 492 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 501 def add_incoming(self, addr): 502 self._args.append('-incoming') 503 self._args.append(addr) 504 return self 505 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 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 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 544 def get_qmp_events_filtered(self, wait=60.0): 545 result = [] 546 for ev in self.get_qmp_events(wait=wait): 547 result.append(filter_qmp_event(ev)) 548 return result 549 550 def qmp_log(self, cmd, filters=[], indent=None, **kwargs): 551 full_cmd = OrderedDict(( 552 ("execute", cmd), 553 ("arguments", ordered_qmp(kwargs)) 554 )) 555 log(full_cmd, filters, indent=indent) 556 result = self.qmp(cmd, **kwargs) 557 log(result, filters, indent=indent) 558 return result 559 560 # Returns None on success, and an error string on failure 561 def run_job(self, job, auto_finalize=True, auto_dismiss=False, 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 """ 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 ] 589 error = None 590 while True: 591 ev = filter_qmp_event(self.events_wait(events)) 592 if ev['event'] != 'JOB_STATUS_CHANGE': 593 if use_log: 594 log(ev) 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'] 602 if use_log: 603 log('Job failed: %s' % (j['error'])) 604 elif status == 'pending' and not auto_finalize: 605 if pre_finalize: 606 pre_finalize() 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: 612 self.qmp_log('job-finalize', id=job) 613 else: 614 self.qmp('job-finalize', id=job) 615 elif status == 'concluded' and not auto_dismiss: 616 if use_log: 617 self.qmp_log('job-dismiss', id=job) 618 else: 619 self.qmp('job-dismiss', id=job) 620 elif status == 'null': 621 return error 622 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 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 646 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 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 680 def assert_qmp(self, d, path, value): 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 685 result = self.dictpath(d, path) 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))) 698 699 def assert_no_active_block_jobs(self): 700 result = self.vm.qmp('query-block-jobs') 701 self.assert_qmp(result, 'return', []) 702 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 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:') 721 self.assertEqual(self.vm.flatten_qmp_object(json.loads(json_filename[5:])), 722 self.vm.flatten_qmp_object(reference)) 723 724 def cancel_and_wait(self, drive='drive0', force=False, resume=False, wait=60.0): 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 729 if resume: 730 self.vm.resume_drive(drive) 731 732 cancelled = False 733 result = None 734 while not cancelled: 735 for event in self.vm.get_qmp_events(wait=wait): 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 741 elif event['event'] == 'JOB_STATUS_CHANGE': 742 self.assert_qmp(event, 'data/id', drive) 743 744 745 self.assert_no_active_block_jobs() 746 return result 747 748 def wait_until_completed(self, drive='drive0', check_offset=True, wait=60.0): 749 '''Wait for a block job to finish, returning the event''' 750 while True: 751 for event in self.vm.get_qmp_events(wait=wait): 752 if event['event'] == 'BLOCK_JOB_COMPLETED': 753 self.assert_qmp(event, 'data/device', drive) 754 self.assert_qmp_absent(event, 'data/error') 755 if check_offset: 756 self.assert_qmp(event, 'data/offset', event['data']['len']) 757 self.assert_no_active_block_jobs() 758 return event 759 elif event['event'] == 'JOB_STATUS_CHANGE': 760 self.assert_qmp(event, 'data/id', drive) 761 762 def wait_ready(self, drive='drive0'): 763 '''Wait until a block job BLOCK_JOB_READY event''' 764 f = {'data': {'type': 'mirror', 'device': drive } } 765 event = self.vm.event_wait(name='BLOCK_JOB_READY', match=f) 766 767 def wait_ready_and_cancel(self, drive='drive0'): 768 self.wait_ready(drive=drive) 769 event = self.cancel_and_wait(drive=drive) 770 self.assertEqual(event['event'], 'BLOCK_JOB_COMPLETED') 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 785 def pause_wait(self, job_id='job0'): 786 with Timeout(1, "Timeout waiting for job to pause"): 787 while True: 788 result = self.vm.qmp('query-block-jobs') 789 found = False 790 for job in result['return']: 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 797 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 805 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 811 open('%s/%s.notrun' % (output_dir, seq), 'w').write(reason + '\n') 812 print('%s not run: %s' % (seq, reason)) 813 sys.exit(0) 814 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 823def verify_image_format(supported_fmts=[], unsupported_fmts=[]): 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): 835 notrun('not suitable for this image format: %s' % imgfmt) 836 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 847def verify_platform(supported_oses=['linux']): 848 if True not in [sys.platform.startswith(x) for x in supported_oses]: 849 notrun('not suitable for this OS: %s' % sys.platform) 850 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 855def supports_quorum(): 856 return 'quorum' in qemu_img_pipe('--help') 857 858def verify_quorum(): 859 '''Skip test suite if quorum support is not available''' 860 if not supports_quorum(): 861 notrun('quorum support missing') 862 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 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())) 909 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.""" 914 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 923 debug = '-d' in sys.argv 924 verbosity = 1 925 verify_image_format(supported_fmts, unsupported_fmts) 926 verify_platform(supported_oses) 927 verify_cache_mode(supported_cache_modes) 928 929 if debug: 930 output = sys.stdout 931 verbosity = 2 932 sys.argv.remove('-d') 933 else: 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() 942 943 logging.basicConfig(level=(logging.DEBUG if debug else logging.WARN)) 944 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) 953 954def main(*args, **kwargs): 955 """Run tests using the unittest framework""" 956 execute_test(None, *args, **kwargs) 957