1beb6b57bSJohn Snow""" 2beb6b57bSJohn SnowQEMU machine module: 3beb6b57bSJohn Snow 4beb6b57bSJohn SnowThe machine module primarily provides the QEMUMachine class, 5beb6b57bSJohn Snowwhich provides facilities for managing the lifetime of a QEMU VM. 6beb6b57bSJohn Snow""" 7beb6b57bSJohn Snow 8beb6b57bSJohn Snow# Copyright (C) 2015-2016 Red Hat Inc. 9beb6b57bSJohn Snow# Copyright (C) 2012 IBM Corp. 10beb6b57bSJohn Snow# 11beb6b57bSJohn Snow# Authors: 12beb6b57bSJohn Snow# Fam Zheng <famz@redhat.com> 13beb6b57bSJohn Snow# 14beb6b57bSJohn Snow# This work is licensed under the terms of the GNU GPL, version 2. See 15beb6b57bSJohn Snow# the COPYING file in the top-level directory. 16beb6b57bSJohn Snow# 17beb6b57bSJohn Snow# Based on qmp.py. 18beb6b57bSJohn Snow# 19beb6b57bSJohn Snow 20beb6b57bSJohn Snowimport errno 21beb6b57bSJohn Snowfrom itertools import chain 225690b437SJohn Snowimport locale 23beb6b57bSJohn Snowimport logging 24beb6b57bSJohn Snowimport os 25beb6b57bSJohn Snowimport shutil 26beb6b57bSJohn Snowimport signal 27beb6b57bSJohn Snowimport socket 28beb6b57bSJohn Snowimport subprocess 29beb6b57bSJohn Snowimport tempfile 30beb6b57bSJohn Snowfrom types import TracebackType 31beb6b57bSJohn Snowfrom typing import ( 32beb6b57bSJohn Snow Any, 33beb6b57bSJohn Snow BinaryIO, 34beb6b57bSJohn Snow Dict, 35beb6b57bSJohn Snow List, 36beb6b57bSJohn Snow Optional, 37beb6b57bSJohn Snow Sequence, 38beb6b57bSJohn Snow Tuple, 39beb6b57bSJohn Snow Type, 4015c3b863SVladimir Sementsov-Ogievskiy TypeVar, 41beb6b57bSJohn Snow) 42beb6b57bSJohn Snow 4337094b6dSJohn Snowfrom qemu.qmp import SocketAddrT 4437094b6dSJohn Snowfrom qemu.qmp.legacy import ( 45a4225303SJohn Snow QEMUMonitorProtocol, 46beb6b57bSJohn Snow QMPMessage, 47beb6b57bSJohn Snow QMPReturnValue, 48beb6b57bSJohn Snow) 49beb6b57bSJohn Snow 50beb6b57bSJohn Snowfrom . import console_socket 51beb6b57bSJohn Snow 52beb6b57bSJohn Snow 53beb6b57bSJohn SnowLOG = logging.getLogger(__name__) 54beb6b57bSJohn Snow 55beb6b57bSJohn Snow 56beb6b57bSJohn Snowclass QEMUMachineError(Exception): 57beb6b57bSJohn Snow """ 58beb6b57bSJohn Snow Exception called when an error in QEMUMachine happens. 59beb6b57bSJohn Snow """ 60beb6b57bSJohn Snow 61beb6b57bSJohn Snow 62beb6b57bSJohn Snowclass QEMUMachineAddDeviceError(QEMUMachineError): 63beb6b57bSJohn Snow """ 64beb6b57bSJohn Snow Exception raised when a request to add a device can not be fulfilled 65beb6b57bSJohn Snow 66beb6b57bSJohn Snow The failures are caused by limitations, lack of information or conflicting 67beb6b57bSJohn Snow requests on the QEMUMachine methods. This exception does not represent 68beb6b57bSJohn Snow failures reported by the QEMU binary itself. 69beb6b57bSJohn Snow """ 70beb6b57bSJohn Snow 71beb6b57bSJohn Snow 7250465f94SJohn Snowclass VMLaunchFailure(QEMUMachineError): 7350465f94SJohn Snow """ 7450465f94SJohn Snow Exception raised when a VM launch was attempted, but failed. 7550465f94SJohn Snow """ 7650465f94SJohn Snow def __init__(self, exitcode: Optional[int], 7750465f94SJohn Snow command: str, output: Optional[str]): 7850465f94SJohn Snow super().__init__(exitcode, command, output) 7950465f94SJohn Snow self.exitcode = exitcode 8050465f94SJohn Snow self.command = command 8150465f94SJohn Snow self.output = output 8250465f94SJohn Snow 8350465f94SJohn Snow def __str__(self) -> str: 8450465f94SJohn Snow ret = '' 8550465f94SJohn Snow if self.__cause__ is not None: 8650465f94SJohn Snow name = type(self.__cause__).__name__ 8750465f94SJohn Snow reason = str(self.__cause__) 8850465f94SJohn Snow if reason: 8950465f94SJohn Snow ret += f"{name}: {reason}" 9050465f94SJohn Snow else: 9150465f94SJohn Snow ret += f"{name}" 9250465f94SJohn Snow ret += '\n' 9350465f94SJohn Snow 9450465f94SJohn Snow if self.exitcode is not None: 9550465f94SJohn Snow ret += f"\tExit code: {self.exitcode}\n" 9650465f94SJohn Snow ret += f"\tCommand: {self.command}\n" 9750465f94SJohn Snow ret += f"\tOutput: {self.output}\n" 9850465f94SJohn Snow return ret 9950465f94SJohn Snow 10050465f94SJohn Snow 101beb6b57bSJohn Snowclass AbnormalShutdown(QEMUMachineError): 102beb6b57bSJohn Snow """ 103beb6b57bSJohn Snow Exception raised when a graceful shutdown was requested, but not performed. 104beb6b57bSJohn Snow """ 105beb6b57bSJohn Snow 106beb6b57bSJohn Snow 10715c3b863SVladimir Sementsov-Ogievskiy_T = TypeVar('_T', bound='QEMUMachine') 10815c3b863SVladimir Sementsov-Ogievskiy 10915c3b863SVladimir Sementsov-Ogievskiy 110beb6b57bSJohn Snowclass QEMUMachine: 111beb6b57bSJohn Snow """ 112beb6b57bSJohn Snow A QEMU VM. 113beb6b57bSJohn Snow 114beb6b57bSJohn Snow Use this object as a context manager to ensure 115beb6b57bSJohn Snow the QEMU process terminates:: 116beb6b57bSJohn Snow 117beb6b57bSJohn Snow with VM(binary) as vm: 118beb6b57bSJohn Snow ... 119beb6b57bSJohn Snow # vm is guaranteed to be shut down here 120beb6b57bSJohn Snow """ 12182e6517dSJohn Snow # pylint: disable=too-many-instance-attributes, too-many-public-methods 122beb6b57bSJohn Snow 123beb6b57bSJohn Snow def __init__(self, 124beb6b57bSJohn Snow binary: str, 125beb6b57bSJohn Snow args: Sequence[str] = (), 126beb6b57bSJohn Snow wrapper: Sequence[str] = (), 127beb6b57bSJohn Snow name: Optional[str] = None, 128beb6b57bSJohn Snow base_temp_dir: str = "/var/tmp", 129beb6b57bSJohn Snow monitor_address: Optional[SocketAddrT] = None, 130beb6b57bSJohn Snow drain_console: bool = False, 131b306e26cSCleber Rosa console_log: Optional[str] = None, 132e2f948a8SEmanuele Giuseppe Esposito log_dir: Optional[str] = None, 133ada73a49SVladimir Sementsov-Ogievskiy qmp_timer: Optional[float] = 30): 134beb6b57bSJohn Snow ''' 135beb6b57bSJohn Snow Initialize a QEMUMachine 136beb6b57bSJohn Snow 137beb6b57bSJohn Snow @param binary: path to the qemu binary 138beb6b57bSJohn Snow @param args: list of extra arguments 139beb6b57bSJohn Snow @param wrapper: list of arguments used as prefix to qemu binary 140beb6b57bSJohn Snow @param name: prefix for socket and log file names (default: qemu-PID) 141beb6b57bSJohn Snow @param base_temp_dir: default location where temp files are created 142beb6b57bSJohn Snow @param monitor_address: address for QMP monitor 143beb6b57bSJohn Snow @param drain_console: (optional) True to drain console socket to buffer 144beb6b57bSJohn Snow @param console_log: (optional) path to console log file 145b306e26cSCleber Rosa @param log_dir: where to create and keep log files 146e2f948a8SEmanuele Giuseppe Esposito @param qmp_timer: (optional) default QMP socket timeout 147beb6b57bSJohn Snow @note: Qemu process is not started until launch() is used. 148beb6b57bSJohn Snow ''' 14982e6517dSJohn Snow # pylint: disable=too-many-arguments 15082e6517dSJohn Snow 151beb6b57bSJohn Snow # Direct user configuration 152beb6b57bSJohn Snow 153beb6b57bSJohn Snow self._binary = binary 154beb6b57bSJohn Snow self._args = list(args) 155beb6b57bSJohn Snow self._wrapper = wrapper 156e2f948a8SEmanuele Giuseppe Esposito self._qmp_timer = qmp_timer 157beb6b57bSJohn Snow 158f9922937SPeter Delevoryas self._name = name or f"{id(self):x}" 159bd4c0ef4SMarc-André Lureau self._sock_pair: Optional[Tuple[socket.socket, socket.socket]] = None 1601d4796cdSJohn Snow self._cons_sock_pair: Optional[ 1611d4796cdSJohn Snow Tuple[socket.socket, socket.socket]] = None 16287bf1fe5SJohn Snow self._temp_dir: Optional[str] = None 163beb6b57bSJohn Snow self._base_temp_dir = base_temp_dir 164b306e26cSCleber Rosa self._log_dir = log_dir 165beb6b57bSJohn Snow 166beb6b57bSJohn Snow self._monitor_address = monitor_address 167beb6b57bSJohn Snow 168beb6b57bSJohn Snow self._console_log_path = console_log 169beb6b57bSJohn Snow if self._console_log_path: 170beb6b57bSJohn Snow # In order to log the console, buffering needs to be enabled. 171beb6b57bSJohn Snow self._drain_console = True 172beb6b57bSJohn Snow else: 173beb6b57bSJohn Snow self._drain_console = drain_console 174beb6b57bSJohn Snow 175beb6b57bSJohn Snow # Runstate 176beb6b57bSJohn Snow self._qemu_log_path: Optional[str] = None 177beb6b57bSJohn Snow self._qemu_log_file: Optional[BinaryIO] = None 178beb6b57bSJohn Snow self._popen: Optional['subprocess.Popen[bytes]'] = None 179beb6b57bSJohn Snow self._events: List[QMPMessage] = [] 180beb6b57bSJohn Snow self._iolog: Optional[str] = None 181beb6b57bSJohn Snow self._qmp_set = True # Enable QMP monitor by default. 182beb6b57bSJohn Snow self._qmp_connection: Optional[QEMUMonitorProtocol] = None 183beb6b57bSJohn Snow self._qemu_full_args: Tuple[str, ...] = () 184beb6b57bSJohn Snow self._launched = False 185beb6b57bSJohn Snow self._machine: Optional[str] = None 186beb6b57bSJohn Snow self._console_index = 0 187beb6b57bSJohn Snow self._console_set = False 188beb6b57bSJohn Snow self._console_device_type: Optional[str] = None 189beb6b57bSJohn Snow self._console_socket: Optional[socket.socket] = None 190f0ec14c7SNicholas Piggin self._console_file: Optional[socket.SocketIO] = None 191beb6b57bSJohn Snow self._remove_files: List[str] = [] 192beb6b57bSJohn Snow self._user_killed = False 193b9420e4fSJohn Snow self._quit_issued = False 194beb6b57bSJohn Snow 19515c3b863SVladimir Sementsov-Ogievskiy def __enter__(self: _T) -> _T: 196beb6b57bSJohn Snow return self 197beb6b57bSJohn Snow 198beb6b57bSJohn Snow def __exit__(self, 199beb6b57bSJohn Snow exc_type: Optional[Type[BaseException]], 200beb6b57bSJohn Snow exc_val: Optional[BaseException], 201beb6b57bSJohn Snow exc_tb: Optional[TracebackType]) -> None: 202beb6b57bSJohn Snow self.shutdown() 203beb6b57bSJohn Snow 204beb6b57bSJohn Snow def add_monitor_null(self) -> None: 205beb6b57bSJohn Snow """ 206beb6b57bSJohn Snow This can be used to add an unused monitor instance. 207beb6b57bSJohn Snow """ 208beb6b57bSJohn Snow self._args.append('-monitor') 209beb6b57bSJohn Snow self._args.append('null') 210beb6b57bSJohn Snow 21115c3b863SVladimir Sementsov-Ogievskiy def add_fd(self: _T, fd: int, fdset: int, 21215c3b863SVladimir Sementsov-Ogievskiy opaque: str, opts: str = '') -> _T: 213beb6b57bSJohn Snow """ 214beb6b57bSJohn Snow Pass a file descriptor to the VM 215beb6b57bSJohn Snow """ 216beb6b57bSJohn Snow options = ['fd=%d' % fd, 217beb6b57bSJohn Snow 'set=%d' % fdset, 218beb6b57bSJohn Snow 'opaque=%s' % opaque] 219beb6b57bSJohn Snow if opts: 220beb6b57bSJohn Snow options.append(opts) 221beb6b57bSJohn Snow 222beb6b57bSJohn Snow # This did not exist before 3.4, but since then it is 223beb6b57bSJohn Snow # mandatory for our purpose 224beb6b57bSJohn Snow if hasattr(os, 'set_inheritable'): 225beb6b57bSJohn Snow os.set_inheritable(fd, True) 226beb6b57bSJohn Snow 227beb6b57bSJohn Snow self._args.append('-add-fd') 228beb6b57bSJohn Snow self._args.append(','.join(options)) 229beb6b57bSJohn Snow return self 230beb6b57bSJohn Snow 231beb6b57bSJohn Snow def send_fd_scm(self, fd: Optional[int] = None, 232beb6b57bSJohn Snow file_path: Optional[str] = None) -> int: 233beb6b57bSJohn Snow """ 234514d00dfSJohn Snow Send an fd or file_path to the remote via SCM_RIGHTS. 235beb6b57bSJohn Snow 236514d00dfSJohn Snow Exactly one of fd and file_path must be given. If it is 237514d00dfSJohn Snow file_path, the file will be opened read-only and the new file 238514d00dfSJohn Snow descriptor will be sent to the remote. 239beb6b57bSJohn Snow """ 240beb6b57bSJohn Snow if file_path is not None: 241beb6b57bSJohn Snow assert fd is None 242514d00dfSJohn Snow with open(file_path, "rb") as passfile: 243514d00dfSJohn Snow fd = passfile.fileno() 244514d00dfSJohn Snow self._qmp.send_fd_scm(fd) 245beb6b57bSJohn Snow else: 246beb6b57bSJohn Snow assert fd is not None 247514d00dfSJohn Snow self._qmp.send_fd_scm(fd) 248beb6b57bSJohn Snow 249514d00dfSJohn Snow return 0 250beb6b57bSJohn Snow 251beb6b57bSJohn Snow @staticmethod 252beb6b57bSJohn Snow def _remove_if_exists(path: str) -> None: 253beb6b57bSJohn Snow """ 254beb6b57bSJohn Snow Remove file object at path if it exists 255beb6b57bSJohn Snow """ 256beb6b57bSJohn Snow try: 257beb6b57bSJohn Snow os.remove(path) 258beb6b57bSJohn Snow except OSError as exception: 259beb6b57bSJohn Snow if exception.errno == errno.ENOENT: 260beb6b57bSJohn Snow return 261beb6b57bSJohn Snow raise 262beb6b57bSJohn Snow 263beb6b57bSJohn Snow def is_running(self) -> bool: 264beb6b57bSJohn Snow """Returns true if the VM is running.""" 265beb6b57bSJohn Snow return self._popen is not None and self._popen.poll() is None 266beb6b57bSJohn Snow 267beb6b57bSJohn Snow @property 268beb6b57bSJohn Snow def _subp(self) -> 'subprocess.Popen[bytes]': 269beb6b57bSJohn Snow if self._popen is None: 270beb6b57bSJohn Snow raise QEMUMachineError('Subprocess pipe not present') 271beb6b57bSJohn Snow return self._popen 272beb6b57bSJohn Snow 273beb6b57bSJohn Snow def exitcode(self) -> Optional[int]: 274beb6b57bSJohn Snow """Returns the exit code if possible, or None.""" 275beb6b57bSJohn Snow if self._popen is None: 276beb6b57bSJohn Snow return None 277beb6b57bSJohn Snow return self._popen.poll() 278beb6b57bSJohn Snow 279beb6b57bSJohn Snow def get_pid(self) -> Optional[int]: 280beb6b57bSJohn Snow """Returns the PID of the running process, or None.""" 281beb6b57bSJohn Snow if not self.is_running(): 282beb6b57bSJohn Snow return None 283beb6b57bSJohn Snow return self._subp.pid 284beb6b57bSJohn Snow 285beb6b57bSJohn Snow def _load_io_log(self) -> None: 2865690b437SJohn Snow # Assume that the output encoding of QEMU's terminal output is 2875690b437SJohn Snow # defined by our locale. If indeterminate, allow open() to fall 2885690b437SJohn Snow # back to the platform default. 2895690b437SJohn Snow _, encoding = locale.getlocale() 290beb6b57bSJohn Snow if self._qemu_log_path is not None: 2915690b437SJohn Snow with open(self._qemu_log_path, "r", encoding=encoding) as iolog: 292beb6b57bSJohn Snow self._iolog = iolog.read() 293beb6b57bSJohn Snow 294beb6b57bSJohn Snow @property 295beb6b57bSJohn Snow def _base_args(self) -> List[str]: 296beb6b57bSJohn Snow args = ['-display', 'none', '-vga', 'none'] 297beb6b57bSJohn Snow 298beb6b57bSJohn Snow if self._qmp_set: 299bd4c0ef4SMarc-André Lureau if self._sock_pair: 30091e11db7SJohn Snow moncdev = f"socket,id=mon,fd={self._sock_pair[0].fileno()}" 301bd4c0ef4SMarc-André Lureau elif isinstance(self._monitor_address, tuple): 302beb6b57bSJohn Snow moncdev = "socket,id=mon,host={},port={}".format( 303beb6b57bSJohn Snow *self._monitor_address 304beb6b57bSJohn Snow ) 305beb6b57bSJohn Snow else: 306beb6b57bSJohn Snow moncdev = f"socket,id=mon,path={self._monitor_address}" 307beb6b57bSJohn Snow args.extend(['-chardev', moncdev, '-mon', 308beb6b57bSJohn Snow 'chardev=mon,mode=control']) 309beb6b57bSJohn Snow 310beb6b57bSJohn Snow if self._machine is not None: 311beb6b57bSJohn Snow args.extend(['-machine', self._machine]) 312beb6b57bSJohn Snow for _ in range(self._console_index): 313beb6b57bSJohn Snow args.extend(['-serial', 'null']) 314beb6b57bSJohn Snow if self._console_set: 3151d4796cdSJohn Snow assert self._cons_sock_pair is not None 3161d4796cdSJohn Snow fd = self._cons_sock_pair[0].fileno() 3171d4796cdSJohn Snow chardev = f"socket,id=console,fd={fd}" 318beb6b57bSJohn Snow args.extend(['-chardev', chardev]) 319beb6b57bSJohn Snow if self._console_device_type is None: 320beb6b57bSJohn Snow args.extend(['-serial', 'chardev:console']) 321beb6b57bSJohn Snow else: 322beb6b57bSJohn Snow device = '%s,chardev=console' % self._console_device_type 323beb6b57bSJohn Snow args.extend(['-device', device]) 324beb6b57bSJohn Snow return args 325beb6b57bSJohn Snow 326555fe0c2SWainer dos Santos Moschetta @property 327555fe0c2SWainer dos Santos Moschetta def args(self) -> List[str]: 328555fe0c2SWainer dos Santos Moschetta """Returns the list of arguments given to the QEMU binary.""" 329555fe0c2SWainer dos Santos Moschetta return self._args 330555fe0c2SWainer dos Santos Moschetta 33133956e47SMaksim Davydov @property 33233956e47SMaksim Davydov def binary(self) -> str: 33333956e47SMaksim Davydov """Returns path to the QEMU binary""" 33433956e47SMaksim Davydov return self._binary 33533956e47SMaksim Davydov 336beb6b57bSJohn Snow def _pre_launch(self) -> None: 337beb6b57bSJohn Snow if self._qmp_set: 338*84e327e8SJohn Snow sock = None 339bd4c0ef4SMarc-André Lureau if self._monitor_address is None: 340bd4c0ef4SMarc-André Lureau self._sock_pair = socket.socketpair() 34191e11db7SJohn Snow os.set_inheritable(self._sock_pair[0].fileno(), True) 342bd4c0ef4SMarc-André Lureau sock = self._sock_pair[1] 3436eeb3de7SJohn Snow if isinstance(self._monitor_address, str): 344beb6b57bSJohn Snow self._remove_files.append(self._monitor_address) 3457f5f3ae7SJohn Snow 3465bbc5936SJohn Snow sock_or_addr = self._monitor_address or sock 3475bbc5936SJohn Snow assert sock_or_addr is not None 3485bbc5936SJohn Snow 349beb6b57bSJohn Snow self._qmp_connection = QEMUMonitorProtocol( 3505bbc5936SJohn Snow sock_or_addr, 3517f5f3ae7SJohn Snow server=bool(self._monitor_address), 352beb6b57bSJohn Snow nickname=self._name 353beb6b57bSJohn Snow ) 354beb6b57bSJohn Snow 3551d4796cdSJohn Snow if self._console_set: 3561d4796cdSJohn Snow self._cons_sock_pair = socket.socketpair() 3571d4796cdSJohn Snow os.set_inheritable(self._cons_sock_pair[0].fileno(), True) 3581d4796cdSJohn Snow 359beb6b57bSJohn Snow # NOTE: Make sure any opened resources are *definitely* freed in 360beb6b57bSJohn Snow # _post_shutdown()! 361beb6b57bSJohn Snow # pylint: disable=consider-using-with 362b306e26cSCleber Rosa self._qemu_log_path = os.path.join(self.log_dir, self._name + ".log") 363beb6b57bSJohn Snow self._qemu_log_file = open(self._qemu_log_path, 'wb') 364beb6b57bSJohn Snow 365b1ca9919SJohn Snow self._iolog = None 366b1ca9919SJohn Snow self._qemu_full_args = tuple(chain( 367b1ca9919SJohn Snow self._wrapper, 368b1ca9919SJohn Snow [self._binary], 369b1ca9919SJohn Snow self._base_args, 370b1ca9919SJohn Snow self._args 371b1ca9919SJohn Snow )) 372b1ca9919SJohn Snow 373beb6b57bSJohn Snow def _post_launch(self) -> None: 374bd4c0ef4SMarc-André Lureau if self._sock_pair: 375bd4c0ef4SMarc-André Lureau self._sock_pair[0].close() 3761d4796cdSJohn Snow if self._cons_sock_pair: 3771d4796cdSJohn Snow self._cons_sock_pair[0].close() 3781d4796cdSJohn Snow 379beb6b57bSJohn Snow if self._qmp_connection: 3807f5f3ae7SJohn Snow if self._sock_pair: 3817f5f3ae7SJohn Snow self._qmp.connect() 3827f5f3ae7SJohn Snow else: 383e2f948a8SEmanuele Giuseppe Esposito self._qmp.accept(self._qmp_timer) 384beb6b57bSJohn Snow 385eb7a91d0SEmanuele Giuseppe Esposito def _close_qemu_log_file(self) -> None: 386eb7a91d0SEmanuele Giuseppe Esposito if self._qemu_log_file is not None: 387eb7a91d0SEmanuele Giuseppe Esposito self._qemu_log_file.close() 388eb7a91d0SEmanuele Giuseppe Esposito self._qemu_log_file = None 389eb7a91d0SEmanuele Giuseppe Esposito 390beb6b57bSJohn Snow def _post_shutdown(self) -> None: 391beb6b57bSJohn Snow """ 392beb6b57bSJohn Snow Called to cleanup the VM instance after the process has exited. 393beb6b57bSJohn Snow May also be called after a failed launch. 394beb6b57bSJohn Snow """ 3959cccb330SJohn Snow LOG.debug("Cleaning up after VM process") 39649a608b8SJohn Snow try: 39749a608b8SJohn Snow self._close_qmp_connection() 39849a608b8SJohn Snow except Exception as err: # pylint: disable=broad-except 39949a608b8SJohn Snow LOG.warning( 40049a608b8SJohn Snow "Exception closing QMP connection: %s", 40149a608b8SJohn Snow str(err) if str(err) else type(err).__name__ 40249a608b8SJohn Snow ) 40349a608b8SJohn Snow finally: 40449a608b8SJohn Snow assert self._qmp_connection is None 405beb6b57bSJohn Snow 406612b3ba2SJohn Snow if self._sock_pair: 407612b3ba2SJohn Snow self._sock_pair[0].close() 408612b3ba2SJohn Snow self._sock_pair[1].close() 409612b3ba2SJohn Snow self._sock_pair = None 410612b3ba2SJohn Snow 411eb7a91d0SEmanuele Giuseppe Esposito self._close_qemu_log_file() 412beb6b57bSJohn Snow 413beb6b57bSJohn Snow self._load_io_log() 414beb6b57bSJohn Snow 415beb6b57bSJohn Snow self._qemu_log_path = None 416beb6b57bSJohn Snow 417beb6b57bSJohn Snow if self._temp_dir is not None: 418beb6b57bSJohn Snow shutil.rmtree(self._temp_dir) 419beb6b57bSJohn Snow self._temp_dir = None 420beb6b57bSJohn Snow 421beb6b57bSJohn Snow while len(self._remove_files) > 0: 422beb6b57bSJohn Snow self._remove_if_exists(self._remove_files.pop()) 423beb6b57bSJohn Snow 424beb6b57bSJohn Snow exitcode = self.exitcode() 425beb6b57bSJohn Snow if (exitcode is not None and exitcode < 0 426beb6b57bSJohn Snow and not (self._user_killed and exitcode == -signal.SIGKILL)): 427beb6b57bSJohn Snow msg = 'qemu received signal %i; command: "%s"' 428beb6b57bSJohn Snow if self._qemu_full_args: 429beb6b57bSJohn Snow command = ' '.join(self._qemu_full_args) 430beb6b57bSJohn Snow else: 431beb6b57bSJohn Snow command = '' 432beb6b57bSJohn Snow LOG.warning(msg, -int(exitcode), command) 433beb6b57bSJohn Snow 434b9420e4fSJohn Snow self._quit_issued = False 435beb6b57bSJohn Snow self._user_killed = False 436beb6b57bSJohn Snow self._launched = False 437beb6b57bSJohn Snow 438beb6b57bSJohn Snow def launch(self) -> None: 439beb6b57bSJohn Snow """ 440beb6b57bSJohn Snow Launch the VM and make sure we cleanup and expose the 441beb6b57bSJohn Snow command line/output in case of exception 442beb6b57bSJohn Snow """ 443beb6b57bSJohn Snow 444beb6b57bSJohn Snow if self._launched: 445beb6b57bSJohn Snow raise QEMUMachineError('VM already launched') 446beb6b57bSJohn Snow 447beb6b57bSJohn Snow try: 448beb6b57bSJohn Snow self._launch() 44950465f94SJohn Snow except BaseException as exc: 4501611e6cfSJohn Snow # We may have launched the process but it may 4511611e6cfSJohn Snow # have exited before we could connect via QMP. 4521611e6cfSJohn Snow # Assume the VM didn't launch or is exiting. 4531611e6cfSJohn Snow # If we don't wait for the process, exitcode() may still be 4541611e6cfSJohn Snow # 'None' by the time control is ceded back to the caller. 4551611e6cfSJohn Snow if self._launched: 4561611e6cfSJohn Snow self.wait() 4571611e6cfSJohn Snow else: 458beb6b57bSJohn Snow self._post_shutdown() 459beb6b57bSJohn Snow 46050465f94SJohn Snow if isinstance(exc, Exception): 46150465f94SJohn Snow raise VMLaunchFailure( 46250465f94SJohn Snow exitcode=self.exitcode(), 46350465f94SJohn Snow command=' '.join(self._qemu_full_args), 46450465f94SJohn Snow output=self._iolog 46550465f94SJohn Snow ) from exc 46650465f94SJohn Snow 46750465f94SJohn Snow # Don't wrap 'BaseException'; doing so would downgrade 46850465f94SJohn Snow # that exception. However, we still want to clean up. 469beb6b57bSJohn Snow raise 470beb6b57bSJohn Snow 471beb6b57bSJohn Snow def _launch(self) -> None: 472beb6b57bSJohn Snow """ 473beb6b57bSJohn Snow Launch the VM and establish a QMP connection 474beb6b57bSJohn Snow """ 475beb6b57bSJohn Snow self._pre_launch() 476beb6b57bSJohn Snow LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args)) 477beb6b57bSJohn Snow 478beb6b57bSJohn Snow # Cleaning up of this subprocess is guaranteed by _do_shutdown. 479beb6b57bSJohn Snow # pylint: disable=consider-using-with 480beb6b57bSJohn Snow self._popen = subprocess.Popen(self._qemu_full_args, 481beb6b57bSJohn Snow stdin=subprocess.DEVNULL, 482beb6b57bSJohn Snow stdout=self._qemu_log_file, 483beb6b57bSJohn Snow stderr=subprocess.STDOUT, 484beb6b57bSJohn Snow shell=False, 485beb6b57bSJohn Snow close_fds=False) 4861611e6cfSJohn Snow self._launched = True 487beb6b57bSJohn Snow self._post_launch() 488beb6b57bSJohn Snow 48949a608b8SJohn Snow def _close_qmp_connection(self) -> None: 49049a608b8SJohn Snow """ 49149a608b8SJohn Snow Close the underlying QMP connection, if any. 49249a608b8SJohn Snow 49349a608b8SJohn Snow Dutifully report errors that occurred while closing, but assume 49449a608b8SJohn Snow that any error encountered indicates an abnormal termination 49549a608b8SJohn Snow process and not a failure to close. 49649a608b8SJohn Snow """ 49749a608b8SJohn Snow if self._qmp_connection is None: 49849a608b8SJohn Snow return 49949a608b8SJohn Snow 50049a608b8SJohn Snow try: 50149a608b8SJohn Snow self._qmp.close() 50249a608b8SJohn Snow except EOFError: 50349a608b8SJohn Snow # EOF can occur as an Exception here when using the Async 50449a608b8SJohn Snow # QMP backend. It indicates that the server closed the 50549a608b8SJohn Snow # stream. If we successfully issued 'quit' at any point, 50649a608b8SJohn Snow # then this was expected. If the remote went away without 50749a608b8SJohn Snow # our permission, it's worth reporting that as an abnormal 50849a608b8SJohn Snow # shutdown case. 50949a608b8SJohn Snow if not (self._user_killed or self._quit_issued): 51049a608b8SJohn Snow raise 51149a608b8SJohn Snow finally: 51249a608b8SJohn Snow self._qmp_connection = None 51349a608b8SJohn Snow 514beb6b57bSJohn Snow def _early_cleanup(self) -> None: 515beb6b57bSJohn Snow """ 516beb6b57bSJohn Snow Perform any cleanup that needs to happen before the VM exits. 517beb6b57bSJohn Snow 5181611e6cfSJohn Snow This method may be called twice upon shutdown, once each by soft 5191611e6cfSJohn Snow and hard shutdown in failover scenarios. 520beb6b57bSJohn Snow """ 521beb6b57bSJohn Snow # If we keep the console socket open, we may deadlock waiting 522beb6b57bSJohn Snow # for QEMU to exit, while QEMU is waiting for the socket to 5239323e79fSPeter Maydell # become writable. 524f0ec14c7SNicholas Piggin if self._console_file is not None: 525f0ec14c7SNicholas Piggin LOG.debug("Closing console file") 526f0ec14c7SNicholas Piggin self._console_file.close() 527f0ec14c7SNicholas Piggin self._console_file = None 528f0ec14c7SNicholas Piggin 529beb6b57bSJohn Snow if self._console_socket is not None: 5309cccb330SJohn Snow LOG.debug("Closing console socket") 531beb6b57bSJohn Snow self._console_socket.close() 532beb6b57bSJohn Snow self._console_socket = None 533beb6b57bSJohn Snow 5341d4796cdSJohn Snow if self._cons_sock_pair: 5351d4796cdSJohn Snow self._cons_sock_pair[0].close() 5361d4796cdSJohn Snow self._cons_sock_pair[1].close() 5371d4796cdSJohn Snow self._cons_sock_pair = None 5381d4796cdSJohn Snow 539beb6b57bSJohn Snow def _hard_shutdown(self) -> None: 540beb6b57bSJohn Snow """ 541beb6b57bSJohn Snow Perform early cleanup, kill the VM, and wait for it to terminate. 542beb6b57bSJohn Snow 543beb6b57bSJohn Snow :raise subprocess.Timeout: When timeout is exceeds 60 seconds 544beb6b57bSJohn Snow waiting for the QEMU process to terminate. 545beb6b57bSJohn Snow """ 5469cccb330SJohn Snow LOG.debug("Performing hard shutdown") 547beb6b57bSJohn Snow self._early_cleanup() 548beb6b57bSJohn Snow self._subp.kill() 549beb6b57bSJohn Snow self._subp.wait(timeout=60) 550beb6b57bSJohn Snow 551b9420e4fSJohn Snow def _soft_shutdown(self, timeout: Optional[int]) -> None: 552beb6b57bSJohn Snow """ 553beb6b57bSJohn Snow Perform early cleanup, attempt to gracefully shut down the VM, and wait 554beb6b57bSJohn Snow for it to terminate. 555beb6b57bSJohn Snow 556beb6b57bSJohn Snow :param timeout: Timeout in seconds for graceful shutdown. 557beb6b57bSJohn Snow A value of None is an infinite wait. 558beb6b57bSJohn Snow 559beb6b57bSJohn Snow :raise ConnectionReset: On QMP communication errors 560beb6b57bSJohn Snow :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for 561beb6b57bSJohn Snow the QEMU process to terminate. 562beb6b57bSJohn Snow """ 5639cccb330SJohn Snow LOG.debug("Attempting graceful termination") 5649cccb330SJohn Snow 565beb6b57bSJohn Snow self._early_cleanup() 566beb6b57bSJohn Snow 5679cccb330SJohn Snow if self._quit_issued: 5689cccb330SJohn Snow LOG.debug( 5699cccb330SJohn Snow "Anticipating QEMU termination due to prior 'quit' command, " 5709cccb330SJohn Snow "or explicit call to wait()" 5719cccb330SJohn Snow ) 5729cccb330SJohn Snow else: 5739cccb330SJohn Snow LOG.debug("Politely asking QEMU to terminate") 5749cccb330SJohn Snow 575beb6b57bSJohn Snow if self._qmp_connection: 57649a608b8SJohn Snow try: 577b9420e4fSJohn Snow if not self._quit_issued: 57849a608b8SJohn Snow # May raise ExecInterruptedError or StateError if the 57949a608b8SJohn Snow # connection dies or has *already* died. 580b9420e4fSJohn Snow self.qmp('quit') 58149a608b8SJohn Snow finally: 58249a608b8SJohn Snow # Regardless, we want to quiesce the connection. 58349a608b8SJohn Snow self._close_qmp_connection() 5843c6e5e8cSJohn Snow elif not self._quit_issued: 5853c6e5e8cSJohn Snow LOG.debug( 5863c6e5e8cSJohn Snow "Not anticipating QEMU quit and no QMP connection present, " 5873c6e5e8cSJohn Snow "issuing SIGTERM" 5883c6e5e8cSJohn Snow ) 5893c6e5e8cSJohn Snow self._subp.terminate() 590beb6b57bSJohn Snow 591beb6b57bSJohn Snow # May raise subprocess.TimeoutExpired 5929cccb330SJohn Snow LOG.debug( 5939cccb330SJohn Snow "Waiting (timeout=%s) for QEMU process (pid=%s) to terminate", 5949cccb330SJohn Snow timeout, self._subp.pid 5959cccb330SJohn Snow ) 596beb6b57bSJohn Snow self._subp.wait(timeout=timeout) 597beb6b57bSJohn Snow 598b9420e4fSJohn Snow def _do_shutdown(self, timeout: Optional[int]) -> None: 599beb6b57bSJohn Snow """ 600beb6b57bSJohn Snow Attempt to shutdown the VM gracefully; fallback to a hard shutdown. 601beb6b57bSJohn Snow 602beb6b57bSJohn Snow :param timeout: Timeout in seconds for graceful shutdown. 603beb6b57bSJohn Snow A value of None is an infinite wait. 604beb6b57bSJohn Snow 605beb6b57bSJohn Snow :raise AbnormalShutdown: When the VM could not be shut down gracefully. 606beb6b57bSJohn Snow The inner exception will likely be ConnectionReset or 607beb6b57bSJohn Snow subprocess.TimeoutExpired. In rare cases, non-graceful termination 608beb6b57bSJohn Snow may result in its own exceptions, likely subprocess.TimeoutExpired. 609beb6b57bSJohn Snow """ 610beb6b57bSJohn Snow try: 611b9420e4fSJohn Snow self._soft_shutdown(timeout) 612beb6b57bSJohn Snow except Exception as exc: 6139cccb330SJohn Snow if isinstance(exc, subprocess.TimeoutExpired): 6149cccb330SJohn Snow LOG.debug("Timed out waiting for QEMU process to exit") 6159cccb330SJohn Snow LOG.debug("Graceful shutdown failed", exc_info=True) 6169cccb330SJohn Snow LOG.debug("Falling back to hard shutdown") 617beb6b57bSJohn Snow self._hard_shutdown() 618beb6b57bSJohn Snow raise AbnormalShutdown("Could not perform graceful shutdown") \ 619beb6b57bSJohn Snow from exc 620beb6b57bSJohn Snow 621b9420e4fSJohn Snow def shutdown(self, 622beb6b57bSJohn Snow hard: bool = False, 623beb6b57bSJohn Snow timeout: Optional[int] = 30) -> None: 624beb6b57bSJohn Snow """ 625beb6b57bSJohn Snow Terminate the VM (gracefully if possible) and perform cleanup. 626beb6b57bSJohn Snow Cleanup will always be performed. 627beb6b57bSJohn Snow 628beb6b57bSJohn Snow If the VM has not yet been launched, or shutdown(), wait(), or kill() 629beb6b57bSJohn Snow have already been called, this method does nothing. 630beb6b57bSJohn Snow 631beb6b57bSJohn Snow :param hard: When true, do not attempt graceful shutdown, and 632beb6b57bSJohn Snow suppress the SIGKILL warning log message. 633beb6b57bSJohn Snow :param timeout: Optional timeout in seconds for graceful shutdown. 634beb6b57bSJohn Snow Default 30 seconds, A `None` value is an infinite wait. 635beb6b57bSJohn Snow """ 636beb6b57bSJohn Snow if not self._launched: 637beb6b57bSJohn Snow return 638beb6b57bSJohn Snow 6399cccb330SJohn Snow LOG.debug("Shutting down VM appliance; timeout=%s", timeout) 6409cccb330SJohn Snow if hard: 6419cccb330SJohn Snow LOG.debug("Caller requests immediate termination of QEMU process.") 6429cccb330SJohn Snow 643beb6b57bSJohn Snow try: 644beb6b57bSJohn Snow if hard: 645beb6b57bSJohn Snow self._user_killed = True 646beb6b57bSJohn Snow self._hard_shutdown() 647beb6b57bSJohn Snow else: 648b9420e4fSJohn Snow self._do_shutdown(timeout) 649beb6b57bSJohn Snow finally: 650beb6b57bSJohn Snow self._post_shutdown() 651beb6b57bSJohn Snow 652beb6b57bSJohn Snow def kill(self) -> None: 653beb6b57bSJohn Snow """ 654beb6b57bSJohn Snow Terminate the VM forcefully, wait for it to exit, and perform cleanup. 655beb6b57bSJohn Snow """ 656beb6b57bSJohn Snow self.shutdown(hard=True) 657beb6b57bSJohn Snow 658beb6b57bSJohn Snow def wait(self, timeout: Optional[int] = 30) -> None: 659beb6b57bSJohn Snow """ 660beb6b57bSJohn Snow Wait for the VM to power off and perform post-shutdown cleanup. 661beb6b57bSJohn Snow 662beb6b57bSJohn Snow :param timeout: Optional timeout in seconds. Default 30 seconds. 663beb6b57bSJohn Snow A value of `None` is an infinite wait. 664beb6b57bSJohn Snow """ 665b9420e4fSJohn Snow self._quit_issued = True 666b9420e4fSJohn Snow self.shutdown(timeout=timeout) 667beb6b57bSJohn Snow 668beb6b57bSJohn Snow def set_qmp_monitor(self, enabled: bool = True) -> None: 669beb6b57bSJohn Snow """ 670beb6b57bSJohn Snow Set the QMP monitor. 671beb6b57bSJohn Snow 672beb6b57bSJohn Snow @param enabled: if False, qmp monitor options will be removed from 673beb6b57bSJohn Snow the base arguments of the resulting QEMU command 674beb6b57bSJohn Snow line. Default is True. 6755c02c865SJohn Snow 6765c02c865SJohn Snow .. note:: Call this function before launch(). 677beb6b57bSJohn Snow """ 678beb6b57bSJohn Snow self._qmp_set = enabled 679beb6b57bSJohn Snow 680beb6b57bSJohn Snow @property 681beb6b57bSJohn Snow def _qmp(self) -> QEMUMonitorProtocol: 682beb6b57bSJohn Snow if self._qmp_connection is None: 683beb6b57bSJohn Snow raise QEMUMachineError("Attempt to access QMP with no connection") 684beb6b57bSJohn Snow return self._qmp_connection 685beb6b57bSJohn Snow 686beb6b57bSJohn Snow @classmethod 687c7daa57eSVladimir Sementsov-Ogievskiy def _qmp_args(cls, conv_keys: bool, 688c7daa57eSVladimir Sementsov-Ogievskiy args: Dict[str, Any]) -> Dict[str, object]: 689c7daa57eSVladimir Sementsov-Ogievskiy if conv_keys: 690c7daa57eSVladimir Sementsov-Ogievskiy return {k.replace('_', '-'): v for k, v in args.items()} 691c7daa57eSVladimir Sementsov-Ogievskiy 692c7daa57eSVladimir Sementsov-Ogievskiy return args 693beb6b57bSJohn Snow 694beb6b57bSJohn Snow def qmp(self, cmd: str, 6953f3c9b4cSVladimir Sementsov-Ogievskiy args_dict: Optional[Dict[str, object]] = None, 6963f3c9b4cSVladimir Sementsov-Ogievskiy conv_keys: Optional[bool] = None, 697beb6b57bSJohn Snow **args: Any) -> QMPMessage: 698beb6b57bSJohn Snow """ 699beb6b57bSJohn Snow Invoke a QMP command and return the response dict 700beb6b57bSJohn Snow """ 7013f3c9b4cSVladimir Sementsov-Ogievskiy if args_dict is not None: 7023f3c9b4cSVladimir Sementsov-Ogievskiy assert not args 7033f3c9b4cSVladimir Sementsov-Ogievskiy assert conv_keys is None 7043f3c9b4cSVladimir Sementsov-Ogievskiy args = args_dict 7053f3c9b4cSVladimir Sementsov-Ogievskiy conv_keys = False 7063f3c9b4cSVladimir Sementsov-Ogievskiy 7073f3c9b4cSVladimir Sementsov-Ogievskiy if conv_keys is None: 7083f3c9b4cSVladimir Sementsov-Ogievskiy conv_keys = True 7093f3c9b4cSVladimir Sementsov-Ogievskiy 710c7daa57eSVladimir Sementsov-Ogievskiy qmp_args = self._qmp_args(conv_keys, args) 71137274707SVladimir Sementsov-Ogievskiy ret = self._qmp.cmd_raw(cmd, args=qmp_args) 712b9420e4fSJohn Snow if cmd == 'quit' and 'error' not in ret and 'return' in ret: 713b9420e4fSJohn Snow self._quit_issued = True 714b9420e4fSJohn Snow return ret 715beb6b57bSJohn Snow 716684750abSVladimir Sementsov-Ogievskiy def cmd(self, cmd: str, 7174e620ff4SVladimir Sementsov-Ogievskiy args_dict: Optional[Dict[str, object]] = None, 7184e620ff4SVladimir Sementsov-Ogievskiy conv_keys: Optional[bool] = None, 719beb6b57bSJohn Snow **args: Any) -> QMPReturnValue: 720beb6b57bSJohn Snow """ 721beb6b57bSJohn Snow Invoke a QMP command. 722beb6b57bSJohn Snow On success return the response dict. 723beb6b57bSJohn Snow On failure raise an exception. 724beb6b57bSJohn Snow """ 7254e620ff4SVladimir Sementsov-Ogievskiy if args_dict is not None: 7264e620ff4SVladimir Sementsov-Ogievskiy assert not args 7274e620ff4SVladimir Sementsov-Ogievskiy assert conv_keys is None 7284e620ff4SVladimir Sementsov-Ogievskiy args = args_dict 7294e620ff4SVladimir Sementsov-Ogievskiy conv_keys = False 7304e620ff4SVladimir Sementsov-Ogievskiy 7314e620ff4SVladimir Sementsov-Ogievskiy if conv_keys is None: 7324e620ff4SVladimir Sementsov-Ogievskiy conv_keys = True 7334e620ff4SVladimir Sementsov-Ogievskiy 734c7daa57eSVladimir Sementsov-Ogievskiy qmp_args = self._qmp_args(conv_keys, args) 735684750abSVladimir Sementsov-Ogievskiy ret = self._qmp.cmd(cmd, **qmp_args) 736b9420e4fSJohn Snow if cmd == 'quit': 737b9420e4fSJohn Snow self._quit_issued = True 738b9420e4fSJohn Snow return ret 739beb6b57bSJohn Snow 740beb6b57bSJohn Snow def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]: 741beb6b57bSJohn Snow """ 742beb6b57bSJohn Snow Poll for one queued QMP events and return it 743beb6b57bSJohn Snow """ 744beb6b57bSJohn Snow if self._events: 745beb6b57bSJohn Snow return self._events.pop(0) 746beb6b57bSJohn Snow return self._qmp.pull_event(wait=wait) 747beb6b57bSJohn Snow 748beb6b57bSJohn Snow def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]: 749beb6b57bSJohn Snow """ 750beb6b57bSJohn Snow Poll for queued QMP events and return a list of dicts 751beb6b57bSJohn Snow """ 752beb6b57bSJohn Snow events = self._qmp.get_events(wait=wait) 753beb6b57bSJohn Snow events.extend(self._events) 754beb6b57bSJohn Snow del self._events[:] 755beb6b57bSJohn Snow return events 756beb6b57bSJohn Snow 757beb6b57bSJohn Snow @staticmethod 758beb6b57bSJohn Snow def event_match(event: Any, match: Optional[Any]) -> bool: 759beb6b57bSJohn Snow """ 760beb6b57bSJohn Snow Check if an event matches optional match criteria. 761beb6b57bSJohn Snow 762beb6b57bSJohn Snow The match criteria takes the form of a matching subdict. The event is 763beb6b57bSJohn Snow checked to be a superset of the subdict, recursively, with matching 764beb6b57bSJohn Snow values whenever the subdict values are not None. 765beb6b57bSJohn Snow 766beb6b57bSJohn Snow This has a limitation that you cannot explicitly check for None values. 767beb6b57bSJohn Snow 768beb6b57bSJohn Snow Examples, with the subdict queries on the left: 769beb6b57bSJohn Snow - None matches any object. 770beb6b57bSJohn Snow - {"foo": None} matches {"foo": {"bar": 1}} 771beb6b57bSJohn Snow - {"foo": None} matches {"foo": 5} 772beb6b57bSJohn Snow - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}} 773beb6b57bSJohn Snow - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}} 774beb6b57bSJohn Snow """ 775beb6b57bSJohn Snow if match is None: 776beb6b57bSJohn Snow return True 777beb6b57bSJohn Snow 778beb6b57bSJohn Snow try: 779beb6b57bSJohn Snow for key in match: 780beb6b57bSJohn Snow if key in event: 781beb6b57bSJohn Snow if not QEMUMachine.event_match(event[key], match[key]): 782beb6b57bSJohn Snow return False 783beb6b57bSJohn Snow else: 784beb6b57bSJohn Snow return False 785beb6b57bSJohn Snow return True 786beb6b57bSJohn Snow except TypeError: 787beb6b57bSJohn Snow # either match or event wasn't iterable (not a dict) 788beb6b57bSJohn Snow return bool(match == event) 789beb6b57bSJohn Snow 790beb6b57bSJohn Snow def event_wait(self, name: str, 791beb6b57bSJohn Snow timeout: float = 60.0, 792beb6b57bSJohn Snow match: Optional[QMPMessage] = None) -> Optional[QMPMessage]: 793beb6b57bSJohn Snow """ 794beb6b57bSJohn Snow event_wait waits for and returns a named event from QMP with a timeout. 795beb6b57bSJohn Snow 796beb6b57bSJohn Snow name: The event to wait for. 797beb6b57bSJohn Snow timeout: QEMUMonitorProtocol.pull_event timeout parameter. 798beb6b57bSJohn Snow match: Optional match criteria. See event_match for details. 799beb6b57bSJohn Snow """ 800beb6b57bSJohn Snow return self.events_wait([(name, match)], timeout) 801beb6b57bSJohn Snow 802beb6b57bSJohn Snow def events_wait(self, 803beb6b57bSJohn Snow events: Sequence[Tuple[str, Any]], 804beb6b57bSJohn Snow timeout: float = 60.0) -> Optional[QMPMessage]: 805beb6b57bSJohn Snow """ 806beb6b57bSJohn Snow events_wait waits for and returns a single named event from QMP. 807beb6b57bSJohn Snow In the case of multiple qualifying events, this function returns the 808beb6b57bSJohn Snow first one. 809beb6b57bSJohn Snow 810beb6b57bSJohn Snow :param events: A sequence of (name, match_criteria) tuples. 811beb6b57bSJohn Snow The match criteria are optional and may be None. 812beb6b57bSJohn Snow See event_match for details. 813beb6b57bSJohn Snow :param timeout: Optional timeout, in seconds. 814beb6b57bSJohn Snow See QEMUMonitorProtocol.pull_event. 815beb6b57bSJohn Snow 816a4225303SJohn Snow :raise asyncio.TimeoutError: 817a4225303SJohn Snow If timeout was non-zero and no matching events were found. 818a4225303SJohn Snow 819beb6b57bSJohn Snow :return: A QMP event matching the filter criteria. 820beb6b57bSJohn Snow If timeout was 0 and no event matched, None. 821beb6b57bSJohn Snow """ 822beb6b57bSJohn Snow def _match(event: QMPMessage) -> bool: 823beb6b57bSJohn Snow for name, match in events: 824beb6b57bSJohn Snow if event['event'] == name and self.event_match(event, match): 825beb6b57bSJohn Snow return True 826beb6b57bSJohn Snow return False 827beb6b57bSJohn Snow 828beb6b57bSJohn Snow event: Optional[QMPMessage] 829beb6b57bSJohn Snow 830beb6b57bSJohn Snow # Search cached events 831beb6b57bSJohn Snow for event in self._events: 832beb6b57bSJohn Snow if _match(event): 833beb6b57bSJohn Snow self._events.remove(event) 834beb6b57bSJohn Snow return event 835beb6b57bSJohn Snow 836beb6b57bSJohn Snow # Poll for new events 837beb6b57bSJohn Snow while True: 838beb6b57bSJohn Snow event = self._qmp.pull_event(wait=timeout) 839beb6b57bSJohn Snow if event is None: 840beb6b57bSJohn Snow # NB: None is only returned when timeout is false-ish. 841a4225303SJohn Snow # Timeouts raise asyncio.TimeoutError instead! 842beb6b57bSJohn Snow break 843beb6b57bSJohn Snow if _match(event): 844beb6b57bSJohn Snow return event 845beb6b57bSJohn Snow self._events.append(event) 846beb6b57bSJohn Snow 847beb6b57bSJohn Snow return None 848beb6b57bSJohn Snow 849beb6b57bSJohn Snow def get_log(self) -> Optional[str]: 850beb6b57bSJohn Snow """ 851beb6b57bSJohn Snow After self.shutdown or failed qemu execution, this returns the output 852beb6b57bSJohn Snow of the qemu process. 853beb6b57bSJohn Snow """ 854beb6b57bSJohn Snow return self._iolog 855beb6b57bSJohn Snow 856beb6b57bSJohn Snow def add_args(self, *args: str) -> None: 857beb6b57bSJohn Snow """ 858beb6b57bSJohn Snow Adds to the list of extra arguments to be given to the QEMU binary 859beb6b57bSJohn Snow """ 860beb6b57bSJohn Snow self._args.extend(args) 861beb6b57bSJohn Snow 862beb6b57bSJohn Snow def set_machine(self, machine_type: str) -> None: 863beb6b57bSJohn Snow """ 864beb6b57bSJohn Snow Sets the machine type 865beb6b57bSJohn Snow 866beb6b57bSJohn Snow If set, the machine type will be added to the base arguments 867beb6b57bSJohn Snow of the resulting QEMU command line. 868beb6b57bSJohn Snow """ 869beb6b57bSJohn Snow self._machine = machine_type 870beb6b57bSJohn Snow 871beb6b57bSJohn Snow def set_console(self, 872beb6b57bSJohn Snow device_type: Optional[str] = None, 873beb6b57bSJohn Snow console_index: int = 0) -> None: 874beb6b57bSJohn Snow """ 875beb6b57bSJohn Snow Sets the device type for a console device 876beb6b57bSJohn Snow 877beb6b57bSJohn Snow If set, the console device and a backing character device will 878beb6b57bSJohn Snow be added to the base arguments of the resulting QEMU command 879beb6b57bSJohn Snow line. 880beb6b57bSJohn Snow 881beb6b57bSJohn Snow This is a convenience method that will either use the provided 882beb6b57bSJohn Snow device type, or default to a "-serial chardev:console" command 883beb6b57bSJohn Snow line argument. 884beb6b57bSJohn Snow 885beb6b57bSJohn Snow The actual setting of command line arguments will be be done at 886beb6b57bSJohn Snow machine launch time, as it depends on the temporary directory 887beb6b57bSJohn Snow to be created. 888beb6b57bSJohn Snow 889beb6b57bSJohn Snow @param device_type: the device type, such as "isa-serial". If 890beb6b57bSJohn Snow None is given (the default value) a "-serial 891beb6b57bSJohn Snow chardev:console" command line argument will 892beb6b57bSJohn Snow be used instead, resorting to the machine's 893beb6b57bSJohn Snow default device type. 894beb6b57bSJohn Snow @param console_index: the index of the console device to use. 895beb6b57bSJohn Snow If not zero, the command line will create 896beb6b57bSJohn Snow 'index - 1' consoles and connect them to 897beb6b57bSJohn Snow the 'null' backing character device. 898beb6b57bSJohn Snow """ 899beb6b57bSJohn Snow self._console_set = True 900beb6b57bSJohn Snow self._console_device_type = device_type 901beb6b57bSJohn Snow self._console_index = console_index 902beb6b57bSJohn Snow 903beb6b57bSJohn Snow @property 904beb6b57bSJohn Snow def console_socket(self) -> socket.socket: 905beb6b57bSJohn Snow """ 906beb6b57bSJohn Snow Returns a socket connected to the console 907beb6b57bSJohn Snow """ 908beb6b57bSJohn Snow if self._console_socket is None: 909f0ec14c7SNicholas Piggin LOG.debug("Opening console socket") 9101d4796cdSJohn Snow if not self._console_set: 9111d4796cdSJohn Snow raise QEMUMachineError( 9121d4796cdSJohn Snow "Attempt to access console socket with no connection") 9131d4796cdSJohn Snow assert self._cons_sock_pair is not None 9141d4796cdSJohn Snow # os.dup() is used here for sock_fd because otherwise we'd 9151d4796cdSJohn Snow # have two rich python socket objects that would each try to 9161d4796cdSJohn Snow # close the same underlying fd when either one gets garbage 9171d4796cdSJohn Snow # collected. 918beb6b57bSJohn Snow self._console_socket = console_socket.ConsoleSocket( 9191d4796cdSJohn Snow sock_fd=os.dup(self._cons_sock_pair[1].fileno()), 920beb6b57bSJohn Snow file=self._console_log_path, 921beb6b57bSJohn Snow drain=self._drain_console) 9221d4796cdSJohn Snow self._cons_sock_pair[1].close() 923beb6b57bSJohn Snow return self._console_socket 924beb6b57bSJohn Snow 925beb6b57bSJohn Snow @property 926f0ec14c7SNicholas Piggin def console_file(self) -> socket.SocketIO: 927f0ec14c7SNicholas Piggin """ 928f0ec14c7SNicholas Piggin Returns a file associated with the console socket 929f0ec14c7SNicholas Piggin """ 930f0ec14c7SNicholas Piggin if self._console_file is None: 931f0ec14c7SNicholas Piggin LOG.debug("Opening console file") 932f0ec14c7SNicholas Piggin self._console_file = self.console_socket.makefile(mode='rb', 933f0ec14c7SNicholas Piggin buffering=0, 934f0ec14c7SNicholas Piggin encoding='utf-8') 935f0ec14c7SNicholas Piggin return self._console_file 936f0ec14c7SNicholas Piggin 937f0ec14c7SNicholas Piggin @property 938beb6b57bSJohn Snow def temp_dir(self) -> str: 939beb6b57bSJohn Snow """ 940beb6b57bSJohn Snow Returns a temporary directory to be used for this machine 941beb6b57bSJohn Snow """ 942beb6b57bSJohn Snow if self._temp_dir is None: 943beb6b57bSJohn Snow self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-", 944beb6b57bSJohn Snow dir=self._base_temp_dir) 945beb6b57bSJohn Snow return self._temp_dir 946b306e26cSCleber Rosa 947b306e26cSCleber Rosa @property 948b306e26cSCleber Rosa def log_dir(self) -> str: 949b306e26cSCleber Rosa """ 950b306e26cSCleber Rosa Returns a directory to be used for writing logs 951b306e26cSCleber Rosa """ 952b306e26cSCleber Rosa if self._log_dir is None: 953b306e26cSCleber Rosa return self.temp_dir 954b306e26cSCleber Rosa return self._log_dir 955