1""" 2QEMU machine module: 3 4The machine module primarily provides the QEMUMachine class, 5which provides facilities for managing the lifetime of a QEMU VM. 6""" 7 8# Copyright (C) 2015-2016 Red Hat Inc. 9# Copyright (C) 2012 IBM Corp. 10# 11# Authors: 12# Fam Zheng <famz@redhat.com> 13# 14# This work is licensed under the terms of the GNU GPL, version 2. See 15# the COPYING file in the top-level directory. 16# 17# Based on qmp.py. 18# 19 20import errno 21from itertools import chain 22import locale 23import logging 24import os 25import shutil 26import signal 27import socket 28import subprocess 29import tempfile 30from types import TracebackType 31from typing import ( 32 Any, 33 BinaryIO, 34 Dict, 35 List, 36 Optional, 37 Sequence, 38 Tuple, 39 Type, 40 TypeVar, 41) 42 43from qemu.qmp import SocketAddrT 44from qemu.qmp.legacy import ( 45 QEMUMonitorProtocol, 46 QMPMessage, 47 QMPReturnValue, 48) 49 50from . import console_socket 51 52 53LOG = logging.getLogger(__name__) 54 55 56class QEMUMachineError(Exception): 57 """ 58 Exception called when an error in QEMUMachine happens. 59 """ 60 61 62class QEMUMachineAddDeviceError(QEMUMachineError): 63 """ 64 Exception raised when a request to add a device can not be fulfilled 65 66 The failures are caused by limitations, lack of information or conflicting 67 requests on the QEMUMachine methods. This exception does not represent 68 failures reported by the QEMU binary itself. 69 """ 70 71 72class VMLaunchFailure(QEMUMachineError): 73 """ 74 Exception raised when a VM launch was attempted, but failed. 75 """ 76 def __init__(self, exitcode: Optional[int], 77 command: str, output: Optional[str]): 78 super().__init__(exitcode, command, output) 79 self.exitcode = exitcode 80 self.command = command 81 self.output = output 82 83 def __str__(self) -> str: 84 ret = '' 85 if self.__cause__ is not None: 86 name = type(self.__cause__).__name__ 87 reason = str(self.__cause__) 88 if reason: 89 ret += f"{name}: {reason}" 90 else: 91 ret += f"{name}" 92 ret += '\n' 93 94 if self.exitcode is not None: 95 ret += f"\tExit code: {self.exitcode}\n" 96 ret += f"\tCommand: {self.command}\n" 97 ret += f"\tOutput: {self.output}\n" 98 return ret 99 100 101class AbnormalShutdown(QEMUMachineError): 102 """ 103 Exception raised when a graceful shutdown was requested, but not performed. 104 """ 105 106 107_T = TypeVar('_T', bound='QEMUMachine') 108 109 110class QEMUMachine: 111 """ 112 A QEMU VM. 113 114 Use this object as a context manager to ensure 115 the QEMU process terminates:: 116 117 with VM(binary) as vm: 118 ... 119 # vm is guaranteed to be shut down here 120 """ 121 # pylint: disable=too-many-instance-attributes, too-many-public-methods 122 123 def __init__(self, 124 binary: str, 125 args: Sequence[str] = (), 126 wrapper: Sequence[str] = (), 127 name: Optional[str] = None, 128 base_temp_dir: str = "/var/tmp", 129 monitor_address: Optional[SocketAddrT] = None, 130 drain_console: bool = False, 131 console_log: Optional[str] = None, 132 log_dir: Optional[str] = None, 133 qmp_timer: Optional[float] = 30): 134 ''' 135 Initialize a QEMUMachine 136 137 @param binary: path to the qemu binary 138 @param args: list of extra arguments 139 @param wrapper: list of arguments used as prefix to qemu binary 140 @param name: prefix for socket and log file names (default: qemu-PID) 141 @param base_temp_dir: default location where temp files are created 142 @param monitor_address: address for QMP monitor 143 @param drain_console: (optional) True to drain console socket to buffer 144 @param console_log: (optional) path to console log file 145 @param log_dir: where to create and keep log files 146 @param qmp_timer: (optional) default QMP socket timeout 147 @note: Qemu process is not started until launch() is used. 148 ''' 149 # pylint: disable=too-many-arguments 150 151 # Direct user configuration 152 153 self._binary = binary 154 self._args = list(args) 155 self._wrapper = wrapper 156 self._qmp_timer = qmp_timer 157 158 self._name = name or f"{id(self):x}" 159 self._sock_pair: Optional[Tuple[socket.socket, socket.socket]] = None 160 self._cons_sock_pair: Optional[ 161 Tuple[socket.socket, socket.socket]] = None 162 self._temp_dir: Optional[str] = None 163 self._base_temp_dir = base_temp_dir 164 self._log_dir = log_dir 165 166 self._monitor_address = monitor_address 167 168 self._console_log_path = console_log 169 if self._console_log_path: 170 # In order to log the console, buffering needs to be enabled. 171 self._drain_console = True 172 else: 173 self._drain_console = drain_console 174 175 # Runstate 176 self._qemu_log_path: Optional[str] = None 177 self._qemu_log_file: Optional[BinaryIO] = None 178 self._popen: Optional['subprocess.Popen[bytes]'] = None 179 self._events: List[QMPMessage] = [] 180 self._iolog: Optional[str] = None 181 self._qmp_set = True # Enable QMP monitor by default. 182 self._qmp_connection: Optional[QEMUMonitorProtocol] = None 183 self._qemu_full_args: Tuple[str, ...] = () 184 self._launched = False 185 self._machine: Optional[str] = None 186 self._console_index = 0 187 self._console_set = False 188 self._console_device_type: Optional[str] = None 189 self._console_socket: Optional[socket.socket] = None 190 self._console_file: Optional[socket.SocketIO] = None 191 self._remove_files: List[str] = [] 192 self._user_killed = False 193 self._quit_issued = False 194 195 def __enter__(self: _T) -> _T: 196 return self 197 198 def __exit__(self, 199 exc_type: Optional[Type[BaseException]], 200 exc_val: Optional[BaseException], 201 exc_tb: Optional[TracebackType]) -> None: 202 self.shutdown() 203 204 def add_monitor_null(self) -> None: 205 """ 206 This can be used to add an unused monitor instance. 207 """ 208 self._args.append('-monitor') 209 self._args.append('null') 210 211 def add_fd(self: _T, fd: int, fdset: int, 212 opaque: str, opts: str = '') -> _T: 213 """ 214 Pass a file descriptor to the VM 215 """ 216 options = ['fd=%d' % fd, 217 'set=%d' % fdset, 218 'opaque=%s' % opaque] 219 if opts: 220 options.append(opts) 221 222 # This did not exist before 3.4, but since then it is 223 # mandatory for our purpose 224 if hasattr(os, 'set_inheritable'): 225 os.set_inheritable(fd, True) 226 227 self._args.append('-add-fd') 228 self._args.append(','.join(options)) 229 return self 230 231 def send_fd_scm(self, fd: Optional[int] = None, 232 file_path: Optional[str] = None) -> int: 233 """ 234 Send an fd or file_path to the remote via SCM_RIGHTS. 235 236 Exactly one of fd and file_path must be given. If it is 237 file_path, the file will be opened read-only and the new file 238 descriptor will be sent to the remote. 239 """ 240 if file_path is not None: 241 assert fd is None 242 with open(file_path, "rb") as passfile: 243 fd = passfile.fileno() 244 self._qmp.send_fd_scm(fd) 245 else: 246 assert fd is not None 247 self._qmp.send_fd_scm(fd) 248 249 return 0 250 251 @staticmethod 252 def _remove_if_exists(path: str) -> None: 253 """ 254 Remove file object at path if it exists 255 """ 256 try: 257 os.remove(path) 258 except OSError as exception: 259 if exception.errno == errno.ENOENT: 260 return 261 raise 262 263 def is_running(self) -> bool: 264 """Returns true if the VM is running.""" 265 return self._popen is not None and self._popen.poll() is None 266 267 @property 268 def _subp(self) -> 'subprocess.Popen[bytes]': 269 if self._popen is None: 270 raise QEMUMachineError('Subprocess pipe not present') 271 return self._popen 272 273 def exitcode(self) -> Optional[int]: 274 """Returns the exit code if possible, or None.""" 275 if self._popen is None: 276 return None 277 return self._popen.poll() 278 279 def get_pid(self) -> Optional[int]: 280 """Returns the PID of the running process, or None.""" 281 if not self.is_running(): 282 return None 283 return self._subp.pid 284 285 def _load_io_log(self) -> None: 286 # Assume that the output encoding of QEMU's terminal output is 287 # defined by our locale. If indeterminate, allow open() to fall 288 # back to the platform default. 289 _, encoding = locale.getlocale() 290 if self._qemu_log_path is not None: 291 with open(self._qemu_log_path, "r", encoding=encoding) as iolog: 292 self._iolog = iolog.read() 293 294 @property 295 def _base_args(self) -> List[str]: 296 args = ['-display', 'none', '-vga', 'none'] 297 298 if self._qmp_set: 299 if self._sock_pair: 300 moncdev = f"socket,id=mon,fd={self._sock_pair[0].fileno()}" 301 elif isinstance(self._monitor_address, tuple): 302 moncdev = "socket,id=mon,host={},port={}".format( 303 *self._monitor_address 304 ) 305 else: 306 moncdev = f"socket,id=mon,path={self._monitor_address}" 307 args.extend(['-chardev', moncdev, '-mon', 308 'chardev=mon,mode=control']) 309 310 if self._machine is not None: 311 args.extend(['-machine', self._machine]) 312 for _ in range(self._console_index): 313 args.extend(['-serial', 'null']) 314 if self._console_set: 315 assert self._cons_sock_pair is not None 316 fd = self._cons_sock_pair[0].fileno() 317 chardev = f"socket,id=console,fd={fd}" 318 args.extend(['-chardev', chardev]) 319 if self._console_device_type is None: 320 args.extend(['-serial', 'chardev:console']) 321 else: 322 device = '%s,chardev=console' % self._console_device_type 323 args.extend(['-device', device]) 324 return args 325 326 @property 327 def args(self) -> List[str]: 328 """Returns the list of arguments given to the QEMU binary.""" 329 return self._args 330 331 @property 332 def binary(self) -> str: 333 """Returns path to the QEMU binary""" 334 return self._binary 335 336 def _pre_launch(self) -> None: 337 if self._qmp_set: 338 if self._monitor_address is None: 339 self._sock_pair = socket.socketpair() 340 os.set_inheritable(self._sock_pair[0].fileno(), True) 341 sock = self._sock_pair[1] 342 if isinstance(self._monitor_address, str): 343 self._remove_files.append(self._monitor_address) 344 345 sock_or_addr = self._monitor_address or sock 346 assert sock_or_addr is not None 347 348 self._qmp_connection = QEMUMonitorProtocol( 349 sock_or_addr, 350 server=bool(self._monitor_address), 351 nickname=self._name 352 ) 353 354 if self._console_set: 355 self._cons_sock_pair = socket.socketpair() 356 os.set_inheritable(self._cons_sock_pair[0].fileno(), True) 357 358 # NOTE: Make sure any opened resources are *definitely* freed in 359 # _post_shutdown()! 360 # pylint: disable=consider-using-with 361 self._qemu_log_path = os.path.join(self.log_dir, self._name + ".log") 362 self._qemu_log_file = open(self._qemu_log_path, 'wb') 363 364 self._iolog = None 365 self._qemu_full_args = tuple(chain( 366 self._wrapper, 367 [self._binary], 368 self._base_args, 369 self._args 370 )) 371 372 def _post_launch(self) -> None: 373 if self._sock_pair: 374 self._sock_pair[0].close() 375 if self._cons_sock_pair: 376 self._cons_sock_pair[0].close() 377 378 if self._qmp_connection: 379 if self._sock_pair: 380 self._qmp.connect() 381 else: 382 self._qmp.accept(self._qmp_timer) 383 384 def _close_qemu_log_file(self) -> None: 385 if self._qemu_log_file is not None: 386 self._qemu_log_file.close() 387 self._qemu_log_file = None 388 389 def _post_shutdown(self) -> None: 390 """ 391 Called to cleanup the VM instance after the process has exited. 392 May also be called after a failed launch. 393 """ 394 LOG.debug("Cleaning up after VM process") 395 try: 396 self._close_qmp_connection() 397 except Exception as err: # pylint: disable=broad-except 398 LOG.warning( 399 "Exception closing QMP connection: %s", 400 str(err) if str(err) else type(err).__name__ 401 ) 402 finally: 403 assert self._qmp_connection is None 404 405 if self._sock_pair: 406 self._sock_pair[0].close() 407 self._sock_pair[1].close() 408 self._sock_pair = None 409 410 self._close_qemu_log_file() 411 412 self._load_io_log() 413 414 self._qemu_log_path = None 415 416 if self._temp_dir is not None: 417 shutil.rmtree(self._temp_dir) 418 self._temp_dir = None 419 420 while len(self._remove_files) > 0: 421 self._remove_if_exists(self._remove_files.pop()) 422 423 exitcode = self.exitcode() 424 if (exitcode is not None and exitcode < 0 425 and not (self._user_killed and exitcode == -signal.SIGKILL)): 426 msg = 'qemu received signal %i; command: "%s"' 427 if self._qemu_full_args: 428 command = ' '.join(self._qemu_full_args) 429 else: 430 command = '' 431 LOG.warning(msg, -int(exitcode), command) 432 433 self._quit_issued = False 434 self._user_killed = False 435 self._launched = False 436 437 def launch(self) -> None: 438 """ 439 Launch the VM and make sure we cleanup and expose the 440 command line/output in case of exception 441 """ 442 443 if self._launched: 444 raise QEMUMachineError('VM already launched') 445 446 try: 447 self._launch() 448 except BaseException as exc: 449 # We may have launched the process but it may 450 # have exited before we could connect via QMP. 451 # Assume the VM didn't launch or is exiting. 452 # If we don't wait for the process, exitcode() may still be 453 # 'None' by the time control is ceded back to the caller. 454 if self._launched: 455 self.wait() 456 else: 457 self._post_shutdown() 458 459 if isinstance(exc, Exception): 460 raise VMLaunchFailure( 461 exitcode=self.exitcode(), 462 command=' '.join(self._qemu_full_args), 463 output=self._iolog 464 ) from exc 465 466 # Don't wrap 'BaseException'; doing so would downgrade 467 # that exception. However, we still want to clean up. 468 raise 469 470 def _launch(self) -> None: 471 """ 472 Launch the VM and establish a QMP connection 473 """ 474 self._pre_launch() 475 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args)) 476 477 # Cleaning up of this subprocess is guaranteed by _do_shutdown. 478 # pylint: disable=consider-using-with 479 self._popen = subprocess.Popen(self._qemu_full_args, 480 stdin=subprocess.DEVNULL, 481 stdout=self._qemu_log_file, 482 stderr=subprocess.STDOUT, 483 shell=False, 484 close_fds=False) 485 self._launched = True 486 self._post_launch() 487 488 def _close_qmp_connection(self) -> None: 489 """ 490 Close the underlying QMP connection, if any. 491 492 Dutifully report errors that occurred while closing, but assume 493 that any error encountered indicates an abnormal termination 494 process and not a failure to close. 495 """ 496 if self._qmp_connection is None: 497 return 498 499 try: 500 self._qmp.close() 501 except EOFError: 502 # EOF can occur as an Exception here when using the Async 503 # QMP backend. It indicates that the server closed the 504 # stream. If we successfully issued 'quit' at any point, 505 # then this was expected. If the remote went away without 506 # our permission, it's worth reporting that as an abnormal 507 # shutdown case. 508 if not (self._user_killed or self._quit_issued): 509 raise 510 finally: 511 self._qmp_connection = None 512 513 def _early_cleanup(self) -> None: 514 """ 515 Perform any cleanup that needs to happen before the VM exits. 516 517 This method may be called twice upon shutdown, once each by soft 518 and hard shutdown in failover scenarios. 519 """ 520 # If we keep the console socket open, we may deadlock waiting 521 # for QEMU to exit, while QEMU is waiting for the socket to 522 # become writable. 523 if self._console_file is not None: 524 LOG.debug("Closing console file") 525 self._console_file.close() 526 self._console_file = None 527 528 if self._console_socket is not None: 529 LOG.debug("Closing console socket") 530 self._console_socket.close() 531 self._console_socket = None 532 533 if self._cons_sock_pair: 534 self._cons_sock_pair[0].close() 535 self._cons_sock_pair[1].close() 536 self._cons_sock_pair = None 537 538 def _hard_shutdown(self) -> None: 539 """ 540 Perform early cleanup, kill the VM, and wait for it to terminate. 541 542 :raise subprocess.Timeout: When timeout is exceeds 60 seconds 543 waiting for the QEMU process to terminate. 544 """ 545 LOG.debug("Performing hard shutdown") 546 self._early_cleanup() 547 self._subp.kill() 548 self._subp.wait(timeout=60) 549 550 def _soft_shutdown(self, timeout: Optional[int]) -> None: 551 """ 552 Perform early cleanup, attempt to gracefully shut down the VM, and wait 553 for it to terminate. 554 555 :param timeout: Timeout in seconds for graceful shutdown. 556 A value of None is an infinite wait. 557 558 :raise ConnectionReset: On QMP communication errors 559 :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for 560 the QEMU process to terminate. 561 """ 562 LOG.debug("Attempting graceful termination") 563 564 self._early_cleanup() 565 566 if self._quit_issued: 567 LOG.debug( 568 "Anticipating QEMU termination due to prior 'quit' command, " 569 "or explicit call to wait()" 570 ) 571 else: 572 LOG.debug("Politely asking QEMU to terminate") 573 574 if self._qmp_connection: 575 try: 576 if not self._quit_issued: 577 # May raise ExecInterruptedError or StateError if the 578 # connection dies or has *already* died. 579 self.qmp('quit') 580 finally: 581 # Regardless, we want to quiesce the connection. 582 self._close_qmp_connection() 583 elif not self._quit_issued: 584 LOG.debug( 585 "Not anticipating QEMU quit and no QMP connection present, " 586 "issuing SIGTERM" 587 ) 588 self._subp.terminate() 589 590 # May raise subprocess.TimeoutExpired 591 LOG.debug( 592 "Waiting (timeout=%s) for QEMU process (pid=%s) to terminate", 593 timeout, self._subp.pid 594 ) 595 self._subp.wait(timeout=timeout) 596 597 def _do_shutdown(self, timeout: Optional[int]) -> None: 598 """ 599 Attempt to shutdown the VM gracefully; fallback to a hard shutdown. 600 601 :param timeout: Timeout in seconds for graceful shutdown. 602 A value of None is an infinite wait. 603 604 :raise AbnormalShutdown: When the VM could not be shut down gracefully. 605 The inner exception will likely be ConnectionReset or 606 subprocess.TimeoutExpired. In rare cases, non-graceful termination 607 may result in its own exceptions, likely subprocess.TimeoutExpired. 608 """ 609 try: 610 self._soft_shutdown(timeout) 611 except Exception as exc: 612 if isinstance(exc, subprocess.TimeoutExpired): 613 LOG.debug("Timed out waiting for QEMU process to exit") 614 LOG.debug("Graceful shutdown failed", exc_info=True) 615 LOG.debug("Falling back to hard shutdown") 616 self._hard_shutdown() 617 raise AbnormalShutdown("Could not perform graceful shutdown") \ 618 from exc 619 620 def shutdown(self, 621 hard: bool = False, 622 timeout: Optional[int] = 30) -> None: 623 """ 624 Terminate the VM (gracefully if possible) and perform cleanup. 625 Cleanup will always be performed. 626 627 If the VM has not yet been launched, or shutdown(), wait(), or kill() 628 have already been called, this method does nothing. 629 630 :param hard: When true, do not attempt graceful shutdown, and 631 suppress the SIGKILL warning log message. 632 :param timeout: Optional timeout in seconds for graceful shutdown. 633 Default 30 seconds, A `None` value is an infinite wait. 634 """ 635 if not self._launched: 636 return 637 638 LOG.debug("Shutting down VM appliance; timeout=%s", timeout) 639 if hard: 640 LOG.debug("Caller requests immediate termination of QEMU process.") 641 642 try: 643 if hard: 644 self._user_killed = True 645 self._hard_shutdown() 646 else: 647 self._do_shutdown(timeout) 648 finally: 649 self._post_shutdown() 650 651 def kill(self) -> None: 652 """ 653 Terminate the VM forcefully, wait for it to exit, and perform cleanup. 654 """ 655 self.shutdown(hard=True) 656 657 def wait(self, timeout: Optional[int] = 30) -> None: 658 """ 659 Wait for the VM to power off and perform post-shutdown cleanup. 660 661 :param timeout: Optional timeout in seconds. Default 30 seconds. 662 A value of `None` is an infinite wait. 663 """ 664 self._quit_issued = True 665 self.shutdown(timeout=timeout) 666 667 def set_qmp_monitor(self, enabled: bool = True) -> None: 668 """ 669 Set the QMP monitor. 670 671 @param enabled: if False, qmp monitor options will be removed from 672 the base arguments of the resulting QEMU command 673 line. Default is True. 674 675 .. note:: Call this function before launch(). 676 """ 677 self._qmp_set = enabled 678 679 @property 680 def _qmp(self) -> QEMUMonitorProtocol: 681 if self._qmp_connection is None: 682 raise QEMUMachineError("Attempt to access QMP with no connection") 683 return self._qmp_connection 684 685 @classmethod 686 def _qmp_args(cls, conv_keys: bool, 687 args: Dict[str, Any]) -> Dict[str, object]: 688 if conv_keys: 689 return {k.replace('_', '-'): v for k, v in args.items()} 690 691 return args 692 693 def qmp(self, cmd: str, 694 args_dict: Optional[Dict[str, object]] = None, 695 conv_keys: Optional[bool] = None, 696 **args: Any) -> QMPMessage: 697 """ 698 Invoke a QMP command and return the response dict 699 """ 700 if args_dict is not None: 701 assert not args 702 assert conv_keys is None 703 args = args_dict 704 conv_keys = False 705 706 if conv_keys is None: 707 conv_keys = True 708 709 qmp_args = self._qmp_args(conv_keys, args) 710 ret = self._qmp.cmd_raw(cmd, args=qmp_args) 711 if cmd == 'quit' and 'error' not in ret and 'return' in ret: 712 self._quit_issued = True 713 return ret 714 715 def cmd(self, cmd: str, 716 args_dict: Optional[Dict[str, object]] = None, 717 conv_keys: Optional[bool] = None, 718 **args: Any) -> QMPReturnValue: 719 """ 720 Invoke a QMP command. 721 On success return the response dict. 722 On failure raise an exception. 723 """ 724 if args_dict is not None: 725 assert not args 726 assert conv_keys is None 727 args = args_dict 728 conv_keys = False 729 730 if conv_keys is None: 731 conv_keys = True 732 733 qmp_args = self._qmp_args(conv_keys, args) 734 ret = self._qmp.cmd(cmd, **qmp_args) 735 if cmd == 'quit': 736 self._quit_issued = True 737 return ret 738 739 def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]: 740 """ 741 Poll for one queued QMP events and return it 742 """ 743 if self._events: 744 return self._events.pop(0) 745 return self._qmp.pull_event(wait=wait) 746 747 def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]: 748 """ 749 Poll for queued QMP events and return a list of dicts 750 """ 751 events = self._qmp.get_events(wait=wait) 752 events.extend(self._events) 753 del self._events[:] 754 return events 755 756 @staticmethod 757 def event_match(event: Any, match: Optional[Any]) -> bool: 758 """ 759 Check if an event matches optional match criteria. 760 761 The match criteria takes the form of a matching subdict. The event is 762 checked to be a superset of the subdict, recursively, with matching 763 values whenever the subdict values are not None. 764 765 This has a limitation that you cannot explicitly check for None values. 766 767 Examples, with the subdict queries on the left: 768 - None matches any object. 769 - {"foo": None} matches {"foo": {"bar": 1}} 770 - {"foo": None} matches {"foo": 5} 771 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}} 772 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}} 773 """ 774 if match is None: 775 return True 776 777 try: 778 for key in match: 779 if key in event: 780 if not QEMUMachine.event_match(event[key], match[key]): 781 return False 782 else: 783 return False 784 return True 785 except TypeError: 786 # either match or event wasn't iterable (not a dict) 787 return bool(match == event) 788 789 def event_wait(self, name: str, 790 timeout: float = 60.0, 791 match: Optional[QMPMessage] = None) -> Optional[QMPMessage]: 792 """ 793 event_wait waits for and returns a named event from QMP with a timeout. 794 795 name: The event to wait for. 796 timeout: QEMUMonitorProtocol.pull_event timeout parameter. 797 match: Optional match criteria. See event_match for details. 798 """ 799 return self.events_wait([(name, match)], timeout) 800 801 def events_wait(self, 802 events: Sequence[Tuple[str, Any]], 803 timeout: float = 60.0) -> Optional[QMPMessage]: 804 """ 805 events_wait waits for and returns a single named event from QMP. 806 In the case of multiple qualifying events, this function returns the 807 first one. 808 809 :param events: A sequence of (name, match_criteria) tuples. 810 The match criteria are optional and may be None. 811 See event_match for details. 812 :param timeout: Optional timeout, in seconds. 813 See QEMUMonitorProtocol.pull_event. 814 815 :raise asyncio.TimeoutError: 816 If timeout was non-zero and no matching events were found. 817 818 :return: A QMP event matching the filter criteria. 819 If timeout was 0 and no event matched, None. 820 """ 821 def _match(event: QMPMessage) -> bool: 822 for name, match in events: 823 if event['event'] == name and self.event_match(event, match): 824 return True 825 return False 826 827 event: Optional[QMPMessage] 828 829 # Search cached events 830 for event in self._events: 831 if _match(event): 832 self._events.remove(event) 833 return event 834 835 # Poll for new events 836 while True: 837 event = self._qmp.pull_event(wait=timeout) 838 if event is None: 839 # NB: None is only returned when timeout is false-ish. 840 # Timeouts raise asyncio.TimeoutError instead! 841 break 842 if _match(event): 843 return event 844 self._events.append(event) 845 846 return None 847 848 def get_log(self) -> Optional[str]: 849 """ 850 After self.shutdown or failed qemu execution, this returns the output 851 of the qemu process. 852 """ 853 return self._iolog 854 855 def add_args(self, *args: str) -> None: 856 """ 857 Adds to the list of extra arguments to be given to the QEMU binary 858 """ 859 self._args.extend(args) 860 861 def set_machine(self, machine_type: str) -> None: 862 """ 863 Sets the machine type 864 865 If set, the machine type will be added to the base arguments 866 of the resulting QEMU command line. 867 """ 868 self._machine = machine_type 869 870 def set_console(self, 871 device_type: Optional[str] = None, 872 console_index: int = 0) -> None: 873 """ 874 Sets the device type for a console device 875 876 If set, the console device and a backing character device will 877 be added to the base arguments of the resulting QEMU command 878 line. 879 880 This is a convenience method that will either use the provided 881 device type, or default to a "-serial chardev:console" command 882 line argument. 883 884 The actual setting of command line arguments will be be done at 885 machine launch time, as it depends on the temporary directory 886 to be created. 887 888 @param device_type: the device type, such as "isa-serial". If 889 None is given (the default value) a "-serial 890 chardev:console" command line argument will 891 be used instead, resorting to the machine's 892 default device type. 893 @param console_index: the index of the console device to use. 894 If not zero, the command line will create 895 'index - 1' consoles and connect them to 896 the 'null' backing character device. 897 """ 898 self._console_set = True 899 self._console_device_type = device_type 900 self._console_index = console_index 901 902 @property 903 def console_socket(self) -> socket.socket: 904 """ 905 Returns a socket connected to the console 906 """ 907 if self._console_socket is None: 908 LOG.debug("Opening console socket") 909 if not self._console_set: 910 raise QEMUMachineError( 911 "Attempt to access console socket with no connection") 912 assert self._cons_sock_pair is not None 913 # os.dup() is used here for sock_fd because otherwise we'd 914 # have two rich python socket objects that would each try to 915 # close the same underlying fd when either one gets garbage 916 # collected. 917 self._console_socket = console_socket.ConsoleSocket( 918 sock_fd=os.dup(self._cons_sock_pair[1].fileno()), 919 file=self._console_log_path, 920 drain=self._drain_console) 921 self._cons_sock_pair[1].close() 922 return self._console_socket 923 924 @property 925 def console_file(self) -> socket.SocketIO: 926 """ 927 Returns a file associated with the console socket 928 """ 929 if self._console_file is None: 930 LOG.debug("Opening console file") 931 self._console_file = self.console_socket.makefile(mode='rb', 932 buffering=0, 933 encoding='utf-8') 934 return self._console_file 935 936 @property 937 def temp_dir(self) -> str: 938 """ 939 Returns a temporary directory to be used for this machine 940 """ 941 if self._temp_dir is None: 942 self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-", 943 dir=self._base_temp_dir) 944 return self._temp_dir 945 946 @property 947 def log_dir(self) -> str: 948 """ 949 Returns a directory to be used for writing logs 950 """ 951 if self._log_dir is None: 952 return self.temp_dir 953 return self._log_dir 954