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