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 43d1e04769SJohn Snowfrom qemu.qmp import ( # pylint: disable=import-error 44beb6b57bSJohn Snow QMPMessage, 45beb6b57bSJohn Snow QMPReturnValue, 46beb6b57bSJohn Snow SocketAddrT, 47beb6b57bSJohn Snow) 48beb6b57bSJohn Snow 49beb6b57bSJohn Snowfrom . import console_socket 50beb6b57bSJohn Snow 51beb6b57bSJohn Snow 5276cd3586SJohn Snowif os.environ.get('QEMU_PYTHON_LEGACY_QMP'): 5376cd3586SJohn Snow from qemu.qmp import QEMUMonitorProtocol 5476cd3586SJohn Snowelse: 5576cd3586SJohn Snow from qemu.aqmp.legacy import QEMUMonitorProtocol 5676cd3586SJohn Snow 5776cd3586SJohn Snow 58beb6b57bSJohn SnowLOG = logging.getLogger(__name__) 59beb6b57bSJohn Snow 60beb6b57bSJohn Snow 61beb6b57bSJohn Snowclass QEMUMachineError(Exception): 62beb6b57bSJohn Snow """ 63beb6b57bSJohn Snow Exception called when an error in QEMUMachine happens. 64beb6b57bSJohn Snow """ 65beb6b57bSJohn Snow 66beb6b57bSJohn Snow 67beb6b57bSJohn Snowclass QEMUMachineAddDeviceError(QEMUMachineError): 68beb6b57bSJohn Snow """ 69beb6b57bSJohn Snow Exception raised when a request to add a device can not be fulfilled 70beb6b57bSJohn Snow 71beb6b57bSJohn Snow The failures are caused by limitations, lack of information or conflicting 72beb6b57bSJohn Snow requests on the QEMUMachine methods. This exception does not represent 73beb6b57bSJohn Snow failures reported by the QEMU binary itself. 74beb6b57bSJohn Snow """ 75beb6b57bSJohn Snow 76beb6b57bSJohn Snow 77beb6b57bSJohn Snowclass AbnormalShutdown(QEMUMachineError): 78beb6b57bSJohn Snow """ 79beb6b57bSJohn Snow Exception raised when a graceful shutdown was requested, but not performed. 80beb6b57bSJohn Snow """ 81beb6b57bSJohn Snow 82beb6b57bSJohn Snow 8315c3b863SVladimir Sementsov-Ogievskiy_T = TypeVar('_T', bound='QEMUMachine') 8415c3b863SVladimir Sementsov-Ogievskiy 8515c3b863SVladimir Sementsov-Ogievskiy 86beb6b57bSJohn Snowclass QEMUMachine: 87beb6b57bSJohn Snow """ 88beb6b57bSJohn Snow A QEMU VM. 89beb6b57bSJohn Snow 90beb6b57bSJohn Snow Use this object as a context manager to ensure 91beb6b57bSJohn Snow the QEMU process terminates:: 92beb6b57bSJohn Snow 93beb6b57bSJohn Snow with VM(binary) as vm: 94beb6b57bSJohn Snow ... 95beb6b57bSJohn Snow # vm is guaranteed to be shut down here 96beb6b57bSJohn Snow """ 9782e6517dSJohn Snow # pylint: disable=too-many-instance-attributes, too-many-public-methods 98beb6b57bSJohn Snow 99beb6b57bSJohn Snow def __init__(self, 100beb6b57bSJohn Snow binary: str, 101beb6b57bSJohn Snow args: Sequence[str] = (), 102beb6b57bSJohn Snow wrapper: Sequence[str] = (), 103beb6b57bSJohn Snow name: Optional[str] = None, 104beb6b57bSJohn Snow base_temp_dir: str = "/var/tmp", 105beb6b57bSJohn Snow monitor_address: Optional[SocketAddrT] = None, 106beb6b57bSJohn Snow sock_dir: Optional[str] = None, 107beb6b57bSJohn Snow drain_console: bool = False, 108b306e26cSCleber Rosa console_log: Optional[str] = None, 109e2f948a8SEmanuele Giuseppe Esposito log_dir: Optional[str] = None, 110e2f948a8SEmanuele Giuseppe Esposito qmp_timer: Optional[float] = None): 111beb6b57bSJohn Snow ''' 112beb6b57bSJohn Snow Initialize a QEMUMachine 113beb6b57bSJohn Snow 114beb6b57bSJohn Snow @param binary: path to the qemu binary 115beb6b57bSJohn Snow @param args: list of extra arguments 116beb6b57bSJohn Snow @param wrapper: list of arguments used as prefix to qemu binary 117beb6b57bSJohn Snow @param name: prefix for socket and log file names (default: qemu-PID) 118beb6b57bSJohn Snow @param base_temp_dir: default location where temp files are created 119beb6b57bSJohn Snow @param monitor_address: address for QMP monitor 120beb6b57bSJohn Snow @param sock_dir: where to create socket (defaults to base_temp_dir) 121beb6b57bSJohn Snow @param drain_console: (optional) True to drain console socket to buffer 122beb6b57bSJohn Snow @param console_log: (optional) path to console log file 123b306e26cSCleber Rosa @param log_dir: where to create and keep log files 124e2f948a8SEmanuele Giuseppe Esposito @param qmp_timer: (optional) default QMP socket timeout 125beb6b57bSJohn Snow @note: Qemu process is not started until launch() is used. 126beb6b57bSJohn Snow ''' 12782e6517dSJohn Snow # pylint: disable=too-many-arguments 12882e6517dSJohn Snow 129beb6b57bSJohn Snow # Direct user configuration 130beb6b57bSJohn Snow 131beb6b57bSJohn Snow self._binary = binary 132beb6b57bSJohn Snow self._args = list(args) 133beb6b57bSJohn Snow self._wrapper = wrapper 134e2f948a8SEmanuele Giuseppe Esposito self._qmp_timer = qmp_timer 135beb6b57bSJohn Snow 136*72b17fe7SJohn Snow self._name = name or f"qemu-{os.getpid()}-{id(self):02x}" 13787bf1fe5SJohn Snow self._temp_dir: Optional[str] = None 138beb6b57bSJohn Snow self._base_temp_dir = base_temp_dir 13987bf1fe5SJohn Snow self._sock_dir = sock_dir 140b306e26cSCleber Rosa self._log_dir = log_dir 141beb6b57bSJohn Snow 142beb6b57bSJohn Snow if monitor_address is not None: 143beb6b57bSJohn Snow self._monitor_address = monitor_address 144beb6b57bSJohn Snow else: 145beb6b57bSJohn Snow self._monitor_address = os.path.join( 14687bf1fe5SJohn Snow self.sock_dir, f"{self._name}-monitor.sock" 147beb6b57bSJohn Snow ) 148beb6b57bSJohn Snow 149beb6b57bSJohn Snow self._console_log_path = console_log 150beb6b57bSJohn Snow if self._console_log_path: 151beb6b57bSJohn Snow # In order to log the console, buffering needs to be enabled. 152beb6b57bSJohn Snow self._drain_console = True 153beb6b57bSJohn Snow else: 154beb6b57bSJohn Snow self._drain_console = drain_console 155beb6b57bSJohn Snow 156beb6b57bSJohn Snow # Runstate 157beb6b57bSJohn Snow self._qemu_log_path: Optional[str] = None 158beb6b57bSJohn Snow self._qemu_log_file: Optional[BinaryIO] = None 159beb6b57bSJohn Snow self._popen: Optional['subprocess.Popen[bytes]'] = None 160beb6b57bSJohn Snow self._events: List[QMPMessage] = [] 161beb6b57bSJohn Snow self._iolog: Optional[str] = None 162beb6b57bSJohn Snow self._qmp_set = True # Enable QMP monitor by default. 163beb6b57bSJohn Snow self._qmp_connection: Optional[QEMUMonitorProtocol] = None 164beb6b57bSJohn Snow self._qemu_full_args: Tuple[str, ...] = () 165beb6b57bSJohn Snow self._launched = False 166beb6b57bSJohn Snow self._machine: Optional[str] = None 167beb6b57bSJohn Snow self._console_index = 0 168beb6b57bSJohn Snow self._console_set = False 169beb6b57bSJohn Snow self._console_device_type: Optional[str] = None 170beb6b57bSJohn Snow self._console_address = os.path.join( 17187bf1fe5SJohn Snow self.sock_dir, f"{self._name}-console.sock" 172beb6b57bSJohn Snow ) 173beb6b57bSJohn Snow self._console_socket: Optional[socket.socket] = None 174beb6b57bSJohn Snow self._remove_files: List[str] = [] 175beb6b57bSJohn Snow self._user_killed = False 176b9420e4fSJohn Snow self._quit_issued = False 177beb6b57bSJohn Snow 17815c3b863SVladimir Sementsov-Ogievskiy def __enter__(self: _T) -> _T: 179beb6b57bSJohn Snow return self 180beb6b57bSJohn Snow 181beb6b57bSJohn Snow def __exit__(self, 182beb6b57bSJohn Snow exc_type: Optional[Type[BaseException]], 183beb6b57bSJohn Snow exc_val: Optional[BaseException], 184beb6b57bSJohn Snow exc_tb: Optional[TracebackType]) -> None: 185beb6b57bSJohn Snow self.shutdown() 186beb6b57bSJohn Snow 187beb6b57bSJohn Snow def add_monitor_null(self) -> None: 188beb6b57bSJohn Snow """ 189beb6b57bSJohn Snow This can be used to add an unused monitor instance. 190beb6b57bSJohn Snow """ 191beb6b57bSJohn Snow self._args.append('-monitor') 192beb6b57bSJohn Snow self._args.append('null') 193beb6b57bSJohn Snow 19415c3b863SVladimir Sementsov-Ogievskiy def add_fd(self: _T, fd: int, fdset: int, 19515c3b863SVladimir Sementsov-Ogievskiy opaque: str, opts: str = '') -> _T: 196beb6b57bSJohn Snow """ 197beb6b57bSJohn Snow Pass a file descriptor to the VM 198beb6b57bSJohn Snow """ 199beb6b57bSJohn Snow options = ['fd=%d' % fd, 200beb6b57bSJohn Snow 'set=%d' % fdset, 201beb6b57bSJohn Snow 'opaque=%s' % opaque] 202beb6b57bSJohn Snow if opts: 203beb6b57bSJohn Snow options.append(opts) 204beb6b57bSJohn Snow 205beb6b57bSJohn Snow # This did not exist before 3.4, but since then it is 206beb6b57bSJohn Snow # mandatory for our purpose 207beb6b57bSJohn Snow if hasattr(os, 'set_inheritable'): 208beb6b57bSJohn Snow os.set_inheritable(fd, True) 209beb6b57bSJohn Snow 210beb6b57bSJohn Snow self._args.append('-add-fd') 211beb6b57bSJohn Snow self._args.append(','.join(options)) 212beb6b57bSJohn Snow return self 213beb6b57bSJohn Snow 214beb6b57bSJohn Snow def send_fd_scm(self, fd: Optional[int] = None, 215beb6b57bSJohn Snow file_path: Optional[str] = None) -> int: 216beb6b57bSJohn Snow """ 217514d00dfSJohn Snow Send an fd or file_path to the remote via SCM_RIGHTS. 218beb6b57bSJohn Snow 219514d00dfSJohn Snow Exactly one of fd and file_path must be given. If it is 220514d00dfSJohn Snow file_path, the file will be opened read-only and the new file 221514d00dfSJohn Snow descriptor will be sent to the remote. 222beb6b57bSJohn Snow """ 223beb6b57bSJohn Snow if file_path is not None: 224beb6b57bSJohn Snow assert fd is None 225514d00dfSJohn Snow with open(file_path, "rb") as passfile: 226514d00dfSJohn Snow fd = passfile.fileno() 227514d00dfSJohn Snow self._qmp.send_fd_scm(fd) 228beb6b57bSJohn Snow else: 229beb6b57bSJohn Snow assert fd is not None 230514d00dfSJohn Snow self._qmp.send_fd_scm(fd) 231beb6b57bSJohn Snow 232514d00dfSJohn Snow return 0 233beb6b57bSJohn Snow 234beb6b57bSJohn Snow @staticmethod 235beb6b57bSJohn Snow def _remove_if_exists(path: str) -> None: 236beb6b57bSJohn Snow """ 237beb6b57bSJohn Snow Remove file object at path if it exists 238beb6b57bSJohn Snow """ 239beb6b57bSJohn Snow try: 240beb6b57bSJohn Snow os.remove(path) 241beb6b57bSJohn Snow except OSError as exception: 242beb6b57bSJohn Snow if exception.errno == errno.ENOENT: 243beb6b57bSJohn Snow return 244beb6b57bSJohn Snow raise 245beb6b57bSJohn Snow 246beb6b57bSJohn Snow def is_running(self) -> bool: 247beb6b57bSJohn Snow """Returns true if the VM is running.""" 248beb6b57bSJohn Snow return self._popen is not None and self._popen.poll() is None 249beb6b57bSJohn Snow 250beb6b57bSJohn Snow @property 251beb6b57bSJohn Snow def _subp(self) -> 'subprocess.Popen[bytes]': 252beb6b57bSJohn Snow if self._popen is None: 253beb6b57bSJohn Snow raise QEMUMachineError('Subprocess pipe not present') 254beb6b57bSJohn Snow return self._popen 255beb6b57bSJohn Snow 256beb6b57bSJohn Snow def exitcode(self) -> Optional[int]: 257beb6b57bSJohn Snow """Returns the exit code if possible, or None.""" 258beb6b57bSJohn Snow if self._popen is None: 259beb6b57bSJohn Snow return None 260beb6b57bSJohn Snow return self._popen.poll() 261beb6b57bSJohn Snow 262beb6b57bSJohn Snow def get_pid(self) -> Optional[int]: 263beb6b57bSJohn Snow """Returns the PID of the running process, or None.""" 264beb6b57bSJohn Snow if not self.is_running(): 265beb6b57bSJohn Snow return None 266beb6b57bSJohn Snow return self._subp.pid 267beb6b57bSJohn Snow 268beb6b57bSJohn Snow def _load_io_log(self) -> None: 2695690b437SJohn Snow # Assume that the output encoding of QEMU's terminal output is 2705690b437SJohn Snow # defined by our locale. If indeterminate, allow open() to fall 2715690b437SJohn Snow # back to the platform default. 2725690b437SJohn Snow _, encoding = locale.getlocale() 273beb6b57bSJohn Snow if self._qemu_log_path is not None: 2745690b437SJohn Snow with open(self._qemu_log_path, "r", encoding=encoding) as iolog: 275beb6b57bSJohn Snow self._iolog = iolog.read() 276beb6b57bSJohn Snow 277beb6b57bSJohn Snow @property 278beb6b57bSJohn Snow def _base_args(self) -> List[str]: 279beb6b57bSJohn Snow args = ['-display', 'none', '-vga', 'none'] 280beb6b57bSJohn Snow 281beb6b57bSJohn Snow if self._qmp_set: 282beb6b57bSJohn Snow if isinstance(self._monitor_address, tuple): 283beb6b57bSJohn Snow moncdev = "socket,id=mon,host={},port={}".format( 284beb6b57bSJohn Snow *self._monitor_address 285beb6b57bSJohn Snow ) 286beb6b57bSJohn Snow else: 287beb6b57bSJohn Snow moncdev = f"socket,id=mon,path={self._monitor_address}" 288beb6b57bSJohn Snow args.extend(['-chardev', moncdev, '-mon', 289beb6b57bSJohn Snow 'chardev=mon,mode=control']) 290beb6b57bSJohn Snow 291beb6b57bSJohn Snow if self._machine is not None: 292beb6b57bSJohn Snow args.extend(['-machine', self._machine]) 293beb6b57bSJohn Snow for _ in range(self._console_index): 294beb6b57bSJohn Snow args.extend(['-serial', 'null']) 295beb6b57bSJohn Snow if self._console_set: 296beb6b57bSJohn Snow chardev = ('socket,id=console,path=%s,server=on,wait=off' % 297beb6b57bSJohn Snow self._console_address) 298beb6b57bSJohn Snow args.extend(['-chardev', chardev]) 299beb6b57bSJohn Snow if self._console_device_type is None: 300beb6b57bSJohn Snow args.extend(['-serial', 'chardev:console']) 301beb6b57bSJohn Snow else: 302beb6b57bSJohn Snow device = '%s,chardev=console' % self._console_device_type 303beb6b57bSJohn Snow args.extend(['-device', device]) 304beb6b57bSJohn Snow return args 305beb6b57bSJohn Snow 306555fe0c2SWainer dos Santos Moschetta @property 307555fe0c2SWainer dos Santos Moschetta def args(self) -> List[str]: 308555fe0c2SWainer dos Santos Moschetta """Returns the list of arguments given to the QEMU binary.""" 309555fe0c2SWainer dos Santos Moschetta return self._args 310555fe0c2SWainer dos Santos Moschetta 311beb6b57bSJohn Snow def _pre_launch(self) -> None: 312beb6b57bSJohn Snow if self._console_set: 313beb6b57bSJohn Snow self._remove_files.append(self._console_address) 314beb6b57bSJohn Snow 315beb6b57bSJohn Snow if self._qmp_set: 3166eeb3de7SJohn Snow if isinstance(self._monitor_address, str): 317beb6b57bSJohn Snow self._remove_files.append(self._monitor_address) 318beb6b57bSJohn Snow self._qmp_connection = QEMUMonitorProtocol( 319beb6b57bSJohn Snow self._monitor_address, 320beb6b57bSJohn Snow server=True, 321beb6b57bSJohn Snow nickname=self._name 322beb6b57bSJohn Snow ) 323beb6b57bSJohn Snow 324beb6b57bSJohn Snow # NOTE: Make sure any opened resources are *definitely* freed in 325beb6b57bSJohn Snow # _post_shutdown()! 326beb6b57bSJohn Snow # pylint: disable=consider-using-with 327b306e26cSCleber Rosa self._qemu_log_path = os.path.join(self.log_dir, self._name + ".log") 328beb6b57bSJohn Snow self._qemu_log_file = open(self._qemu_log_path, 'wb') 329beb6b57bSJohn Snow 330beb6b57bSJohn Snow def _post_launch(self) -> None: 331beb6b57bSJohn Snow if self._qmp_connection: 332e2f948a8SEmanuele Giuseppe Esposito self._qmp.accept(self._qmp_timer) 333beb6b57bSJohn Snow 334eb7a91d0SEmanuele Giuseppe Esposito def _close_qemu_log_file(self) -> None: 335eb7a91d0SEmanuele Giuseppe Esposito if self._qemu_log_file is not None: 336eb7a91d0SEmanuele Giuseppe Esposito self._qemu_log_file.close() 337eb7a91d0SEmanuele Giuseppe Esposito self._qemu_log_file = None 338eb7a91d0SEmanuele Giuseppe Esposito 339beb6b57bSJohn Snow def _post_shutdown(self) -> None: 340beb6b57bSJohn Snow """ 341beb6b57bSJohn Snow Called to cleanup the VM instance after the process has exited. 342beb6b57bSJohn Snow May also be called after a failed launch. 343beb6b57bSJohn Snow """ 344beb6b57bSJohn Snow # Comprehensive reset for the failed launch case: 345beb6b57bSJohn Snow self._early_cleanup() 346beb6b57bSJohn Snow 34749a608b8SJohn Snow try: 34849a608b8SJohn Snow self._close_qmp_connection() 34949a608b8SJohn Snow except Exception as err: # pylint: disable=broad-except 35049a608b8SJohn Snow LOG.warning( 35149a608b8SJohn Snow "Exception closing QMP connection: %s", 35249a608b8SJohn Snow str(err) if str(err) else type(err).__name__ 35349a608b8SJohn Snow ) 35449a608b8SJohn Snow finally: 35549a608b8SJohn Snow assert self._qmp_connection is None 356beb6b57bSJohn Snow 357eb7a91d0SEmanuele Giuseppe Esposito self._close_qemu_log_file() 358beb6b57bSJohn Snow 359beb6b57bSJohn Snow self._load_io_log() 360beb6b57bSJohn Snow 361beb6b57bSJohn Snow self._qemu_log_path = None 362beb6b57bSJohn Snow 363beb6b57bSJohn Snow if self._temp_dir is not None: 364beb6b57bSJohn Snow shutil.rmtree(self._temp_dir) 365beb6b57bSJohn Snow self._temp_dir = None 366beb6b57bSJohn Snow 367beb6b57bSJohn Snow while len(self._remove_files) > 0: 368beb6b57bSJohn Snow self._remove_if_exists(self._remove_files.pop()) 369beb6b57bSJohn Snow 370beb6b57bSJohn Snow exitcode = self.exitcode() 371beb6b57bSJohn Snow if (exitcode is not None and exitcode < 0 372beb6b57bSJohn Snow and not (self._user_killed and exitcode == -signal.SIGKILL)): 373beb6b57bSJohn Snow msg = 'qemu received signal %i; command: "%s"' 374beb6b57bSJohn Snow if self._qemu_full_args: 375beb6b57bSJohn Snow command = ' '.join(self._qemu_full_args) 376beb6b57bSJohn Snow else: 377beb6b57bSJohn Snow command = '' 378beb6b57bSJohn Snow LOG.warning(msg, -int(exitcode), command) 379beb6b57bSJohn Snow 380b9420e4fSJohn Snow self._quit_issued = False 381beb6b57bSJohn Snow self._user_killed = False 382beb6b57bSJohn Snow self._launched = False 383beb6b57bSJohn Snow 384beb6b57bSJohn Snow def launch(self) -> None: 385beb6b57bSJohn Snow """ 386beb6b57bSJohn Snow Launch the VM and make sure we cleanup and expose the 387beb6b57bSJohn Snow command line/output in case of exception 388beb6b57bSJohn Snow """ 389beb6b57bSJohn Snow 390beb6b57bSJohn Snow if self._launched: 391beb6b57bSJohn Snow raise QEMUMachineError('VM already launched') 392beb6b57bSJohn Snow 393beb6b57bSJohn Snow self._iolog = None 394beb6b57bSJohn Snow self._qemu_full_args = () 395beb6b57bSJohn Snow try: 396beb6b57bSJohn Snow self._launch() 397beb6b57bSJohn Snow self._launched = True 398beb6b57bSJohn Snow except: 399beb6b57bSJohn Snow self._post_shutdown() 400beb6b57bSJohn Snow 401beb6b57bSJohn Snow LOG.debug('Error launching VM') 402beb6b57bSJohn Snow if self._qemu_full_args: 403beb6b57bSJohn Snow LOG.debug('Command: %r', ' '.join(self._qemu_full_args)) 404beb6b57bSJohn Snow if self._iolog: 405beb6b57bSJohn Snow LOG.debug('Output: %r', self._iolog) 406beb6b57bSJohn Snow raise 407beb6b57bSJohn Snow 408beb6b57bSJohn Snow def _launch(self) -> None: 409beb6b57bSJohn Snow """ 410beb6b57bSJohn Snow Launch the VM and establish a QMP connection 411beb6b57bSJohn Snow """ 412beb6b57bSJohn Snow self._pre_launch() 413beb6b57bSJohn Snow self._qemu_full_args = tuple( 414beb6b57bSJohn Snow chain(self._wrapper, 415beb6b57bSJohn Snow [self._binary], 416beb6b57bSJohn Snow self._base_args, 417beb6b57bSJohn Snow self._args) 418beb6b57bSJohn Snow ) 419beb6b57bSJohn Snow LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args)) 420beb6b57bSJohn Snow 421beb6b57bSJohn Snow # Cleaning up of this subprocess is guaranteed by _do_shutdown. 422beb6b57bSJohn Snow # pylint: disable=consider-using-with 423beb6b57bSJohn Snow self._popen = subprocess.Popen(self._qemu_full_args, 424beb6b57bSJohn Snow stdin=subprocess.DEVNULL, 425beb6b57bSJohn Snow stdout=self._qemu_log_file, 426beb6b57bSJohn Snow stderr=subprocess.STDOUT, 427beb6b57bSJohn Snow shell=False, 428beb6b57bSJohn Snow close_fds=False) 429beb6b57bSJohn Snow self._post_launch() 430beb6b57bSJohn Snow 43149a608b8SJohn Snow def _close_qmp_connection(self) -> None: 43249a608b8SJohn Snow """ 43349a608b8SJohn Snow Close the underlying QMP connection, if any. 43449a608b8SJohn Snow 43549a608b8SJohn Snow Dutifully report errors that occurred while closing, but assume 43649a608b8SJohn Snow that any error encountered indicates an abnormal termination 43749a608b8SJohn Snow process and not a failure to close. 43849a608b8SJohn Snow """ 43949a608b8SJohn Snow if self._qmp_connection is None: 44049a608b8SJohn Snow return 44149a608b8SJohn Snow 44249a608b8SJohn Snow try: 44349a608b8SJohn Snow self._qmp.close() 44449a608b8SJohn Snow except EOFError: 44549a608b8SJohn Snow # EOF can occur as an Exception here when using the Async 44649a608b8SJohn Snow # QMP backend. It indicates that the server closed the 44749a608b8SJohn Snow # stream. If we successfully issued 'quit' at any point, 44849a608b8SJohn Snow # then this was expected. If the remote went away without 44949a608b8SJohn Snow # our permission, it's worth reporting that as an abnormal 45049a608b8SJohn Snow # shutdown case. 45149a608b8SJohn Snow if not (self._user_killed or self._quit_issued): 45249a608b8SJohn Snow raise 45349a608b8SJohn Snow finally: 45449a608b8SJohn Snow self._qmp_connection = None 45549a608b8SJohn Snow 456beb6b57bSJohn Snow def _early_cleanup(self) -> None: 457beb6b57bSJohn Snow """ 458beb6b57bSJohn Snow Perform any cleanup that needs to happen before the VM exits. 459beb6b57bSJohn Snow 460beb6b57bSJohn Snow May be invoked by both soft and hard shutdown in failover scenarios. 461beb6b57bSJohn Snow Called additionally by _post_shutdown for comprehensive cleanup. 462beb6b57bSJohn Snow """ 463beb6b57bSJohn Snow # If we keep the console socket open, we may deadlock waiting 464beb6b57bSJohn Snow # for QEMU to exit, while QEMU is waiting for the socket to 465beb6b57bSJohn Snow # become writeable. 466beb6b57bSJohn Snow if self._console_socket is not None: 467beb6b57bSJohn Snow self._console_socket.close() 468beb6b57bSJohn Snow self._console_socket = None 469beb6b57bSJohn Snow 470beb6b57bSJohn Snow def _hard_shutdown(self) -> None: 471beb6b57bSJohn Snow """ 472beb6b57bSJohn Snow Perform early cleanup, kill the VM, and wait for it to terminate. 473beb6b57bSJohn Snow 474beb6b57bSJohn Snow :raise subprocess.Timeout: When timeout is exceeds 60 seconds 475beb6b57bSJohn Snow waiting for the QEMU process to terminate. 476beb6b57bSJohn Snow """ 477beb6b57bSJohn Snow self._early_cleanup() 478beb6b57bSJohn Snow self._subp.kill() 479beb6b57bSJohn Snow self._subp.wait(timeout=60) 480beb6b57bSJohn Snow 481b9420e4fSJohn Snow def _soft_shutdown(self, timeout: Optional[int]) -> None: 482beb6b57bSJohn Snow """ 483beb6b57bSJohn Snow Perform early cleanup, attempt to gracefully shut down the VM, and wait 484beb6b57bSJohn Snow for it to terminate. 485beb6b57bSJohn Snow 486beb6b57bSJohn Snow :param timeout: Timeout in seconds for graceful shutdown. 487beb6b57bSJohn Snow A value of None is an infinite wait. 488beb6b57bSJohn Snow 489beb6b57bSJohn Snow :raise ConnectionReset: On QMP communication errors 490beb6b57bSJohn Snow :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for 491beb6b57bSJohn Snow the QEMU process to terminate. 492beb6b57bSJohn Snow """ 493beb6b57bSJohn Snow self._early_cleanup() 494beb6b57bSJohn Snow 495beb6b57bSJohn Snow if self._qmp_connection: 49649a608b8SJohn Snow try: 497b9420e4fSJohn Snow if not self._quit_issued: 49849a608b8SJohn Snow # May raise ExecInterruptedError or StateError if the 49949a608b8SJohn Snow # connection dies or has *already* died. 500b9420e4fSJohn Snow self.qmp('quit') 50149a608b8SJohn Snow finally: 50249a608b8SJohn Snow # Regardless, we want to quiesce the connection. 50349a608b8SJohn Snow self._close_qmp_connection() 504beb6b57bSJohn Snow 505beb6b57bSJohn Snow # May raise subprocess.TimeoutExpired 506beb6b57bSJohn Snow self._subp.wait(timeout=timeout) 507beb6b57bSJohn Snow 508b9420e4fSJohn Snow def _do_shutdown(self, timeout: Optional[int]) -> None: 509beb6b57bSJohn Snow """ 510beb6b57bSJohn Snow Attempt to shutdown the VM gracefully; fallback to a hard shutdown. 511beb6b57bSJohn Snow 512beb6b57bSJohn Snow :param timeout: Timeout in seconds for graceful shutdown. 513beb6b57bSJohn Snow A value of None is an infinite wait. 514beb6b57bSJohn Snow 515beb6b57bSJohn Snow :raise AbnormalShutdown: When the VM could not be shut down gracefully. 516beb6b57bSJohn Snow The inner exception will likely be ConnectionReset or 517beb6b57bSJohn Snow subprocess.TimeoutExpired. In rare cases, non-graceful termination 518beb6b57bSJohn Snow may result in its own exceptions, likely subprocess.TimeoutExpired. 519beb6b57bSJohn Snow """ 520beb6b57bSJohn Snow try: 521b9420e4fSJohn Snow self._soft_shutdown(timeout) 522beb6b57bSJohn Snow except Exception as exc: 523beb6b57bSJohn Snow self._hard_shutdown() 524beb6b57bSJohn Snow raise AbnormalShutdown("Could not perform graceful shutdown") \ 525beb6b57bSJohn Snow from exc 526beb6b57bSJohn Snow 527b9420e4fSJohn Snow def shutdown(self, 528beb6b57bSJohn Snow hard: bool = False, 529beb6b57bSJohn Snow timeout: Optional[int] = 30) -> None: 530beb6b57bSJohn Snow """ 531beb6b57bSJohn Snow Terminate the VM (gracefully if possible) and perform cleanup. 532beb6b57bSJohn Snow Cleanup will always be performed. 533beb6b57bSJohn Snow 534beb6b57bSJohn Snow If the VM has not yet been launched, or shutdown(), wait(), or kill() 535beb6b57bSJohn Snow have already been called, this method does nothing. 536beb6b57bSJohn Snow 537beb6b57bSJohn Snow :param hard: When true, do not attempt graceful shutdown, and 538beb6b57bSJohn Snow suppress the SIGKILL warning log message. 539beb6b57bSJohn Snow :param timeout: Optional timeout in seconds for graceful shutdown. 540beb6b57bSJohn Snow Default 30 seconds, A `None` value is an infinite wait. 541beb6b57bSJohn Snow """ 542beb6b57bSJohn Snow if not self._launched: 543beb6b57bSJohn Snow return 544beb6b57bSJohn Snow 545beb6b57bSJohn Snow try: 546beb6b57bSJohn Snow if hard: 547beb6b57bSJohn Snow self._user_killed = True 548beb6b57bSJohn Snow self._hard_shutdown() 549beb6b57bSJohn Snow else: 550b9420e4fSJohn Snow self._do_shutdown(timeout) 551beb6b57bSJohn Snow finally: 552beb6b57bSJohn Snow self._post_shutdown() 553beb6b57bSJohn Snow 554beb6b57bSJohn Snow def kill(self) -> None: 555beb6b57bSJohn Snow """ 556beb6b57bSJohn Snow Terminate the VM forcefully, wait for it to exit, and perform cleanup. 557beb6b57bSJohn Snow """ 558beb6b57bSJohn Snow self.shutdown(hard=True) 559beb6b57bSJohn Snow 560beb6b57bSJohn Snow def wait(self, timeout: Optional[int] = 30) -> None: 561beb6b57bSJohn Snow """ 562beb6b57bSJohn Snow Wait for the VM to power off and perform post-shutdown cleanup. 563beb6b57bSJohn Snow 564beb6b57bSJohn Snow :param timeout: Optional timeout in seconds. Default 30 seconds. 565beb6b57bSJohn Snow A value of `None` is an infinite wait. 566beb6b57bSJohn Snow """ 567b9420e4fSJohn Snow self._quit_issued = True 568b9420e4fSJohn Snow self.shutdown(timeout=timeout) 569beb6b57bSJohn Snow 570beb6b57bSJohn Snow def set_qmp_monitor(self, enabled: bool = True) -> None: 571beb6b57bSJohn Snow """ 572beb6b57bSJohn Snow Set the QMP monitor. 573beb6b57bSJohn Snow 574beb6b57bSJohn Snow @param enabled: if False, qmp monitor options will be removed from 575beb6b57bSJohn Snow the base arguments of the resulting QEMU command 576beb6b57bSJohn Snow line. Default is True. 5775c02c865SJohn Snow 5785c02c865SJohn Snow .. note:: Call this function before launch(). 579beb6b57bSJohn Snow """ 580beb6b57bSJohn Snow self._qmp_set = enabled 581beb6b57bSJohn Snow 582beb6b57bSJohn Snow @property 583beb6b57bSJohn Snow def _qmp(self) -> QEMUMonitorProtocol: 584beb6b57bSJohn Snow if self._qmp_connection is None: 585beb6b57bSJohn Snow raise QEMUMachineError("Attempt to access QMP with no connection") 586beb6b57bSJohn Snow return self._qmp_connection 587beb6b57bSJohn Snow 588beb6b57bSJohn Snow @classmethod 589c7daa57eSVladimir Sementsov-Ogievskiy def _qmp_args(cls, conv_keys: bool, 590c7daa57eSVladimir Sementsov-Ogievskiy args: Dict[str, Any]) -> Dict[str, object]: 591c7daa57eSVladimir Sementsov-Ogievskiy if conv_keys: 592c7daa57eSVladimir Sementsov-Ogievskiy return {k.replace('_', '-'): v for k, v in args.items()} 593c7daa57eSVladimir Sementsov-Ogievskiy 594c7daa57eSVladimir Sementsov-Ogievskiy return args 595beb6b57bSJohn Snow 596beb6b57bSJohn Snow def qmp(self, cmd: str, 5973f3c9b4cSVladimir Sementsov-Ogievskiy args_dict: Optional[Dict[str, object]] = None, 5983f3c9b4cSVladimir Sementsov-Ogievskiy conv_keys: Optional[bool] = None, 599beb6b57bSJohn Snow **args: Any) -> QMPMessage: 600beb6b57bSJohn Snow """ 601beb6b57bSJohn Snow Invoke a QMP command and return the response dict 602beb6b57bSJohn Snow """ 6033f3c9b4cSVladimir Sementsov-Ogievskiy if args_dict is not None: 6043f3c9b4cSVladimir Sementsov-Ogievskiy assert not args 6053f3c9b4cSVladimir Sementsov-Ogievskiy assert conv_keys is None 6063f3c9b4cSVladimir Sementsov-Ogievskiy args = args_dict 6073f3c9b4cSVladimir Sementsov-Ogievskiy conv_keys = False 6083f3c9b4cSVladimir Sementsov-Ogievskiy 6093f3c9b4cSVladimir Sementsov-Ogievskiy if conv_keys is None: 6103f3c9b4cSVladimir Sementsov-Ogievskiy conv_keys = True 6113f3c9b4cSVladimir Sementsov-Ogievskiy 612c7daa57eSVladimir Sementsov-Ogievskiy qmp_args = self._qmp_args(conv_keys, args) 613b9420e4fSJohn Snow ret = self._qmp.cmd(cmd, args=qmp_args) 614b9420e4fSJohn Snow if cmd == 'quit' and 'error' not in ret and 'return' in ret: 615b9420e4fSJohn Snow self._quit_issued = True 616b9420e4fSJohn Snow return ret 617beb6b57bSJohn Snow 618beb6b57bSJohn Snow def command(self, cmd: str, 619beb6b57bSJohn Snow conv_keys: bool = True, 620beb6b57bSJohn Snow **args: Any) -> QMPReturnValue: 621beb6b57bSJohn Snow """ 622beb6b57bSJohn Snow Invoke a QMP command. 623beb6b57bSJohn Snow On success return the response dict. 624beb6b57bSJohn Snow On failure raise an exception. 625beb6b57bSJohn Snow """ 626c7daa57eSVladimir Sementsov-Ogievskiy qmp_args = self._qmp_args(conv_keys, args) 627b9420e4fSJohn Snow ret = self._qmp.command(cmd, **qmp_args) 628b9420e4fSJohn Snow if cmd == 'quit': 629b9420e4fSJohn Snow self._quit_issued = True 630b9420e4fSJohn Snow return ret 631beb6b57bSJohn Snow 632beb6b57bSJohn Snow def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]: 633beb6b57bSJohn Snow """ 634beb6b57bSJohn Snow Poll for one queued QMP events and return it 635beb6b57bSJohn Snow """ 636beb6b57bSJohn Snow if self._events: 637beb6b57bSJohn Snow return self._events.pop(0) 638beb6b57bSJohn Snow return self._qmp.pull_event(wait=wait) 639beb6b57bSJohn Snow 640beb6b57bSJohn Snow def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]: 641beb6b57bSJohn Snow """ 642beb6b57bSJohn Snow Poll for queued QMP events and return a list of dicts 643beb6b57bSJohn Snow """ 644beb6b57bSJohn Snow events = self._qmp.get_events(wait=wait) 645beb6b57bSJohn Snow events.extend(self._events) 646beb6b57bSJohn Snow del self._events[:] 647beb6b57bSJohn Snow return events 648beb6b57bSJohn Snow 649beb6b57bSJohn Snow @staticmethod 650beb6b57bSJohn Snow def event_match(event: Any, match: Optional[Any]) -> bool: 651beb6b57bSJohn Snow """ 652beb6b57bSJohn Snow Check if an event matches optional match criteria. 653beb6b57bSJohn Snow 654beb6b57bSJohn Snow The match criteria takes the form of a matching subdict. The event is 655beb6b57bSJohn Snow checked to be a superset of the subdict, recursively, with matching 656beb6b57bSJohn Snow values whenever the subdict values are not None. 657beb6b57bSJohn Snow 658beb6b57bSJohn Snow This has a limitation that you cannot explicitly check for None values. 659beb6b57bSJohn Snow 660beb6b57bSJohn Snow Examples, with the subdict queries on the left: 661beb6b57bSJohn Snow - None matches any object. 662beb6b57bSJohn Snow - {"foo": None} matches {"foo": {"bar": 1}} 663beb6b57bSJohn Snow - {"foo": None} matches {"foo": 5} 664beb6b57bSJohn Snow - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}} 665beb6b57bSJohn Snow - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}} 666beb6b57bSJohn Snow """ 667beb6b57bSJohn Snow if match is None: 668beb6b57bSJohn Snow return True 669beb6b57bSJohn Snow 670beb6b57bSJohn Snow try: 671beb6b57bSJohn Snow for key in match: 672beb6b57bSJohn Snow if key in event: 673beb6b57bSJohn Snow if not QEMUMachine.event_match(event[key], match[key]): 674beb6b57bSJohn Snow return False 675beb6b57bSJohn Snow else: 676beb6b57bSJohn Snow return False 677beb6b57bSJohn Snow return True 678beb6b57bSJohn Snow except TypeError: 679beb6b57bSJohn Snow # either match or event wasn't iterable (not a dict) 680beb6b57bSJohn Snow return bool(match == event) 681beb6b57bSJohn Snow 682beb6b57bSJohn Snow def event_wait(self, name: str, 683beb6b57bSJohn Snow timeout: float = 60.0, 684beb6b57bSJohn Snow match: Optional[QMPMessage] = None) -> Optional[QMPMessage]: 685beb6b57bSJohn Snow """ 686beb6b57bSJohn Snow event_wait waits for and returns a named event from QMP with a timeout. 687beb6b57bSJohn Snow 688beb6b57bSJohn Snow name: The event to wait for. 689beb6b57bSJohn Snow timeout: QEMUMonitorProtocol.pull_event timeout parameter. 690beb6b57bSJohn Snow match: Optional match criteria. See event_match for details. 691beb6b57bSJohn Snow """ 692beb6b57bSJohn Snow return self.events_wait([(name, match)], timeout) 693beb6b57bSJohn Snow 694beb6b57bSJohn Snow def events_wait(self, 695beb6b57bSJohn Snow events: Sequence[Tuple[str, Any]], 696beb6b57bSJohn Snow timeout: float = 60.0) -> Optional[QMPMessage]: 697beb6b57bSJohn Snow """ 698beb6b57bSJohn Snow events_wait waits for and returns a single named event from QMP. 699beb6b57bSJohn Snow In the case of multiple qualifying events, this function returns the 700beb6b57bSJohn Snow first one. 701beb6b57bSJohn Snow 702beb6b57bSJohn Snow :param events: A sequence of (name, match_criteria) tuples. 703beb6b57bSJohn Snow The match criteria are optional and may be None. 704beb6b57bSJohn Snow See event_match for details. 705beb6b57bSJohn Snow :param timeout: Optional timeout, in seconds. 706beb6b57bSJohn Snow See QEMUMonitorProtocol.pull_event. 707beb6b57bSJohn Snow 708beb6b57bSJohn Snow :raise QMPTimeoutError: If timeout was non-zero and no matching events 709beb6b57bSJohn Snow were found. 710beb6b57bSJohn Snow :return: A QMP event matching the filter criteria. 711beb6b57bSJohn Snow If timeout was 0 and no event matched, None. 712beb6b57bSJohn Snow """ 713beb6b57bSJohn Snow def _match(event: QMPMessage) -> bool: 714beb6b57bSJohn Snow for name, match in events: 715beb6b57bSJohn Snow if event['event'] == name and self.event_match(event, match): 716beb6b57bSJohn Snow return True 717beb6b57bSJohn Snow return False 718beb6b57bSJohn Snow 719beb6b57bSJohn Snow event: Optional[QMPMessage] 720beb6b57bSJohn Snow 721beb6b57bSJohn Snow # Search cached events 722beb6b57bSJohn Snow for event in self._events: 723beb6b57bSJohn Snow if _match(event): 724beb6b57bSJohn Snow self._events.remove(event) 725beb6b57bSJohn Snow return event 726beb6b57bSJohn Snow 727beb6b57bSJohn Snow # Poll for new events 728beb6b57bSJohn Snow while True: 729beb6b57bSJohn Snow event = self._qmp.pull_event(wait=timeout) 730beb6b57bSJohn Snow if event is None: 731beb6b57bSJohn Snow # NB: None is only returned when timeout is false-ish. 732beb6b57bSJohn Snow # Timeouts raise QMPTimeoutError instead! 733beb6b57bSJohn Snow break 734beb6b57bSJohn Snow if _match(event): 735beb6b57bSJohn Snow return event 736beb6b57bSJohn Snow self._events.append(event) 737beb6b57bSJohn Snow 738beb6b57bSJohn Snow return None 739beb6b57bSJohn Snow 740beb6b57bSJohn Snow def get_log(self) -> Optional[str]: 741beb6b57bSJohn Snow """ 742beb6b57bSJohn Snow After self.shutdown or failed qemu execution, this returns the output 743beb6b57bSJohn Snow of the qemu process. 744beb6b57bSJohn Snow """ 745beb6b57bSJohn Snow return self._iolog 746beb6b57bSJohn Snow 747beb6b57bSJohn Snow def add_args(self, *args: str) -> None: 748beb6b57bSJohn Snow """ 749beb6b57bSJohn Snow Adds to the list of extra arguments to be given to the QEMU binary 750beb6b57bSJohn Snow """ 751beb6b57bSJohn Snow self._args.extend(args) 752beb6b57bSJohn Snow 753beb6b57bSJohn Snow def set_machine(self, machine_type: str) -> None: 754beb6b57bSJohn Snow """ 755beb6b57bSJohn Snow Sets the machine type 756beb6b57bSJohn Snow 757beb6b57bSJohn Snow If set, the machine type will be added to the base arguments 758beb6b57bSJohn Snow of the resulting QEMU command line. 759beb6b57bSJohn Snow """ 760beb6b57bSJohn Snow self._machine = machine_type 761beb6b57bSJohn Snow 762beb6b57bSJohn Snow def set_console(self, 763beb6b57bSJohn Snow device_type: Optional[str] = None, 764beb6b57bSJohn Snow console_index: int = 0) -> None: 765beb6b57bSJohn Snow """ 766beb6b57bSJohn Snow Sets the device type for a console device 767beb6b57bSJohn Snow 768beb6b57bSJohn Snow If set, the console device and a backing character device will 769beb6b57bSJohn Snow be added to the base arguments of the resulting QEMU command 770beb6b57bSJohn Snow line. 771beb6b57bSJohn Snow 772beb6b57bSJohn Snow This is a convenience method that will either use the provided 773beb6b57bSJohn Snow device type, or default to a "-serial chardev:console" command 774beb6b57bSJohn Snow line argument. 775beb6b57bSJohn Snow 776beb6b57bSJohn Snow The actual setting of command line arguments will be be done at 777beb6b57bSJohn Snow machine launch time, as it depends on the temporary directory 778beb6b57bSJohn Snow to be created. 779beb6b57bSJohn Snow 780beb6b57bSJohn Snow @param device_type: the device type, such as "isa-serial". If 781beb6b57bSJohn Snow None is given (the default value) a "-serial 782beb6b57bSJohn Snow chardev:console" command line argument will 783beb6b57bSJohn Snow be used instead, resorting to the machine's 784beb6b57bSJohn Snow default device type. 785beb6b57bSJohn Snow @param console_index: the index of the console device to use. 786beb6b57bSJohn Snow If not zero, the command line will create 787beb6b57bSJohn Snow 'index - 1' consoles and connect them to 788beb6b57bSJohn Snow the 'null' backing character device. 789beb6b57bSJohn Snow """ 790beb6b57bSJohn Snow self._console_set = True 791beb6b57bSJohn Snow self._console_device_type = device_type 792beb6b57bSJohn Snow self._console_index = console_index 793beb6b57bSJohn Snow 794beb6b57bSJohn Snow @property 795beb6b57bSJohn Snow def console_socket(self) -> socket.socket: 796beb6b57bSJohn Snow """ 797beb6b57bSJohn Snow Returns a socket connected to the console 798beb6b57bSJohn Snow """ 799beb6b57bSJohn Snow if self._console_socket is None: 800beb6b57bSJohn Snow self._console_socket = console_socket.ConsoleSocket( 801beb6b57bSJohn Snow self._console_address, 802beb6b57bSJohn Snow file=self._console_log_path, 803beb6b57bSJohn Snow drain=self._drain_console) 804beb6b57bSJohn Snow return self._console_socket 805beb6b57bSJohn Snow 806beb6b57bSJohn Snow @property 807beb6b57bSJohn Snow def temp_dir(self) -> str: 808beb6b57bSJohn Snow """ 809beb6b57bSJohn Snow Returns a temporary directory to be used for this machine 810beb6b57bSJohn Snow """ 811beb6b57bSJohn Snow if self._temp_dir is None: 812beb6b57bSJohn Snow self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-", 813beb6b57bSJohn Snow dir=self._base_temp_dir) 814beb6b57bSJohn Snow return self._temp_dir 815b306e26cSCleber Rosa 816b306e26cSCleber Rosa @property 81787bf1fe5SJohn Snow def sock_dir(self) -> str: 81887bf1fe5SJohn Snow """ 81987bf1fe5SJohn Snow Returns the directory used for sockfiles by this machine. 82087bf1fe5SJohn Snow """ 82187bf1fe5SJohn Snow if self._sock_dir: 82287bf1fe5SJohn Snow return self._sock_dir 82387bf1fe5SJohn Snow return self.temp_dir 82487bf1fe5SJohn Snow 82587bf1fe5SJohn Snow @property 826b306e26cSCleber Rosa def log_dir(self) -> str: 827b306e26cSCleber Rosa """ 828b306e26cSCleber Rosa Returns a directory to be used for writing logs 829b306e26cSCleber Rosa """ 830b306e26cSCleber Rosa if self._log_dir is None: 831b306e26cSCleber Rosa return self.temp_dir 832b306e26cSCleber Rosa return self._log_dir 833