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