blob: ef9b2dc02ee3a55a3ecdcb6a5ae0e69fa2355635 [file] [log] [blame]
John Snow306dfcd2019-06-27 17:28:15 -04001"""
2QEMU machine module:
3
4The machine module primarily provides the QEMUMachine class,
5which provides facilities for managing the lifetime of a QEMU VM.
6"""
7
John Snowabf0bf92019-06-27 17:28:14 -04008# Copyright (C) 2015-2016 Red Hat Inc.
9# Copyright (C) 2012 IBM Corp.
10#
11# Authors:
12# Fam Zheng <famz@redhat.com>
13#
14# This work is licensed under the terms of the GNU GPL, version 2. See
15# the COPYING file in the top-level directory.
16#
17# Based on qmp.py.
18#
19
20import errno
John Snowaad3f3b2020-10-06 19:58:06 -040021from itertools import chain
John Snow5690b432021-09-16 14:22:47 -040022import locale
John Snowabf0bf92019-06-27 17:28:14 -040023import logging
24import os
John Snowabf0bf92019-06-27 17:28:14 -040025import shutil
John Snowde6e08b2020-07-10 01:06:48 -040026import signal
John Snowf12a2822020-10-06 19:58:08 -040027import socket
John Snow932ca4b2020-10-06 19:57:58 -040028import subprocess
John Snowabf0bf92019-06-27 17:28:14 -040029import tempfile
John Snow1dda0402020-05-14 01:53:44 -040030from types import TracebackType
John Snowaaa81ec2020-10-06 19:58:03 -040031from typing import (
32 Any,
John Snowf12a2822020-10-06 19:58:08 -040033 BinaryIO,
John Snowaaa81ec2020-10-06 19:58:03 -040034 Dict,
35 List,
36 Optional,
John Snowaad3f3b2020-10-06 19:58:06 -040037 Sequence,
38 Tuple,
John Snowaaa81ec2020-10-06 19:58:03 -040039 Type,
Vladimir Sementsov-Ogievskiy15c3b862021-08-24 11:38:47 +030040 TypeVar,
John Snowaaa81ec2020-10-06 19:58:03 -040041)
John Snowabf0bf92019-06-27 17:28:14 -040042
John Snow37094b62022-03-30 13:28:10 -040043from qemu.qmp import SocketAddrT
44from qemu.qmp.legacy import (
John Snowa4225302022-03-21 16:33:12 -040045 QEMUMonitorProtocol,
John Snowbeb6b572021-05-27 17:16:53 -040046 QMPMessage,
47 QMPReturnValue,
John Snowbeb6b572021-05-27 17:16:53 -040048)
49
50from . import console_socket
John Snow932ca4b2020-10-06 19:57:58 -040051
John Snowabf0bf92019-06-27 17:28:14 -040052
53LOG = logging.getLogger(__name__)
54
John Snow8dfac2e2020-05-28 18:21:29 -040055
John Snowabf0bf92019-06-27 17:28:14 -040056class QEMUMachineError(Exception):
57 """
58 Exception called when an error in QEMUMachine happens.
59 """
60
61
62class QEMUMachineAddDeviceError(QEMUMachineError):
63 """
64 Exception raised when a request to add a device can not be fulfilled
65
66 The failures are caused by limitations, lack of information or conflicting
67 requests on the QEMUMachine methods. This exception does not represent
68 failures reported by the QEMU binary itself.
69 """
70
71
John Snow50465f92022-01-31 23:11:32 -050072class VMLaunchFailure(QEMUMachineError):
73 """
74 Exception raised when a VM launch was attempted, but failed.
75 """
76 def __init__(self, exitcode: Optional[int],
77 command: str, output: Optional[str]):
78 super().__init__(exitcode, command, output)
79 self.exitcode = exitcode
80 self.command = command
81 self.output = output
82
83 def __str__(self) -> str:
84 ret = ''
85 if self.__cause__ is not None:
86 name = type(self.__cause__).__name__
87 reason = str(self.__cause__)
88 if reason:
89 ret += f"{name}: {reason}"
90 else:
91 ret += f"{name}"
92 ret += '\n'
93
94 if self.exitcode is not None:
95 ret += f"\tExit code: {self.exitcode}\n"
96 ret += f"\tCommand: {self.command}\n"
97 ret += f"\tOutput: {self.output}\n"
98 return ret
99
100
John Snow193bf1c2020-07-10 01:06:47 -0400101class AbnormalShutdown(QEMUMachineError):
102 """
103 Exception raised when a graceful shutdown was requested, but not performed.
104 """
105
106
Vladimir Sementsov-Ogievskiy15c3b862021-08-24 11:38:47 +0300107_T = TypeVar('_T', bound='QEMUMachine')
108
109
John Snow9b8ccd62020-05-28 18:21:28 -0400110class QEMUMachine:
John Snowabf0bf92019-06-27 17:28:14 -0400111 """
John Snowf12a2822020-10-06 19:58:08 -0400112 A QEMU VM.
John Snowabf0bf92019-06-27 17:28:14 -0400113
John Snow8dfac2e2020-05-28 18:21:29 -0400114 Use this object as a context manager to ensure
115 the QEMU process terminates::
John Snowabf0bf92019-06-27 17:28:14 -0400116
117 with VM(binary) as vm:
118 ...
119 # vm is guaranteed to be shut down here
120 """
John Snow82e65172021-06-29 17:43:11 -0400121 # pylint: disable=too-many-instance-attributes, too-many-public-methods
John Snowabf0bf92019-06-27 17:28:14 -0400122
John Snowaad3f3b2020-10-06 19:58:06 -0400123 def __init__(self,
124 binary: str,
125 args: Sequence[str] = (),
126 wrapper: Sequence[str] = (),
127 name: Optional[str] = None,
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500128 base_temp_dir: str = "/var/tmp",
John Snowc4e60232020-10-06 19:57:59 -0400129 monitor_address: Optional[SocketAddrT] = None,
John Snowf12a2822020-10-06 19:58:08 -0400130 sock_dir: Optional[str] = None,
131 drain_console: bool = False,
Cleber Rosab306e262021-02-11 16:55:05 -0500132 console_log: Optional[str] = None,
Emanuele Giuseppe Espositoe2f948a2021-08-09 11:00:59 +0200133 log_dir: Optional[str] = None,
Vladimir Sementsov-Ogievskiyada73a42022-06-24 22:52:52 +0300134 qmp_timer: Optional[float] = 30):
John Snowabf0bf92019-06-27 17:28:14 -0400135 '''
136 Initialize a QEMUMachine
137
138 @param binary: path to the qemu binary
139 @param args: list of extra arguments
140 @param wrapper: list of arguments used as prefix to qemu binary
141 @param name: prefix for socket and log file names (default: qemu-PID)
John Snow859aeb62021-05-27 17:16:51 -0400142 @param base_temp_dir: default location where temp files are created
John Snowabf0bf92019-06-27 17:28:14 -0400143 @param monitor_address: address for QMP monitor
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500144 @param sock_dir: where to create socket (defaults to base_temp_dir)
Robert Foley0fc8f662020-07-01 14:56:24 +0100145 @param drain_console: (optional) True to drain console socket to buffer
John Snowc5e61a62020-10-06 19:58:00 -0400146 @param console_log: (optional) path to console log file
Cleber Rosab306e262021-02-11 16:55:05 -0500147 @param log_dir: where to create and keep log files
Emanuele Giuseppe Espositoe2f948a2021-08-09 11:00:59 +0200148 @param qmp_timer: (optional) default QMP socket timeout
John Snowabf0bf92019-06-27 17:28:14 -0400149 @note: Qemu process is not started until launch() is used.
150 '''
John Snow82e65172021-06-29 17:43:11 -0400151 # pylint: disable=too-many-arguments
152
John Snowc5e61a62020-10-06 19:58:00 -0400153 # Direct user configuration
154
155 self._binary = binary
John Snowc5e61a62020-10-06 19:58:00 -0400156 self._args = list(args)
John Snowc5e61a62020-10-06 19:58:00 -0400157 self._wrapper = wrapper
Emanuele Giuseppe Espositoe2f948a2021-08-09 11:00:59 +0200158 self._qmp_timer = qmp_timer
John Snowc5e61a62020-10-06 19:58:00 -0400159
Peter Delevoryasf9922932023-01-10 00:29:30 -0800160 self._name = name or f"{id(self):x}"
Marc-André Lureaubd4c0ef2023-01-11 12:01:01 +0400161 self._sock_pair: Optional[Tuple[socket.socket, socket.socket]] = None
John Snoweb55fae2023-07-20 09:04:47 -0400162 self._cons_sock_pair: Optional[
163 Tuple[socket.socket, socket.socket]] = None
John Snow87bf1fe2021-11-18 15:46:14 -0500164 self._temp_dir: Optional[str] = None
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500165 self._base_temp_dir = base_temp_dir
John Snow87bf1fe2021-11-18 15:46:14 -0500166 self._sock_dir = sock_dir
Cleber Rosab306e262021-02-11 16:55:05 -0500167 self._log_dir = log_dir
John Snowc5e61a62020-10-06 19:58:00 -0400168
Marc-André Lureaubd4c0ef2023-01-11 12:01:01 +0400169 self._monitor_address = monitor_address
John Snowc5e61a62020-10-06 19:58:00 -0400170
171 self._console_log_path = console_log
172 if self._console_log_path:
173 # In order to log the console, buffering needs to be enabled.
174 self._drain_console = True
175 else:
176 self._drain_console = drain_console
177
178 # Runstate
John Snowf12a2822020-10-06 19:58:08 -0400179 self._qemu_log_path: Optional[str] = None
180 self._qemu_log_file: Optional[BinaryIO] = None
John Snow9223fda2020-10-06 19:58:05 -0400181 self._popen: Optional['subprocess.Popen[bytes]'] = None
John Snowf12a2822020-10-06 19:58:08 -0400182 self._events: List[QMPMessage] = []
183 self._iolog: Optional[str] = None
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500184 self._qmp_set = True # Enable QMP monitor by default.
John Snowbeb6b572021-05-27 17:16:53 -0400185 self._qmp_connection: Optional[QEMUMonitorProtocol] = None
John Snowaad3f3b2020-10-06 19:58:06 -0400186 self._qemu_full_args: Tuple[str, ...] = ()
John Snowabf0bf92019-06-27 17:28:14 -0400187 self._launched = False
John Snowf12a2822020-10-06 19:58:08 -0400188 self._machine: Optional[str] = None
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100189 self._console_index = 0
John Snowabf0bf92019-06-27 17:28:14 -0400190 self._console_set = False
John Snowf12a2822020-10-06 19:58:08 -0400191 self._console_device_type: Optional[str] = None
John Snow652809d2020-10-06 19:58:01 -0400192 self._console_address = os.path.join(
Peter Delevoryasf9922932023-01-10 00:29:30 -0800193 self.sock_dir, f"{self._name}.con"
John Snow652809d2020-10-06 19:58:01 -0400194 )
John Snowf12a2822020-10-06 19:58:08 -0400195 self._console_socket: Optional[socket.socket] = None
196 self._remove_files: List[str] = []
John Snowde6e08b2020-07-10 01:06:48 -0400197 self._user_killed = False
John Snowb9420e42021-10-26 13:56:05 -0400198 self._quit_issued = False
John Snowabf0bf92019-06-27 17:28:14 -0400199
Vladimir Sementsov-Ogievskiy15c3b862021-08-24 11:38:47 +0300200 def __enter__(self: _T) -> _T:
John Snowabf0bf92019-06-27 17:28:14 -0400201 return self
202
John Snow1dda0402020-05-14 01:53:44 -0400203 def __exit__(self,
204 exc_type: Optional[Type[BaseException]],
205 exc_val: Optional[BaseException],
206 exc_tb: Optional[TracebackType]) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400207 self.shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400208
John Snowf12a2822020-10-06 19:58:08 -0400209 def add_monitor_null(self) -> None:
John Snow306dfcd2019-06-27 17:28:15 -0400210 """
211 This can be used to add an unused monitor instance.
212 """
John Snowabf0bf92019-06-27 17:28:14 -0400213 self._args.append('-monitor')
214 self._args.append('null')
215
Vladimir Sementsov-Ogievskiy15c3b862021-08-24 11:38:47 +0300216 def add_fd(self: _T, fd: int, fdset: int,
217 opaque: str, opts: str = '') -> _T:
John Snowabf0bf92019-06-27 17:28:14 -0400218 """
219 Pass a file descriptor to the VM
220 """
221 options = ['fd=%d' % fd,
222 'set=%d' % fdset,
223 'opaque=%s' % opaque]
224 if opts:
225 options.append(opts)
226
227 # This did not exist before 3.4, but since then it is
228 # mandatory for our purpose
229 if hasattr(os, 'set_inheritable'):
230 os.set_inheritable(fd, True)
231
232 self._args.append('-add-fd')
233 self._args.append(','.join(options))
234 return self
235
John Snowf12a2822020-10-06 19:58:08 -0400236 def send_fd_scm(self, fd: Optional[int] = None,
237 file_path: Optional[str] = None) -> int:
John Snow306dfcd2019-06-27 17:28:15 -0400238 """
John Snow514d00d2021-09-22 20:49:30 -0400239 Send an fd or file_path to the remote via SCM_RIGHTS.
John Snow306dfcd2019-06-27 17:28:15 -0400240
John Snow514d00d2021-09-22 20:49:30 -0400241 Exactly one of fd and file_path must be given. If it is
242 file_path, the file will be opened read-only and the new file
243 descriptor will be sent to the remote.
John Snow306dfcd2019-06-27 17:28:15 -0400244 """
John Snowabf0bf92019-06-27 17:28:14 -0400245 if file_path is not None:
246 assert fd is None
John Snow514d00d2021-09-22 20:49:30 -0400247 with open(file_path, "rb") as passfile:
248 fd = passfile.fileno()
249 self._qmp.send_fd_scm(fd)
John Snowabf0bf92019-06-27 17:28:14 -0400250 else:
251 assert fd is not None
John Snow514d00d2021-09-22 20:49:30 -0400252 self._qmp.send_fd_scm(fd)
John Snowabf0bf92019-06-27 17:28:14 -0400253
John Snow514d00d2021-09-22 20:49:30 -0400254 return 0
John Snowabf0bf92019-06-27 17:28:14 -0400255
256 @staticmethod
John Snowf12a2822020-10-06 19:58:08 -0400257 def _remove_if_exists(path: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400258 """
259 Remove file object at path if it exists
260 """
261 try:
262 os.remove(path)
263 except OSError as exception:
264 if exception.errno == errno.ENOENT:
265 return
266 raise
267
John Snowf12a2822020-10-06 19:58:08 -0400268 def is_running(self) -> bool:
John Snow306dfcd2019-06-27 17:28:15 -0400269 """Returns true if the VM is running."""
John Snowabf0bf92019-06-27 17:28:14 -0400270 return self._popen is not None and self._popen.poll() is None
271
John Snow9223fda2020-10-06 19:58:05 -0400272 @property
273 def _subp(self) -> 'subprocess.Popen[bytes]':
274 if self._popen is None:
275 raise QEMUMachineError('Subprocess pipe not present')
276 return self._popen
277
John Snowf12a2822020-10-06 19:58:08 -0400278 def exitcode(self) -> Optional[int]:
John Snow306dfcd2019-06-27 17:28:15 -0400279 """Returns the exit code if possible, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400280 if self._popen is None:
281 return None
282 return self._popen.poll()
283
John Snowf12a2822020-10-06 19:58:08 -0400284 def get_pid(self) -> Optional[int]:
John Snow306dfcd2019-06-27 17:28:15 -0400285 """Returns the PID of the running process, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400286 if not self.is_running():
287 return None
John Snow9223fda2020-10-06 19:58:05 -0400288 return self._subp.pid
John Snowabf0bf92019-06-27 17:28:14 -0400289
John Snowf12a2822020-10-06 19:58:08 -0400290 def _load_io_log(self) -> None:
John Snow5690b432021-09-16 14:22:47 -0400291 # Assume that the output encoding of QEMU's terminal output is
292 # defined by our locale. If indeterminate, allow open() to fall
293 # back to the platform default.
294 _, encoding = locale.getlocale()
John Snowabf0bf92019-06-27 17:28:14 -0400295 if self._qemu_log_path is not None:
John Snow5690b432021-09-16 14:22:47 -0400296 with open(self._qemu_log_path, "r", encoding=encoding) as iolog:
John Snowabf0bf92019-06-27 17:28:14 -0400297 self._iolog = iolog.read()
298
John Snow652809d2020-10-06 19:58:01 -0400299 @property
300 def _base_args(self) -> List[str]:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500301 args = ['-display', 'none', '-vga', 'none']
John Snowc4e60232020-10-06 19:57:59 -0400302
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500303 if self._qmp_set:
Marc-André Lureaubd4c0ef2023-01-11 12:01:01 +0400304 if self._sock_pair:
John Snowfe9ce0b2023-07-20 09:04:45 -0400305 moncdev = f"socket,id=mon,fd={self._sock_pair[0].fileno()}"
Marc-André Lureaubd4c0ef2023-01-11 12:01:01 +0400306 elif isinstance(self._monitor_address, tuple):
John Snowc4e60232020-10-06 19:57:59 -0400307 moncdev = "socket,id=mon,host={},port={}".format(
308 *self._monitor_address
309 )
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500310 else:
John Snowc4e60232020-10-06 19:57:59 -0400311 moncdev = f"socket,id=mon,path={self._monitor_address}"
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500312 args.extend(['-chardev', moncdev, '-mon',
313 'chardev=mon,mode=control'])
John Snowc4e60232020-10-06 19:57:59 -0400314
John Snowabf0bf92019-06-27 17:28:14 -0400315 if self._machine is not None:
316 args.extend(['-machine', self._machine])
John Snow9b8ccd62020-05-28 18:21:28 -0400317 for _ in range(self._console_index):
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100318 args.extend(['-serial', 'null'])
John Snowabf0bf92019-06-27 17:28:14 -0400319 if self._console_set:
John Snoweb55fae2023-07-20 09:04:47 -0400320 assert self._cons_sock_pair is not None
321 fd = self._cons_sock_pair[0].fileno()
322 chardev = f"socket,id=console,fd={fd}"
John Snowabf0bf92019-06-27 17:28:14 -0400323 args.extend(['-chardev', chardev])
324 if self._console_device_type is None:
325 args.extend(['-serial', 'chardev:console'])
326 else:
327 device = '%s,chardev=console' % self._console_device_type
328 args.extend(['-device', device])
329 return args
330
Wainer dos Santos Moschetta555fe0c2021-04-30 10:34:12 -0300331 @property
332 def args(self) -> List[str]:
333 """Returns the list of arguments given to the QEMU binary."""
334 return self._args
335
John Snowf12a2822020-10-06 19:58:08 -0400336 def _pre_launch(self) -> None:
John Snow652809d2020-10-06 19:58:01 -0400337 if self._console_set:
338 self._remove_files.append(self._console_address)
339
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500340 if self._qmp_set:
Marc-André Lureaubd4c0ef2023-01-11 12:01:01 +0400341 if self._monitor_address is None:
342 self._sock_pair = socket.socketpair()
John Snowfe9ce0b2023-07-20 09:04:45 -0400343 os.set_inheritable(self._sock_pair[0].fileno(), True)
Marc-André Lureaubd4c0ef2023-01-11 12:01:01 +0400344 sock = self._sock_pair[1]
John Snow6eeb3de2021-11-18 15:46:15 -0500345 if isinstance(self._monitor_address, str):
John Snowc4e60232020-10-06 19:57:59 -0400346 self._remove_files.append(self._monitor_address)
John Snow7f5f3ae2023-05-17 12:34:04 -0400347
John Snow5bbc5932023-05-17 12:34:05 -0400348 sock_or_addr = self._monitor_address or sock
349 assert sock_or_addr is not None
350
John Snowbeb6b572021-05-27 17:16:53 -0400351 self._qmp_connection = QEMUMonitorProtocol(
John Snow5bbc5932023-05-17 12:34:05 -0400352 sock_or_addr,
John Snow7f5f3ae2023-05-17 12:34:04 -0400353 server=bool(self._monitor_address),
John Snowc4e60232020-10-06 19:57:59 -0400354 nickname=self._name
355 )
John Snowabf0bf92019-06-27 17:28:14 -0400356
John Snoweb55fae2023-07-20 09:04:47 -0400357 if self._console_set:
358 self._cons_sock_pair = socket.socketpair()
359 os.set_inheritable(self._cons_sock_pair[0].fileno(), True)
360
John Snow63c33f32021-05-27 17:16:49 -0400361 # NOTE: Make sure any opened resources are *definitely* freed in
362 # _post_shutdown()!
363 # pylint: disable=consider-using-with
Cleber Rosab306e262021-02-11 16:55:05 -0500364 self._qemu_log_path = os.path.join(self.log_dir, self._name + ".log")
John Snow63c33f32021-05-27 17:16:49 -0400365 self._qemu_log_file = open(self._qemu_log_path, 'wb')
366
John Snowb1ca9912021-11-18 15:46:17 -0500367 self._iolog = None
368 self._qemu_full_args = tuple(chain(
369 self._wrapper,
370 [self._binary],
371 self._base_args,
372 self._args
373 ))
374
John Snowf12a2822020-10-06 19:58:08 -0400375 def _post_launch(self) -> None:
Marc-André Lureaubd4c0ef2023-01-11 12:01:01 +0400376 if self._sock_pair:
377 self._sock_pair[0].close()
John Snowbe1183e2020-10-06 19:58:04 -0400378 if self._qmp_connection:
John Snow7f5f3ae2023-05-17 12:34:04 -0400379 if self._sock_pair:
380 self._qmp.connect()
381 else:
382 self._qmp.accept(self._qmp_timer)
John Snowabf0bf92019-06-27 17:28:14 -0400383
Emanuele Giuseppe Espositoeb7a91d2021-08-09 11:01:13 +0200384 def _close_qemu_log_file(self) -> None:
385 if self._qemu_log_file is not None:
386 self._qemu_log_file.close()
387 self._qemu_log_file = None
388
John Snowf12a2822020-10-06 19:58:08 -0400389 def _post_shutdown(self) -> None:
John Snowa3842cb2020-07-10 01:06:42 -0400390 """
391 Called to cleanup the VM instance after the process has exited.
392 May also be called after a failed launch.
393 """
John Snow9cccb332022-10-27 14:58:35 -0400394 LOG.debug("Cleaning up after VM process")
John Snow49a608b2021-10-26 13:56:06 -0400395 try:
396 self._close_qmp_connection()
397 except Exception as err: # pylint: disable=broad-except
398 LOG.warning(
399 "Exception closing QMP connection: %s",
400 str(err) if str(err) else type(err).__name__
401 )
402 finally:
403 assert self._qmp_connection is None
John Snow671940e2020-07-10 01:06:39 -0400404
Emanuele Giuseppe Espositoeb7a91d2021-08-09 11:01:13 +0200405 self._close_qemu_log_file()
John Snowabf0bf92019-06-27 17:28:14 -0400406
Cleber Rosa3c1e16c2021-02-11 17:01:41 -0500407 self._load_io_log()
408
John Snowabf0bf92019-06-27 17:28:14 -0400409 self._qemu_log_path = None
410
John Snowabf0bf92019-06-27 17:28:14 -0400411 if self._temp_dir is not None:
412 shutil.rmtree(self._temp_dir)
413 self._temp_dir = None
414
Max Reitz32558ce2019-10-17 15:31:34 +0200415 while len(self._remove_files) > 0:
416 self._remove_if_exists(self._remove_files.pop())
417
John Snow14661d92020-07-10 01:06:38 -0400418 exitcode = self.exitcode()
John Snowde6e08b2020-07-10 01:06:48 -0400419 if (exitcode is not None and exitcode < 0
420 and not (self._user_killed and exitcode == -signal.SIGKILL)):
John Snow14661d92020-07-10 01:06:38 -0400421 msg = 'qemu received signal %i; command: "%s"'
422 if self._qemu_full_args:
423 command = ' '.join(self._qemu_full_args)
424 else:
425 command = ''
426 LOG.warning(msg, -int(exitcode), command)
427
John Snowb9420e42021-10-26 13:56:05 -0400428 self._quit_issued = False
John Snowde6e08b2020-07-10 01:06:48 -0400429 self._user_killed = False
John Snow14661d92020-07-10 01:06:38 -0400430 self._launched = False
431
John Snowf12a2822020-10-06 19:58:08 -0400432 def launch(self) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400433 """
434 Launch the VM and make sure we cleanup and expose the
435 command line/output in case of exception
436 """
437
438 if self._launched:
439 raise QEMUMachineError('VM already launched')
440
John Snowabf0bf92019-06-27 17:28:14 -0400441 try:
442 self._launch()
John Snow50465f92022-01-31 23:11:32 -0500443 except BaseException as exc:
John Snow1611e6c2021-11-18 15:46:18 -0500444 # We may have launched the process but it may
445 # have exited before we could connect via QMP.
446 # Assume the VM didn't launch or is exiting.
447 # If we don't wait for the process, exitcode() may still be
448 # 'None' by the time control is ceded back to the caller.
449 if self._launched:
450 self.wait()
451 else:
452 self._post_shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400453
John Snow50465f92022-01-31 23:11:32 -0500454 if isinstance(exc, Exception):
455 raise VMLaunchFailure(
456 exitcode=self.exitcode(),
457 command=' '.join(self._qemu_full_args),
458 output=self._iolog
459 ) from exc
460
461 # Don't wrap 'BaseException'; doing so would downgrade
462 # that exception. However, we still want to clean up.
John Snowabf0bf92019-06-27 17:28:14 -0400463 raise
464
John Snowf12a2822020-10-06 19:58:08 -0400465 def _launch(self) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400466 """
467 Launch the VM and establish a QMP connection
468 """
John Snowabf0bf92019-06-27 17:28:14 -0400469 self._pre_launch()
John Snowabf0bf92019-06-27 17:28:14 -0400470 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
John Snowa0eae172021-05-27 17:16:50 -0400471
472 # Cleaning up of this subprocess is guaranteed by _do_shutdown.
473 # pylint: disable=consider-using-with
John Snowabf0bf92019-06-27 17:28:14 -0400474 self._popen = subprocess.Popen(self._qemu_full_args,
John Snow07b71232021-05-27 17:16:46 -0400475 stdin=subprocess.DEVNULL,
John Snowabf0bf92019-06-27 17:28:14 -0400476 stdout=self._qemu_log_file,
477 stderr=subprocess.STDOUT,
478 shell=False,
479 close_fds=False)
John Snow1611e6c2021-11-18 15:46:18 -0500480 self._launched = True
John Snowabf0bf92019-06-27 17:28:14 -0400481 self._post_launch()
482
John Snow49a608b2021-10-26 13:56:06 -0400483 def _close_qmp_connection(self) -> None:
484 """
485 Close the underlying QMP connection, if any.
486
487 Dutifully report errors that occurred while closing, but assume
488 that any error encountered indicates an abnormal termination
489 process and not a failure to close.
490 """
491 if self._qmp_connection is None:
492 return
493
494 try:
495 self._qmp.close()
496 except EOFError:
497 # EOF can occur as an Exception here when using the Async
498 # QMP backend. It indicates that the server closed the
499 # stream. If we successfully issued 'quit' at any point,
500 # then this was expected. If the remote went away without
501 # our permission, it's worth reporting that as an abnormal
502 # shutdown case.
503 if not (self._user_killed or self._quit_issued):
504 raise
505 finally:
506 self._qmp_connection = None
507
John Snowe2c97f12020-07-10 01:06:40 -0400508 def _early_cleanup(self) -> None:
509 """
510 Perform any cleanup that needs to happen before the VM exits.
John Snowa3842cb2020-07-10 01:06:42 -0400511
John Snow1611e6c2021-11-18 15:46:18 -0500512 This method may be called twice upon shutdown, once each by soft
513 and hard shutdown in failover scenarios.
John Snowe2c97f12020-07-10 01:06:40 -0400514 """
515 # If we keep the console socket open, we may deadlock waiting
516 # for QEMU to exit, while QEMU is waiting for the socket to
Peter Maydell9323e792022-06-08 19:38:47 +0100517 # become writable.
John Snowe2c97f12020-07-10 01:06:40 -0400518 if self._console_socket is not None:
John Snow9cccb332022-10-27 14:58:35 -0400519 LOG.debug("Closing console socket")
John Snowe2c97f12020-07-10 01:06:40 -0400520 self._console_socket.close()
521 self._console_socket = None
522
John Snow193bf1c2020-07-10 01:06:47 -0400523 def _hard_shutdown(self) -> None:
524 """
525 Perform early cleanup, kill the VM, and wait for it to terminate.
526
527 :raise subprocess.Timeout: When timeout is exceeds 60 seconds
528 waiting for the QEMU process to terminate.
529 """
John Snow9cccb332022-10-27 14:58:35 -0400530 LOG.debug("Performing hard shutdown")
John Snow193bf1c2020-07-10 01:06:47 -0400531 self._early_cleanup()
John Snow9223fda2020-10-06 19:58:05 -0400532 self._subp.kill()
533 self._subp.wait(timeout=60)
John Snow193bf1c2020-07-10 01:06:47 -0400534
John Snowb9420e42021-10-26 13:56:05 -0400535 def _soft_shutdown(self, timeout: Optional[int]) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400536 """
537 Perform early cleanup, attempt to gracefully shut down the VM, and wait
538 for it to terminate.
539
John Snow8226a4b2020-07-20 12:02:52 -0400540 :param timeout: Timeout in seconds for graceful shutdown.
541 A value of None is an infinite wait.
John Snow193bf1c2020-07-10 01:06:47 -0400542
543 :raise ConnectionReset: On QMP communication errors
544 :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
545 the QEMU process to terminate.
546 """
John Snow9cccb332022-10-27 14:58:35 -0400547 LOG.debug("Attempting graceful termination")
548
John Snow193bf1c2020-07-10 01:06:47 -0400549 self._early_cleanup()
550
John Snow9cccb332022-10-27 14:58:35 -0400551 if self._quit_issued:
552 LOG.debug(
553 "Anticipating QEMU termination due to prior 'quit' command, "
554 "or explicit call to wait()"
555 )
556 else:
557 LOG.debug("Politely asking QEMU to terminate")
558
John Snowbe1183e2020-10-06 19:58:04 -0400559 if self._qmp_connection:
John Snow49a608b2021-10-26 13:56:06 -0400560 try:
561 if not self._quit_issued:
562 # May raise ExecInterruptedError or StateError if the
563 # connection dies or has *already* died.
564 self.qmp('quit')
565 finally:
566 # Regardless, we want to quiesce the connection.
567 self._close_qmp_connection()
John Snow3c6e5e82022-10-27 14:58:36 -0400568 elif not self._quit_issued:
569 LOG.debug(
570 "Not anticipating QEMU quit and no QMP connection present, "
571 "issuing SIGTERM"
572 )
573 self._subp.terminate()
John Snow193bf1c2020-07-10 01:06:47 -0400574
575 # May raise subprocess.TimeoutExpired
John Snow9cccb332022-10-27 14:58:35 -0400576 LOG.debug(
577 "Waiting (timeout=%s) for QEMU process (pid=%s) to terminate",
578 timeout, self._subp.pid
579 )
John Snow9223fda2020-10-06 19:58:05 -0400580 self._subp.wait(timeout=timeout)
John Snow193bf1c2020-07-10 01:06:47 -0400581
John Snowb9420e42021-10-26 13:56:05 -0400582 def _do_shutdown(self, timeout: Optional[int]) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400583 """
584 Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
585
John Snow8226a4b2020-07-20 12:02:52 -0400586 :param timeout: Timeout in seconds for graceful shutdown.
587 A value of None is an infinite wait.
John Snow193bf1c2020-07-10 01:06:47 -0400588
589 :raise AbnormalShutdown: When the VM could not be shut down gracefully.
590 The inner exception will likely be ConnectionReset or
591 subprocess.TimeoutExpired. In rare cases, non-graceful termination
592 may result in its own exceptions, likely subprocess.TimeoutExpired.
593 """
594 try:
John Snowb9420e42021-10-26 13:56:05 -0400595 self._soft_shutdown(timeout)
John Snow193bf1c2020-07-10 01:06:47 -0400596 except Exception as exc:
John Snow9cccb332022-10-27 14:58:35 -0400597 if isinstance(exc, subprocess.TimeoutExpired):
598 LOG.debug("Timed out waiting for QEMU process to exit")
599 LOG.debug("Graceful shutdown failed", exc_info=True)
600 LOG.debug("Falling back to hard shutdown")
John Snow193bf1c2020-07-10 01:06:47 -0400601 self._hard_shutdown()
602 raise AbnormalShutdown("Could not perform graceful shutdown") \
603 from exc
604
John Snowb9420e42021-10-26 13:56:05 -0400605 def shutdown(self,
John Snowc9b30452020-07-10 01:06:43 -0400606 hard: bool = False,
John Snow8226a4b2020-07-20 12:02:52 -0400607 timeout: Optional[int] = 30) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400608 """
John Snow193bf1c2020-07-10 01:06:47 -0400609 Terminate the VM (gracefully if possible) and perform cleanup.
610 Cleanup will always be performed.
611
612 If the VM has not yet been launched, or shutdown(), wait(), or kill()
613 have already been called, this method does nothing.
614
John Snow193bf1c2020-07-10 01:06:47 -0400615 :param hard: When true, do not attempt graceful shutdown, and
616 suppress the SIGKILL warning log message.
617 :param timeout: Optional timeout in seconds for graceful shutdown.
John Snow8226a4b2020-07-20 12:02:52 -0400618 Default 30 seconds, A `None` value is an infinite wait.
John Snowabf0bf92019-06-27 17:28:14 -0400619 """
John Snowa3842cb2020-07-10 01:06:42 -0400620 if not self._launched:
621 return
622
John Snow9cccb332022-10-27 14:58:35 -0400623 LOG.debug("Shutting down VM appliance; timeout=%s", timeout)
624 if hard:
625 LOG.debug("Caller requests immediate termination of QEMU process.")
626
John Snow193bf1c2020-07-10 01:06:47 -0400627 try:
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300628 if hard:
John Snowde6e08b2020-07-10 01:06:48 -0400629 self._user_killed = True
John Snow193bf1c2020-07-10 01:06:47 -0400630 self._hard_shutdown()
631 else:
John Snowb9420e42021-10-26 13:56:05 -0400632 self._do_shutdown(timeout)
John Snow193bf1c2020-07-10 01:06:47 -0400633 finally:
634 self._post_shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400635
John Snowf12a2822020-10-06 19:58:08 -0400636 def kill(self) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400637 """
638 Terminate the VM forcefully, wait for it to exit, and perform cleanup.
639 """
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300640 self.shutdown(hard=True)
641
John Snow8226a4b2020-07-20 12:02:52 -0400642 def wait(self, timeout: Optional[int] = 30) -> None:
John Snow89528052020-07-10 01:06:44 -0400643 """
644 Wait for the VM to power off and perform post-shutdown cleanup.
645
John Snow8226a4b2020-07-20 12:02:52 -0400646 :param timeout: Optional timeout in seconds. Default 30 seconds.
647 A value of `None` is an infinite wait.
John Snow89528052020-07-10 01:06:44 -0400648 """
John Snowb9420e42021-10-26 13:56:05 -0400649 self._quit_issued = True
650 self.shutdown(timeout=timeout)
John Snow89528052020-07-10 01:06:44 -0400651
John Snowf12a2822020-10-06 19:58:08 -0400652 def set_qmp_monitor(self, enabled: bool = True) -> None:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500653 """
654 Set the QMP monitor.
655
656 @param enabled: if False, qmp monitor options will be removed from
657 the base arguments of the resulting QEMU command
658 line. Default is True.
John Snow5c02c862021-06-29 17:43:23 -0400659
660 .. note:: Call this function before launch().
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500661 """
John Snowbe1183e2020-10-06 19:58:04 -0400662 self._qmp_set = enabled
663
664 @property
John Snowbeb6b572021-05-27 17:16:53 -0400665 def _qmp(self) -> QEMUMonitorProtocol:
John Snowbe1183e2020-10-06 19:58:04 -0400666 if self._qmp_connection is None:
667 raise QEMUMachineError("Attempt to access QMP with no connection")
668 return self._qmp_connection
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500669
John Snowaaa81ec2020-10-06 19:58:03 -0400670 @classmethod
Vladimir Sementsov-Ogievskiyc7daa572021-08-24 11:38:45 +0300671 def _qmp_args(cls, conv_keys: bool,
672 args: Dict[str, Any]) -> Dict[str, object]:
673 if conv_keys:
674 return {k.replace('_', '-'): v for k, v in args.items()}
675
676 return args
John Snowabf0bf92019-06-27 17:28:14 -0400677
John Snowaaa81ec2020-10-06 19:58:03 -0400678 def qmp(self, cmd: str,
Vladimir Sementsov-Ogievskiy3f3c9b42021-08-24 11:38:46 +0300679 args_dict: Optional[Dict[str, object]] = None,
680 conv_keys: Optional[bool] = None,
John Snowaaa81ec2020-10-06 19:58:03 -0400681 **args: Any) -> QMPMessage:
682 """
683 Invoke a QMP command and return the response dict
684 """
Vladimir Sementsov-Ogievskiy3f3c9b42021-08-24 11:38:46 +0300685 if args_dict is not None:
686 assert not args
687 assert conv_keys is None
688 args = args_dict
689 conv_keys = False
690
691 if conv_keys is None:
692 conv_keys = True
693
Vladimir Sementsov-Ogievskiyc7daa572021-08-24 11:38:45 +0300694 qmp_args = self._qmp_args(conv_keys, args)
John Snowb9420e42021-10-26 13:56:05 -0400695 ret = self._qmp.cmd(cmd, args=qmp_args)
696 if cmd == 'quit' and 'error' not in ret and 'return' in ret:
697 self._quit_issued = True
698 return ret
John Snowabf0bf92019-06-27 17:28:14 -0400699
John Snowf12a2822020-10-06 19:58:08 -0400700 def command(self, cmd: str,
701 conv_keys: bool = True,
702 **args: Any) -> QMPReturnValue:
John Snowabf0bf92019-06-27 17:28:14 -0400703 """
704 Invoke a QMP command.
705 On success return the response dict.
706 On failure raise an exception.
707 """
Vladimir Sementsov-Ogievskiyc7daa572021-08-24 11:38:45 +0300708 qmp_args = self._qmp_args(conv_keys, args)
John Snowb9420e42021-10-26 13:56:05 -0400709 ret = self._qmp.command(cmd, **qmp_args)
710 if cmd == 'quit':
711 self._quit_issued = True
712 return ret
John Snowabf0bf92019-06-27 17:28:14 -0400713
John Snowf12a2822020-10-06 19:58:08 -0400714 def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400715 """
716 Poll for one queued QMP events and return it
717 """
John Snow306dfcd2019-06-27 17:28:15 -0400718 if self._events:
John Snowabf0bf92019-06-27 17:28:14 -0400719 return self._events.pop(0)
720 return self._qmp.pull_event(wait=wait)
721
John Snowf12a2822020-10-06 19:58:08 -0400722 def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400723 """
724 Poll for queued QMP events and return a list of dicts
725 """
726 events = self._qmp.get_events(wait=wait)
727 events.extend(self._events)
728 del self._events[:]
John Snowabf0bf92019-06-27 17:28:14 -0400729 return events
730
731 @staticmethod
John Snowf12a2822020-10-06 19:58:08 -0400732 def event_match(event: Any, match: Optional[Any]) -> bool:
John Snowabf0bf92019-06-27 17:28:14 -0400733 """
734 Check if an event matches optional match criteria.
735
736 The match criteria takes the form of a matching subdict. The event is
737 checked to be a superset of the subdict, recursively, with matching
738 values whenever the subdict values are not None.
739
740 This has a limitation that you cannot explicitly check for None values.
741
742 Examples, with the subdict queries on the left:
743 - None matches any object.
744 - {"foo": None} matches {"foo": {"bar": 1}}
745 - {"foo": None} matches {"foo": 5}
746 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
747 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
748 """
749 if match is None:
750 return True
751
752 try:
753 for key in match:
754 if key in event:
755 if not QEMUMachine.event_match(event[key], match[key]):
756 return False
757 else:
758 return False
759 return True
760 except TypeError:
761 # either match or event wasn't iterable (not a dict)
John Snowf12a2822020-10-06 19:58:08 -0400762 return bool(match == event)
John Snowabf0bf92019-06-27 17:28:14 -0400763
John Snowf12a2822020-10-06 19:58:08 -0400764 def event_wait(self, name: str,
765 timeout: float = 60.0,
766 match: Optional[QMPMessage] = None) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400767 """
768 event_wait waits for and returns a named event from QMP with a timeout.
769
770 name: The event to wait for.
771 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
772 match: Optional match criteria. See event_match for details.
773 """
774 return self.events_wait([(name, match)], timeout)
775
John Snowf12a2822020-10-06 19:58:08 -0400776 def events_wait(self,
777 events: Sequence[Tuple[str, Any]],
778 timeout: float = 60.0) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400779 """
John Snow1847a4a2020-10-06 19:58:02 -0400780 events_wait waits for and returns a single named event from QMP.
781 In the case of multiple qualifying events, this function returns the
782 first one.
John Snowabf0bf92019-06-27 17:28:14 -0400783
John Snow1847a4a2020-10-06 19:58:02 -0400784 :param events: A sequence of (name, match_criteria) tuples.
785 The match criteria are optional and may be None.
786 See event_match for details.
787 :param timeout: Optional timeout, in seconds.
788 See QEMUMonitorProtocol.pull_event.
789
John Snowa4225302022-03-21 16:33:12 -0400790 :raise asyncio.TimeoutError:
791 If timeout was non-zero and no matching events were found.
792
John Snow1847a4a2020-10-06 19:58:02 -0400793 :return: A QMP event matching the filter criteria.
794 If timeout was 0 and no event matched, None.
John Snowabf0bf92019-06-27 17:28:14 -0400795 """
John Snowf12a2822020-10-06 19:58:08 -0400796 def _match(event: QMPMessage) -> bool:
John Snowabf0bf92019-06-27 17:28:14 -0400797 for name, match in events:
John Snow306dfcd2019-06-27 17:28:15 -0400798 if event['event'] == name and self.event_match(event, match):
John Snowabf0bf92019-06-27 17:28:14 -0400799 return True
800 return False
801
John Snow1847a4a2020-10-06 19:58:02 -0400802 event: Optional[QMPMessage]
803
John Snowabf0bf92019-06-27 17:28:14 -0400804 # Search cached events
805 for event in self._events:
806 if _match(event):
807 self._events.remove(event)
808 return event
809
810 # Poll for new events
811 while True:
812 event = self._qmp.pull_event(wait=timeout)
John Snow1847a4a2020-10-06 19:58:02 -0400813 if event is None:
814 # NB: None is only returned when timeout is false-ish.
John Snowa4225302022-03-21 16:33:12 -0400815 # Timeouts raise asyncio.TimeoutError instead!
John Snow1847a4a2020-10-06 19:58:02 -0400816 break
John Snowabf0bf92019-06-27 17:28:14 -0400817 if _match(event):
818 return event
819 self._events.append(event)
820
821 return None
822
John Snowf12a2822020-10-06 19:58:08 -0400823 def get_log(self) -> Optional[str]:
John Snowabf0bf92019-06-27 17:28:14 -0400824 """
825 After self.shutdown or failed qemu execution, this returns the output
826 of the qemu process.
827 """
828 return self._iolog
829
John Snowf12a2822020-10-06 19:58:08 -0400830 def add_args(self, *args: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400831 """
832 Adds to the list of extra arguments to be given to the QEMU binary
833 """
834 self._args.extend(args)
835
John Snowf12a2822020-10-06 19:58:08 -0400836 def set_machine(self, machine_type: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400837 """
838 Sets the machine type
839
840 If set, the machine type will be added to the base arguments
841 of the resulting QEMU command line.
842 """
843 self._machine = machine_type
844
John Snowf12a2822020-10-06 19:58:08 -0400845 def set_console(self,
846 device_type: Optional[str] = None,
847 console_index: int = 0) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400848 """
849 Sets the device type for a console device
850
851 If set, the console device and a backing character device will
852 be added to the base arguments of the resulting QEMU command
853 line.
854
855 This is a convenience method that will either use the provided
856 device type, or default to a "-serial chardev:console" command
857 line argument.
858
859 The actual setting of command line arguments will be be done at
860 machine launch time, as it depends on the temporary directory
861 to be created.
862
863 @param device_type: the device type, such as "isa-serial". If
864 None is given (the default value) a "-serial
865 chardev:console" command line argument will
866 be used instead, resorting to the machine's
867 default device type.
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100868 @param console_index: the index of the console device to use.
869 If not zero, the command line will create
870 'index - 1' consoles and connect them to
871 the 'null' backing character device.
John Snowabf0bf92019-06-27 17:28:14 -0400872 """
873 self._console_set = True
874 self._console_device_type = device_type
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100875 self._console_index = console_index
John Snowabf0bf92019-06-27 17:28:14 -0400876
877 @property
John Snowf12a2822020-10-06 19:58:08 -0400878 def console_socket(self) -> socket.socket:
John Snowabf0bf92019-06-27 17:28:14 -0400879 """
880 Returns a socket connected to the console
881 """
882 if self._console_socket is None:
John Snoweb55fae2023-07-20 09:04:47 -0400883 if not self._console_set:
884 raise QEMUMachineError(
885 "Attempt to access console socket with no connection")
886 assert self._cons_sock_pair is not None
Robert Foley80ded8e2020-07-24 07:45:08 +0100887 self._console_socket = console_socket.ConsoleSocket(
John Snoweb55fae2023-07-20 09:04:47 -0400888 self._cons_sock_pair[1].fileno(),
Robert Foley80ded8e2020-07-24 07:45:08 +0100889 file=self._console_log_path,
890 drain=self._drain_console)
John Snowabf0bf92019-06-27 17:28:14 -0400891 return self._console_socket
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500892
893 @property
894 def temp_dir(self) -> str:
895 """
896 Returns a temporary directory to be used for this machine
897 """
898 if self._temp_dir is None:
899 self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-",
900 dir=self._base_temp_dir)
901 return self._temp_dir
Cleber Rosab306e262021-02-11 16:55:05 -0500902
903 @property
John Snow87bf1fe2021-11-18 15:46:14 -0500904 def sock_dir(self) -> str:
905 """
906 Returns the directory used for sockfiles by this machine.
907 """
908 if self._sock_dir:
909 return self._sock_dir
910 return self.temp_dir
911
912 @property
Cleber Rosab306e262021-02-11 16:55:05 -0500913 def log_dir(self) -> str:
914 """
915 Returns a directory to be used for writing logs
916 """
917 if self._log_dir is None:
918 return self.temp_dir
919 return self._log_dir