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 def _pre_launch(self) -> None: 332 if self._qmp_set: 333 if self._monitor_address is None: 334 self._sock_pair = socket.socketpair() 335 os.set_inheritable(self._sock_pair[0].fileno(), True) 336 sock = self._sock_pair[1] 337 if isinstance(self._monitor_address, str): 338 self._remove_files.append(self._monitor_address) 339 340 sock_or_addr = self._monitor_address or sock 341 assert sock_or_addr is not None 342 343 self._qmp_connection = QEMUMonitorProtocol( 344 sock_or_addr, 345 server=bool(self._monitor_address), 346 nickname=self._name 347 ) 348 349 if self._console_set: 350 self._cons_sock_pair = socket.socketpair() 351 os.set_inheritable(self._cons_sock_pair[0].fileno(), True) 352 353 # NOTE: Make sure any opened resources are *definitely* freed in 354 # _post_shutdown()! 355 # pylint: disable=consider-using-with 356 self._qemu_log_path = os.path.join(self.log_dir, self._name + ".log") 357 self._qemu_log_file = open(self._qemu_log_path, 'wb') 358 359 self._iolog = None 360 self._qemu_full_args = tuple(chain( 361 self._wrapper, 362 [self._binary], 363 self._base_args, 364 self._args 365 )) 366 367 def _post_launch(self) -> None: 368 if self._sock_pair: 369 self._sock_pair[0].close() 370 if self._cons_sock_pair: 371 self._cons_sock_pair[0].close() 372 373 if self._qmp_connection: 374 if self._sock_pair: 375 self._qmp.connect() 376 else: 377 self._qmp.accept(self._qmp_timer) 378 379 def _close_qemu_log_file(self) -> None: 380 if self._qemu_log_file is not None: 381 self._qemu_log_file.close() 382 self._qemu_log_file = None 383 384 def _post_shutdown(self) -> None: 385 """ 386 Called to cleanup the VM instance after the process has exited. 387 May also be called after a failed launch. 388 """ 389 LOG.debug("Cleaning up after VM process") 390 try: 391 self._close_qmp_connection() 392 except Exception as err: # pylint: disable=broad-except 393 LOG.warning( 394 "Exception closing QMP connection: %s", 395 str(err) if str(err) else type(err).__name__ 396 ) 397 finally: 398 assert self._qmp_connection is None 399 400 if self._sock_pair: 401 self._sock_pair[0].close() 402 self._sock_pair[1].close() 403 self._sock_pair = None 404 405 self._close_qemu_log_file() 406 407 self._load_io_log() 408 409 self._qemu_log_path = None 410 411 if self._temp_dir is not None: 412 shutil.rmtree(self._temp_dir) 413 self._temp_dir = None 414 415 while len(self._remove_files) > 0: 416 self._remove_if_exists(self._remove_files.pop()) 417 418 exitcode = self.exitcode() 419 if (exitcode is not None and exitcode < 0 420 and not (self._user_killed and exitcode == -signal.SIGKILL)): 421 msg = 'qemu received signal %i; command: "%s"' 422 if self._qemu_full_args: 423 command = ' '.join(self._qemu_full_args) 424 else: 425 command = '' 426 LOG.warning(msg, -int(exitcode), command) 427 428 self._quit_issued = False 429 self._user_killed = False 430 self._launched = False 431 432 def launch(self) -> None: 433 """ 434 Launch the VM and make sure we cleanup and expose the 435 command line/output in case of exception 436 """ 437 438 if self._launched: 439 raise QEMUMachineError('VM already launched') 440 441 try: 442 self._launch() 443 except BaseException as exc: 444 # We may have launched the process but it may 445 # have exited before we could connect via QMP. 446 # Assume the VM didn't launch or is exiting. 447 # If we don't wait for the process, exitcode() may still be 448 # 'None' by the time control is ceded back to the caller. 449 if self._launched: 450 self.wait() 451 else: 452 self._post_shutdown() 453 454 if isinstance(exc, Exception): 455 raise VMLaunchFailure( 456 exitcode=self.exitcode(), 457 command=' '.join(self._qemu_full_args), 458 output=self._iolog 459 ) from exc 460 461 # Don't wrap 'BaseException'; doing so would downgrade 462 # that exception. However, we still want to clean up. 463 raise 464 465 def _launch(self) -> None: 466 """ 467 Launch the VM and establish a QMP connection 468 """ 469 self._pre_launch() 470 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args)) 471 472 # Cleaning up of this subprocess is guaranteed by _do_shutdown. 473 # pylint: disable=consider-using-with 474 self._popen = subprocess.Popen(self._qemu_full_args, 475 stdin=subprocess.DEVNULL, 476 stdout=self._qemu_log_file, 477 stderr=subprocess.STDOUT, 478 shell=False, 479 close_fds=False) 480 self._launched = True 481 self._post_launch() 482 483 def _close_qmp_connection(self) -> None: 484 """ 485 Close the underlying QMP connection, if any. 486 487 Dutifully report errors that occurred while closing, but assume 488 that any error encountered indicates an abnormal termination 489 process and not a failure to close. 490 """ 491 if self._qmp_connection is None: 492 return 493 494 try: 495 self._qmp.close() 496 except EOFError: 497 # EOF can occur as an Exception here when using the Async 498 # QMP backend. It indicates that the server closed the 499 # stream. If we successfully issued 'quit' at any point, 500 # then this was expected. If the remote went away without 501 # our permission, it's worth reporting that as an abnormal 502 # shutdown case. 503 if not (self._user_killed or self._quit_issued): 504 raise 505 finally: 506 self._qmp_connection = None 507 508 def _early_cleanup(self) -> None: 509 """ 510 Perform any cleanup that needs to happen before the VM exits. 511 512 This method may be called twice upon shutdown, once each by soft 513 and hard shutdown in failover scenarios. 514 """ 515 # If we keep the console socket open, we may deadlock waiting 516 # for QEMU to exit, while QEMU is waiting for the socket to 517 # become writable. 518 if self._console_file is not None: 519 LOG.debug("Closing console file") 520 self._console_file.close() 521 self._console_file = None 522 523 if self._console_socket is not None: 524 LOG.debug("Closing console socket") 525 self._console_socket.close() 526 self._console_socket = None 527 528 if self._cons_sock_pair: 529 self._cons_sock_pair[0].close() 530 self._cons_sock_pair[1].close() 531 self._cons_sock_pair = None 532 533 def _hard_shutdown(self) -> None: 534 """ 535 Perform early cleanup, kill the VM, and wait for it to terminate. 536 537 :raise subprocess.Timeout: When timeout is exceeds 60 seconds 538 waiting for the QEMU process to terminate. 539 """ 540 LOG.debug("Performing hard shutdown") 541 self._early_cleanup() 542 self._subp.kill() 543 self._subp.wait(timeout=60) 544 545 def _soft_shutdown(self, timeout: Optional[int]) -> None: 546 """ 547 Perform early cleanup, attempt to gracefully shut down the VM, and wait 548 for it to terminate. 549 550 :param timeout: Timeout in seconds for graceful shutdown. 551 A value of None is an infinite wait. 552 553 :raise ConnectionReset: On QMP communication errors 554 :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for 555 the QEMU process to terminate. 556 """ 557 LOG.debug("Attempting graceful termination") 558 559 self._early_cleanup() 560 561 if self._quit_issued: 562 LOG.debug( 563 "Anticipating QEMU termination due to prior 'quit' command, " 564 "or explicit call to wait()" 565 ) 566 else: 567 LOG.debug("Politely asking QEMU to terminate") 568 569 if self._qmp_connection: 570 try: 571 if not self._quit_issued: 572 # May raise ExecInterruptedError or StateError if the 573 # connection dies or has *already* died. 574 self.qmp('quit') 575 finally: 576 # Regardless, we want to quiesce the connection. 577 self._close_qmp_connection() 578 elif not self._quit_issued: 579 LOG.debug( 580 "Not anticipating QEMU quit and no QMP connection present, " 581 "issuing SIGTERM" 582 ) 583 self._subp.terminate() 584 585 # May raise subprocess.TimeoutExpired 586 LOG.debug( 587 "Waiting (timeout=%s) for QEMU process (pid=%s) to terminate", 588 timeout, self._subp.pid 589 ) 590 self._subp.wait(timeout=timeout) 591 592 def _do_shutdown(self, timeout: Optional[int]) -> None: 593 """ 594 Attempt to shutdown the VM gracefully; fallback to a hard shutdown. 595 596 :param timeout: Timeout in seconds for graceful shutdown. 597 A value of None is an infinite wait. 598 599 :raise AbnormalShutdown: When the VM could not be shut down gracefully. 600 The inner exception will likely be ConnectionReset or 601 subprocess.TimeoutExpired. In rare cases, non-graceful termination 602 may result in its own exceptions, likely subprocess.TimeoutExpired. 603 """ 604 try: 605 self._soft_shutdown(timeout) 606 except Exception as exc: 607 if isinstance(exc, subprocess.TimeoutExpired): 608 LOG.debug("Timed out waiting for QEMU process to exit") 609 LOG.debug("Graceful shutdown failed", exc_info=True) 610 LOG.debug("Falling back to hard shutdown") 611 self._hard_shutdown() 612 raise AbnormalShutdown("Could not perform graceful shutdown") \ 613 from exc 614 615 def shutdown(self, 616 hard: bool = False, 617 timeout: Optional[int] = 30) -> None: 618 """ 619 Terminate the VM (gracefully if possible) and perform cleanup. 620 Cleanup will always be performed. 621 622 If the VM has not yet been launched, or shutdown(), wait(), or kill() 623 have already been called, this method does nothing. 624 625 :param hard: When true, do not attempt graceful shutdown, and 626 suppress the SIGKILL warning log message. 627 :param timeout: Optional timeout in seconds for graceful shutdown. 628 Default 30 seconds, A `None` value is an infinite wait. 629 """ 630 if not self._launched: 631 return 632 633 LOG.debug("Shutting down VM appliance; timeout=%s", timeout) 634 if hard: 635 LOG.debug("Caller requests immediate termination of QEMU process.") 636 637 try: 638 if hard: 639 self._user_killed = True 640 self._hard_shutdown() 641 else: 642 self._do_shutdown(timeout) 643 finally: 644 self._post_shutdown() 645 646 def kill(self) -> None: 647 """ 648 Terminate the VM forcefully, wait for it to exit, and perform cleanup. 649 """ 650 self.shutdown(hard=True) 651 652 def wait(self, timeout: Optional[int] = 30) -> None: 653 """ 654 Wait for the VM to power off and perform post-shutdown cleanup. 655 656 :param timeout: Optional timeout in seconds. Default 30 seconds. 657 A value of `None` is an infinite wait. 658 """ 659 self._quit_issued = True 660 self.shutdown(timeout=timeout) 661 662 def set_qmp_monitor(self, enabled: bool = True) -> None: 663 """ 664 Set the QMP monitor. 665 666 @param enabled: if False, qmp monitor options will be removed from 667 the base arguments of the resulting QEMU command 668 line. Default is True. 669 670 .. note:: Call this function before launch(). 671 """ 672 self._qmp_set = enabled 673 674 @property 675 def _qmp(self) -> QEMUMonitorProtocol: 676 if self._qmp_connection is None: 677 raise QEMUMachineError("Attempt to access QMP with no connection") 678 return self._qmp_connection 679 680 @classmethod 681 def _qmp_args(cls, conv_keys: bool, 682 args: Dict[str, Any]) -> Dict[str, object]: 683 if conv_keys: 684 return {k.replace('_', '-'): v for k, v in args.items()} 685 686 return args 687 688 def qmp(self, cmd: str, 689 args_dict: Optional[Dict[str, object]] = None, 690 conv_keys: Optional[bool] = None, 691 **args: Any) -> QMPMessage: 692 """ 693 Invoke a QMP command and return the response dict 694 """ 695 if args_dict is not None: 696 assert not args 697 assert conv_keys is None 698 args = args_dict 699 conv_keys = False 700 701 if conv_keys is None: 702 conv_keys = True 703 704 qmp_args = self._qmp_args(conv_keys, args) 705 ret = self._qmp.cmd_raw(cmd, args=qmp_args) 706 if cmd == 'quit' and 'error' not in ret and 'return' in ret: 707 self._quit_issued = True 708 return ret 709 710 def cmd(self, cmd: str, 711 args_dict: Optional[Dict[str, object]] = None, 712 conv_keys: Optional[bool] = None, 713 **args: Any) -> QMPReturnValue: 714 """ 715 Invoke a QMP command. 716 On success return the response dict. 717 On failure raise an exception. 718 """ 719 if args_dict is not None: 720 assert not args 721 assert conv_keys is None 722 args = args_dict 723 conv_keys = False 724 725 if conv_keys is None: 726 conv_keys = True 727 728 qmp_args = self._qmp_args(conv_keys, args) 729 ret = self._qmp.cmd(cmd, **qmp_args) 730 if cmd == 'quit': 731 self._quit_issued = True 732 return ret 733 734 def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]: 735 """ 736 Poll for one queued QMP events and return it 737 """ 738 if self._events: 739 return self._events.pop(0) 740 return self._qmp.pull_event(wait=wait) 741 742 def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]: 743 """ 744 Poll for queued QMP events and return a list of dicts 745 """ 746 events = self._qmp.get_events(wait=wait) 747 events.extend(self._events) 748 del self._events[:] 749 return events 750 751 @staticmethod 752 def event_match(event: Any, match: Optional[Any]) -> bool: 753 """ 754 Check if an event matches optional match criteria. 755 756 The match criteria takes the form of a matching subdict. The event is 757 checked to be a superset of the subdict, recursively, with matching 758 values whenever the subdict values are not None. 759 760 This has a limitation that you cannot explicitly check for None values. 761 762 Examples, with the subdict queries on the left: 763 - None matches any object. 764 - {"foo": None} matches {"foo": {"bar": 1}} 765 - {"foo": None} matches {"foo": 5} 766 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}} 767 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}} 768 """ 769 if match is None: 770 return True 771 772 try: 773 for key in match: 774 if key in event: 775 if not QEMUMachine.event_match(event[key], match[key]): 776 return False 777 else: 778 return False 779 return True 780 except TypeError: 781 # either match or event wasn't iterable (not a dict) 782 return bool(match == event) 783 784 def event_wait(self, name: str, 785 timeout: float = 60.0, 786 match: Optional[QMPMessage] = None) -> Optional[QMPMessage]: 787 """ 788 event_wait waits for and returns a named event from QMP with a timeout. 789 790 name: The event to wait for. 791 timeout: QEMUMonitorProtocol.pull_event timeout parameter. 792 match: Optional match criteria. See event_match for details. 793 """ 794 return self.events_wait([(name, match)], timeout) 795 796 def events_wait(self, 797 events: Sequence[Tuple[str, Any]], 798 timeout: float = 60.0) -> Optional[QMPMessage]: 799 """ 800 events_wait waits for and returns a single named event from QMP. 801 In the case of multiple qualifying events, this function returns the 802 first one. 803 804 :param events: A sequence of (name, match_criteria) tuples. 805 The match criteria are optional and may be None. 806 See event_match for details. 807 :param timeout: Optional timeout, in seconds. 808 See QEMUMonitorProtocol.pull_event. 809 810 :raise asyncio.TimeoutError: 811 If timeout was non-zero and no matching events were found. 812 813 :return: A QMP event matching the filter criteria. 814 If timeout was 0 and no event matched, None. 815 """ 816 def _match(event: QMPMessage) -> bool: 817 for name, match in events: 818 if event['event'] == name and self.event_match(event, match): 819 return True 820 return False 821 822 event: Optional[QMPMessage] 823 824 # Search cached events 825 for event in self._events: 826 if _match(event): 827 self._events.remove(event) 828 return event 829 830 # Poll for new events 831 while True: 832 event = self._qmp.pull_event(wait=timeout) 833 if event is None: 834 # NB: None is only returned when timeout is false-ish. 835 # Timeouts raise asyncio.TimeoutError instead! 836 break 837 if _match(event): 838 return event 839 self._events.append(event) 840 841 return None 842 843 def get_log(self) -> Optional[str]: 844 """ 845 After self.shutdown or failed qemu execution, this returns the output 846 of the qemu process. 847 """ 848 return self._iolog 849 850 def add_args(self, *args: str) -> None: 851 """ 852 Adds to the list of extra arguments to be given to the QEMU binary 853 """ 854 self._args.extend(args) 855 856 def set_machine(self, machine_type: str) -> None: 857 """ 858 Sets the machine type 859 860 If set, the machine type will be added to the base arguments 861 of the resulting QEMU command line. 862 """ 863 self._machine = machine_type 864 865 def set_console(self, 866 device_type: Optional[str] = None, 867 console_index: int = 0) -> None: 868 """ 869 Sets the device type for a console device 870 871 If set, the console device and a backing character device will 872 be added to the base arguments of the resulting QEMU command 873 line. 874 875 This is a convenience method that will either use the provided 876 device type, or default to a "-serial chardev:console" command 877 line argument. 878 879 The actual setting of command line arguments will be be done at 880 machine launch time, as it depends on the temporary directory 881 to be created. 882 883 @param device_type: the device type, such as "isa-serial". If 884 None is given (the default value) a "-serial 885 chardev:console" command line argument will 886 be used instead, resorting to the machine's 887 default device type. 888 @param console_index: the index of the console device to use. 889 If not zero, the command line will create 890 'index - 1' consoles and connect them to 891 the 'null' backing character device. 892 """ 893 self._console_set = True 894 self._console_device_type = device_type 895 self._console_index = console_index 896 897 @property 898 def console_socket(self) -> socket.socket: 899 """ 900 Returns a socket connected to the console 901 """ 902 if self._console_socket is None: 903 LOG.debug("Opening console socket") 904 if not self._console_set: 905 raise QEMUMachineError( 906 "Attempt to access console socket with no connection") 907 assert self._cons_sock_pair is not None 908 # os.dup() is used here for sock_fd because otherwise we'd 909 # have two rich python socket objects that would each try to 910 # close the same underlying fd when either one gets garbage 911 # collected. 912 self._console_socket = console_socket.ConsoleSocket( 913 sock_fd=os.dup(self._cons_sock_pair[1].fileno()), 914 file=self._console_log_path, 915 drain=self._drain_console) 916 self._cons_sock_pair[1].close() 917 return self._console_socket 918 919 @property 920 def console_file(self) -> socket.SocketIO: 921 """ 922 Returns a file associated with the console socket 923 """ 924 if self._console_file is None: 925 LOG.debug("Opening console file") 926 self._console_file = self.console_socket.makefile(mode='rb', 927 buffering=0, 928 encoding='utf-8') 929 return self._console_file 930 931 @property 932 def temp_dir(self) -> str: 933 """ 934 Returns a temporary directory to be used for this machine 935 """ 936 if self._temp_dir is None: 937 self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-", 938 dir=self._base_temp_dir) 939 return self._temp_dir 940 941 @property 942 def log_dir(self) -> str: 943 """ 944 Returns a directory to be used for writing logs 945 """ 946 if self._log_dir is None: 947 return self.temp_dir 948 return self._log_dir 949