blob: 350aa8bb26043c07857323049c26480102fd1b4e [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 drain_console: bool = False,
Cleber Rosab306e262021-02-11 16:55:05 -0500131 console_log: Optional[str] = None,
Emanuele Giuseppe Espositoe2f948a2021-08-09 11:00:59 +0200132 log_dir: Optional[str] = None,
Vladimir Sementsov-Ogievskiyada73a42022-06-24 22:52:52 +0300133 qmp_timer: Optional[float] = 30):
John Snowabf0bf92019-06-27 17:28:14 -0400134 '''
135 Initialize a QEMUMachine
136
137 @param binary: path to the qemu binary
138 @param args: list of extra arguments
139 @param wrapper: list of arguments used as prefix to qemu binary
140 @param name: prefix for socket and log file names (default: qemu-PID)
John Snow859aeb62021-05-27 17:16:51 -0400141 @param base_temp_dir: default location where temp files are created
John Snowabf0bf92019-06-27 17:28:14 -0400142 @param monitor_address: address for QMP monitor
Robert Foley0fc8f662020-07-01 14:56:24 +0100143 @param drain_console: (optional) True to drain console socket to buffer
John Snowc5e61a62020-10-06 19:58:00 -0400144 @param console_log: (optional) path to console log file
Cleber Rosab306e262021-02-11 16:55:05 -0500145 @param log_dir: where to create and keep log files
Emanuele Giuseppe Espositoe2f948a2021-08-09 11:00:59 +0200146 @param qmp_timer: (optional) default QMP socket timeout
John Snowabf0bf92019-06-27 17:28:14 -0400147 @note: Qemu process is not started until launch() is used.
148 '''
John Snow82e65172021-06-29 17:43:11 -0400149 # pylint: disable=too-many-arguments
150
John Snowc5e61a62020-10-06 19:58:00 -0400151 # Direct user configuration
152
153 self._binary = binary
John Snowc5e61a62020-10-06 19:58:00 -0400154 self._args = list(args)
John Snowc5e61a62020-10-06 19:58:00 -0400155 self._wrapper = wrapper
Emanuele Giuseppe Espositoe2f948a2021-08-09 11:00:59 +0200156 self._qmp_timer = qmp_timer
John Snowc5e61a62020-10-06 19:58:00 -0400157
Peter Delevoryasf9922932023-01-10 00:29:30 -0800158 self._name = name or f"{id(self):x}"
Marc-André Lureaubd4c0ef2023-01-11 12:01:01 +0400159 self._sock_pair: Optional[Tuple[socket.socket, socket.socket]] = None
John Snoweb55fae2023-07-20 09:04:47 -0400160 self._cons_sock_pair: Optional[
161 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
Cleber Rosab306e262021-02-11 16:55:05 -0500164 self._log_dir = log_dir
John Snowc5e61a62020-10-06 19:58:00 -0400165
Marc-André Lureaubd4c0ef2023-01-11 12:01:01 +0400166 self._monitor_address = monitor_address
John Snowc5e61a62020-10-06 19:58:00 -0400167
168 self._console_log_path = console_log
169 if self._console_log_path:
170 # In order to log the console, buffering needs to be enabled.
171 self._drain_console = True
172 else:
173 self._drain_console = drain_console
174
175 # Runstate
John Snowf12a2822020-10-06 19:58:08 -0400176 self._qemu_log_path: Optional[str] = None
177 self._qemu_log_file: Optional[BinaryIO] = None
John Snow9223fda2020-10-06 19:58:05 -0400178 self._popen: Optional['subprocess.Popen[bytes]'] = None
John Snowf12a2822020-10-06 19:58:08 -0400179 self._events: List[QMPMessage] = []
180 self._iolog: Optional[str] = None
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500181 self._qmp_set = True # Enable QMP monitor by default.
John Snowbeb6b572021-05-27 17:16:53 -0400182 self._qmp_connection: Optional[QEMUMonitorProtocol] = None
John Snowaad3f3b2020-10-06 19:58:06 -0400183 self._qemu_full_args: Tuple[str, ...] = ()
John Snowabf0bf92019-06-27 17:28:14 -0400184 self._launched = False
John Snowf12a2822020-10-06 19:58:08 -0400185 self._machine: Optional[str] = None
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100186 self._console_index = 0
John Snowabf0bf92019-06-27 17:28:14 -0400187 self._console_set = False
John Snowf12a2822020-10-06 19:58:08 -0400188 self._console_device_type: Optional[str] = None
John Snowf12a2822020-10-06 19:58:08 -0400189 self._console_socket: Optional[socket.socket] = None
190 self._remove_files: List[str] = []
John Snowde6e08b2020-07-10 01:06:48 -0400191 self._user_killed = False
John Snowb9420e42021-10-26 13:56:05 -0400192 self._quit_issued = False
John Snowabf0bf92019-06-27 17:28:14 -0400193
Vladimir Sementsov-Ogievskiy15c3b862021-08-24 11:38:47 +0300194 def __enter__(self: _T) -> _T:
John Snowabf0bf92019-06-27 17:28:14 -0400195 return self
196
John Snow1dda0402020-05-14 01:53:44 -0400197 def __exit__(self,
198 exc_type: Optional[Type[BaseException]],
199 exc_val: Optional[BaseException],
200 exc_tb: Optional[TracebackType]) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400201 self.shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400202
John Snowf12a2822020-10-06 19:58:08 -0400203 def add_monitor_null(self) -> None:
John Snow306dfcd2019-06-27 17:28:15 -0400204 """
205 This can be used to add an unused monitor instance.
206 """
John Snowabf0bf92019-06-27 17:28:14 -0400207 self._args.append('-monitor')
208 self._args.append('null')
209
Vladimir Sementsov-Ogievskiy15c3b862021-08-24 11:38:47 +0300210 def add_fd(self: _T, fd: int, fdset: int,
211 opaque: str, opts: str = '') -> _T:
John Snowabf0bf92019-06-27 17:28:14 -0400212 """
213 Pass a file descriptor to the VM
214 """
215 options = ['fd=%d' % fd,
216 'set=%d' % fdset,
217 'opaque=%s' % opaque]
218 if opts:
219 options.append(opts)
220
221 # This did not exist before 3.4, but since then it is
222 # mandatory for our purpose
223 if hasattr(os, 'set_inheritable'):
224 os.set_inheritable(fd, True)
225
226 self._args.append('-add-fd')
227 self._args.append(','.join(options))
228 return self
229
John Snowf12a2822020-10-06 19:58:08 -0400230 def send_fd_scm(self, fd: Optional[int] = None,
231 file_path: Optional[str] = None) -> int:
John Snow306dfcd2019-06-27 17:28:15 -0400232 """
John Snow514d00d2021-09-22 20:49:30 -0400233 Send an fd or file_path to the remote via SCM_RIGHTS.
John Snow306dfcd2019-06-27 17:28:15 -0400234
John Snow514d00d2021-09-22 20:49:30 -0400235 Exactly one of fd and file_path must be given. If it is
236 file_path, the file will be opened read-only and the new file
237 descriptor will be sent to the remote.
John Snow306dfcd2019-06-27 17:28:15 -0400238 """
John Snowabf0bf92019-06-27 17:28:14 -0400239 if file_path is not None:
240 assert fd is None
John Snow514d00d2021-09-22 20:49:30 -0400241 with open(file_path, "rb") as passfile:
242 fd = passfile.fileno()
243 self._qmp.send_fd_scm(fd)
John Snowabf0bf92019-06-27 17:28:14 -0400244 else:
245 assert fd is not None
John Snow514d00d2021-09-22 20:49:30 -0400246 self._qmp.send_fd_scm(fd)
John Snowabf0bf92019-06-27 17:28:14 -0400247
John Snow514d00d2021-09-22 20:49:30 -0400248 return 0
John Snowabf0bf92019-06-27 17:28:14 -0400249
250 @staticmethod
John Snowf12a2822020-10-06 19:58:08 -0400251 def _remove_if_exists(path: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400252 """
253 Remove file object at path if it exists
254 """
255 try:
256 os.remove(path)
257 except OSError as exception:
258 if exception.errno == errno.ENOENT:
259 return
260 raise
261
John Snowf12a2822020-10-06 19:58:08 -0400262 def is_running(self) -> bool:
John Snow306dfcd2019-06-27 17:28:15 -0400263 """Returns true if the VM is running."""
John Snowabf0bf92019-06-27 17:28:14 -0400264 return self._popen is not None and self._popen.poll() is None
265
John Snow9223fda2020-10-06 19:58:05 -0400266 @property
267 def _subp(self) -> 'subprocess.Popen[bytes]':
268 if self._popen is None:
269 raise QEMUMachineError('Subprocess pipe not present')
270 return self._popen
271
John Snowf12a2822020-10-06 19:58:08 -0400272 def exitcode(self) -> Optional[int]:
John Snow306dfcd2019-06-27 17:28:15 -0400273 """Returns the exit code if possible, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400274 if self._popen is None:
275 return None
276 return self._popen.poll()
277
John Snowf12a2822020-10-06 19:58:08 -0400278 def get_pid(self) -> Optional[int]:
John Snow306dfcd2019-06-27 17:28:15 -0400279 """Returns the PID of the running process, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400280 if not self.is_running():
281 return None
John Snow9223fda2020-10-06 19:58:05 -0400282 return self._subp.pid
John Snowabf0bf92019-06-27 17:28:14 -0400283
John Snowf12a2822020-10-06 19:58:08 -0400284 def _load_io_log(self) -> None:
John Snow5690b432021-09-16 14:22:47 -0400285 # Assume that the output encoding of QEMU's terminal output is
286 # defined by our locale. If indeterminate, allow open() to fall
287 # back to the platform default.
288 _, encoding = locale.getlocale()
John Snowabf0bf92019-06-27 17:28:14 -0400289 if self._qemu_log_path is not None:
John Snow5690b432021-09-16 14:22:47 -0400290 with open(self._qemu_log_path, "r", encoding=encoding) as iolog:
John Snowabf0bf92019-06-27 17:28:14 -0400291 self._iolog = iolog.read()
292
John Snow652809d2020-10-06 19:58:01 -0400293 @property
294 def _base_args(self) -> List[str]:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500295 args = ['-display', 'none', '-vga', 'none']
John Snowc4e60232020-10-06 19:57:59 -0400296
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500297 if self._qmp_set:
Marc-André Lureaubd4c0ef2023-01-11 12:01:01 +0400298 if self._sock_pair:
John Snowfe9ce0b2023-07-20 09:04:45 -0400299 moncdev = f"socket,id=mon,fd={self._sock_pair[0].fileno()}"
Marc-André Lureaubd4c0ef2023-01-11 12:01:01 +0400300 elif isinstance(self._monitor_address, tuple):
John Snowc4e60232020-10-06 19:57:59 -0400301 moncdev = "socket,id=mon,host={},port={}".format(
302 *self._monitor_address
303 )
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500304 else:
John Snowc4e60232020-10-06 19:57:59 -0400305 moncdev = f"socket,id=mon,path={self._monitor_address}"
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500306 args.extend(['-chardev', moncdev, '-mon',
307 'chardev=mon,mode=control'])
John Snowc4e60232020-10-06 19:57:59 -0400308
John Snowabf0bf92019-06-27 17:28:14 -0400309 if self._machine is not None:
310 args.extend(['-machine', self._machine])
John Snow9b8ccd62020-05-28 18:21:28 -0400311 for _ in range(self._console_index):
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100312 args.extend(['-serial', 'null'])
John Snowabf0bf92019-06-27 17:28:14 -0400313 if self._console_set:
John Snoweb55fae2023-07-20 09:04:47 -0400314 assert self._cons_sock_pair is not None
315 fd = self._cons_sock_pair[0].fileno()
316 chardev = f"socket,id=console,fd={fd}"
John Snowabf0bf92019-06-27 17:28:14 -0400317 args.extend(['-chardev', chardev])
318 if self._console_device_type is None:
319 args.extend(['-serial', 'chardev:console'])
320 else:
321 device = '%s,chardev=console' % self._console_device_type
322 args.extend(['-device', device])
323 return args
324
Wainer dos Santos Moschetta555fe0c2021-04-30 10:34:12 -0300325 @property
326 def args(self) -> List[str]:
327 """Returns the list of arguments given to the QEMU binary."""
328 return self._args
329
John Snowf12a2822020-10-06 19:58:08 -0400330 def _pre_launch(self) -> None:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500331 if self._qmp_set:
Marc-André Lureaubd4c0ef2023-01-11 12:01:01 +0400332 if self._monitor_address is None:
333 self._sock_pair = socket.socketpair()
John Snowfe9ce0b2023-07-20 09:04:45 -0400334 os.set_inheritable(self._sock_pair[0].fileno(), True)
Marc-André Lureaubd4c0ef2023-01-11 12:01:01 +0400335 sock = self._sock_pair[1]
John Snow6eeb3de2021-11-18 15:46:15 -0500336 if isinstance(self._monitor_address, str):
John Snowc4e60232020-10-06 19:57:59 -0400337 self._remove_files.append(self._monitor_address)
John Snow7f5f3ae2023-05-17 12:34:04 -0400338
John Snow5bbc5932023-05-17 12:34:05 -0400339 sock_or_addr = self._monitor_address or sock
340 assert sock_or_addr is not None
341
John Snowbeb6b572021-05-27 17:16:53 -0400342 self._qmp_connection = QEMUMonitorProtocol(
John Snow5bbc5932023-05-17 12:34:05 -0400343 sock_or_addr,
John Snow7f5f3ae2023-05-17 12:34:04 -0400344 server=bool(self._monitor_address),
John Snowc4e60232020-10-06 19:57:59 -0400345 nickname=self._name
346 )
John Snowabf0bf92019-06-27 17:28:14 -0400347
John Snoweb55fae2023-07-20 09:04:47 -0400348 if self._console_set:
349 self._cons_sock_pair = socket.socketpair()
350 os.set_inheritable(self._cons_sock_pair[0].fileno(), True)
351
John Snow63c33f32021-05-27 17:16:49 -0400352 # NOTE: Make sure any opened resources are *definitely* freed in
353 # _post_shutdown()!
354 # pylint: disable=consider-using-with
Cleber Rosab306e262021-02-11 16:55:05 -0500355 self._qemu_log_path = os.path.join(self.log_dir, self._name + ".log")
John Snow63c33f32021-05-27 17:16:49 -0400356 self._qemu_log_file = open(self._qemu_log_path, 'wb')
357
John Snowb1ca9912021-11-18 15:46:17 -0500358 self._iolog = None
359 self._qemu_full_args = tuple(chain(
360 self._wrapper,
361 [self._binary],
362 self._base_args,
363 self._args
364 ))
365
John Snowf12a2822020-10-06 19:58:08 -0400366 def _post_launch(self) -> None:
Marc-André Lureaubd4c0ef2023-01-11 12:01:01 +0400367 if self._sock_pair:
368 self._sock_pair[0].close()
John Snowbe1183e2020-10-06 19:58:04 -0400369 if self._qmp_connection:
John Snow7f5f3ae2023-05-17 12:34:04 -0400370 if self._sock_pair:
371 self._qmp.connect()
372 else:
373 self._qmp.accept(self._qmp_timer)
John Snowabf0bf92019-06-27 17:28:14 -0400374
Emanuele Giuseppe Espositoeb7a91d2021-08-09 11:01:13 +0200375 def _close_qemu_log_file(self) -> None:
376 if self._qemu_log_file is not None:
377 self._qemu_log_file.close()
378 self._qemu_log_file = None
379
John Snowf12a2822020-10-06 19:58:08 -0400380 def _post_shutdown(self) -> None:
John Snowa3842cb2020-07-10 01:06:42 -0400381 """
382 Called to cleanup the VM instance after the process has exited.
383 May also be called after a failed launch.
384 """
John Snow9cccb332022-10-27 14:58:35 -0400385 LOG.debug("Cleaning up after VM process")
John Snow49a608b2021-10-26 13:56:06 -0400386 try:
387 self._close_qmp_connection()
388 except Exception as err: # pylint: disable=broad-except
389 LOG.warning(
390 "Exception closing QMP connection: %s",
391 str(err) if str(err) else type(err).__name__
392 )
393 finally:
394 assert self._qmp_connection is None
John Snow671940e2020-07-10 01:06:39 -0400395
Emanuele Giuseppe Espositoeb7a91d2021-08-09 11:01:13 +0200396 self._close_qemu_log_file()
John Snowabf0bf92019-06-27 17:28:14 -0400397
Cleber Rosa3c1e16c2021-02-11 17:01:41 -0500398 self._load_io_log()
399
John Snowabf0bf92019-06-27 17:28:14 -0400400 self._qemu_log_path = None
401
John Snowabf0bf92019-06-27 17:28:14 -0400402 if self._temp_dir is not None:
403 shutil.rmtree(self._temp_dir)
404 self._temp_dir = None
405
Max Reitz32558ce2019-10-17 15:31:34 +0200406 while len(self._remove_files) > 0:
407 self._remove_if_exists(self._remove_files.pop())
408
John Snow14661d92020-07-10 01:06:38 -0400409 exitcode = self.exitcode()
John Snowde6e08b2020-07-10 01:06:48 -0400410 if (exitcode is not None and exitcode < 0
411 and not (self._user_killed and exitcode == -signal.SIGKILL)):
John Snow14661d92020-07-10 01:06:38 -0400412 msg = 'qemu received signal %i; command: "%s"'
413 if self._qemu_full_args:
414 command = ' '.join(self._qemu_full_args)
415 else:
416 command = ''
417 LOG.warning(msg, -int(exitcode), command)
418
John Snowb9420e42021-10-26 13:56:05 -0400419 self._quit_issued = False
John Snowde6e08b2020-07-10 01:06:48 -0400420 self._user_killed = False
John Snow14661d92020-07-10 01:06:38 -0400421 self._launched = False
422
John Snowf12a2822020-10-06 19:58:08 -0400423 def launch(self) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400424 """
425 Launch the VM and make sure we cleanup and expose the
426 command line/output in case of exception
427 """
428
429 if self._launched:
430 raise QEMUMachineError('VM already launched')
431
John Snowabf0bf92019-06-27 17:28:14 -0400432 try:
433 self._launch()
John Snow50465f92022-01-31 23:11:32 -0500434 except BaseException as exc:
John Snow1611e6c2021-11-18 15:46:18 -0500435 # We may have launched the process but it may
436 # have exited before we could connect via QMP.
437 # Assume the VM didn't launch or is exiting.
438 # If we don't wait for the process, exitcode() may still be
439 # 'None' by the time control is ceded back to the caller.
440 if self._launched:
441 self.wait()
442 else:
443 self._post_shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400444
John Snow50465f92022-01-31 23:11:32 -0500445 if isinstance(exc, Exception):
446 raise VMLaunchFailure(
447 exitcode=self.exitcode(),
448 command=' '.join(self._qemu_full_args),
449 output=self._iolog
450 ) from exc
451
452 # Don't wrap 'BaseException'; doing so would downgrade
453 # that exception. However, we still want to clean up.
John Snowabf0bf92019-06-27 17:28:14 -0400454 raise
455
John Snowf12a2822020-10-06 19:58:08 -0400456 def _launch(self) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400457 """
458 Launch the VM and establish a QMP connection
459 """
John Snowabf0bf92019-06-27 17:28:14 -0400460 self._pre_launch()
John Snowabf0bf92019-06-27 17:28:14 -0400461 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
John Snowa0eae172021-05-27 17:16:50 -0400462
463 # Cleaning up of this subprocess is guaranteed by _do_shutdown.
464 # pylint: disable=consider-using-with
John Snowabf0bf92019-06-27 17:28:14 -0400465 self._popen = subprocess.Popen(self._qemu_full_args,
John Snow07b71232021-05-27 17:16:46 -0400466 stdin=subprocess.DEVNULL,
John Snowabf0bf92019-06-27 17:28:14 -0400467 stdout=self._qemu_log_file,
468 stderr=subprocess.STDOUT,
469 shell=False,
470 close_fds=False)
John Snow1611e6c2021-11-18 15:46:18 -0500471 self._launched = True
John Snowabf0bf92019-06-27 17:28:14 -0400472 self._post_launch()
473
John Snow49a608b2021-10-26 13:56:06 -0400474 def _close_qmp_connection(self) -> None:
475 """
476 Close the underlying QMP connection, if any.
477
478 Dutifully report errors that occurred while closing, but assume
479 that any error encountered indicates an abnormal termination
480 process and not a failure to close.
481 """
482 if self._qmp_connection is None:
483 return
484
485 try:
486 self._qmp.close()
487 except EOFError:
488 # EOF can occur as an Exception here when using the Async
489 # QMP backend. It indicates that the server closed the
490 # stream. If we successfully issued 'quit' at any point,
491 # then this was expected. If the remote went away without
492 # our permission, it's worth reporting that as an abnormal
493 # shutdown case.
494 if not (self._user_killed or self._quit_issued):
495 raise
496 finally:
497 self._qmp_connection = None
498
John Snowe2c97f12020-07-10 01:06:40 -0400499 def _early_cleanup(self) -> None:
500 """
501 Perform any cleanup that needs to happen before the VM exits.
John Snowa3842cb2020-07-10 01:06:42 -0400502
John Snow1611e6c2021-11-18 15:46:18 -0500503 This method may be called twice upon shutdown, once each by soft
504 and hard shutdown in failover scenarios.
John Snowe2c97f12020-07-10 01:06:40 -0400505 """
506 # If we keep the console socket open, we may deadlock waiting
507 # for QEMU to exit, while QEMU is waiting for the socket to
Peter Maydell9323e792022-06-08 19:38:47 +0100508 # become writable.
John Snowe2c97f12020-07-10 01:06:40 -0400509 if self._console_socket is not None:
John Snow9cccb332022-10-27 14:58:35 -0400510 LOG.debug("Closing console socket")
John Snowe2c97f12020-07-10 01:06:40 -0400511 self._console_socket.close()
512 self._console_socket = None
513
John Snow193bf1c2020-07-10 01:06:47 -0400514 def _hard_shutdown(self) -> None:
515 """
516 Perform early cleanup, kill the VM, and wait for it to terminate.
517
518 :raise subprocess.Timeout: When timeout is exceeds 60 seconds
519 waiting for the QEMU process to terminate.
520 """
John Snow9cccb332022-10-27 14:58:35 -0400521 LOG.debug("Performing hard shutdown")
John Snow193bf1c2020-07-10 01:06:47 -0400522 self._early_cleanup()
John Snow9223fda2020-10-06 19:58:05 -0400523 self._subp.kill()
524 self._subp.wait(timeout=60)
John Snow193bf1c2020-07-10 01:06:47 -0400525
John Snowb9420e42021-10-26 13:56:05 -0400526 def _soft_shutdown(self, timeout: Optional[int]) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400527 """
528 Perform early cleanup, attempt to gracefully shut down the VM, and wait
529 for it to terminate.
530
John Snow8226a4b2020-07-20 12:02:52 -0400531 :param timeout: Timeout in seconds for graceful shutdown.
532 A value of None is an infinite wait.
John Snow193bf1c2020-07-10 01:06:47 -0400533
534 :raise ConnectionReset: On QMP communication errors
535 :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
536 the QEMU process to terminate.
537 """
John Snow9cccb332022-10-27 14:58:35 -0400538 LOG.debug("Attempting graceful termination")
539
John Snow193bf1c2020-07-10 01:06:47 -0400540 self._early_cleanup()
541
John Snow9cccb332022-10-27 14:58:35 -0400542 if self._quit_issued:
543 LOG.debug(
544 "Anticipating QEMU termination due to prior 'quit' command, "
545 "or explicit call to wait()"
546 )
547 else:
548 LOG.debug("Politely asking QEMU to terminate")
549
John Snowbe1183e2020-10-06 19:58:04 -0400550 if self._qmp_connection:
John Snow49a608b2021-10-26 13:56:06 -0400551 try:
552 if not self._quit_issued:
553 # May raise ExecInterruptedError or StateError if the
554 # connection dies or has *already* died.
555 self.qmp('quit')
556 finally:
557 # Regardless, we want to quiesce the connection.
558 self._close_qmp_connection()
John Snow3c6e5e82022-10-27 14:58:36 -0400559 elif not self._quit_issued:
560 LOG.debug(
561 "Not anticipating QEMU quit and no QMP connection present, "
562 "issuing SIGTERM"
563 )
564 self._subp.terminate()
John Snow193bf1c2020-07-10 01:06:47 -0400565
566 # May raise subprocess.TimeoutExpired
John Snow9cccb332022-10-27 14:58:35 -0400567 LOG.debug(
568 "Waiting (timeout=%s) for QEMU process (pid=%s) to terminate",
569 timeout, self._subp.pid
570 )
John Snow9223fda2020-10-06 19:58:05 -0400571 self._subp.wait(timeout=timeout)
John Snow193bf1c2020-07-10 01:06:47 -0400572
John Snowb9420e42021-10-26 13:56:05 -0400573 def _do_shutdown(self, timeout: Optional[int]) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400574 """
575 Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
576
John Snow8226a4b2020-07-20 12:02:52 -0400577 :param timeout: Timeout in seconds for graceful shutdown.
578 A value of None is an infinite wait.
John Snow193bf1c2020-07-10 01:06:47 -0400579
580 :raise AbnormalShutdown: When the VM could not be shut down gracefully.
581 The inner exception will likely be ConnectionReset or
582 subprocess.TimeoutExpired. In rare cases, non-graceful termination
583 may result in its own exceptions, likely subprocess.TimeoutExpired.
584 """
585 try:
John Snowb9420e42021-10-26 13:56:05 -0400586 self._soft_shutdown(timeout)
John Snow193bf1c2020-07-10 01:06:47 -0400587 except Exception as exc:
John Snow9cccb332022-10-27 14:58:35 -0400588 if isinstance(exc, subprocess.TimeoutExpired):
589 LOG.debug("Timed out waiting for QEMU process to exit")
590 LOG.debug("Graceful shutdown failed", exc_info=True)
591 LOG.debug("Falling back to hard shutdown")
John Snow193bf1c2020-07-10 01:06:47 -0400592 self._hard_shutdown()
593 raise AbnormalShutdown("Could not perform graceful shutdown") \
594 from exc
595
John Snowb9420e42021-10-26 13:56:05 -0400596 def shutdown(self,
John Snowc9b30452020-07-10 01:06:43 -0400597 hard: bool = False,
John Snow8226a4b2020-07-20 12:02:52 -0400598 timeout: Optional[int] = 30) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400599 """
John Snow193bf1c2020-07-10 01:06:47 -0400600 Terminate the VM (gracefully if possible) and perform cleanup.
601 Cleanup will always be performed.
602
603 If the VM has not yet been launched, or shutdown(), wait(), or kill()
604 have already been called, this method does nothing.
605
John Snow193bf1c2020-07-10 01:06:47 -0400606 :param hard: When true, do not attempt graceful shutdown, and
607 suppress the SIGKILL warning log message.
608 :param timeout: Optional timeout in seconds for graceful shutdown.
John Snow8226a4b2020-07-20 12:02:52 -0400609 Default 30 seconds, A `None` value is an infinite wait.
John Snowabf0bf92019-06-27 17:28:14 -0400610 """
John Snowa3842cb2020-07-10 01:06:42 -0400611 if not self._launched:
612 return
613
John Snow9cccb332022-10-27 14:58:35 -0400614 LOG.debug("Shutting down VM appliance; timeout=%s", timeout)
615 if hard:
616 LOG.debug("Caller requests immediate termination of QEMU process.")
617
John Snow193bf1c2020-07-10 01:06:47 -0400618 try:
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300619 if hard:
John Snowde6e08b2020-07-10 01:06:48 -0400620 self._user_killed = True
John Snow193bf1c2020-07-10 01:06:47 -0400621 self._hard_shutdown()
622 else:
John Snowb9420e42021-10-26 13:56:05 -0400623 self._do_shutdown(timeout)
John Snow193bf1c2020-07-10 01:06:47 -0400624 finally:
625 self._post_shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400626
John Snowf12a2822020-10-06 19:58:08 -0400627 def kill(self) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400628 """
629 Terminate the VM forcefully, wait for it to exit, and perform cleanup.
630 """
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300631 self.shutdown(hard=True)
632
John Snow8226a4b2020-07-20 12:02:52 -0400633 def wait(self, timeout: Optional[int] = 30) -> None:
John Snow89528052020-07-10 01:06:44 -0400634 """
635 Wait for the VM to power off and perform post-shutdown cleanup.
636
John Snow8226a4b2020-07-20 12:02:52 -0400637 :param timeout: Optional timeout in seconds. Default 30 seconds.
638 A value of `None` is an infinite wait.
John Snow89528052020-07-10 01:06:44 -0400639 """
John Snowb9420e42021-10-26 13:56:05 -0400640 self._quit_issued = True
641 self.shutdown(timeout=timeout)
John Snow89528052020-07-10 01:06:44 -0400642
John Snowf12a2822020-10-06 19:58:08 -0400643 def set_qmp_monitor(self, enabled: bool = True) -> None:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500644 """
645 Set the QMP monitor.
646
647 @param enabled: if False, qmp monitor options will be removed from
648 the base arguments of the resulting QEMU command
649 line. Default is True.
John Snow5c02c862021-06-29 17:43:23 -0400650
651 .. note:: Call this function before launch().
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500652 """
John Snowbe1183e2020-10-06 19:58:04 -0400653 self._qmp_set = enabled
654
655 @property
John Snowbeb6b572021-05-27 17:16:53 -0400656 def _qmp(self) -> QEMUMonitorProtocol:
John Snowbe1183e2020-10-06 19:58:04 -0400657 if self._qmp_connection is None:
658 raise QEMUMachineError("Attempt to access QMP with no connection")
659 return self._qmp_connection
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500660
John Snowaaa81ec2020-10-06 19:58:03 -0400661 @classmethod
Vladimir Sementsov-Ogievskiyc7daa572021-08-24 11:38:45 +0300662 def _qmp_args(cls, conv_keys: bool,
663 args: Dict[str, Any]) -> Dict[str, object]:
664 if conv_keys:
665 return {k.replace('_', '-'): v for k, v in args.items()}
666
667 return args
John Snowabf0bf92019-06-27 17:28:14 -0400668
John Snowaaa81ec2020-10-06 19:58:03 -0400669 def qmp(self, cmd: str,
Vladimir Sementsov-Ogievskiy3f3c9b42021-08-24 11:38:46 +0300670 args_dict: Optional[Dict[str, object]] = None,
671 conv_keys: Optional[bool] = None,
John Snowaaa81ec2020-10-06 19:58:03 -0400672 **args: Any) -> QMPMessage:
673 """
674 Invoke a QMP command and return the response dict
675 """
Vladimir Sementsov-Ogievskiy3f3c9b42021-08-24 11:38:46 +0300676 if args_dict is not None:
677 assert not args
678 assert conv_keys is None
679 args = args_dict
680 conv_keys = False
681
682 if conv_keys is None:
683 conv_keys = True
684
Vladimir Sementsov-Ogievskiyc7daa572021-08-24 11:38:45 +0300685 qmp_args = self._qmp_args(conv_keys, args)
John Snowb9420e42021-10-26 13:56:05 -0400686 ret = self._qmp.cmd(cmd, args=qmp_args)
687 if cmd == 'quit' and 'error' not in ret and 'return' in ret:
688 self._quit_issued = True
689 return ret
John Snowabf0bf92019-06-27 17:28:14 -0400690
John Snowf12a2822020-10-06 19:58:08 -0400691 def command(self, cmd: str,
692 conv_keys: bool = True,
693 **args: Any) -> QMPReturnValue:
John Snowabf0bf92019-06-27 17:28:14 -0400694 """
695 Invoke a QMP command.
696 On success return the response dict.
697 On failure raise an exception.
698 """
Vladimir Sementsov-Ogievskiyc7daa572021-08-24 11:38:45 +0300699 qmp_args = self._qmp_args(conv_keys, args)
John Snowb9420e42021-10-26 13:56:05 -0400700 ret = self._qmp.command(cmd, **qmp_args)
701 if cmd == 'quit':
702 self._quit_issued = True
703 return ret
John Snowabf0bf92019-06-27 17:28:14 -0400704
John Snowf12a2822020-10-06 19:58:08 -0400705 def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400706 """
707 Poll for one queued QMP events and return it
708 """
John Snow306dfcd2019-06-27 17:28:15 -0400709 if self._events:
John Snowabf0bf92019-06-27 17:28:14 -0400710 return self._events.pop(0)
711 return self._qmp.pull_event(wait=wait)
712
John Snowf12a2822020-10-06 19:58:08 -0400713 def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400714 """
715 Poll for queued QMP events and return a list of dicts
716 """
717 events = self._qmp.get_events(wait=wait)
718 events.extend(self._events)
719 del self._events[:]
John Snowabf0bf92019-06-27 17:28:14 -0400720 return events
721
722 @staticmethod
John Snowf12a2822020-10-06 19:58:08 -0400723 def event_match(event: Any, match: Optional[Any]) -> bool:
John Snowabf0bf92019-06-27 17:28:14 -0400724 """
725 Check if an event matches optional match criteria.
726
727 The match criteria takes the form of a matching subdict. The event is
728 checked to be a superset of the subdict, recursively, with matching
729 values whenever the subdict values are not None.
730
731 This has a limitation that you cannot explicitly check for None values.
732
733 Examples, with the subdict queries on the left:
734 - None matches any object.
735 - {"foo": None} matches {"foo": {"bar": 1}}
736 - {"foo": None} matches {"foo": 5}
737 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
738 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
739 """
740 if match is None:
741 return True
742
743 try:
744 for key in match:
745 if key in event:
746 if not QEMUMachine.event_match(event[key], match[key]):
747 return False
748 else:
749 return False
750 return True
751 except TypeError:
752 # either match or event wasn't iterable (not a dict)
John Snowf12a2822020-10-06 19:58:08 -0400753 return bool(match == event)
John Snowabf0bf92019-06-27 17:28:14 -0400754
John Snowf12a2822020-10-06 19:58:08 -0400755 def event_wait(self, name: str,
756 timeout: float = 60.0,
757 match: Optional[QMPMessage] = None) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400758 """
759 event_wait waits for and returns a named event from QMP with a timeout.
760
761 name: The event to wait for.
762 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
763 match: Optional match criteria. See event_match for details.
764 """
765 return self.events_wait([(name, match)], timeout)
766
John Snowf12a2822020-10-06 19:58:08 -0400767 def events_wait(self,
768 events: Sequence[Tuple[str, Any]],
769 timeout: float = 60.0) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400770 """
John Snow1847a4a2020-10-06 19:58:02 -0400771 events_wait waits for and returns a single named event from QMP.
772 In the case of multiple qualifying events, this function returns the
773 first one.
John Snowabf0bf92019-06-27 17:28:14 -0400774
John Snow1847a4a2020-10-06 19:58:02 -0400775 :param events: A sequence of (name, match_criteria) tuples.
776 The match criteria are optional and may be None.
777 See event_match for details.
778 :param timeout: Optional timeout, in seconds.
779 See QEMUMonitorProtocol.pull_event.
780
John Snowa4225302022-03-21 16:33:12 -0400781 :raise asyncio.TimeoutError:
782 If timeout was non-zero and no matching events were found.
783
John Snow1847a4a2020-10-06 19:58:02 -0400784 :return: A QMP event matching the filter criteria.
785 If timeout was 0 and no event matched, None.
John Snowabf0bf92019-06-27 17:28:14 -0400786 """
John Snowf12a2822020-10-06 19:58:08 -0400787 def _match(event: QMPMessage) -> bool:
John Snowabf0bf92019-06-27 17:28:14 -0400788 for name, match in events:
John Snow306dfcd2019-06-27 17:28:15 -0400789 if event['event'] == name and self.event_match(event, match):
John Snowabf0bf92019-06-27 17:28:14 -0400790 return True
791 return False
792
John Snow1847a4a2020-10-06 19:58:02 -0400793 event: Optional[QMPMessage]
794
John Snowabf0bf92019-06-27 17:28:14 -0400795 # Search cached events
796 for event in self._events:
797 if _match(event):
798 self._events.remove(event)
799 return event
800
801 # Poll for new events
802 while True:
803 event = self._qmp.pull_event(wait=timeout)
John Snow1847a4a2020-10-06 19:58:02 -0400804 if event is None:
805 # NB: None is only returned when timeout is false-ish.
John Snowa4225302022-03-21 16:33:12 -0400806 # Timeouts raise asyncio.TimeoutError instead!
John Snow1847a4a2020-10-06 19:58:02 -0400807 break
John Snowabf0bf92019-06-27 17:28:14 -0400808 if _match(event):
809 return event
810 self._events.append(event)
811
812 return None
813
John Snowf12a2822020-10-06 19:58:08 -0400814 def get_log(self) -> Optional[str]:
John Snowabf0bf92019-06-27 17:28:14 -0400815 """
816 After self.shutdown or failed qemu execution, this returns the output
817 of the qemu process.
818 """
819 return self._iolog
820
John Snowf12a2822020-10-06 19:58:08 -0400821 def add_args(self, *args: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400822 """
823 Adds to the list of extra arguments to be given to the QEMU binary
824 """
825 self._args.extend(args)
826
John Snowf12a2822020-10-06 19:58:08 -0400827 def set_machine(self, machine_type: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400828 """
829 Sets the machine type
830
831 If set, the machine type will be added to the base arguments
832 of the resulting QEMU command line.
833 """
834 self._machine = machine_type
835
John Snowf12a2822020-10-06 19:58:08 -0400836 def set_console(self,
837 device_type: Optional[str] = None,
838 console_index: int = 0) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400839 """
840 Sets the device type for a console device
841
842 If set, the console device and a backing character device will
843 be added to the base arguments of the resulting QEMU command
844 line.
845
846 This is a convenience method that will either use the provided
847 device type, or default to a "-serial chardev:console" command
848 line argument.
849
850 The actual setting of command line arguments will be be done at
851 machine launch time, as it depends on the temporary directory
852 to be created.
853
854 @param device_type: the device type, such as "isa-serial". If
855 None is given (the default value) a "-serial
856 chardev:console" command line argument will
857 be used instead, resorting to the machine's
858 default device type.
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100859 @param console_index: the index of the console device to use.
860 If not zero, the command line will create
861 'index - 1' consoles and connect them to
862 the 'null' backing character device.
John Snowabf0bf92019-06-27 17:28:14 -0400863 """
864 self._console_set = True
865 self._console_device_type = device_type
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100866 self._console_index = console_index
John Snowabf0bf92019-06-27 17:28:14 -0400867
868 @property
John Snowf12a2822020-10-06 19:58:08 -0400869 def console_socket(self) -> socket.socket:
John Snowabf0bf92019-06-27 17:28:14 -0400870 """
871 Returns a socket connected to the console
872 """
873 if self._console_socket is None:
John Snoweb55fae2023-07-20 09:04:47 -0400874 if not self._console_set:
875 raise QEMUMachineError(
876 "Attempt to access console socket with no connection")
877 assert self._cons_sock_pair is not None
Robert Foley80ded8e2020-07-24 07:45:08 +0100878 self._console_socket = console_socket.ConsoleSocket(
John Snoweb55fae2023-07-20 09:04:47 -0400879 self._cons_sock_pair[1].fileno(),
Robert Foley80ded8e2020-07-24 07:45:08 +0100880 file=self._console_log_path,
881 drain=self._drain_console)
John Snowabf0bf92019-06-27 17:28:14 -0400882 return self._console_socket
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500883
884 @property
885 def temp_dir(self) -> str:
886 """
887 Returns a temporary directory to be used for this machine
888 """
889 if self._temp_dir is None:
890 self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-",
891 dir=self._base_temp_dir)
892 return self._temp_dir
Cleber Rosab306e262021-02-11 16:55:05 -0500893
894 @property
895 def log_dir(self) -> str:
896 """
897 Returns a directory to be used for writing logs
898 """
899 if self._log_dir is None:
900 return self.temp_dir
901 return self._log_dir