blob: 67ab06ca2b6daa531b7c0ad9f7c241c1ca5f6787 [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 Snowd1e04762021-05-27 17:16:59 -040043from qemu.qmp import ( # pylint: disable=import-error
John Snowbeb6b572021-05-27 17:16:53 -040044 QMPMessage,
45 QMPReturnValue,
46 SocketAddrT,
47)
48
49from . import console_socket
John Snow932ca4b2020-10-06 19:57:58 -040050
John Snowabf0bf92019-06-27 17:28:14 -040051
John Snow76cd3582021-10-26 13:56:12 -040052if os.environ.get('QEMU_PYTHON_LEGACY_QMP'):
53 from qemu.qmp import QEMUMonitorProtocol
54else:
55 from qemu.aqmp.legacy import QEMUMonitorProtocol
56
57
John Snowabf0bf92019-06-27 17:28:14 -040058LOG = logging.getLogger(__name__)
59
John Snow8dfac2e2020-05-28 18:21:29 -040060
John Snowabf0bf92019-06-27 17:28:14 -040061class QEMUMachineError(Exception):
62 """
63 Exception called when an error in QEMUMachine happens.
64 """
65
66
67class QEMUMachineAddDeviceError(QEMUMachineError):
68 """
69 Exception raised when a request to add a device can not be fulfilled
70
71 The failures are caused by limitations, lack of information or conflicting
72 requests on the QEMUMachine methods. This exception does not represent
73 failures reported by the QEMU binary itself.
74 """
75
76
John Snow193bf1c2020-07-10 01:06:47 -040077class AbnormalShutdown(QEMUMachineError):
78 """
79 Exception raised when a graceful shutdown was requested, but not performed.
80 """
81
82
Vladimir Sementsov-Ogievskiy15c3b862021-08-24 11:38:47 +030083_T = TypeVar('_T', bound='QEMUMachine')
84
85
John Snow9b8ccd62020-05-28 18:21:28 -040086class QEMUMachine:
John Snowabf0bf92019-06-27 17:28:14 -040087 """
John Snowf12a2822020-10-06 19:58:08 -040088 A QEMU VM.
John Snowabf0bf92019-06-27 17:28:14 -040089
John Snow8dfac2e2020-05-28 18:21:29 -040090 Use this object as a context manager to ensure
91 the QEMU process terminates::
John Snowabf0bf92019-06-27 17:28:14 -040092
93 with VM(binary) as vm:
94 ...
95 # vm is guaranteed to be shut down here
96 """
John Snow82e65172021-06-29 17:43:11 -040097 # pylint: disable=too-many-instance-attributes, too-many-public-methods
John Snowabf0bf92019-06-27 17:28:14 -040098
John Snowaad3f3b2020-10-06 19:58:06 -040099 def __init__(self,
100 binary: str,
101 args: Sequence[str] = (),
102 wrapper: Sequence[str] = (),
103 name: Optional[str] = None,
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500104 base_temp_dir: str = "/var/tmp",
John Snowc4e60232020-10-06 19:57:59 -0400105 monitor_address: Optional[SocketAddrT] = None,
John Snowf12a2822020-10-06 19:58:08 -0400106 sock_dir: Optional[str] = None,
107 drain_console: bool = False,
Cleber Rosab306e262021-02-11 16:55:05 -0500108 console_log: Optional[str] = None,
Emanuele Giuseppe Espositoe2f948a2021-08-09 11:00:59 +0200109 log_dir: Optional[str] = None,
110 qmp_timer: Optional[float] = None):
John Snowabf0bf92019-06-27 17:28:14 -0400111 '''
112 Initialize a QEMUMachine
113
114 @param binary: path to the qemu binary
115 @param args: list of extra arguments
116 @param wrapper: list of arguments used as prefix to qemu binary
117 @param name: prefix for socket and log file names (default: qemu-PID)
John Snow859aeb62021-05-27 17:16:51 -0400118 @param base_temp_dir: default location where temp files are created
John Snowabf0bf92019-06-27 17:28:14 -0400119 @param monitor_address: address for QMP monitor
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500120 @param sock_dir: where to create socket (defaults to base_temp_dir)
Robert Foley0fc8f662020-07-01 14:56:24 +0100121 @param drain_console: (optional) True to drain console socket to buffer
John Snowc5e61a62020-10-06 19:58:00 -0400122 @param console_log: (optional) path to console log file
Cleber Rosab306e262021-02-11 16:55:05 -0500123 @param log_dir: where to create and keep log files
Emanuele Giuseppe Espositoe2f948a2021-08-09 11:00:59 +0200124 @param qmp_timer: (optional) default QMP socket timeout
John Snowabf0bf92019-06-27 17:28:14 -0400125 @note: Qemu process is not started until launch() is used.
126 '''
John Snow82e65172021-06-29 17:43:11 -0400127 # pylint: disable=too-many-arguments
128
John Snowc5e61a62020-10-06 19:58:00 -0400129 # Direct user configuration
130
131 self._binary = binary
John Snowc5e61a62020-10-06 19:58:00 -0400132 self._args = list(args)
John Snowc5e61a62020-10-06 19:58:00 -0400133 self._wrapper = wrapper
Emanuele Giuseppe Espositoe2f948a2021-08-09 11:00:59 +0200134 self._qmp_timer = qmp_timer
John Snowc5e61a62020-10-06 19:58:00 -0400135
John Snow72b17fe2021-11-18 15:46:16 -0500136 self._name = name or f"qemu-{os.getpid()}-{id(self):02x}"
John Snow87bf1fe2021-11-18 15:46:14 -0500137 self._temp_dir: Optional[str] = None
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500138 self._base_temp_dir = base_temp_dir
John Snow87bf1fe2021-11-18 15:46:14 -0500139 self._sock_dir = sock_dir
Cleber Rosab306e262021-02-11 16:55:05 -0500140 self._log_dir = log_dir
John Snowc5e61a62020-10-06 19:58:00 -0400141
John Snowc4e60232020-10-06 19:57:59 -0400142 if monitor_address is not None:
143 self._monitor_address = monitor_address
John Snowc4e60232020-10-06 19:57:59 -0400144 else:
145 self._monitor_address = os.path.join(
John Snow87bf1fe2021-11-18 15:46:14 -0500146 self.sock_dir, f"{self._name}-monitor.sock"
John Snowc4e60232020-10-06 19:57:59 -0400147 )
John Snowc5e61a62020-10-06 19:58:00 -0400148
149 self._console_log_path = console_log
150 if self._console_log_path:
151 # In order to log the console, buffering needs to be enabled.
152 self._drain_console = True
153 else:
154 self._drain_console = drain_console
155
156 # Runstate
John Snowf12a2822020-10-06 19:58:08 -0400157 self._qemu_log_path: Optional[str] = None
158 self._qemu_log_file: Optional[BinaryIO] = None
John Snow9223fda2020-10-06 19:58:05 -0400159 self._popen: Optional['subprocess.Popen[bytes]'] = None
John Snowf12a2822020-10-06 19:58:08 -0400160 self._events: List[QMPMessage] = []
161 self._iolog: Optional[str] = None
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500162 self._qmp_set = True # Enable QMP monitor by default.
John Snowbeb6b572021-05-27 17:16:53 -0400163 self._qmp_connection: Optional[QEMUMonitorProtocol] = None
John Snowaad3f3b2020-10-06 19:58:06 -0400164 self._qemu_full_args: Tuple[str, ...] = ()
John Snowabf0bf92019-06-27 17:28:14 -0400165 self._launched = False
John Snowf12a2822020-10-06 19:58:08 -0400166 self._machine: Optional[str] = None
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100167 self._console_index = 0
John Snowabf0bf92019-06-27 17:28:14 -0400168 self._console_set = False
John Snowf12a2822020-10-06 19:58:08 -0400169 self._console_device_type: Optional[str] = None
John Snow652809d2020-10-06 19:58:01 -0400170 self._console_address = os.path.join(
John Snow87bf1fe2021-11-18 15:46:14 -0500171 self.sock_dir, f"{self._name}-console.sock"
John Snow652809d2020-10-06 19:58:01 -0400172 )
John Snowf12a2822020-10-06 19:58:08 -0400173 self._console_socket: Optional[socket.socket] = None
174 self._remove_files: List[str] = []
John Snowde6e08b2020-07-10 01:06:48 -0400175 self._user_killed = False
John Snowb9420e42021-10-26 13:56:05 -0400176 self._quit_issued = False
John Snowabf0bf92019-06-27 17:28:14 -0400177
Vladimir Sementsov-Ogievskiy15c3b862021-08-24 11:38:47 +0300178 def __enter__(self: _T) -> _T:
John Snowabf0bf92019-06-27 17:28:14 -0400179 return self
180
John Snow1dda0402020-05-14 01:53:44 -0400181 def __exit__(self,
182 exc_type: Optional[Type[BaseException]],
183 exc_val: Optional[BaseException],
184 exc_tb: Optional[TracebackType]) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400185 self.shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400186
John Snowf12a2822020-10-06 19:58:08 -0400187 def add_monitor_null(self) -> None:
John Snow306dfcd2019-06-27 17:28:15 -0400188 """
189 This can be used to add an unused monitor instance.
190 """
John Snowabf0bf92019-06-27 17:28:14 -0400191 self._args.append('-monitor')
192 self._args.append('null')
193
Vladimir Sementsov-Ogievskiy15c3b862021-08-24 11:38:47 +0300194 def add_fd(self: _T, fd: int, fdset: int,
195 opaque: str, opts: str = '') -> _T:
John Snowabf0bf92019-06-27 17:28:14 -0400196 """
197 Pass a file descriptor to the VM
198 """
199 options = ['fd=%d' % fd,
200 'set=%d' % fdset,
201 'opaque=%s' % opaque]
202 if opts:
203 options.append(opts)
204
205 # This did not exist before 3.4, but since then it is
206 # mandatory for our purpose
207 if hasattr(os, 'set_inheritable'):
208 os.set_inheritable(fd, True)
209
210 self._args.append('-add-fd')
211 self._args.append(','.join(options))
212 return self
213
John Snowf12a2822020-10-06 19:58:08 -0400214 def send_fd_scm(self, fd: Optional[int] = None,
215 file_path: Optional[str] = None) -> int:
John Snow306dfcd2019-06-27 17:28:15 -0400216 """
John Snow514d00d2021-09-22 20:49:30 -0400217 Send an fd or file_path to the remote via SCM_RIGHTS.
John Snow306dfcd2019-06-27 17:28:15 -0400218
John Snow514d00d2021-09-22 20:49:30 -0400219 Exactly one of fd and file_path must be given. If it is
220 file_path, the file will be opened read-only and the new file
221 descriptor will be sent to the remote.
John Snow306dfcd2019-06-27 17:28:15 -0400222 """
John Snowabf0bf92019-06-27 17:28:14 -0400223 if file_path is not None:
224 assert fd is None
John Snow514d00d2021-09-22 20:49:30 -0400225 with open(file_path, "rb") as passfile:
226 fd = passfile.fileno()
227 self._qmp.send_fd_scm(fd)
John Snowabf0bf92019-06-27 17:28:14 -0400228 else:
229 assert fd is not None
John Snow514d00d2021-09-22 20:49:30 -0400230 self._qmp.send_fd_scm(fd)
John Snowabf0bf92019-06-27 17:28:14 -0400231
John Snow514d00d2021-09-22 20:49:30 -0400232 return 0
John Snowabf0bf92019-06-27 17:28:14 -0400233
234 @staticmethod
John Snowf12a2822020-10-06 19:58:08 -0400235 def _remove_if_exists(path: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400236 """
237 Remove file object at path if it exists
238 """
239 try:
240 os.remove(path)
241 except OSError as exception:
242 if exception.errno == errno.ENOENT:
243 return
244 raise
245
John Snowf12a2822020-10-06 19:58:08 -0400246 def is_running(self) -> bool:
John Snow306dfcd2019-06-27 17:28:15 -0400247 """Returns true if the VM is running."""
John Snowabf0bf92019-06-27 17:28:14 -0400248 return self._popen is not None and self._popen.poll() is None
249
John Snow9223fda2020-10-06 19:58:05 -0400250 @property
251 def _subp(self) -> 'subprocess.Popen[bytes]':
252 if self._popen is None:
253 raise QEMUMachineError('Subprocess pipe not present')
254 return self._popen
255
John Snowf12a2822020-10-06 19:58:08 -0400256 def exitcode(self) -> Optional[int]:
John Snow306dfcd2019-06-27 17:28:15 -0400257 """Returns the exit code if possible, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400258 if self._popen is None:
259 return None
260 return self._popen.poll()
261
John Snowf12a2822020-10-06 19:58:08 -0400262 def get_pid(self) -> Optional[int]:
John Snow306dfcd2019-06-27 17:28:15 -0400263 """Returns the PID of the running process, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400264 if not self.is_running():
265 return None
John Snow9223fda2020-10-06 19:58:05 -0400266 return self._subp.pid
John Snowabf0bf92019-06-27 17:28:14 -0400267
John Snowf12a2822020-10-06 19:58:08 -0400268 def _load_io_log(self) -> None:
John Snow5690b432021-09-16 14:22:47 -0400269 # Assume that the output encoding of QEMU's terminal output is
270 # defined by our locale. If indeterminate, allow open() to fall
271 # back to the platform default.
272 _, encoding = locale.getlocale()
John Snowabf0bf92019-06-27 17:28:14 -0400273 if self._qemu_log_path is not None:
John Snow5690b432021-09-16 14:22:47 -0400274 with open(self._qemu_log_path, "r", encoding=encoding) as iolog:
John Snowabf0bf92019-06-27 17:28:14 -0400275 self._iolog = iolog.read()
276
John Snow652809d2020-10-06 19:58:01 -0400277 @property
278 def _base_args(self) -> List[str]:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500279 args = ['-display', 'none', '-vga', 'none']
John Snowc4e60232020-10-06 19:57:59 -0400280
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500281 if self._qmp_set:
282 if isinstance(self._monitor_address, tuple):
John Snowc4e60232020-10-06 19:57:59 -0400283 moncdev = "socket,id=mon,host={},port={}".format(
284 *self._monitor_address
285 )
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500286 else:
John Snowc4e60232020-10-06 19:57:59 -0400287 moncdev = f"socket,id=mon,path={self._monitor_address}"
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500288 args.extend(['-chardev', moncdev, '-mon',
289 'chardev=mon,mode=control'])
John Snowc4e60232020-10-06 19:57:59 -0400290
John Snowabf0bf92019-06-27 17:28:14 -0400291 if self._machine is not None:
292 args.extend(['-machine', self._machine])
John Snow9b8ccd62020-05-28 18:21:28 -0400293 for _ in range(self._console_index):
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100294 args.extend(['-serial', 'null'])
John Snowabf0bf92019-06-27 17:28:14 -0400295 if self._console_set:
Paolo Bonzini991c1802020-11-13 03:10:52 -0500296 chardev = ('socket,id=console,path=%s,server=on,wait=off' %
John Snowabf0bf92019-06-27 17:28:14 -0400297 self._console_address)
298 args.extend(['-chardev', chardev])
299 if self._console_device_type is None:
300 args.extend(['-serial', 'chardev:console'])
301 else:
302 device = '%s,chardev=console' % self._console_device_type
303 args.extend(['-device', device])
304 return args
305
Wainer dos Santos Moschetta555fe0c2021-04-30 10:34:12 -0300306 @property
307 def args(self) -> List[str]:
308 """Returns the list of arguments given to the QEMU binary."""
309 return self._args
310
John Snowf12a2822020-10-06 19:58:08 -0400311 def _pre_launch(self) -> None:
John Snow652809d2020-10-06 19:58:01 -0400312 if self._console_set:
313 self._remove_files.append(self._console_address)
314
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500315 if self._qmp_set:
John Snow6eeb3de2021-11-18 15:46:15 -0500316 if isinstance(self._monitor_address, str):
John Snowc4e60232020-10-06 19:57:59 -0400317 self._remove_files.append(self._monitor_address)
John Snowbeb6b572021-05-27 17:16:53 -0400318 self._qmp_connection = QEMUMonitorProtocol(
John Snowc4e60232020-10-06 19:57:59 -0400319 self._monitor_address,
320 server=True,
321 nickname=self._name
322 )
John Snowabf0bf92019-06-27 17:28:14 -0400323
John Snow63c33f32021-05-27 17:16:49 -0400324 # NOTE: Make sure any opened resources are *definitely* freed in
325 # _post_shutdown()!
326 # pylint: disable=consider-using-with
Cleber Rosab306e262021-02-11 16:55:05 -0500327 self._qemu_log_path = os.path.join(self.log_dir, self._name + ".log")
John Snow63c33f32021-05-27 17:16:49 -0400328 self._qemu_log_file = open(self._qemu_log_path, 'wb')
329
John Snowb1ca9912021-11-18 15:46:17 -0500330 self._iolog = None
331 self._qemu_full_args = tuple(chain(
332 self._wrapper,
333 [self._binary],
334 self._base_args,
335 self._args
336 ))
337
John Snowf12a2822020-10-06 19:58:08 -0400338 def _post_launch(self) -> None:
John Snowbe1183e2020-10-06 19:58:04 -0400339 if self._qmp_connection:
Emanuele Giuseppe Espositoe2f948a2021-08-09 11:00:59 +0200340 self._qmp.accept(self._qmp_timer)
John Snowabf0bf92019-06-27 17:28:14 -0400341
Emanuele Giuseppe Espositoeb7a91d2021-08-09 11:01:13 +0200342 def _close_qemu_log_file(self) -> None:
343 if self._qemu_log_file is not None:
344 self._qemu_log_file.close()
345 self._qemu_log_file = None
346
John Snowf12a2822020-10-06 19:58:08 -0400347 def _post_shutdown(self) -> None:
John Snowa3842cb2020-07-10 01:06:42 -0400348 """
349 Called to cleanup the VM instance after the process has exited.
350 May also be called after a failed launch.
351 """
John Snow49a608b2021-10-26 13:56:06 -0400352 try:
353 self._close_qmp_connection()
354 except Exception as err: # pylint: disable=broad-except
355 LOG.warning(
356 "Exception closing QMP connection: %s",
357 str(err) if str(err) else type(err).__name__
358 )
359 finally:
360 assert self._qmp_connection is None
John Snow671940e2020-07-10 01:06:39 -0400361
Emanuele Giuseppe Espositoeb7a91d2021-08-09 11:01:13 +0200362 self._close_qemu_log_file()
John Snowabf0bf92019-06-27 17:28:14 -0400363
Cleber Rosa3c1e16c2021-02-11 17:01:41 -0500364 self._load_io_log()
365
John Snowabf0bf92019-06-27 17:28:14 -0400366 self._qemu_log_path = None
367
John Snowabf0bf92019-06-27 17:28:14 -0400368 if self._temp_dir is not None:
369 shutil.rmtree(self._temp_dir)
370 self._temp_dir = None
371
Max Reitz32558ce2019-10-17 15:31:34 +0200372 while len(self._remove_files) > 0:
373 self._remove_if_exists(self._remove_files.pop())
374
John Snow14661d92020-07-10 01:06:38 -0400375 exitcode = self.exitcode()
John Snowde6e08b2020-07-10 01:06:48 -0400376 if (exitcode is not None and exitcode < 0
377 and not (self._user_killed and exitcode == -signal.SIGKILL)):
John Snow14661d92020-07-10 01:06:38 -0400378 msg = 'qemu received signal %i; command: "%s"'
379 if self._qemu_full_args:
380 command = ' '.join(self._qemu_full_args)
381 else:
382 command = ''
383 LOG.warning(msg, -int(exitcode), command)
384
John Snowb9420e42021-10-26 13:56:05 -0400385 self._quit_issued = False
John Snowde6e08b2020-07-10 01:06:48 -0400386 self._user_killed = False
John Snow14661d92020-07-10 01:06:38 -0400387 self._launched = False
388
John Snowf12a2822020-10-06 19:58:08 -0400389 def launch(self) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400390 """
391 Launch the VM and make sure we cleanup and expose the
392 command line/output in case of exception
393 """
394
395 if self._launched:
396 raise QEMUMachineError('VM already launched')
397
John Snowabf0bf92019-06-27 17:28:14 -0400398 try:
399 self._launch()
John Snowabf0bf92019-06-27 17:28:14 -0400400 except:
John Snow1611e6c2021-11-18 15:46:18 -0500401 # We may have launched the process but it may
402 # have exited before we could connect via QMP.
403 # Assume the VM didn't launch or is exiting.
404 # If we don't wait for the process, exitcode() may still be
405 # 'None' by the time control is ceded back to the caller.
406 if self._launched:
407 self.wait()
408 else:
409 self._post_shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400410
411 LOG.debug('Error launching VM')
412 if self._qemu_full_args:
413 LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
414 if self._iolog:
415 LOG.debug('Output: %r', self._iolog)
416 raise
417
John Snowf12a2822020-10-06 19:58:08 -0400418 def _launch(self) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400419 """
420 Launch the VM and establish a QMP connection
421 """
John Snowabf0bf92019-06-27 17:28:14 -0400422 self._pre_launch()
John Snowabf0bf92019-06-27 17:28:14 -0400423 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
John Snowa0eae172021-05-27 17:16:50 -0400424
425 # Cleaning up of this subprocess is guaranteed by _do_shutdown.
426 # pylint: disable=consider-using-with
John Snowabf0bf92019-06-27 17:28:14 -0400427 self._popen = subprocess.Popen(self._qemu_full_args,
John Snow07b71232021-05-27 17:16:46 -0400428 stdin=subprocess.DEVNULL,
John Snowabf0bf92019-06-27 17:28:14 -0400429 stdout=self._qemu_log_file,
430 stderr=subprocess.STDOUT,
431 shell=False,
432 close_fds=False)
John Snow1611e6c2021-11-18 15:46:18 -0500433 self._launched = True
John Snowabf0bf92019-06-27 17:28:14 -0400434 self._post_launch()
435
John Snow49a608b2021-10-26 13:56:06 -0400436 def _close_qmp_connection(self) -> None:
437 """
438 Close the underlying QMP connection, if any.
439
440 Dutifully report errors that occurred while closing, but assume
441 that any error encountered indicates an abnormal termination
442 process and not a failure to close.
443 """
444 if self._qmp_connection is None:
445 return
446
447 try:
448 self._qmp.close()
449 except EOFError:
450 # EOF can occur as an Exception here when using the Async
451 # QMP backend. It indicates that the server closed the
452 # stream. If we successfully issued 'quit' at any point,
453 # then this was expected. If the remote went away without
454 # our permission, it's worth reporting that as an abnormal
455 # shutdown case.
456 if not (self._user_killed or self._quit_issued):
457 raise
458 finally:
459 self._qmp_connection = None
460
John Snowe2c97f12020-07-10 01:06:40 -0400461 def _early_cleanup(self) -> None:
462 """
463 Perform any cleanup that needs to happen before the VM exits.
John Snowa3842cb2020-07-10 01:06:42 -0400464
John Snow1611e6c2021-11-18 15:46:18 -0500465 This method may be called twice upon shutdown, once each by soft
466 and hard shutdown in failover scenarios.
John Snowe2c97f12020-07-10 01:06:40 -0400467 """
468 # If we keep the console socket open, we may deadlock waiting
469 # for QEMU to exit, while QEMU is waiting for the socket to
470 # become writeable.
471 if self._console_socket is not None:
472 self._console_socket.close()
473 self._console_socket = None
474
John Snow193bf1c2020-07-10 01:06:47 -0400475 def _hard_shutdown(self) -> None:
476 """
477 Perform early cleanup, kill the VM, and wait for it to terminate.
478
479 :raise subprocess.Timeout: When timeout is exceeds 60 seconds
480 waiting for the QEMU process to terminate.
481 """
482 self._early_cleanup()
John Snow9223fda2020-10-06 19:58:05 -0400483 self._subp.kill()
484 self._subp.wait(timeout=60)
John Snow193bf1c2020-07-10 01:06:47 -0400485
John Snowb9420e42021-10-26 13:56:05 -0400486 def _soft_shutdown(self, timeout: Optional[int]) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400487 """
488 Perform early cleanup, attempt to gracefully shut down the VM, and wait
489 for it to terminate.
490
John Snow8226a4b2020-07-20 12:02:52 -0400491 :param timeout: Timeout in seconds for graceful shutdown.
492 A value of None is an infinite wait.
John Snow193bf1c2020-07-10 01:06:47 -0400493
494 :raise ConnectionReset: On QMP communication errors
495 :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
496 the QEMU process to terminate.
497 """
498 self._early_cleanup()
499
John Snowbe1183e2020-10-06 19:58:04 -0400500 if self._qmp_connection:
John Snow49a608b2021-10-26 13:56:06 -0400501 try:
502 if not self._quit_issued:
503 # May raise ExecInterruptedError or StateError if the
504 # connection dies or has *already* died.
505 self.qmp('quit')
506 finally:
507 # Regardless, we want to quiesce the connection.
508 self._close_qmp_connection()
John Snow193bf1c2020-07-10 01:06:47 -0400509
510 # May raise subprocess.TimeoutExpired
John Snow9223fda2020-10-06 19:58:05 -0400511 self._subp.wait(timeout=timeout)
John Snow193bf1c2020-07-10 01:06:47 -0400512
John Snowb9420e42021-10-26 13:56:05 -0400513 def _do_shutdown(self, timeout: Optional[int]) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400514 """
515 Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
516
John Snow8226a4b2020-07-20 12:02:52 -0400517 :param timeout: Timeout in seconds for graceful shutdown.
518 A value of None is an infinite wait.
John Snow193bf1c2020-07-10 01:06:47 -0400519
520 :raise AbnormalShutdown: When the VM could not be shut down gracefully.
521 The inner exception will likely be ConnectionReset or
522 subprocess.TimeoutExpired. In rare cases, non-graceful termination
523 may result in its own exceptions, likely subprocess.TimeoutExpired.
524 """
525 try:
John Snowb9420e42021-10-26 13:56:05 -0400526 self._soft_shutdown(timeout)
John Snow193bf1c2020-07-10 01:06:47 -0400527 except Exception as exc:
528 self._hard_shutdown()
529 raise AbnormalShutdown("Could not perform graceful shutdown") \
530 from exc
531
John Snowb9420e42021-10-26 13:56:05 -0400532 def shutdown(self,
John Snowc9b30452020-07-10 01:06:43 -0400533 hard: bool = False,
John Snow8226a4b2020-07-20 12:02:52 -0400534 timeout: Optional[int] = 30) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400535 """
John Snow193bf1c2020-07-10 01:06:47 -0400536 Terminate the VM (gracefully if possible) and perform cleanup.
537 Cleanup will always be performed.
538
539 If the VM has not yet been launched, or shutdown(), wait(), or kill()
540 have already been called, this method does nothing.
541
John Snow193bf1c2020-07-10 01:06:47 -0400542 :param hard: When true, do not attempt graceful shutdown, and
543 suppress the SIGKILL warning log message.
544 :param timeout: Optional timeout in seconds for graceful shutdown.
John Snow8226a4b2020-07-20 12:02:52 -0400545 Default 30 seconds, A `None` value is an infinite wait.
John Snowabf0bf92019-06-27 17:28:14 -0400546 """
John Snowa3842cb2020-07-10 01:06:42 -0400547 if not self._launched:
548 return
549
John Snow193bf1c2020-07-10 01:06:47 -0400550 try:
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300551 if hard:
John Snowde6e08b2020-07-10 01:06:48 -0400552 self._user_killed = True
John Snow193bf1c2020-07-10 01:06:47 -0400553 self._hard_shutdown()
554 else:
John Snowb9420e42021-10-26 13:56:05 -0400555 self._do_shutdown(timeout)
John Snow193bf1c2020-07-10 01:06:47 -0400556 finally:
557 self._post_shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400558
John Snowf12a2822020-10-06 19:58:08 -0400559 def kill(self) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400560 """
561 Terminate the VM forcefully, wait for it to exit, and perform cleanup.
562 """
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300563 self.shutdown(hard=True)
564
John Snow8226a4b2020-07-20 12:02:52 -0400565 def wait(self, timeout: Optional[int] = 30) -> None:
John Snow89528052020-07-10 01:06:44 -0400566 """
567 Wait for the VM to power off and perform post-shutdown cleanup.
568
John Snow8226a4b2020-07-20 12:02:52 -0400569 :param timeout: Optional timeout in seconds. Default 30 seconds.
570 A value of `None` is an infinite wait.
John Snow89528052020-07-10 01:06:44 -0400571 """
John Snowb9420e42021-10-26 13:56:05 -0400572 self._quit_issued = True
573 self.shutdown(timeout=timeout)
John Snow89528052020-07-10 01:06:44 -0400574
John Snowf12a2822020-10-06 19:58:08 -0400575 def set_qmp_monitor(self, enabled: bool = True) -> None:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500576 """
577 Set the QMP monitor.
578
579 @param enabled: if False, qmp monitor options will be removed from
580 the base arguments of the resulting QEMU command
581 line. Default is True.
John Snow5c02c862021-06-29 17:43:23 -0400582
583 .. note:: Call this function before launch().
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500584 """
John Snowbe1183e2020-10-06 19:58:04 -0400585 self._qmp_set = enabled
586
587 @property
John Snowbeb6b572021-05-27 17:16:53 -0400588 def _qmp(self) -> QEMUMonitorProtocol:
John Snowbe1183e2020-10-06 19:58:04 -0400589 if self._qmp_connection is None:
590 raise QEMUMachineError("Attempt to access QMP with no connection")
591 return self._qmp_connection
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500592
John Snowaaa81ec2020-10-06 19:58:03 -0400593 @classmethod
Vladimir Sementsov-Ogievskiyc7daa572021-08-24 11:38:45 +0300594 def _qmp_args(cls, conv_keys: bool,
595 args: Dict[str, Any]) -> Dict[str, object]:
596 if conv_keys:
597 return {k.replace('_', '-'): v for k, v in args.items()}
598
599 return args
John Snowabf0bf92019-06-27 17:28:14 -0400600
John Snowaaa81ec2020-10-06 19:58:03 -0400601 def qmp(self, cmd: str,
Vladimir Sementsov-Ogievskiy3f3c9b42021-08-24 11:38:46 +0300602 args_dict: Optional[Dict[str, object]] = None,
603 conv_keys: Optional[bool] = None,
John Snowaaa81ec2020-10-06 19:58:03 -0400604 **args: Any) -> QMPMessage:
605 """
606 Invoke a QMP command and return the response dict
607 """
Vladimir Sementsov-Ogievskiy3f3c9b42021-08-24 11:38:46 +0300608 if args_dict is not None:
609 assert not args
610 assert conv_keys is None
611 args = args_dict
612 conv_keys = False
613
614 if conv_keys is None:
615 conv_keys = True
616
Vladimir Sementsov-Ogievskiyc7daa572021-08-24 11:38:45 +0300617 qmp_args = self._qmp_args(conv_keys, args)
John Snowb9420e42021-10-26 13:56:05 -0400618 ret = self._qmp.cmd(cmd, args=qmp_args)
619 if cmd == 'quit' and 'error' not in ret and 'return' in ret:
620 self._quit_issued = True
621 return ret
John Snowabf0bf92019-06-27 17:28:14 -0400622
John Snowf12a2822020-10-06 19:58:08 -0400623 def command(self, cmd: str,
624 conv_keys: bool = True,
625 **args: Any) -> QMPReturnValue:
John Snowabf0bf92019-06-27 17:28:14 -0400626 """
627 Invoke a QMP command.
628 On success return the response dict.
629 On failure raise an exception.
630 """
Vladimir Sementsov-Ogievskiyc7daa572021-08-24 11:38:45 +0300631 qmp_args = self._qmp_args(conv_keys, args)
John Snowb9420e42021-10-26 13:56:05 -0400632 ret = self._qmp.command(cmd, **qmp_args)
633 if cmd == 'quit':
634 self._quit_issued = True
635 return ret
John Snowabf0bf92019-06-27 17:28:14 -0400636
John Snowf12a2822020-10-06 19:58:08 -0400637 def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400638 """
639 Poll for one queued QMP events and return it
640 """
John Snow306dfcd2019-06-27 17:28:15 -0400641 if self._events:
John Snowabf0bf92019-06-27 17:28:14 -0400642 return self._events.pop(0)
643 return self._qmp.pull_event(wait=wait)
644
John Snowf12a2822020-10-06 19:58:08 -0400645 def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400646 """
647 Poll for queued QMP events and return a list of dicts
648 """
649 events = self._qmp.get_events(wait=wait)
650 events.extend(self._events)
651 del self._events[:]
John Snowabf0bf92019-06-27 17:28:14 -0400652 return events
653
654 @staticmethod
John Snowf12a2822020-10-06 19:58:08 -0400655 def event_match(event: Any, match: Optional[Any]) -> bool:
John Snowabf0bf92019-06-27 17:28:14 -0400656 """
657 Check if an event matches optional match criteria.
658
659 The match criteria takes the form of a matching subdict. The event is
660 checked to be a superset of the subdict, recursively, with matching
661 values whenever the subdict values are not None.
662
663 This has a limitation that you cannot explicitly check for None values.
664
665 Examples, with the subdict queries on the left:
666 - None matches any object.
667 - {"foo": None} matches {"foo": {"bar": 1}}
668 - {"foo": None} matches {"foo": 5}
669 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
670 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
671 """
672 if match is None:
673 return True
674
675 try:
676 for key in match:
677 if key in event:
678 if not QEMUMachine.event_match(event[key], match[key]):
679 return False
680 else:
681 return False
682 return True
683 except TypeError:
684 # either match or event wasn't iterable (not a dict)
John Snowf12a2822020-10-06 19:58:08 -0400685 return bool(match == event)
John Snowabf0bf92019-06-27 17:28:14 -0400686
John Snowf12a2822020-10-06 19:58:08 -0400687 def event_wait(self, name: str,
688 timeout: float = 60.0,
689 match: Optional[QMPMessage] = None) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400690 """
691 event_wait waits for and returns a named event from QMP with a timeout.
692
693 name: The event to wait for.
694 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
695 match: Optional match criteria. See event_match for details.
696 """
697 return self.events_wait([(name, match)], timeout)
698
John Snowf12a2822020-10-06 19:58:08 -0400699 def events_wait(self,
700 events: Sequence[Tuple[str, Any]],
701 timeout: float = 60.0) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400702 """
John Snow1847a4a2020-10-06 19:58:02 -0400703 events_wait waits for and returns a single named event from QMP.
704 In the case of multiple qualifying events, this function returns the
705 first one.
John Snowabf0bf92019-06-27 17:28:14 -0400706
John Snow1847a4a2020-10-06 19:58:02 -0400707 :param events: A sequence of (name, match_criteria) tuples.
708 The match criteria are optional and may be None.
709 See event_match for details.
710 :param timeout: Optional timeout, in seconds.
711 See QEMUMonitorProtocol.pull_event.
712
713 :raise QMPTimeoutError: If timeout was non-zero and no matching events
714 were found.
715 :return: A QMP event matching the filter criteria.
716 If timeout was 0 and no event matched, None.
John Snowabf0bf92019-06-27 17:28:14 -0400717 """
John Snowf12a2822020-10-06 19:58:08 -0400718 def _match(event: QMPMessage) -> bool:
John Snowabf0bf92019-06-27 17:28:14 -0400719 for name, match in events:
John Snow306dfcd2019-06-27 17:28:15 -0400720 if event['event'] == name and self.event_match(event, match):
John Snowabf0bf92019-06-27 17:28:14 -0400721 return True
722 return False
723
John Snow1847a4a2020-10-06 19:58:02 -0400724 event: Optional[QMPMessage]
725
John Snowabf0bf92019-06-27 17:28:14 -0400726 # Search cached events
727 for event in self._events:
728 if _match(event):
729 self._events.remove(event)
730 return event
731
732 # Poll for new events
733 while True:
734 event = self._qmp.pull_event(wait=timeout)
John Snow1847a4a2020-10-06 19:58:02 -0400735 if event is None:
736 # NB: None is only returned when timeout is false-ish.
737 # Timeouts raise QMPTimeoutError instead!
738 break
John Snowabf0bf92019-06-27 17:28:14 -0400739 if _match(event):
740 return event
741 self._events.append(event)
742
743 return None
744
John Snowf12a2822020-10-06 19:58:08 -0400745 def get_log(self) -> Optional[str]:
John Snowabf0bf92019-06-27 17:28:14 -0400746 """
747 After self.shutdown or failed qemu execution, this returns the output
748 of the qemu process.
749 """
750 return self._iolog
751
John Snowf12a2822020-10-06 19:58:08 -0400752 def add_args(self, *args: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400753 """
754 Adds to the list of extra arguments to be given to the QEMU binary
755 """
756 self._args.extend(args)
757
John Snowf12a2822020-10-06 19:58:08 -0400758 def set_machine(self, machine_type: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400759 """
760 Sets the machine type
761
762 If set, the machine type will be added to the base arguments
763 of the resulting QEMU command line.
764 """
765 self._machine = machine_type
766
John Snowf12a2822020-10-06 19:58:08 -0400767 def set_console(self,
768 device_type: Optional[str] = None,
769 console_index: int = 0) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400770 """
771 Sets the device type for a console device
772
773 If set, the console device and a backing character device will
774 be added to the base arguments of the resulting QEMU command
775 line.
776
777 This is a convenience method that will either use the provided
778 device type, or default to a "-serial chardev:console" command
779 line argument.
780
781 The actual setting of command line arguments will be be done at
782 machine launch time, as it depends on the temporary directory
783 to be created.
784
785 @param device_type: the device type, such as "isa-serial". If
786 None is given (the default value) a "-serial
787 chardev:console" command line argument will
788 be used instead, resorting to the machine's
789 default device type.
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100790 @param console_index: the index of the console device to use.
791 If not zero, the command line will create
792 'index - 1' consoles and connect them to
793 the 'null' backing character device.
John Snowabf0bf92019-06-27 17:28:14 -0400794 """
795 self._console_set = True
796 self._console_device_type = device_type
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100797 self._console_index = console_index
John Snowabf0bf92019-06-27 17:28:14 -0400798
799 @property
John Snowf12a2822020-10-06 19:58:08 -0400800 def console_socket(self) -> socket.socket:
John Snowabf0bf92019-06-27 17:28:14 -0400801 """
802 Returns a socket connected to the console
803 """
804 if self._console_socket is None:
Robert Foley80ded8e2020-07-24 07:45:08 +0100805 self._console_socket = console_socket.ConsoleSocket(
806 self._console_address,
807 file=self._console_log_path,
808 drain=self._drain_console)
John Snowabf0bf92019-06-27 17:28:14 -0400809 return self._console_socket
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500810
811 @property
812 def temp_dir(self) -> str:
813 """
814 Returns a temporary directory to be used for this machine
815 """
816 if self._temp_dir is None:
817 self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-",
818 dir=self._base_temp_dir)
819 return self._temp_dir
Cleber Rosab306e262021-02-11 16:55:05 -0500820
821 @property
John Snow87bf1fe2021-11-18 15:46:14 -0500822 def sock_dir(self) -> str:
823 """
824 Returns the directory used for sockfiles by this machine.
825 """
826 if self._sock_dir:
827 return self._sock_dir
828 return self.temp_dir
829
830 @property
Cleber Rosab306e262021-02-11 16:55:05 -0500831 def log_dir(self) -> str:
832 """
833 Returns a directory to be used for writing logs
834 """
835 if self._log_dir is None:
836 return self.temp_dir
837 return self._log_dir