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