blob: 8be0f684feb6a3810b7bee3a2652031df3380d23 [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 Snow87bf1fe2021-11-18 15:46:14 -0500162 self._temp_dir: Optional[str] = None
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500163 self._base_temp_dir = base_temp_dir
John Snow87bf1fe2021-11-18 15:46:14 -0500164 self._sock_dir = sock_dir
Cleber Rosab306e262021-02-11 16:55:05 -0500165 self._log_dir = log_dir
John Snowc5e61a62020-10-06 19:58:00 -0400166
Marc-André Lureaubd4c0ef2023-01-11 12:01:01 +0400167 self._monitor_address = monitor_address
John Snowc5e61a62020-10-06 19:58:00 -0400168
169 self._console_log_path = console_log
170 if self._console_log_path:
171 # In order to log the console, buffering needs to be enabled.
172 self._drain_console = True
173 else:
174 self._drain_console = drain_console
175
176 # Runstate
John Snowf12a2822020-10-06 19:58:08 -0400177 self._qemu_log_path: Optional[str] = None
178 self._qemu_log_file: Optional[BinaryIO] = None
John Snow9223fda2020-10-06 19:58:05 -0400179 self._popen: Optional['subprocess.Popen[bytes]'] = None
John Snowf12a2822020-10-06 19:58:08 -0400180 self._events: List[QMPMessage] = []
181 self._iolog: Optional[str] = None
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500182 self._qmp_set = True # Enable QMP monitor by default.
John Snowbeb6b572021-05-27 17:16:53 -0400183 self._qmp_connection: Optional[QEMUMonitorProtocol] = None
John Snowaad3f3b2020-10-06 19:58:06 -0400184 self._qemu_full_args: Tuple[str, ...] = ()
John Snowabf0bf92019-06-27 17:28:14 -0400185 self._launched = False
John Snowf12a2822020-10-06 19:58:08 -0400186 self._machine: Optional[str] = None
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100187 self._console_index = 0
John Snowabf0bf92019-06-27 17:28:14 -0400188 self._console_set = False
John Snowf12a2822020-10-06 19:58:08 -0400189 self._console_device_type: Optional[str] = None
John Snow652809d2020-10-06 19:58:01 -0400190 self._console_address = os.path.join(
Peter Delevoryasf9922932023-01-10 00:29:30 -0800191 self.sock_dir, f"{self._name}.con"
John Snow652809d2020-10-06 19:58:01 -0400192 )
John Snowf12a2822020-10-06 19:58:08 -0400193 self._console_socket: Optional[socket.socket] = None
194 self._remove_files: List[str] = []
John Snowde6e08b2020-07-10 01:06:48 -0400195 self._user_killed = False
John Snowb9420e42021-10-26 13:56:05 -0400196 self._quit_issued = False
John Snowabf0bf92019-06-27 17:28:14 -0400197
Vladimir Sementsov-Ogievskiy15c3b862021-08-24 11:38:47 +0300198 def __enter__(self: _T) -> _T:
John Snowabf0bf92019-06-27 17:28:14 -0400199 return self
200
John Snow1dda0402020-05-14 01:53:44 -0400201 def __exit__(self,
202 exc_type: Optional[Type[BaseException]],
203 exc_val: Optional[BaseException],
204 exc_tb: Optional[TracebackType]) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400205 self.shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400206
John Snowf12a2822020-10-06 19:58:08 -0400207 def add_monitor_null(self) -> None:
John Snow306dfcd2019-06-27 17:28:15 -0400208 """
209 This can be used to add an unused monitor instance.
210 """
John Snowabf0bf92019-06-27 17:28:14 -0400211 self._args.append('-monitor')
212 self._args.append('null')
213
Vladimir Sementsov-Ogievskiy15c3b862021-08-24 11:38:47 +0300214 def add_fd(self: _T, fd: int, fdset: int,
215 opaque: str, opts: str = '') -> _T:
John Snowabf0bf92019-06-27 17:28:14 -0400216 """
217 Pass a file descriptor to the VM
218 """
219 options = ['fd=%d' % fd,
220 'set=%d' % fdset,
221 'opaque=%s' % opaque]
222 if opts:
223 options.append(opts)
224
225 # This did not exist before 3.4, but since then it is
226 # mandatory for our purpose
227 if hasattr(os, 'set_inheritable'):
228 os.set_inheritable(fd, True)
229
230 self._args.append('-add-fd')
231 self._args.append(','.join(options))
232 return self
233
John Snowf12a2822020-10-06 19:58:08 -0400234 def send_fd_scm(self, fd: Optional[int] = None,
235 file_path: Optional[str] = None) -> int:
John Snow306dfcd2019-06-27 17:28:15 -0400236 """
John Snow514d00d2021-09-22 20:49:30 -0400237 Send an fd or file_path to the remote via SCM_RIGHTS.
John Snow306dfcd2019-06-27 17:28:15 -0400238
John Snow514d00d2021-09-22 20:49:30 -0400239 Exactly one of fd and file_path must be given. If it is
240 file_path, the file will be opened read-only and the new file
241 descriptor will be sent to the remote.
John Snow306dfcd2019-06-27 17:28:15 -0400242 """
John Snowabf0bf92019-06-27 17:28:14 -0400243 if file_path is not None:
244 assert fd is None
John Snow514d00d2021-09-22 20:49:30 -0400245 with open(file_path, "rb") as passfile:
246 fd = passfile.fileno()
247 self._qmp.send_fd_scm(fd)
John Snowabf0bf92019-06-27 17:28:14 -0400248 else:
249 assert fd is not None
John Snow514d00d2021-09-22 20:49:30 -0400250 self._qmp.send_fd_scm(fd)
John Snowabf0bf92019-06-27 17:28:14 -0400251
John Snow514d00d2021-09-22 20:49:30 -0400252 return 0
John Snowabf0bf92019-06-27 17:28:14 -0400253
254 @staticmethod
John Snowf12a2822020-10-06 19:58:08 -0400255 def _remove_if_exists(path: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400256 """
257 Remove file object at path if it exists
258 """
259 try:
260 os.remove(path)
261 except OSError as exception:
262 if exception.errno == errno.ENOENT:
263 return
264 raise
265
John Snowf12a2822020-10-06 19:58:08 -0400266 def is_running(self) -> bool:
John Snow306dfcd2019-06-27 17:28:15 -0400267 """Returns true if the VM is running."""
John Snowabf0bf92019-06-27 17:28:14 -0400268 return self._popen is not None and self._popen.poll() is None
269
John Snow9223fda2020-10-06 19:58:05 -0400270 @property
271 def _subp(self) -> 'subprocess.Popen[bytes]':
272 if self._popen is None:
273 raise QEMUMachineError('Subprocess pipe not present')
274 return self._popen
275
John Snowf12a2822020-10-06 19:58:08 -0400276 def exitcode(self) -> Optional[int]:
John Snow306dfcd2019-06-27 17:28:15 -0400277 """Returns the exit code if possible, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400278 if self._popen is None:
279 return None
280 return self._popen.poll()
281
John Snowf12a2822020-10-06 19:58:08 -0400282 def get_pid(self) -> Optional[int]:
John Snow306dfcd2019-06-27 17:28:15 -0400283 """Returns the PID of the running process, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400284 if not self.is_running():
285 return None
John Snow9223fda2020-10-06 19:58:05 -0400286 return self._subp.pid
John Snowabf0bf92019-06-27 17:28:14 -0400287
John Snowf12a2822020-10-06 19:58:08 -0400288 def _load_io_log(self) -> None:
John Snow5690b432021-09-16 14:22:47 -0400289 # Assume that the output encoding of QEMU's terminal output is
290 # defined by our locale. If indeterminate, allow open() to fall
291 # back to the platform default.
292 _, encoding = locale.getlocale()
John Snowabf0bf92019-06-27 17:28:14 -0400293 if self._qemu_log_path is not None:
John Snow5690b432021-09-16 14:22:47 -0400294 with open(self._qemu_log_path, "r", encoding=encoding) as iolog:
John Snowabf0bf92019-06-27 17:28:14 -0400295 self._iolog = iolog.read()
296
John Snow652809d2020-10-06 19:58:01 -0400297 @property
298 def _base_args(self) -> List[str]:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500299 args = ['-display', 'none', '-vga', 'none']
John Snowc4e60232020-10-06 19:57:59 -0400300
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500301 if self._qmp_set:
Marc-André Lureaubd4c0ef2023-01-11 12:01:01 +0400302 if self._sock_pair:
John Snowfe9ce0b2023-07-20 09:04:45 -0400303 moncdev = f"socket,id=mon,fd={self._sock_pair[0].fileno()}"
Marc-André Lureaubd4c0ef2023-01-11 12:01:01 +0400304 elif isinstance(self._monitor_address, tuple):
John Snowc4e60232020-10-06 19:57:59 -0400305 moncdev = "socket,id=mon,host={},port={}".format(
306 *self._monitor_address
307 )
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500308 else:
John Snowc4e60232020-10-06 19:57:59 -0400309 moncdev = f"socket,id=mon,path={self._monitor_address}"
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500310 args.extend(['-chardev', moncdev, '-mon',
311 'chardev=mon,mode=control'])
John Snowc4e60232020-10-06 19:57:59 -0400312
John Snowabf0bf92019-06-27 17:28:14 -0400313 if self._machine is not None:
314 args.extend(['-machine', self._machine])
John Snow9b8ccd62020-05-28 18:21:28 -0400315 for _ in range(self._console_index):
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100316 args.extend(['-serial', 'null'])
John Snowabf0bf92019-06-27 17:28:14 -0400317 if self._console_set:
Paolo Bonzini991c1802020-11-13 03:10:52 -0500318 chardev = ('socket,id=console,path=%s,server=on,wait=off' %
John Snowabf0bf92019-06-27 17:28:14 -0400319 self._console_address)
320 args.extend(['-chardev', chardev])
321 if self._console_device_type is None:
322 args.extend(['-serial', 'chardev:console'])
323 else:
324 device = '%s,chardev=console' % self._console_device_type
325 args.extend(['-device', device])
326 return args
327
Wainer dos Santos Moschetta555fe0c2021-04-30 10:34:12 -0300328 @property
329 def args(self) -> List[str]:
330 """Returns the list of arguments given to the QEMU binary."""
331 return self._args
332
John Snowf12a2822020-10-06 19:58:08 -0400333 def _pre_launch(self) -> None:
John Snow652809d2020-10-06 19:58:01 -0400334 if self._console_set:
335 self._remove_files.append(self._console_address)
336
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500337 if self._qmp_set:
Marc-André Lureaubd4c0ef2023-01-11 12:01:01 +0400338 if self._monitor_address is None:
339 self._sock_pair = socket.socketpair()
John Snowfe9ce0b2023-07-20 09:04:45 -0400340 os.set_inheritable(self._sock_pair[0].fileno(), True)
Marc-André Lureaubd4c0ef2023-01-11 12:01:01 +0400341 sock = self._sock_pair[1]
John Snow6eeb3de2021-11-18 15:46:15 -0500342 if isinstance(self._monitor_address, str):
John Snowc4e60232020-10-06 19:57:59 -0400343 self._remove_files.append(self._monitor_address)
John Snow7f5f3ae2023-05-17 12:34:04 -0400344
John Snow5bbc5932023-05-17 12:34:05 -0400345 sock_or_addr = self._monitor_address or sock
346 assert sock_or_addr is not None
347
John Snowbeb6b572021-05-27 17:16:53 -0400348 self._qmp_connection = QEMUMonitorProtocol(
John Snow5bbc5932023-05-17 12:34:05 -0400349 sock_or_addr,
John Snow7f5f3ae2023-05-17 12:34:04 -0400350 server=bool(self._monitor_address),
John Snowc4e60232020-10-06 19:57:59 -0400351 nickname=self._name
352 )
John Snowabf0bf92019-06-27 17:28:14 -0400353
John Snow63c33f32021-05-27 17:16:49 -0400354 # NOTE: Make sure any opened resources are *definitely* freed in
355 # _post_shutdown()!
356 # pylint: disable=consider-using-with
Cleber Rosab306e262021-02-11 16:55:05 -0500357 self._qemu_log_path = os.path.join(self.log_dir, self._name + ".log")
John Snow63c33f32021-05-27 17:16:49 -0400358 self._qemu_log_file = open(self._qemu_log_path, 'wb')
359
John Snowb1ca9912021-11-18 15:46:17 -0500360 self._iolog = None
361 self._qemu_full_args = tuple(chain(
362 self._wrapper,
363 [self._binary],
364 self._base_args,
365 self._args
366 ))
367
John Snowf12a2822020-10-06 19:58:08 -0400368 def _post_launch(self) -> None:
Marc-André Lureaubd4c0ef2023-01-11 12:01:01 +0400369 if self._sock_pair:
370 self._sock_pair[0].close()
John Snowbe1183e2020-10-06 19:58:04 -0400371 if self._qmp_connection:
John Snow7f5f3ae2023-05-17 12:34:04 -0400372 if self._sock_pair:
373 self._qmp.connect()
374 else:
375 self._qmp.accept(self._qmp_timer)
John Snowabf0bf92019-06-27 17:28:14 -0400376
Emanuele Giuseppe Espositoeb7a91d2021-08-09 11:01:13 +0200377 def _close_qemu_log_file(self) -> None:
378 if self._qemu_log_file is not None:
379 self._qemu_log_file.close()
380 self._qemu_log_file = None
381
John Snowf12a2822020-10-06 19:58:08 -0400382 def _post_shutdown(self) -> None:
John Snowa3842cb2020-07-10 01:06:42 -0400383 """
384 Called to cleanup the VM instance after the process has exited.
385 May also be called after a failed launch.
386 """
John Snow9cccb332022-10-27 14:58:35 -0400387 LOG.debug("Cleaning up after VM process")
John Snow49a608b2021-10-26 13:56:06 -0400388 try:
389 self._close_qmp_connection()
390 except Exception as err: # pylint: disable=broad-except
391 LOG.warning(
392 "Exception closing QMP connection: %s",
393 str(err) if str(err) else type(err).__name__
394 )
395 finally:
396 assert self._qmp_connection is None
John Snow671940e2020-07-10 01:06:39 -0400397
Emanuele Giuseppe Espositoeb7a91d2021-08-09 11:01:13 +0200398 self._close_qemu_log_file()
John Snowabf0bf92019-06-27 17:28:14 -0400399
Cleber Rosa3c1e16c2021-02-11 17:01:41 -0500400 self._load_io_log()
401
John Snowabf0bf92019-06-27 17:28:14 -0400402 self._qemu_log_path = None
403
John Snowabf0bf92019-06-27 17:28:14 -0400404 if self._temp_dir is not None:
405 shutil.rmtree(self._temp_dir)
406 self._temp_dir = None
407
Max Reitz32558ce2019-10-17 15:31:34 +0200408 while len(self._remove_files) > 0:
409 self._remove_if_exists(self._remove_files.pop())
410
John Snow14661d92020-07-10 01:06:38 -0400411 exitcode = self.exitcode()
John Snowde6e08b2020-07-10 01:06:48 -0400412 if (exitcode is not None and exitcode < 0
413 and not (self._user_killed and exitcode == -signal.SIGKILL)):
John Snow14661d92020-07-10 01:06:38 -0400414 msg = 'qemu received signal %i; command: "%s"'
415 if self._qemu_full_args:
416 command = ' '.join(self._qemu_full_args)
417 else:
418 command = ''
419 LOG.warning(msg, -int(exitcode), command)
420
John Snowb9420e42021-10-26 13:56:05 -0400421 self._quit_issued = False
John Snowde6e08b2020-07-10 01:06:48 -0400422 self._user_killed = False
John Snow14661d92020-07-10 01:06:38 -0400423 self._launched = False
424
John Snowf12a2822020-10-06 19:58:08 -0400425 def launch(self) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400426 """
427 Launch the VM and make sure we cleanup and expose the
428 command line/output in case of exception
429 """
430
431 if self._launched:
432 raise QEMUMachineError('VM already launched')
433
John Snowabf0bf92019-06-27 17:28:14 -0400434 try:
435 self._launch()
John Snow50465f92022-01-31 23:11:32 -0500436 except BaseException as exc:
John Snow1611e6c2021-11-18 15:46:18 -0500437 # We may have launched the process but it may
438 # have exited before we could connect via QMP.
439 # Assume the VM didn't launch or is exiting.
440 # If we don't wait for the process, exitcode() may still be
441 # 'None' by the time control is ceded back to the caller.
442 if self._launched:
443 self.wait()
444 else:
445 self._post_shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400446
John Snow50465f92022-01-31 23:11:32 -0500447 if isinstance(exc, Exception):
448 raise VMLaunchFailure(
449 exitcode=self.exitcode(),
450 command=' '.join(self._qemu_full_args),
451 output=self._iolog
452 ) from exc
453
454 # Don't wrap 'BaseException'; doing so would downgrade
455 # that exception. However, we still want to clean up.
John Snowabf0bf92019-06-27 17:28:14 -0400456 raise
457
John Snowf12a2822020-10-06 19:58:08 -0400458 def _launch(self) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400459 """
460 Launch the VM and establish a QMP connection
461 """
John Snowabf0bf92019-06-27 17:28:14 -0400462 self._pre_launch()
John Snowabf0bf92019-06-27 17:28:14 -0400463 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
John Snowa0eae172021-05-27 17:16:50 -0400464
465 # Cleaning up of this subprocess is guaranteed by _do_shutdown.
466 # pylint: disable=consider-using-with
John Snowabf0bf92019-06-27 17:28:14 -0400467 self._popen = subprocess.Popen(self._qemu_full_args,
John Snow07b71232021-05-27 17:16:46 -0400468 stdin=subprocess.DEVNULL,
John Snowabf0bf92019-06-27 17:28:14 -0400469 stdout=self._qemu_log_file,
470 stderr=subprocess.STDOUT,
471 shell=False,
472 close_fds=False)
John Snow1611e6c2021-11-18 15:46:18 -0500473 self._launched = True
John Snowabf0bf92019-06-27 17:28:14 -0400474 self._post_launch()
475
John Snow49a608b2021-10-26 13:56:06 -0400476 def _close_qmp_connection(self) -> None:
477 """
478 Close the underlying QMP connection, if any.
479
480 Dutifully report errors that occurred while closing, but assume
481 that any error encountered indicates an abnormal termination
482 process and not a failure to close.
483 """
484 if self._qmp_connection is None:
485 return
486
487 try:
488 self._qmp.close()
489 except EOFError:
490 # EOF can occur as an Exception here when using the Async
491 # QMP backend. It indicates that the server closed the
492 # stream. If we successfully issued 'quit' at any point,
493 # then this was expected. If the remote went away without
494 # our permission, it's worth reporting that as an abnormal
495 # shutdown case.
496 if not (self._user_killed or self._quit_issued):
497 raise
498 finally:
499 self._qmp_connection = None
500
John Snowe2c97f12020-07-10 01:06:40 -0400501 def _early_cleanup(self) -> None:
502 """
503 Perform any cleanup that needs to happen before the VM exits.
John Snowa3842cb2020-07-10 01:06:42 -0400504
John Snow1611e6c2021-11-18 15:46:18 -0500505 This method may be called twice upon shutdown, once each by soft
506 and hard shutdown in failover scenarios.
John Snowe2c97f12020-07-10 01:06:40 -0400507 """
508 # If we keep the console socket open, we may deadlock waiting
509 # for QEMU to exit, while QEMU is waiting for the socket to
Peter Maydell9323e792022-06-08 19:38:47 +0100510 # become writable.
John Snowe2c97f12020-07-10 01:06:40 -0400511 if self._console_socket is not None:
John Snow9cccb332022-10-27 14:58:35 -0400512 LOG.debug("Closing console socket")
John Snowe2c97f12020-07-10 01:06:40 -0400513 self._console_socket.close()
514 self._console_socket = None
515
John Snow193bf1c2020-07-10 01:06:47 -0400516 def _hard_shutdown(self) -> None:
517 """
518 Perform early cleanup, kill the VM, and wait for it to terminate.
519
520 :raise subprocess.Timeout: When timeout is exceeds 60 seconds
521 waiting for the QEMU process to terminate.
522 """
John Snow9cccb332022-10-27 14:58:35 -0400523 LOG.debug("Performing hard shutdown")
John Snow193bf1c2020-07-10 01:06:47 -0400524 self._early_cleanup()
John Snow9223fda2020-10-06 19:58:05 -0400525 self._subp.kill()
526 self._subp.wait(timeout=60)
John Snow193bf1c2020-07-10 01:06:47 -0400527
John Snowb9420e42021-10-26 13:56:05 -0400528 def _soft_shutdown(self, timeout: Optional[int]) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400529 """
530 Perform early cleanup, attempt to gracefully shut down the VM, and wait
531 for it to terminate.
532
John Snow8226a4b2020-07-20 12:02:52 -0400533 :param timeout: Timeout in seconds for graceful shutdown.
534 A value of None is an infinite wait.
John Snow193bf1c2020-07-10 01:06:47 -0400535
536 :raise ConnectionReset: On QMP communication errors
537 :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
538 the QEMU process to terminate.
539 """
John Snow9cccb332022-10-27 14:58:35 -0400540 LOG.debug("Attempting graceful termination")
541
John Snow193bf1c2020-07-10 01:06:47 -0400542 self._early_cleanup()
543
John Snow9cccb332022-10-27 14:58:35 -0400544 if self._quit_issued:
545 LOG.debug(
546 "Anticipating QEMU termination due to prior 'quit' command, "
547 "or explicit call to wait()"
548 )
549 else:
550 LOG.debug("Politely asking QEMU to terminate")
551
John Snowbe1183e2020-10-06 19:58:04 -0400552 if self._qmp_connection:
John Snow49a608b2021-10-26 13:56:06 -0400553 try:
554 if not self._quit_issued:
555 # May raise ExecInterruptedError or StateError if the
556 # connection dies or has *already* died.
557 self.qmp('quit')
558 finally:
559 # Regardless, we want to quiesce the connection.
560 self._close_qmp_connection()
John Snow3c6e5e82022-10-27 14:58:36 -0400561 elif not self._quit_issued:
562 LOG.debug(
563 "Not anticipating QEMU quit and no QMP connection present, "
564 "issuing SIGTERM"
565 )
566 self._subp.terminate()
John Snow193bf1c2020-07-10 01:06:47 -0400567
568 # May raise subprocess.TimeoutExpired
John Snow9cccb332022-10-27 14:58:35 -0400569 LOG.debug(
570 "Waiting (timeout=%s) for QEMU process (pid=%s) to terminate",
571 timeout, self._subp.pid
572 )
John Snow9223fda2020-10-06 19:58:05 -0400573 self._subp.wait(timeout=timeout)
John Snow193bf1c2020-07-10 01:06:47 -0400574
John Snowb9420e42021-10-26 13:56:05 -0400575 def _do_shutdown(self, timeout: Optional[int]) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400576 """
577 Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
578
John Snow8226a4b2020-07-20 12:02:52 -0400579 :param timeout: Timeout in seconds for graceful shutdown.
580 A value of None is an infinite wait.
John Snow193bf1c2020-07-10 01:06:47 -0400581
582 :raise AbnormalShutdown: When the VM could not be shut down gracefully.
583 The inner exception will likely be ConnectionReset or
584 subprocess.TimeoutExpired. In rare cases, non-graceful termination
585 may result in its own exceptions, likely subprocess.TimeoutExpired.
586 """
587 try:
John Snowb9420e42021-10-26 13:56:05 -0400588 self._soft_shutdown(timeout)
John Snow193bf1c2020-07-10 01:06:47 -0400589 except Exception as exc:
John Snow9cccb332022-10-27 14:58:35 -0400590 if isinstance(exc, subprocess.TimeoutExpired):
591 LOG.debug("Timed out waiting for QEMU process to exit")
592 LOG.debug("Graceful shutdown failed", exc_info=True)
593 LOG.debug("Falling back to hard shutdown")
John Snow193bf1c2020-07-10 01:06:47 -0400594 self._hard_shutdown()
595 raise AbnormalShutdown("Could not perform graceful shutdown") \
596 from exc
597
John Snowb9420e42021-10-26 13:56:05 -0400598 def shutdown(self,
John Snowc9b30452020-07-10 01:06:43 -0400599 hard: bool = False,
John Snow8226a4b2020-07-20 12:02:52 -0400600 timeout: Optional[int] = 30) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400601 """
John Snow193bf1c2020-07-10 01:06:47 -0400602 Terminate the VM (gracefully if possible) and perform cleanup.
603 Cleanup will always be performed.
604
605 If the VM has not yet been launched, or shutdown(), wait(), or kill()
606 have already been called, this method does nothing.
607
John Snow193bf1c2020-07-10 01:06:47 -0400608 :param hard: When true, do not attempt graceful shutdown, and
609 suppress the SIGKILL warning log message.
610 :param timeout: Optional timeout in seconds for graceful shutdown.
John Snow8226a4b2020-07-20 12:02:52 -0400611 Default 30 seconds, A `None` value is an infinite wait.
John Snowabf0bf92019-06-27 17:28:14 -0400612 """
John Snowa3842cb2020-07-10 01:06:42 -0400613 if not self._launched:
614 return
615
John Snow9cccb332022-10-27 14:58:35 -0400616 LOG.debug("Shutting down VM appliance; timeout=%s", timeout)
617 if hard:
618 LOG.debug("Caller requests immediate termination of QEMU process.")
619
John Snow193bf1c2020-07-10 01:06:47 -0400620 try:
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300621 if hard:
John Snowde6e08b2020-07-10 01:06:48 -0400622 self._user_killed = True
John Snow193bf1c2020-07-10 01:06:47 -0400623 self._hard_shutdown()
624 else:
John Snowb9420e42021-10-26 13:56:05 -0400625 self._do_shutdown(timeout)
John Snow193bf1c2020-07-10 01:06:47 -0400626 finally:
627 self._post_shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400628
John Snowf12a2822020-10-06 19:58:08 -0400629 def kill(self) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400630 """
631 Terminate the VM forcefully, wait for it to exit, and perform cleanup.
632 """
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300633 self.shutdown(hard=True)
634
John Snow8226a4b2020-07-20 12:02:52 -0400635 def wait(self, timeout: Optional[int] = 30) -> None:
John Snow89528052020-07-10 01:06:44 -0400636 """
637 Wait for the VM to power off and perform post-shutdown cleanup.
638
John Snow8226a4b2020-07-20 12:02:52 -0400639 :param timeout: Optional timeout in seconds. Default 30 seconds.
640 A value of `None` is an infinite wait.
John Snow89528052020-07-10 01:06:44 -0400641 """
John Snowb9420e42021-10-26 13:56:05 -0400642 self._quit_issued = True
643 self.shutdown(timeout=timeout)
John Snow89528052020-07-10 01:06:44 -0400644
John Snowf12a2822020-10-06 19:58:08 -0400645 def set_qmp_monitor(self, enabled: bool = True) -> None:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500646 """
647 Set the QMP monitor.
648
649 @param enabled: if False, qmp monitor options will be removed from
650 the base arguments of the resulting QEMU command
651 line. Default is True.
John Snow5c02c862021-06-29 17:43:23 -0400652
653 .. note:: Call this function before launch().
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500654 """
John Snowbe1183e2020-10-06 19:58:04 -0400655 self._qmp_set = enabled
656
657 @property
John Snowbeb6b572021-05-27 17:16:53 -0400658 def _qmp(self) -> QEMUMonitorProtocol:
John Snowbe1183e2020-10-06 19:58:04 -0400659 if self._qmp_connection is None:
660 raise QEMUMachineError("Attempt to access QMP with no connection")
661 return self._qmp_connection
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500662
John Snowaaa81ec2020-10-06 19:58:03 -0400663 @classmethod
Vladimir Sementsov-Ogievskiyc7daa572021-08-24 11:38:45 +0300664 def _qmp_args(cls, conv_keys: bool,
665 args: Dict[str, Any]) -> Dict[str, object]:
666 if conv_keys:
667 return {k.replace('_', '-'): v for k, v in args.items()}
668
669 return args
John Snowabf0bf92019-06-27 17:28:14 -0400670
John Snowaaa81ec2020-10-06 19:58:03 -0400671 def qmp(self, cmd: str,
Vladimir Sementsov-Ogievskiy3f3c9b42021-08-24 11:38:46 +0300672 args_dict: Optional[Dict[str, object]] = None,
673 conv_keys: Optional[bool] = None,
John Snowaaa81ec2020-10-06 19:58:03 -0400674 **args: Any) -> QMPMessage:
675 """
676 Invoke a QMP command and return the response dict
677 """
Vladimir Sementsov-Ogievskiy3f3c9b42021-08-24 11:38:46 +0300678 if args_dict is not None:
679 assert not args
680 assert conv_keys is None
681 args = args_dict
682 conv_keys = False
683
684 if conv_keys is None:
685 conv_keys = True
686
Vladimir Sementsov-Ogievskiyc7daa572021-08-24 11:38:45 +0300687 qmp_args = self._qmp_args(conv_keys, args)
John Snowb9420e42021-10-26 13:56:05 -0400688 ret = self._qmp.cmd(cmd, args=qmp_args)
689 if cmd == 'quit' and 'error' not in ret and 'return' in ret:
690 self._quit_issued = True
691 return ret
John Snowabf0bf92019-06-27 17:28:14 -0400692
John Snowf12a2822020-10-06 19:58:08 -0400693 def command(self, cmd: str,
694 conv_keys: bool = True,
695 **args: Any) -> QMPReturnValue:
John Snowabf0bf92019-06-27 17:28:14 -0400696 """
697 Invoke a QMP command.
698 On success return the response dict.
699 On failure raise an exception.
700 """
Vladimir Sementsov-Ogievskiyc7daa572021-08-24 11:38:45 +0300701 qmp_args = self._qmp_args(conv_keys, args)
John Snowb9420e42021-10-26 13:56:05 -0400702 ret = self._qmp.command(cmd, **qmp_args)
703 if cmd == 'quit':
704 self._quit_issued = True
705 return ret
John Snowabf0bf92019-06-27 17:28:14 -0400706
John Snowf12a2822020-10-06 19:58:08 -0400707 def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400708 """
709 Poll for one queued QMP events and return it
710 """
John Snow306dfcd2019-06-27 17:28:15 -0400711 if self._events:
John Snowabf0bf92019-06-27 17:28:14 -0400712 return self._events.pop(0)
713 return self._qmp.pull_event(wait=wait)
714
John Snowf12a2822020-10-06 19:58:08 -0400715 def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400716 """
717 Poll for queued QMP events and return a list of dicts
718 """
719 events = self._qmp.get_events(wait=wait)
720 events.extend(self._events)
721 del self._events[:]
John Snowabf0bf92019-06-27 17:28:14 -0400722 return events
723
724 @staticmethod
John Snowf12a2822020-10-06 19:58:08 -0400725 def event_match(event: Any, match: Optional[Any]) -> bool:
John Snowabf0bf92019-06-27 17:28:14 -0400726 """
727 Check if an event matches optional match criteria.
728
729 The match criteria takes the form of a matching subdict. The event is
730 checked to be a superset of the subdict, recursively, with matching
731 values whenever the subdict values are not None.
732
733 This has a limitation that you cannot explicitly check for None values.
734
735 Examples, with the subdict queries on the left:
736 - None matches any object.
737 - {"foo": None} matches {"foo": {"bar": 1}}
738 - {"foo": None} matches {"foo": 5}
739 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
740 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
741 """
742 if match is None:
743 return True
744
745 try:
746 for key in match:
747 if key in event:
748 if not QEMUMachine.event_match(event[key], match[key]):
749 return False
750 else:
751 return False
752 return True
753 except TypeError:
754 # either match or event wasn't iterable (not a dict)
John Snowf12a2822020-10-06 19:58:08 -0400755 return bool(match == event)
John Snowabf0bf92019-06-27 17:28:14 -0400756
John Snowf12a2822020-10-06 19:58:08 -0400757 def event_wait(self, name: str,
758 timeout: float = 60.0,
759 match: Optional[QMPMessage] = None) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400760 """
761 event_wait waits for and returns a named event from QMP with a timeout.
762
763 name: The event to wait for.
764 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
765 match: Optional match criteria. See event_match for details.
766 """
767 return self.events_wait([(name, match)], timeout)
768
John Snowf12a2822020-10-06 19:58:08 -0400769 def events_wait(self,
770 events: Sequence[Tuple[str, Any]],
771 timeout: float = 60.0) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400772 """
John Snow1847a4a2020-10-06 19:58:02 -0400773 events_wait waits for and returns a single named event from QMP.
774 In the case of multiple qualifying events, this function returns the
775 first one.
John Snowabf0bf92019-06-27 17:28:14 -0400776
John Snow1847a4a2020-10-06 19:58:02 -0400777 :param events: A sequence of (name, match_criteria) tuples.
778 The match criteria are optional and may be None.
779 See event_match for details.
780 :param timeout: Optional timeout, in seconds.
781 See QEMUMonitorProtocol.pull_event.
782
John Snowa4225302022-03-21 16:33:12 -0400783 :raise asyncio.TimeoutError:
784 If timeout was non-zero and no matching events were found.
785
John Snow1847a4a2020-10-06 19:58:02 -0400786 :return: A QMP event matching the filter criteria.
787 If timeout was 0 and no event matched, None.
John Snowabf0bf92019-06-27 17:28:14 -0400788 """
John Snowf12a2822020-10-06 19:58:08 -0400789 def _match(event: QMPMessage) -> bool:
John Snowabf0bf92019-06-27 17:28:14 -0400790 for name, match in events:
John Snow306dfcd2019-06-27 17:28:15 -0400791 if event['event'] == name and self.event_match(event, match):
John Snowabf0bf92019-06-27 17:28:14 -0400792 return True
793 return False
794
John Snow1847a4a2020-10-06 19:58:02 -0400795 event: Optional[QMPMessage]
796
John Snowabf0bf92019-06-27 17:28:14 -0400797 # Search cached events
798 for event in self._events:
799 if _match(event):
800 self._events.remove(event)
801 return event
802
803 # Poll for new events
804 while True:
805 event = self._qmp.pull_event(wait=timeout)
John Snow1847a4a2020-10-06 19:58:02 -0400806 if event is None:
807 # NB: None is only returned when timeout is false-ish.
John Snowa4225302022-03-21 16:33:12 -0400808 # Timeouts raise asyncio.TimeoutError instead!
John Snow1847a4a2020-10-06 19:58:02 -0400809 break
John Snowabf0bf92019-06-27 17:28:14 -0400810 if _match(event):
811 return event
812 self._events.append(event)
813
814 return None
815
John Snowf12a2822020-10-06 19:58:08 -0400816 def get_log(self) -> Optional[str]:
John Snowabf0bf92019-06-27 17:28:14 -0400817 """
818 After self.shutdown or failed qemu execution, this returns the output
819 of the qemu process.
820 """
821 return self._iolog
822
John Snowf12a2822020-10-06 19:58:08 -0400823 def add_args(self, *args: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400824 """
825 Adds to the list of extra arguments to be given to the QEMU binary
826 """
827 self._args.extend(args)
828
John Snowf12a2822020-10-06 19:58:08 -0400829 def set_machine(self, machine_type: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400830 """
831 Sets the machine type
832
833 If set, the machine type will be added to the base arguments
834 of the resulting QEMU command line.
835 """
836 self._machine = machine_type
837
John Snowf12a2822020-10-06 19:58:08 -0400838 def set_console(self,
839 device_type: Optional[str] = None,
840 console_index: int = 0) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400841 """
842 Sets the device type for a console device
843
844 If set, the console device and a backing character device will
845 be added to the base arguments of the resulting QEMU command
846 line.
847
848 This is a convenience method that will either use the provided
849 device type, or default to a "-serial chardev:console" command
850 line argument.
851
852 The actual setting of command line arguments will be be done at
853 machine launch time, as it depends on the temporary directory
854 to be created.
855
856 @param device_type: the device type, such as "isa-serial". If
857 None is given (the default value) a "-serial
858 chardev:console" command line argument will
859 be used instead, resorting to the machine's
860 default device type.
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100861 @param console_index: the index of the console device to use.
862 If not zero, the command line will create
863 'index - 1' consoles and connect them to
864 the 'null' backing character device.
John Snowabf0bf92019-06-27 17:28:14 -0400865 """
866 self._console_set = True
867 self._console_device_type = device_type
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100868 self._console_index = console_index
John Snowabf0bf92019-06-27 17:28:14 -0400869
870 @property
John Snowf12a2822020-10-06 19:58:08 -0400871 def console_socket(self) -> socket.socket:
John Snowabf0bf92019-06-27 17:28:14 -0400872 """
873 Returns a socket connected to the console
874 """
875 if self._console_socket is None:
Robert Foley80ded8e2020-07-24 07:45:08 +0100876 self._console_socket = console_socket.ConsoleSocket(
877 self._console_address,
878 file=self._console_log_path,
879 drain=self._drain_console)
John Snowabf0bf92019-06-27 17:28:14 -0400880 return self._console_socket
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500881
882 @property
883 def temp_dir(self) -> str:
884 """
885 Returns a temporary directory to be used for this machine
886 """
887 if self._temp_dir is None:
888 self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-",
889 dir=self._base_temp_dir)
890 return self._temp_dir
Cleber Rosab306e262021-02-11 16:55:05 -0500891
892 @property
John Snow87bf1fe2021-11-18 15:46:14 -0500893 def sock_dir(self) -> str:
894 """
895 Returns the directory used for sockfiles by this machine.
896 """
897 if self._sock_dir:
898 return self._sock_dir
899 return self.temp_dir
900
901 @property
Cleber Rosab306e262021-02-11 16:55:05 -0500902 def log_dir(self) -> str:
903 """
904 Returns a directory to be used for writing logs
905 """
906 if self._log_dir is None:
907 return self.temp_dir
908 return self._log_dir