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