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 sock_dir: Optional[str] = None, 131beb6b57bSJohn Snow drain_console: bool = False, 132b306e26cSCleber Rosa console_log: Optional[str] = None, 133e2f948a8SEmanuele Giuseppe Esposito log_dir: Optional[str] = None, 134*ada73a49SVladimir Sementsov-Ogievskiy qmp_timer: Optional[float] = 30): 135beb6b57bSJohn Snow ''' 136beb6b57bSJohn Snow Initialize a QEMUMachine 137beb6b57bSJohn Snow 138beb6b57bSJohn Snow @param binary: path to the qemu binary 139beb6b57bSJohn Snow @param args: list of extra arguments 140beb6b57bSJohn Snow @param wrapper: list of arguments used as prefix to qemu binary 141beb6b57bSJohn Snow @param name: prefix for socket and log file names (default: qemu-PID) 142beb6b57bSJohn Snow @param base_temp_dir: default location where temp files are created 143beb6b57bSJohn Snow @param monitor_address: address for QMP monitor 144beb6b57bSJohn Snow @param sock_dir: where to create socket (defaults to base_temp_dir) 145beb6b57bSJohn Snow @param drain_console: (optional) True to drain console socket to buffer 146beb6b57bSJohn Snow @param console_log: (optional) path to console log file 147b306e26cSCleber Rosa @param log_dir: where to create and keep log files 148e2f948a8SEmanuele Giuseppe Esposito @param qmp_timer: (optional) default QMP socket timeout 149beb6b57bSJohn Snow @note: Qemu process is not started until launch() is used. 150beb6b57bSJohn Snow ''' 15182e6517dSJohn Snow # pylint: disable=too-many-arguments 15282e6517dSJohn Snow 153beb6b57bSJohn Snow # Direct user configuration 154beb6b57bSJohn Snow 155beb6b57bSJohn Snow self._binary = binary 156beb6b57bSJohn Snow self._args = list(args) 157beb6b57bSJohn Snow self._wrapper = wrapper 158e2f948a8SEmanuele Giuseppe Esposito self._qmp_timer = qmp_timer 159beb6b57bSJohn Snow 16072b17fe7SJohn Snow self._name = name or f"qemu-{os.getpid()}-{id(self):02x}" 16187bf1fe5SJohn Snow self._temp_dir: Optional[str] = None 162beb6b57bSJohn Snow self._base_temp_dir = base_temp_dir 16387bf1fe5SJohn Snow self._sock_dir = sock_dir 164b306e26cSCleber Rosa self._log_dir = log_dir 165beb6b57bSJohn Snow 166beb6b57bSJohn Snow if monitor_address is not None: 167beb6b57bSJohn Snow self._monitor_address = monitor_address 168beb6b57bSJohn Snow else: 169beb6b57bSJohn Snow self._monitor_address = os.path.join( 17087bf1fe5SJohn Snow self.sock_dir, f"{self._name}-monitor.sock" 171beb6b57bSJohn Snow ) 172beb6b57bSJohn Snow 173beb6b57bSJohn Snow self._console_log_path = console_log 174beb6b57bSJohn Snow if self._console_log_path: 175beb6b57bSJohn Snow # In order to log the console, buffering needs to be enabled. 176beb6b57bSJohn Snow self._drain_console = True 177beb6b57bSJohn Snow else: 178beb6b57bSJohn Snow self._drain_console = drain_console 179beb6b57bSJohn Snow 180beb6b57bSJohn Snow # Runstate 181beb6b57bSJohn Snow self._qemu_log_path: Optional[str] = None 182beb6b57bSJohn Snow self._qemu_log_file: Optional[BinaryIO] = None 183beb6b57bSJohn Snow self._popen: Optional['subprocess.Popen[bytes]'] = None 184beb6b57bSJohn Snow self._events: List[QMPMessage] = [] 185beb6b57bSJohn Snow self._iolog: Optional[str] = None 186beb6b57bSJohn Snow self._qmp_set = True # Enable QMP monitor by default. 187beb6b57bSJohn Snow self._qmp_connection: Optional[QEMUMonitorProtocol] = None 188beb6b57bSJohn Snow self._qemu_full_args: Tuple[str, ...] = () 189beb6b57bSJohn Snow self._launched = False 190beb6b57bSJohn Snow self._machine: Optional[str] = None 191beb6b57bSJohn Snow self._console_index = 0 192beb6b57bSJohn Snow self._console_set = False 193beb6b57bSJohn Snow self._console_device_type: Optional[str] = None 194beb6b57bSJohn Snow self._console_address = os.path.join( 19587bf1fe5SJohn Snow self.sock_dir, f"{self._name}-console.sock" 196beb6b57bSJohn Snow ) 197beb6b57bSJohn Snow self._console_socket: Optional[socket.socket] = None 198beb6b57bSJohn Snow self._remove_files: List[str] = [] 199beb6b57bSJohn Snow self._user_killed = False 200b9420e4fSJohn Snow self._quit_issued = False 201beb6b57bSJohn Snow 20215c3b863SVladimir Sementsov-Ogievskiy def __enter__(self: _T) -> _T: 203beb6b57bSJohn Snow return self 204beb6b57bSJohn Snow 205beb6b57bSJohn Snow def __exit__(self, 206beb6b57bSJohn Snow exc_type: Optional[Type[BaseException]], 207beb6b57bSJohn Snow exc_val: Optional[BaseException], 208beb6b57bSJohn Snow exc_tb: Optional[TracebackType]) -> None: 209beb6b57bSJohn Snow self.shutdown() 210beb6b57bSJohn Snow 211beb6b57bSJohn Snow def add_monitor_null(self) -> None: 212beb6b57bSJohn Snow """ 213beb6b57bSJohn Snow This can be used to add an unused monitor instance. 214beb6b57bSJohn Snow """ 215beb6b57bSJohn Snow self._args.append('-monitor') 216beb6b57bSJohn Snow self._args.append('null') 217beb6b57bSJohn Snow 21815c3b863SVladimir Sementsov-Ogievskiy def add_fd(self: _T, fd: int, fdset: int, 21915c3b863SVladimir Sementsov-Ogievskiy opaque: str, opts: str = '') -> _T: 220beb6b57bSJohn Snow """ 221beb6b57bSJohn Snow Pass a file descriptor to the VM 222beb6b57bSJohn Snow """ 223beb6b57bSJohn Snow options = ['fd=%d' % fd, 224beb6b57bSJohn Snow 'set=%d' % fdset, 225beb6b57bSJohn Snow 'opaque=%s' % opaque] 226beb6b57bSJohn Snow if opts: 227beb6b57bSJohn Snow options.append(opts) 228beb6b57bSJohn Snow 229beb6b57bSJohn Snow # This did not exist before 3.4, but since then it is 230beb6b57bSJohn Snow # mandatory for our purpose 231beb6b57bSJohn Snow if hasattr(os, 'set_inheritable'): 232beb6b57bSJohn Snow os.set_inheritable(fd, True) 233beb6b57bSJohn Snow 234beb6b57bSJohn Snow self._args.append('-add-fd') 235beb6b57bSJohn Snow self._args.append(','.join(options)) 236beb6b57bSJohn Snow return self 237beb6b57bSJohn Snow 238beb6b57bSJohn Snow def send_fd_scm(self, fd: Optional[int] = None, 239beb6b57bSJohn Snow file_path: Optional[str] = None) -> int: 240beb6b57bSJohn Snow """ 241514d00dfSJohn Snow Send an fd or file_path to the remote via SCM_RIGHTS. 242beb6b57bSJohn Snow 243514d00dfSJohn Snow Exactly one of fd and file_path must be given. If it is 244514d00dfSJohn Snow file_path, the file will be opened read-only and the new file 245514d00dfSJohn Snow descriptor will be sent to the remote. 246beb6b57bSJohn Snow """ 247beb6b57bSJohn Snow if file_path is not None: 248beb6b57bSJohn Snow assert fd is None 249514d00dfSJohn Snow with open(file_path, "rb") as passfile: 250514d00dfSJohn Snow fd = passfile.fileno() 251514d00dfSJohn Snow self._qmp.send_fd_scm(fd) 252beb6b57bSJohn Snow else: 253beb6b57bSJohn Snow assert fd is not None 254514d00dfSJohn Snow self._qmp.send_fd_scm(fd) 255beb6b57bSJohn Snow 256514d00dfSJohn Snow return 0 257beb6b57bSJohn Snow 258beb6b57bSJohn Snow @staticmethod 259beb6b57bSJohn Snow def _remove_if_exists(path: str) -> None: 260beb6b57bSJohn Snow """ 261beb6b57bSJohn Snow Remove file object at path if it exists 262beb6b57bSJohn Snow """ 263beb6b57bSJohn Snow try: 264beb6b57bSJohn Snow os.remove(path) 265beb6b57bSJohn Snow except OSError as exception: 266beb6b57bSJohn Snow if exception.errno == errno.ENOENT: 267beb6b57bSJohn Snow return 268beb6b57bSJohn Snow raise 269beb6b57bSJohn Snow 270beb6b57bSJohn Snow def is_running(self) -> bool: 271beb6b57bSJohn Snow """Returns true if the VM is running.""" 272beb6b57bSJohn Snow return self._popen is not None and self._popen.poll() is None 273beb6b57bSJohn Snow 274beb6b57bSJohn Snow @property 275beb6b57bSJohn Snow def _subp(self) -> 'subprocess.Popen[bytes]': 276beb6b57bSJohn Snow if self._popen is None: 277beb6b57bSJohn Snow raise QEMUMachineError('Subprocess pipe not present') 278beb6b57bSJohn Snow return self._popen 279beb6b57bSJohn Snow 280beb6b57bSJohn Snow def exitcode(self) -> Optional[int]: 281beb6b57bSJohn Snow """Returns the exit code if possible, or None.""" 282beb6b57bSJohn Snow if self._popen is None: 283beb6b57bSJohn Snow return None 284beb6b57bSJohn Snow return self._popen.poll() 285beb6b57bSJohn Snow 286beb6b57bSJohn Snow def get_pid(self) -> Optional[int]: 287beb6b57bSJohn Snow """Returns the PID of the running process, or None.""" 288beb6b57bSJohn Snow if not self.is_running(): 289beb6b57bSJohn Snow return None 290beb6b57bSJohn Snow return self._subp.pid 291beb6b57bSJohn Snow 292beb6b57bSJohn Snow def _load_io_log(self) -> None: 2935690b437SJohn Snow # Assume that the output encoding of QEMU's terminal output is 2945690b437SJohn Snow # defined by our locale. If indeterminate, allow open() to fall 2955690b437SJohn Snow # back to the platform default. 2965690b437SJohn Snow _, encoding = locale.getlocale() 297beb6b57bSJohn Snow if self._qemu_log_path is not None: 2985690b437SJohn Snow with open(self._qemu_log_path, "r", encoding=encoding) as iolog: 299beb6b57bSJohn Snow self._iolog = iolog.read() 300beb6b57bSJohn Snow 301beb6b57bSJohn Snow @property 302beb6b57bSJohn Snow def _base_args(self) -> List[str]: 303beb6b57bSJohn Snow args = ['-display', 'none', '-vga', 'none'] 304beb6b57bSJohn Snow 305beb6b57bSJohn Snow if self._qmp_set: 306beb6b57bSJohn Snow if isinstance(self._monitor_address, tuple): 307beb6b57bSJohn Snow moncdev = "socket,id=mon,host={},port={}".format( 308beb6b57bSJohn Snow *self._monitor_address 309beb6b57bSJohn Snow ) 310beb6b57bSJohn Snow else: 311beb6b57bSJohn Snow moncdev = f"socket,id=mon,path={self._monitor_address}" 312beb6b57bSJohn Snow args.extend(['-chardev', moncdev, '-mon', 313beb6b57bSJohn Snow 'chardev=mon,mode=control']) 314beb6b57bSJohn Snow 315beb6b57bSJohn Snow if self._machine is not None: 316beb6b57bSJohn Snow args.extend(['-machine', self._machine]) 317beb6b57bSJohn Snow for _ in range(self._console_index): 318beb6b57bSJohn Snow args.extend(['-serial', 'null']) 319beb6b57bSJohn Snow if self._console_set: 320beb6b57bSJohn Snow chardev = ('socket,id=console,path=%s,server=on,wait=off' % 321beb6b57bSJohn Snow self._console_address) 322beb6b57bSJohn Snow args.extend(['-chardev', chardev]) 323beb6b57bSJohn Snow if self._console_device_type is None: 324beb6b57bSJohn Snow args.extend(['-serial', 'chardev:console']) 325beb6b57bSJohn Snow else: 326beb6b57bSJohn Snow device = '%s,chardev=console' % self._console_device_type 327beb6b57bSJohn Snow args.extend(['-device', device]) 328beb6b57bSJohn Snow return args 329beb6b57bSJohn Snow 330555fe0c2SWainer dos Santos Moschetta @property 331555fe0c2SWainer dos Santos Moschetta def args(self) -> List[str]: 332555fe0c2SWainer dos Santos Moschetta """Returns the list of arguments given to the QEMU binary.""" 333555fe0c2SWainer dos Santos Moschetta return self._args 334555fe0c2SWainer dos Santos Moschetta 335beb6b57bSJohn Snow def _pre_launch(self) -> None: 336beb6b57bSJohn Snow if self._console_set: 337beb6b57bSJohn Snow self._remove_files.append(self._console_address) 338beb6b57bSJohn Snow 339beb6b57bSJohn Snow if self._qmp_set: 3406eeb3de7SJohn Snow if isinstance(self._monitor_address, str): 341beb6b57bSJohn Snow self._remove_files.append(self._monitor_address) 342beb6b57bSJohn Snow self._qmp_connection = QEMUMonitorProtocol( 343beb6b57bSJohn Snow self._monitor_address, 344beb6b57bSJohn Snow server=True, 345beb6b57bSJohn Snow nickname=self._name 346beb6b57bSJohn Snow ) 347beb6b57bSJohn Snow 348beb6b57bSJohn Snow # NOTE: Make sure any opened resources are *definitely* freed in 349beb6b57bSJohn Snow # _post_shutdown()! 350beb6b57bSJohn Snow # pylint: disable=consider-using-with 351b306e26cSCleber Rosa self._qemu_log_path = os.path.join(self.log_dir, self._name + ".log") 352beb6b57bSJohn Snow self._qemu_log_file = open(self._qemu_log_path, 'wb') 353beb6b57bSJohn Snow 354b1ca9919SJohn Snow self._iolog = None 355b1ca9919SJohn Snow self._qemu_full_args = tuple(chain( 356b1ca9919SJohn Snow self._wrapper, 357b1ca9919SJohn Snow [self._binary], 358b1ca9919SJohn Snow self._base_args, 359b1ca9919SJohn Snow self._args 360b1ca9919SJohn Snow )) 361b1ca9919SJohn Snow 362beb6b57bSJohn Snow def _post_launch(self) -> None: 363beb6b57bSJohn Snow if self._qmp_connection: 364e2f948a8SEmanuele Giuseppe Esposito self._qmp.accept(self._qmp_timer) 365beb6b57bSJohn Snow 366eb7a91d0SEmanuele Giuseppe Esposito def _close_qemu_log_file(self) -> None: 367eb7a91d0SEmanuele Giuseppe Esposito if self._qemu_log_file is not None: 368eb7a91d0SEmanuele Giuseppe Esposito self._qemu_log_file.close() 369eb7a91d0SEmanuele Giuseppe Esposito self._qemu_log_file = None 370eb7a91d0SEmanuele Giuseppe Esposito 371beb6b57bSJohn Snow def _post_shutdown(self) -> None: 372beb6b57bSJohn Snow """ 373beb6b57bSJohn Snow Called to cleanup the VM instance after the process has exited. 374beb6b57bSJohn Snow May also be called after a failed launch. 375beb6b57bSJohn Snow """ 3769cccb330SJohn Snow LOG.debug("Cleaning up after VM process") 37749a608b8SJohn Snow try: 37849a608b8SJohn Snow self._close_qmp_connection() 37949a608b8SJohn Snow except Exception as err: # pylint: disable=broad-except 38049a608b8SJohn Snow LOG.warning( 38149a608b8SJohn Snow "Exception closing QMP connection: %s", 38249a608b8SJohn Snow str(err) if str(err) else type(err).__name__ 38349a608b8SJohn Snow ) 38449a608b8SJohn Snow finally: 38549a608b8SJohn Snow assert self._qmp_connection is None 386beb6b57bSJohn Snow 387eb7a91d0SEmanuele Giuseppe Esposito self._close_qemu_log_file() 388beb6b57bSJohn Snow 389beb6b57bSJohn Snow self._load_io_log() 390beb6b57bSJohn Snow 391beb6b57bSJohn Snow self._qemu_log_path = None 392beb6b57bSJohn Snow 393beb6b57bSJohn Snow if self._temp_dir is not None: 394beb6b57bSJohn Snow shutil.rmtree(self._temp_dir) 395beb6b57bSJohn Snow self._temp_dir = None 396beb6b57bSJohn Snow 397beb6b57bSJohn Snow while len(self._remove_files) > 0: 398beb6b57bSJohn Snow self._remove_if_exists(self._remove_files.pop()) 399beb6b57bSJohn Snow 400beb6b57bSJohn Snow exitcode = self.exitcode() 401beb6b57bSJohn Snow if (exitcode is not None and exitcode < 0 402beb6b57bSJohn Snow and not (self._user_killed and exitcode == -signal.SIGKILL)): 403beb6b57bSJohn Snow msg = 'qemu received signal %i; command: "%s"' 404beb6b57bSJohn Snow if self._qemu_full_args: 405beb6b57bSJohn Snow command = ' '.join(self._qemu_full_args) 406beb6b57bSJohn Snow else: 407beb6b57bSJohn Snow command = '' 408beb6b57bSJohn Snow LOG.warning(msg, -int(exitcode), command) 409beb6b57bSJohn Snow 410b9420e4fSJohn Snow self._quit_issued = False 411beb6b57bSJohn Snow self._user_killed = False 412beb6b57bSJohn Snow self._launched = False 413beb6b57bSJohn Snow 414beb6b57bSJohn Snow def launch(self) -> None: 415beb6b57bSJohn Snow """ 416beb6b57bSJohn Snow Launch the VM and make sure we cleanup and expose the 417beb6b57bSJohn Snow command line/output in case of exception 418beb6b57bSJohn Snow """ 419beb6b57bSJohn Snow 420beb6b57bSJohn Snow if self._launched: 421beb6b57bSJohn Snow raise QEMUMachineError('VM already launched') 422beb6b57bSJohn Snow 423beb6b57bSJohn Snow try: 424beb6b57bSJohn Snow self._launch() 42550465f94SJohn Snow except BaseException as exc: 4261611e6cfSJohn Snow # We may have launched the process but it may 4271611e6cfSJohn Snow # have exited before we could connect via QMP. 4281611e6cfSJohn Snow # Assume the VM didn't launch or is exiting. 4291611e6cfSJohn Snow # If we don't wait for the process, exitcode() may still be 4301611e6cfSJohn Snow # 'None' by the time control is ceded back to the caller. 4311611e6cfSJohn Snow if self._launched: 4321611e6cfSJohn Snow self.wait() 4331611e6cfSJohn Snow else: 434beb6b57bSJohn Snow self._post_shutdown() 435beb6b57bSJohn Snow 43650465f94SJohn Snow if isinstance(exc, Exception): 43750465f94SJohn Snow raise VMLaunchFailure( 43850465f94SJohn Snow exitcode=self.exitcode(), 43950465f94SJohn Snow command=' '.join(self._qemu_full_args), 44050465f94SJohn Snow output=self._iolog 44150465f94SJohn Snow ) from exc 44250465f94SJohn Snow 44350465f94SJohn Snow # Don't wrap 'BaseException'; doing so would downgrade 44450465f94SJohn Snow # that exception. However, we still want to clean up. 445beb6b57bSJohn Snow raise 446beb6b57bSJohn Snow 447beb6b57bSJohn Snow def _launch(self) -> None: 448beb6b57bSJohn Snow """ 449beb6b57bSJohn Snow Launch the VM and establish a QMP connection 450beb6b57bSJohn Snow """ 451beb6b57bSJohn Snow self._pre_launch() 452beb6b57bSJohn Snow LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args)) 453beb6b57bSJohn Snow 454beb6b57bSJohn Snow # Cleaning up of this subprocess is guaranteed by _do_shutdown. 455beb6b57bSJohn Snow # pylint: disable=consider-using-with 456beb6b57bSJohn Snow self._popen = subprocess.Popen(self._qemu_full_args, 457beb6b57bSJohn Snow stdin=subprocess.DEVNULL, 458beb6b57bSJohn Snow stdout=self._qemu_log_file, 459beb6b57bSJohn Snow stderr=subprocess.STDOUT, 460beb6b57bSJohn Snow shell=False, 461beb6b57bSJohn Snow close_fds=False) 4621611e6cfSJohn Snow self._launched = True 463beb6b57bSJohn Snow self._post_launch() 464beb6b57bSJohn Snow 46549a608b8SJohn Snow def _close_qmp_connection(self) -> None: 46649a608b8SJohn Snow """ 46749a608b8SJohn Snow Close the underlying QMP connection, if any. 46849a608b8SJohn Snow 46949a608b8SJohn Snow Dutifully report errors that occurred while closing, but assume 47049a608b8SJohn Snow that any error encountered indicates an abnormal termination 47149a608b8SJohn Snow process and not a failure to close. 47249a608b8SJohn Snow """ 47349a608b8SJohn Snow if self._qmp_connection is None: 47449a608b8SJohn Snow return 47549a608b8SJohn Snow 47649a608b8SJohn Snow try: 47749a608b8SJohn Snow self._qmp.close() 47849a608b8SJohn Snow except EOFError: 47949a608b8SJohn Snow # EOF can occur as an Exception here when using the Async 48049a608b8SJohn Snow # QMP backend. It indicates that the server closed the 48149a608b8SJohn Snow # stream. If we successfully issued 'quit' at any point, 48249a608b8SJohn Snow # then this was expected. If the remote went away without 48349a608b8SJohn Snow # our permission, it's worth reporting that as an abnormal 48449a608b8SJohn Snow # shutdown case. 48549a608b8SJohn Snow if not (self._user_killed or self._quit_issued): 48649a608b8SJohn Snow raise 48749a608b8SJohn Snow finally: 48849a608b8SJohn Snow self._qmp_connection = None 48949a608b8SJohn Snow 490beb6b57bSJohn Snow def _early_cleanup(self) -> None: 491beb6b57bSJohn Snow """ 492beb6b57bSJohn Snow Perform any cleanup that needs to happen before the VM exits. 493beb6b57bSJohn Snow 4941611e6cfSJohn Snow This method may be called twice upon shutdown, once each by soft 4951611e6cfSJohn Snow and hard shutdown in failover scenarios. 496beb6b57bSJohn Snow """ 497beb6b57bSJohn Snow # If we keep the console socket open, we may deadlock waiting 498beb6b57bSJohn Snow # for QEMU to exit, while QEMU is waiting for the socket to 4999323e79fSPeter Maydell # become writable. 500beb6b57bSJohn Snow if self._console_socket is not None: 5019cccb330SJohn Snow LOG.debug("Closing console socket") 502beb6b57bSJohn Snow self._console_socket.close() 503beb6b57bSJohn Snow self._console_socket = None 504beb6b57bSJohn Snow 505beb6b57bSJohn Snow def _hard_shutdown(self) -> None: 506beb6b57bSJohn Snow """ 507beb6b57bSJohn Snow Perform early cleanup, kill the VM, and wait for it to terminate. 508beb6b57bSJohn Snow 509beb6b57bSJohn Snow :raise subprocess.Timeout: When timeout is exceeds 60 seconds 510beb6b57bSJohn Snow waiting for the QEMU process to terminate. 511beb6b57bSJohn Snow """ 5129cccb330SJohn Snow LOG.debug("Performing hard shutdown") 513beb6b57bSJohn Snow self._early_cleanup() 514beb6b57bSJohn Snow self._subp.kill() 515beb6b57bSJohn Snow self._subp.wait(timeout=60) 516beb6b57bSJohn Snow 517b9420e4fSJohn Snow def _soft_shutdown(self, timeout: Optional[int]) -> None: 518beb6b57bSJohn Snow """ 519beb6b57bSJohn Snow Perform early cleanup, attempt to gracefully shut down the VM, and wait 520beb6b57bSJohn Snow for it to terminate. 521beb6b57bSJohn Snow 522beb6b57bSJohn Snow :param timeout: Timeout in seconds for graceful shutdown. 523beb6b57bSJohn Snow A value of None is an infinite wait. 524beb6b57bSJohn Snow 525beb6b57bSJohn Snow :raise ConnectionReset: On QMP communication errors 526beb6b57bSJohn Snow :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for 527beb6b57bSJohn Snow the QEMU process to terminate. 528beb6b57bSJohn Snow """ 5299cccb330SJohn Snow LOG.debug("Attempting graceful termination") 5309cccb330SJohn Snow 531beb6b57bSJohn Snow self._early_cleanup() 532beb6b57bSJohn Snow 5339cccb330SJohn Snow if self._quit_issued: 5349cccb330SJohn Snow LOG.debug( 5359cccb330SJohn Snow "Anticipating QEMU termination due to prior 'quit' command, " 5369cccb330SJohn Snow "or explicit call to wait()" 5379cccb330SJohn Snow ) 5389cccb330SJohn Snow else: 5399cccb330SJohn Snow LOG.debug("Politely asking QEMU to terminate") 5409cccb330SJohn Snow 541beb6b57bSJohn Snow if self._qmp_connection: 54249a608b8SJohn Snow try: 543b9420e4fSJohn Snow if not self._quit_issued: 54449a608b8SJohn Snow # May raise ExecInterruptedError or StateError if the 54549a608b8SJohn Snow # connection dies or has *already* died. 546b9420e4fSJohn Snow self.qmp('quit') 54749a608b8SJohn Snow finally: 54849a608b8SJohn Snow # Regardless, we want to quiesce the connection. 54949a608b8SJohn Snow self._close_qmp_connection() 5503c6e5e8cSJohn Snow elif not self._quit_issued: 5513c6e5e8cSJohn Snow LOG.debug( 5523c6e5e8cSJohn Snow "Not anticipating QEMU quit and no QMP connection present, " 5533c6e5e8cSJohn Snow "issuing SIGTERM" 5543c6e5e8cSJohn Snow ) 5553c6e5e8cSJohn Snow self._subp.terminate() 556beb6b57bSJohn Snow 557beb6b57bSJohn Snow # May raise subprocess.TimeoutExpired 5589cccb330SJohn Snow LOG.debug( 5599cccb330SJohn Snow "Waiting (timeout=%s) for QEMU process (pid=%s) to terminate", 5609cccb330SJohn Snow timeout, self._subp.pid 5619cccb330SJohn Snow ) 562beb6b57bSJohn Snow self._subp.wait(timeout=timeout) 563beb6b57bSJohn Snow 564b9420e4fSJohn Snow def _do_shutdown(self, timeout: Optional[int]) -> None: 565beb6b57bSJohn Snow """ 566beb6b57bSJohn Snow Attempt to shutdown the VM gracefully; fallback to a hard shutdown. 567beb6b57bSJohn Snow 568beb6b57bSJohn Snow :param timeout: Timeout in seconds for graceful shutdown. 569beb6b57bSJohn Snow A value of None is an infinite wait. 570beb6b57bSJohn Snow 571beb6b57bSJohn Snow :raise AbnormalShutdown: When the VM could not be shut down gracefully. 572beb6b57bSJohn Snow The inner exception will likely be ConnectionReset or 573beb6b57bSJohn Snow subprocess.TimeoutExpired. In rare cases, non-graceful termination 574beb6b57bSJohn Snow may result in its own exceptions, likely subprocess.TimeoutExpired. 575beb6b57bSJohn Snow """ 576beb6b57bSJohn Snow try: 577b9420e4fSJohn Snow self._soft_shutdown(timeout) 578beb6b57bSJohn Snow except Exception as exc: 5799cccb330SJohn Snow if isinstance(exc, subprocess.TimeoutExpired): 5809cccb330SJohn Snow LOG.debug("Timed out waiting for QEMU process to exit") 5819cccb330SJohn Snow LOG.debug("Graceful shutdown failed", exc_info=True) 5829cccb330SJohn Snow LOG.debug("Falling back to hard shutdown") 583beb6b57bSJohn Snow self._hard_shutdown() 584beb6b57bSJohn Snow raise AbnormalShutdown("Could not perform graceful shutdown") \ 585beb6b57bSJohn Snow from exc 586beb6b57bSJohn Snow 587b9420e4fSJohn Snow def shutdown(self, 588beb6b57bSJohn Snow hard: bool = False, 589beb6b57bSJohn Snow timeout: Optional[int] = 30) -> None: 590beb6b57bSJohn Snow """ 591beb6b57bSJohn Snow Terminate the VM (gracefully if possible) and perform cleanup. 592beb6b57bSJohn Snow Cleanup will always be performed. 593beb6b57bSJohn Snow 594beb6b57bSJohn Snow If the VM has not yet been launched, or shutdown(), wait(), or kill() 595beb6b57bSJohn Snow have already been called, this method does nothing. 596beb6b57bSJohn Snow 597beb6b57bSJohn Snow :param hard: When true, do not attempt graceful shutdown, and 598beb6b57bSJohn Snow suppress the SIGKILL warning log message. 599beb6b57bSJohn Snow :param timeout: Optional timeout in seconds for graceful shutdown. 600beb6b57bSJohn Snow Default 30 seconds, A `None` value is an infinite wait. 601beb6b57bSJohn Snow """ 602beb6b57bSJohn Snow if not self._launched: 603beb6b57bSJohn Snow return 604beb6b57bSJohn Snow 6059cccb330SJohn Snow LOG.debug("Shutting down VM appliance; timeout=%s", timeout) 6069cccb330SJohn Snow if hard: 6079cccb330SJohn Snow LOG.debug("Caller requests immediate termination of QEMU process.") 6089cccb330SJohn Snow 609beb6b57bSJohn Snow try: 610beb6b57bSJohn Snow if hard: 611beb6b57bSJohn Snow self._user_killed = True 612beb6b57bSJohn Snow self._hard_shutdown() 613beb6b57bSJohn Snow else: 614b9420e4fSJohn Snow self._do_shutdown(timeout) 615beb6b57bSJohn Snow finally: 616beb6b57bSJohn Snow self._post_shutdown() 617beb6b57bSJohn Snow 618beb6b57bSJohn Snow def kill(self) -> None: 619beb6b57bSJohn Snow """ 620beb6b57bSJohn Snow Terminate the VM forcefully, wait for it to exit, and perform cleanup. 621beb6b57bSJohn Snow """ 622beb6b57bSJohn Snow self.shutdown(hard=True) 623beb6b57bSJohn Snow 624beb6b57bSJohn Snow def wait(self, timeout: Optional[int] = 30) -> None: 625beb6b57bSJohn Snow """ 626beb6b57bSJohn Snow Wait for the VM to power off and perform post-shutdown cleanup. 627beb6b57bSJohn Snow 628beb6b57bSJohn Snow :param timeout: Optional timeout in seconds. Default 30 seconds. 629beb6b57bSJohn Snow A value of `None` is an infinite wait. 630beb6b57bSJohn Snow """ 631b9420e4fSJohn Snow self._quit_issued = True 632b9420e4fSJohn Snow self.shutdown(timeout=timeout) 633beb6b57bSJohn Snow 634beb6b57bSJohn Snow def set_qmp_monitor(self, enabled: bool = True) -> None: 635beb6b57bSJohn Snow """ 636beb6b57bSJohn Snow Set the QMP monitor. 637beb6b57bSJohn Snow 638beb6b57bSJohn Snow @param enabled: if False, qmp monitor options will be removed from 639beb6b57bSJohn Snow the base arguments of the resulting QEMU command 640beb6b57bSJohn Snow line. Default is True. 6415c02c865SJohn Snow 6425c02c865SJohn Snow .. note:: Call this function before launch(). 643beb6b57bSJohn Snow """ 644beb6b57bSJohn Snow self._qmp_set = enabled 645beb6b57bSJohn Snow 646beb6b57bSJohn Snow @property 647beb6b57bSJohn Snow def _qmp(self) -> QEMUMonitorProtocol: 648beb6b57bSJohn Snow if self._qmp_connection is None: 649beb6b57bSJohn Snow raise QEMUMachineError("Attempt to access QMP with no connection") 650beb6b57bSJohn Snow return self._qmp_connection 651beb6b57bSJohn Snow 652beb6b57bSJohn Snow @classmethod 653c7daa57eSVladimir Sementsov-Ogievskiy def _qmp_args(cls, conv_keys: bool, 654c7daa57eSVladimir Sementsov-Ogievskiy args: Dict[str, Any]) -> Dict[str, object]: 655c7daa57eSVladimir Sementsov-Ogievskiy if conv_keys: 656c7daa57eSVladimir Sementsov-Ogievskiy return {k.replace('_', '-'): v for k, v in args.items()} 657c7daa57eSVladimir Sementsov-Ogievskiy 658c7daa57eSVladimir Sementsov-Ogievskiy return args 659beb6b57bSJohn Snow 660beb6b57bSJohn Snow def qmp(self, cmd: str, 6613f3c9b4cSVladimir Sementsov-Ogievskiy args_dict: Optional[Dict[str, object]] = None, 6623f3c9b4cSVladimir Sementsov-Ogievskiy conv_keys: Optional[bool] = None, 663beb6b57bSJohn Snow **args: Any) -> QMPMessage: 664beb6b57bSJohn Snow """ 665beb6b57bSJohn Snow Invoke a QMP command and return the response dict 666beb6b57bSJohn Snow """ 6673f3c9b4cSVladimir Sementsov-Ogievskiy if args_dict is not None: 6683f3c9b4cSVladimir Sementsov-Ogievskiy assert not args 6693f3c9b4cSVladimir Sementsov-Ogievskiy assert conv_keys is None 6703f3c9b4cSVladimir Sementsov-Ogievskiy args = args_dict 6713f3c9b4cSVladimir Sementsov-Ogievskiy conv_keys = False 6723f3c9b4cSVladimir Sementsov-Ogievskiy 6733f3c9b4cSVladimir Sementsov-Ogievskiy if conv_keys is None: 6743f3c9b4cSVladimir Sementsov-Ogievskiy conv_keys = True 6753f3c9b4cSVladimir Sementsov-Ogievskiy 676c7daa57eSVladimir Sementsov-Ogievskiy qmp_args = self._qmp_args(conv_keys, args) 677b9420e4fSJohn Snow ret = self._qmp.cmd(cmd, args=qmp_args) 678b9420e4fSJohn Snow if cmd == 'quit' and 'error' not in ret and 'return' in ret: 679b9420e4fSJohn Snow self._quit_issued = True 680b9420e4fSJohn Snow return ret 681beb6b57bSJohn Snow 682beb6b57bSJohn Snow def command(self, cmd: str, 683beb6b57bSJohn Snow conv_keys: bool = True, 684beb6b57bSJohn Snow **args: Any) -> QMPReturnValue: 685beb6b57bSJohn Snow """ 686beb6b57bSJohn Snow Invoke a QMP command. 687beb6b57bSJohn Snow On success return the response dict. 688beb6b57bSJohn Snow On failure raise an exception. 689beb6b57bSJohn Snow """ 690c7daa57eSVladimir Sementsov-Ogievskiy qmp_args = self._qmp_args(conv_keys, args) 691b9420e4fSJohn Snow ret = self._qmp.command(cmd, **qmp_args) 692b9420e4fSJohn Snow if cmd == 'quit': 693b9420e4fSJohn Snow self._quit_issued = True 694b9420e4fSJohn Snow return ret 695beb6b57bSJohn Snow 696beb6b57bSJohn Snow def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]: 697beb6b57bSJohn Snow """ 698beb6b57bSJohn Snow Poll for one queued QMP events and return it 699beb6b57bSJohn Snow """ 700beb6b57bSJohn Snow if self._events: 701beb6b57bSJohn Snow return self._events.pop(0) 702beb6b57bSJohn Snow return self._qmp.pull_event(wait=wait) 703beb6b57bSJohn Snow 704beb6b57bSJohn Snow def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]: 705beb6b57bSJohn Snow """ 706beb6b57bSJohn Snow Poll for queued QMP events and return a list of dicts 707beb6b57bSJohn Snow """ 708beb6b57bSJohn Snow events = self._qmp.get_events(wait=wait) 709beb6b57bSJohn Snow events.extend(self._events) 710beb6b57bSJohn Snow del self._events[:] 711beb6b57bSJohn Snow return events 712beb6b57bSJohn Snow 713beb6b57bSJohn Snow @staticmethod 714beb6b57bSJohn Snow def event_match(event: Any, match: Optional[Any]) -> bool: 715beb6b57bSJohn Snow """ 716beb6b57bSJohn Snow Check if an event matches optional match criteria. 717beb6b57bSJohn Snow 718beb6b57bSJohn Snow The match criteria takes the form of a matching subdict. The event is 719beb6b57bSJohn Snow checked to be a superset of the subdict, recursively, with matching 720beb6b57bSJohn Snow values whenever the subdict values are not None. 721beb6b57bSJohn Snow 722beb6b57bSJohn Snow This has a limitation that you cannot explicitly check for None values. 723beb6b57bSJohn Snow 724beb6b57bSJohn Snow Examples, with the subdict queries on the left: 725beb6b57bSJohn Snow - None matches any object. 726beb6b57bSJohn Snow - {"foo": None} matches {"foo": {"bar": 1}} 727beb6b57bSJohn Snow - {"foo": None} matches {"foo": 5} 728beb6b57bSJohn Snow - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}} 729beb6b57bSJohn Snow - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}} 730beb6b57bSJohn Snow """ 731beb6b57bSJohn Snow if match is None: 732beb6b57bSJohn Snow return True 733beb6b57bSJohn Snow 734beb6b57bSJohn Snow try: 735beb6b57bSJohn Snow for key in match: 736beb6b57bSJohn Snow if key in event: 737beb6b57bSJohn Snow if not QEMUMachine.event_match(event[key], match[key]): 738beb6b57bSJohn Snow return False 739beb6b57bSJohn Snow else: 740beb6b57bSJohn Snow return False 741beb6b57bSJohn Snow return True 742beb6b57bSJohn Snow except TypeError: 743beb6b57bSJohn Snow # either match or event wasn't iterable (not a dict) 744beb6b57bSJohn Snow return bool(match == event) 745beb6b57bSJohn Snow 746beb6b57bSJohn Snow def event_wait(self, name: str, 747beb6b57bSJohn Snow timeout: float = 60.0, 748beb6b57bSJohn Snow match: Optional[QMPMessage] = None) -> Optional[QMPMessage]: 749beb6b57bSJohn Snow """ 750beb6b57bSJohn Snow event_wait waits for and returns a named event from QMP with a timeout. 751beb6b57bSJohn Snow 752beb6b57bSJohn Snow name: The event to wait for. 753beb6b57bSJohn Snow timeout: QEMUMonitorProtocol.pull_event timeout parameter. 754beb6b57bSJohn Snow match: Optional match criteria. See event_match for details. 755beb6b57bSJohn Snow """ 756beb6b57bSJohn Snow return self.events_wait([(name, match)], timeout) 757beb6b57bSJohn Snow 758beb6b57bSJohn Snow def events_wait(self, 759beb6b57bSJohn Snow events: Sequence[Tuple[str, Any]], 760beb6b57bSJohn Snow timeout: float = 60.0) -> Optional[QMPMessage]: 761beb6b57bSJohn Snow """ 762beb6b57bSJohn Snow events_wait waits for and returns a single named event from QMP. 763beb6b57bSJohn Snow In the case of multiple qualifying events, this function returns the 764beb6b57bSJohn Snow first one. 765beb6b57bSJohn Snow 766beb6b57bSJohn Snow :param events: A sequence of (name, match_criteria) tuples. 767beb6b57bSJohn Snow The match criteria are optional and may be None. 768beb6b57bSJohn Snow See event_match for details. 769beb6b57bSJohn Snow :param timeout: Optional timeout, in seconds. 770beb6b57bSJohn Snow See QEMUMonitorProtocol.pull_event. 771beb6b57bSJohn Snow 772a4225303SJohn Snow :raise asyncio.TimeoutError: 773a4225303SJohn Snow If timeout was non-zero and no matching events were found. 774a4225303SJohn Snow 775beb6b57bSJohn Snow :return: A QMP event matching the filter criteria. 776beb6b57bSJohn Snow If timeout was 0 and no event matched, None. 777beb6b57bSJohn Snow """ 778beb6b57bSJohn Snow def _match(event: QMPMessage) -> bool: 779beb6b57bSJohn Snow for name, match in events: 780beb6b57bSJohn Snow if event['event'] == name and self.event_match(event, match): 781beb6b57bSJohn Snow return True 782beb6b57bSJohn Snow return False 783beb6b57bSJohn Snow 784beb6b57bSJohn Snow event: Optional[QMPMessage] 785beb6b57bSJohn Snow 786beb6b57bSJohn Snow # Search cached events 787beb6b57bSJohn Snow for event in self._events: 788beb6b57bSJohn Snow if _match(event): 789beb6b57bSJohn Snow self._events.remove(event) 790beb6b57bSJohn Snow return event 791beb6b57bSJohn Snow 792beb6b57bSJohn Snow # Poll for new events 793beb6b57bSJohn Snow while True: 794beb6b57bSJohn Snow event = self._qmp.pull_event(wait=timeout) 795beb6b57bSJohn Snow if event is None: 796beb6b57bSJohn Snow # NB: None is only returned when timeout is false-ish. 797a4225303SJohn Snow # Timeouts raise asyncio.TimeoutError instead! 798beb6b57bSJohn Snow break 799beb6b57bSJohn Snow if _match(event): 800beb6b57bSJohn Snow return event 801beb6b57bSJohn Snow self._events.append(event) 802beb6b57bSJohn Snow 803beb6b57bSJohn Snow return None 804beb6b57bSJohn Snow 805beb6b57bSJohn Snow def get_log(self) -> Optional[str]: 806beb6b57bSJohn Snow """ 807beb6b57bSJohn Snow After self.shutdown or failed qemu execution, this returns the output 808beb6b57bSJohn Snow of the qemu process. 809beb6b57bSJohn Snow """ 810beb6b57bSJohn Snow return self._iolog 811beb6b57bSJohn Snow 812beb6b57bSJohn Snow def add_args(self, *args: str) -> None: 813beb6b57bSJohn Snow """ 814beb6b57bSJohn Snow Adds to the list of extra arguments to be given to the QEMU binary 815beb6b57bSJohn Snow """ 816beb6b57bSJohn Snow self._args.extend(args) 817beb6b57bSJohn Snow 818beb6b57bSJohn Snow def set_machine(self, machine_type: str) -> None: 819beb6b57bSJohn Snow """ 820beb6b57bSJohn Snow Sets the machine type 821beb6b57bSJohn Snow 822beb6b57bSJohn Snow If set, the machine type will be added to the base arguments 823beb6b57bSJohn Snow of the resulting QEMU command line. 824beb6b57bSJohn Snow """ 825beb6b57bSJohn Snow self._machine = machine_type 826beb6b57bSJohn Snow 827beb6b57bSJohn Snow def set_console(self, 828beb6b57bSJohn Snow device_type: Optional[str] = None, 829beb6b57bSJohn Snow console_index: int = 0) -> None: 830beb6b57bSJohn Snow """ 831beb6b57bSJohn Snow Sets the device type for a console device 832beb6b57bSJohn Snow 833beb6b57bSJohn Snow If set, the console device and a backing character device will 834beb6b57bSJohn Snow be added to the base arguments of the resulting QEMU command 835beb6b57bSJohn Snow line. 836beb6b57bSJohn Snow 837beb6b57bSJohn Snow This is a convenience method that will either use the provided 838beb6b57bSJohn Snow device type, or default to a "-serial chardev:console" command 839beb6b57bSJohn Snow line argument. 840beb6b57bSJohn Snow 841beb6b57bSJohn Snow The actual setting of command line arguments will be be done at 842beb6b57bSJohn Snow machine launch time, as it depends on the temporary directory 843beb6b57bSJohn Snow to be created. 844beb6b57bSJohn Snow 845beb6b57bSJohn Snow @param device_type: the device type, such as "isa-serial". If 846beb6b57bSJohn Snow None is given (the default value) a "-serial 847beb6b57bSJohn Snow chardev:console" command line argument will 848beb6b57bSJohn Snow be used instead, resorting to the machine's 849beb6b57bSJohn Snow default device type. 850beb6b57bSJohn Snow @param console_index: the index of the console device to use. 851beb6b57bSJohn Snow If not zero, the command line will create 852beb6b57bSJohn Snow 'index - 1' consoles and connect them to 853beb6b57bSJohn Snow the 'null' backing character device. 854beb6b57bSJohn Snow """ 855beb6b57bSJohn Snow self._console_set = True 856beb6b57bSJohn Snow self._console_device_type = device_type 857beb6b57bSJohn Snow self._console_index = console_index 858beb6b57bSJohn Snow 859beb6b57bSJohn Snow @property 860beb6b57bSJohn Snow def console_socket(self) -> socket.socket: 861beb6b57bSJohn Snow """ 862beb6b57bSJohn Snow Returns a socket connected to the console 863beb6b57bSJohn Snow """ 864beb6b57bSJohn Snow if self._console_socket is None: 865beb6b57bSJohn Snow self._console_socket = console_socket.ConsoleSocket( 866beb6b57bSJohn Snow self._console_address, 867beb6b57bSJohn Snow file=self._console_log_path, 868beb6b57bSJohn Snow drain=self._drain_console) 869beb6b57bSJohn Snow return self._console_socket 870beb6b57bSJohn Snow 871beb6b57bSJohn Snow @property 872beb6b57bSJohn Snow def temp_dir(self) -> str: 873beb6b57bSJohn Snow """ 874beb6b57bSJohn Snow Returns a temporary directory to be used for this machine 875beb6b57bSJohn Snow """ 876beb6b57bSJohn Snow if self._temp_dir is None: 877beb6b57bSJohn Snow self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-", 878beb6b57bSJohn Snow dir=self._base_temp_dir) 879beb6b57bSJohn Snow return self._temp_dir 880b306e26cSCleber Rosa 881b306e26cSCleber Rosa @property 88287bf1fe5SJohn Snow def sock_dir(self) -> str: 88387bf1fe5SJohn Snow """ 88487bf1fe5SJohn Snow Returns the directory used for sockfiles by this machine. 88587bf1fe5SJohn Snow """ 88687bf1fe5SJohn Snow if self._sock_dir: 88787bf1fe5SJohn Snow return self._sock_dir 88887bf1fe5SJohn Snow return self.temp_dir 88987bf1fe5SJohn Snow 89087bf1fe5SJohn Snow @property 891b306e26cSCleber Rosa def log_dir(self) -> str: 892b306e26cSCleber Rosa """ 893b306e26cSCleber Rosa Returns a directory to be used for writing logs 894b306e26cSCleber Rosa """ 895b306e26cSCleber Rosa if self._log_dir is None: 896b306e26cSCleber Rosa return self.temp_dir 897b306e26cSCleber Rosa return self._log_dir 898