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