blob: a487c397459a1fa6889276ab25381a877df07637 [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
136 self._name = name or "qemu-%d" % os.getpid()
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500137 self._base_temp_dir = base_temp_dir
138 self._sock_dir = sock_dir or self._base_temp_dir
Cleber Rosab306e262021-02-11 16:55:05 -0500139 self._log_dir = log_dir
John Snowc5e61a62020-10-06 19:58:00 -0400140
John Snowc4e60232020-10-06 19:57:59 -0400141 if monitor_address is not None:
142 self._monitor_address = monitor_address
143 self._remove_monitor_sockfile = False
144 else:
145 self._monitor_address = os.path.join(
John Snowc5e61a62020-10-06 19:58:00 -0400146 self._sock_dir, f"{self._name}-monitor.sock"
John Snowc4e60232020-10-06 19:57:59 -0400147 )
148 self._remove_monitor_sockfile = True
John Snowc5e61a62020-10-06 19:58:00 -0400149
150 self._console_log_path = console_log
151 if self._console_log_path:
152 # In order to log the console, buffering needs to be enabled.
153 self._drain_console = True
154 else:
155 self._drain_console = drain_console
156
157 # Runstate
John Snowf12a2822020-10-06 19:58:08 -0400158 self._qemu_log_path: Optional[str] = None
159 self._qemu_log_file: Optional[BinaryIO] = None
John Snow9223fda2020-10-06 19:58:05 -0400160 self._popen: Optional['subprocess.Popen[bytes]'] = None
John Snowf12a2822020-10-06 19:58:08 -0400161 self._events: List[QMPMessage] = []
162 self._iolog: Optional[str] = None
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500163 self._qmp_set = True # Enable QMP monitor by default.
John Snowbeb6b572021-05-27 17:16:53 -0400164 self._qmp_connection: Optional[QEMUMonitorProtocol] = None
John Snowaad3f3b2020-10-06 19:58:06 -0400165 self._qemu_full_args: Tuple[str, ...] = ()
John Snowf12a2822020-10-06 19:58:08 -0400166 self._temp_dir: Optional[str] = None
John Snowabf0bf92019-06-27 17:28:14 -0400167 self._launched = False
John Snowf12a2822020-10-06 19:58:08 -0400168 self._machine: Optional[str] = None
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100169 self._console_index = 0
John Snowabf0bf92019-06-27 17:28:14 -0400170 self._console_set = False
John Snowf12a2822020-10-06 19:58:08 -0400171 self._console_device_type: Optional[str] = None
John Snow652809d2020-10-06 19:58:01 -0400172 self._console_address = os.path.join(
173 self._sock_dir, f"{self._name}-console.sock"
174 )
John Snowf12a2822020-10-06 19:58:08 -0400175 self._console_socket: Optional[socket.socket] = None
176 self._remove_files: List[str] = []
John Snowde6e08b2020-07-10 01:06:48 -0400177 self._user_killed = False
John Snowb9420e42021-10-26 13:56:05 -0400178 self._quit_issued = False
John Snowabf0bf92019-06-27 17:28:14 -0400179
Vladimir Sementsov-Ogievskiy15c3b862021-08-24 11:38:47 +0300180 def __enter__(self: _T) -> _T:
John Snowabf0bf92019-06-27 17:28:14 -0400181 return self
182
John Snow1dda0402020-05-14 01:53:44 -0400183 def __exit__(self,
184 exc_type: Optional[Type[BaseException]],
185 exc_val: Optional[BaseException],
186 exc_tb: Optional[TracebackType]) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400187 self.shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400188
John Snowf12a2822020-10-06 19:58:08 -0400189 def add_monitor_null(self) -> None:
John Snow306dfcd2019-06-27 17:28:15 -0400190 """
191 This can be used to add an unused monitor instance.
192 """
John Snowabf0bf92019-06-27 17:28:14 -0400193 self._args.append('-monitor')
194 self._args.append('null')
195
Vladimir Sementsov-Ogievskiy15c3b862021-08-24 11:38:47 +0300196 def add_fd(self: _T, fd: int, fdset: int,
197 opaque: str, opts: str = '') -> _T:
John Snowabf0bf92019-06-27 17:28:14 -0400198 """
199 Pass a file descriptor to the VM
200 """
201 options = ['fd=%d' % fd,
202 'set=%d' % fdset,
203 'opaque=%s' % opaque]
204 if opts:
205 options.append(opts)
206
207 # This did not exist before 3.4, but since then it is
208 # mandatory for our purpose
209 if hasattr(os, 'set_inheritable'):
210 os.set_inheritable(fd, True)
211
212 self._args.append('-add-fd')
213 self._args.append(','.join(options))
214 return self
215
John Snowf12a2822020-10-06 19:58:08 -0400216 def send_fd_scm(self, fd: Optional[int] = None,
217 file_path: Optional[str] = None) -> int:
John Snow306dfcd2019-06-27 17:28:15 -0400218 """
John Snow514d00d2021-09-22 20:49:30 -0400219 Send an fd or file_path to the remote via SCM_RIGHTS.
John Snow306dfcd2019-06-27 17:28:15 -0400220
John Snow514d00d2021-09-22 20:49:30 -0400221 Exactly one of fd and file_path must be given. If it is
222 file_path, the file will be opened read-only and the new file
223 descriptor will be sent to the remote.
John Snow306dfcd2019-06-27 17:28:15 -0400224 """
John Snowabf0bf92019-06-27 17:28:14 -0400225 if file_path is not None:
226 assert fd is None
John Snow514d00d2021-09-22 20:49:30 -0400227 with open(file_path, "rb") as passfile:
228 fd = passfile.fileno()
229 self._qmp.send_fd_scm(fd)
John Snowabf0bf92019-06-27 17:28:14 -0400230 else:
231 assert fd is not None
John Snow514d00d2021-09-22 20:49:30 -0400232 self._qmp.send_fd_scm(fd)
John Snowabf0bf92019-06-27 17:28:14 -0400233
John Snow514d00d2021-09-22 20:49:30 -0400234 return 0
John Snowabf0bf92019-06-27 17:28:14 -0400235
236 @staticmethod
John Snowf12a2822020-10-06 19:58:08 -0400237 def _remove_if_exists(path: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400238 """
239 Remove file object at path if it exists
240 """
241 try:
242 os.remove(path)
243 except OSError as exception:
244 if exception.errno == errno.ENOENT:
245 return
246 raise
247
John Snowf12a2822020-10-06 19:58:08 -0400248 def is_running(self) -> bool:
John Snow306dfcd2019-06-27 17:28:15 -0400249 """Returns true if the VM is running."""
John Snowabf0bf92019-06-27 17:28:14 -0400250 return self._popen is not None and self._popen.poll() is None
251
John Snow9223fda2020-10-06 19:58:05 -0400252 @property
253 def _subp(self) -> 'subprocess.Popen[bytes]':
254 if self._popen is None:
255 raise QEMUMachineError('Subprocess pipe not present')
256 return self._popen
257
John Snowf12a2822020-10-06 19:58:08 -0400258 def exitcode(self) -> Optional[int]:
John Snow306dfcd2019-06-27 17:28:15 -0400259 """Returns the exit code if possible, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400260 if self._popen is None:
261 return None
262 return self._popen.poll()
263
John Snowf12a2822020-10-06 19:58:08 -0400264 def get_pid(self) -> Optional[int]:
John Snow306dfcd2019-06-27 17:28:15 -0400265 """Returns the PID of the running process, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400266 if not self.is_running():
267 return None
John Snow9223fda2020-10-06 19:58:05 -0400268 return self._subp.pid
John Snowabf0bf92019-06-27 17:28:14 -0400269
John Snowf12a2822020-10-06 19:58:08 -0400270 def _load_io_log(self) -> None:
John Snow5690b432021-09-16 14:22:47 -0400271 # Assume that the output encoding of QEMU's terminal output is
272 # defined by our locale. If indeterminate, allow open() to fall
273 # back to the platform default.
274 _, encoding = locale.getlocale()
John Snowabf0bf92019-06-27 17:28:14 -0400275 if self._qemu_log_path is not None:
John Snow5690b432021-09-16 14:22:47 -0400276 with open(self._qemu_log_path, "r", encoding=encoding) as iolog:
John Snowabf0bf92019-06-27 17:28:14 -0400277 self._iolog = iolog.read()
278
John Snow652809d2020-10-06 19:58:01 -0400279 @property
280 def _base_args(self) -> List[str]:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500281 args = ['-display', 'none', '-vga', 'none']
John Snowc4e60232020-10-06 19:57:59 -0400282
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500283 if self._qmp_set:
284 if isinstance(self._monitor_address, tuple):
John Snowc4e60232020-10-06 19:57:59 -0400285 moncdev = "socket,id=mon,host={},port={}".format(
286 *self._monitor_address
287 )
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500288 else:
John Snowc4e60232020-10-06 19:57:59 -0400289 moncdev = f"socket,id=mon,path={self._monitor_address}"
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500290 args.extend(['-chardev', moncdev, '-mon',
291 'chardev=mon,mode=control'])
John Snowc4e60232020-10-06 19:57:59 -0400292
John Snowabf0bf92019-06-27 17:28:14 -0400293 if self._machine is not None:
294 args.extend(['-machine', self._machine])
John Snow9b8ccd62020-05-28 18:21:28 -0400295 for _ in range(self._console_index):
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100296 args.extend(['-serial', 'null'])
John Snowabf0bf92019-06-27 17:28:14 -0400297 if self._console_set:
Paolo Bonzini991c1802020-11-13 03:10:52 -0500298 chardev = ('socket,id=console,path=%s,server=on,wait=off' %
John Snowabf0bf92019-06-27 17:28:14 -0400299 self._console_address)
300 args.extend(['-chardev', chardev])
301 if self._console_device_type is None:
302 args.extend(['-serial', 'chardev:console'])
303 else:
304 device = '%s,chardev=console' % self._console_device_type
305 args.extend(['-device', device])
306 return args
307
Wainer dos Santos Moschetta555fe0c2021-04-30 10:34:12 -0300308 @property
309 def args(self) -> List[str]:
310 """Returns the list of arguments given to the QEMU binary."""
311 return self._args
312
John Snowf12a2822020-10-06 19:58:08 -0400313 def _pre_launch(self) -> None:
John Snow652809d2020-10-06 19:58:01 -0400314 if self._console_set:
315 self._remove_files.append(self._console_address)
316
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500317 if self._qmp_set:
John Snowc4e60232020-10-06 19:57:59 -0400318 if self._remove_monitor_sockfile:
319 assert isinstance(self._monitor_address, str)
320 self._remove_files.append(self._monitor_address)
John Snowbeb6b572021-05-27 17:16:53 -0400321 self._qmp_connection = QEMUMonitorProtocol(
John Snowc4e60232020-10-06 19:57:59 -0400322 self._monitor_address,
323 server=True,
324 nickname=self._name
325 )
John Snowabf0bf92019-06-27 17:28:14 -0400326
John Snow63c33f32021-05-27 17:16:49 -0400327 # NOTE: Make sure any opened resources are *definitely* freed in
328 # _post_shutdown()!
329 # pylint: disable=consider-using-with
Cleber Rosab306e262021-02-11 16:55:05 -0500330 self._qemu_log_path = os.path.join(self.log_dir, self._name + ".log")
John Snow63c33f32021-05-27 17:16:49 -0400331 self._qemu_log_file = open(self._qemu_log_path, 'wb')
332
John Snowf12a2822020-10-06 19:58:08 -0400333 def _post_launch(self) -> None:
John Snowbe1183e2020-10-06 19:58:04 -0400334 if self._qmp_connection:
Emanuele Giuseppe Espositoe2f948a2021-08-09 11:00:59 +0200335 self._qmp.accept(self._qmp_timer)
John Snowabf0bf92019-06-27 17:28:14 -0400336
Emanuele Giuseppe Espositoeb7a91d2021-08-09 11:01:13 +0200337 def _close_qemu_log_file(self) -> None:
338 if self._qemu_log_file is not None:
339 self._qemu_log_file.close()
340 self._qemu_log_file = None
341
John Snowf12a2822020-10-06 19:58:08 -0400342 def _post_shutdown(self) -> None:
John Snowa3842cb2020-07-10 01:06:42 -0400343 """
344 Called to cleanup the VM instance after the process has exited.
345 May also be called after a failed launch.
346 """
347 # Comprehensive reset for the failed launch case:
348 self._early_cleanup()
349
John Snow49a608b2021-10-26 13:56:06 -0400350 try:
351 self._close_qmp_connection()
352 except Exception as err: # pylint: disable=broad-except
353 LOG.warning(
354 "Exception closing QMP connection: %s",
355 str(err) if str(err) else type(err).__name__
356 )
357 finally:
358 assert self._qmp_connection is None
John Snow671940e2020-07-10 01:06:39 -0400359
Emanuele Giuseppe Espositoeb7a91d2021-08-09 11:01:13 +0200360 self._close_qemu_log_file()
John Snowabf0bf92019-06-27 17:28:14 -0400361
Cleber Rosa3c1e16c2021-02-11 17:01:41 -0500362 self._load_io_log()
363
John Snowabf0bf92019-06-27 17:28:14 -0400364 self._qemu_log_path = None
365
John Snowabf0bf92019-06-27 17:28:14 -0400366 if self._temp_dir is not None:
367 shutil.rmtree(self._temp_dir)
368 self._temp_dir = None
369
Max Reitz32558ce2019-10-17 15:31:34 +0200370 while len(self._remove_files) > 0:
371 self._remove_if_exists(self._remove_files.pop())
372
John Snow14661d92020-07-10 01:06:38 -0400373 exitcode = self.exitcode()
John Snowde6e08b2020-07-10 01:06:48 -0400374 if (exitcode is not None and exitcode < 0
375 and not (self._user_killed and exitcode == -signal.SIGKILL)):
John Snow14661d92020-07-10 01:06:38 -0400376 msg = 'qemu received signal %i; command: "%s"'
377 if self._qemu_full_args:
378 command = ' '.join(self._qemu_full_args)
379 else:
380 command = ''
381 LOG.warning(msg, -int(exitcode), command)
382
John Snowb9420e42021-10-26 13:56:05 -0400383 self._quit_issued = False
John Snowde6e08b2020-07-10 01:06:48 -0400384 self._user_killed = False
John Snow14661d92020-07-10 01:06:38 -0400385 self._launched = False
386
John Snowf12a2822020-10-06 19:58:08 -0400387 def launch(self) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400388 """
389 Launch the VM and make sure we cleanup and expose the
390 command line/output in case of exception
391 """
392
393 if self._launched:
394 raise QEMUMachineError('VM already launched')
395
396 self._iolog = None
John Snowaad3f3b2020-10-06 19:58:06 -0400397 self._qemu_full_args = ()
John Snowabf0bf92019-06-27 17:28:14 -0400398 try:
399 self._launch()
400 self._launched = True
401 except:
John Snowa3842cb2020-07-10 01:06:42 -0400402 self._post_shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400403
404 LOG.debug('Error launching VM')
405 if self._qemu_full_args:
406 LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
407 if self._iolog:
408 LOG.debug('Output: %r', self._iolog)
409 raise
410
John Snowf12a2822020-10-06 19:58:08 -0400411 def _launch(self) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400412 """
413 Launch the VM and establish a QMP connection
414 """
John Snowabf0bf92019-06-27 17:28:14 -0400415 self._pre_launch()
John Snowaad3f3b2020-10-06 19:58:06 -0400416 self._qemu_full_args = tuple(
417 chain(self._wrapper,
418 [self._binary],
419 self._base_args,
420 self._args)
421 )
John Snowabf0bf92019-06-27 17:28:14 -0400422 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
John Snowa0eae172021-05-27 17:16:50 -0400423
424 # Cleaning up of this subprocess is guaranteed by _do_shutdown.
425 # pylint: disable=consider-using-with
John Snowabf0bf92019-06-27 17:28:14 -0400426 self._popen = subprocess.Popen(self._qemu_full_args,
John Snow07b71232021-05-27 17:16:46 -0400427 stdin=subprocess.DEVNULL,
John Snowabf0bf92019-06-27 17:28:14 -0400428 stdout=self._qemu_log_file,
429 stderr=subprocess.STDOUT,
430 shell=False,
431 close_fds=False)
432 self._post_launch()
433
John Snow49a608b2021-10-26 13:56:06 -0400434 def _close_qmp_connection(self) -> None:
435 """
436 Close the underlying QMP connection, if any.
437
438 Dutifully report errors that occurred while closing, but assume
439 that any error encountered indicates an abnormal termination
440 process and not a failure to close.
441 """
442 if self._qmp_connection is None:
443 return
444
445 try:
446 self._qmp.close()
447 except EOFError:
448 # EOF can occur as an Exception here when using the Async
449 # QMP backend. It indicates that the server closed the
450 # stream. If we successfully issued 'quit' at any point,
451 # then this was expected. If the remote went away without
452 # our permission, it's worth reporting that as an abnormal
453 # shutdown case.
454 if not (self._user_killed or self._quit_issued):
455 raise
456 finally:
457 self._qmp_connection = None
458
John Snowe2c97f12020-07-10 01:06:40 -0400459 def _early_cleanup(self) -> None:
460 """
461 Perform any cleanup that needs to happen before the VM exits.
John Snowa3842cb2020-07-10 01:06:42 -0400462
John Snow193bf1c2020-07-10 01:06:47 -0400463 May be invoked by both soft and hard shutdown in failover scenarios.
John Snowa3842cb2020-07-10 01:06:42 -0400464 Called additionally by _post_shutdown for comprehensive cleanup.
John Snowe2c97f12020-07-10 01:06:40 -0400465 """
466 # If we keep the console socket open, we may deadlock waiting
467 # for QEMU to exit, while QEMU is waiting for the socket to
468 # become writeable.
469 if self._console_socket is not None:
470 self._console_socket.close()
471 self._console_socket = None
472
John Snow193bf1c2020-07-10 01:06:47 -0400473 def _hard_shutdown(self) -> None:
474 """
475 Perform early cleanup, kill the VM, and wait for it to terminate.
476
477 :raise subprocess.Timeout: When timeout is exceeds 60 seconds
478 waiting for the QEMU process to terminate.
479 """
480 self._early_cleanup()
John Snow9223fda2020-10-06 19:58:05 -0400481 self._subp.kill()
482 self._subp.wait(timeout=60)
John Snow193bf1c2020-07-10 01:06:47 -0400483
John Snowb9420e42021-10-26 13:56:05 -0400484 def _soft_shutdown(self, timeout: Optional[int]) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400485 """
486 Perform early cleanup, attempt to gracefully shut down the VM, and wait
487 for it to terminate.
488
John Snow8226a4b2020-07-20 12:02:52 -0400489 :param timeout: Timeout in seconds for graceful shutdown.
490 A value of None is an infinite wait.
John Snow193bf1c2020-07-10 01:06:47 -0400491
492 :raise ConnectionReset: On QMP communication errors
493 :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
494 the QEMU process to terminate.
495 """
496 self._early_cleanup()
497
John Snowbe1183e2020-10-06 19:58:04 -0400498 if self._qmp_connection:
John Snow49a608b2021-10-26 13:56:06 -0400499 try:
500 if not self._quit_issued:
501 # May raise ExecInterruptedError or StateError if the
502 # connection dies or has *already* died.
503 self.qmp('quit')
504 finally:
505 # Regardless, we want to quiesce the connection.
506 self._close_qmp_connection()
John Snow193bf1c2020-07-10 01:06:47 -0400507
508 # May raise subprocess.TimeoutExpired
John Snow9223fda2020-10-06 19:58:05 -0400509 self._subp.wait(timeout=timeout)
John Snow193bf1c2020-07-10 01:06:47 -0400510
John Snowb9420e42021-10-26 13:56:05 -0400511 def _do_shutdown(self, timeout: Optional[int]) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400512 """
513 Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
514
John Snow8226a4b2020-07-20 12:02:52 -0400515 :param timeout: Timeout in seconds for graceful shutdown.
516 A value of None is an infinite wait.
John Snow193bf1c2020-07-10 01:06:47 -0400517
518 :raise AbnormalShutdown: When the VM could not be shut down gracefully.
519 The inner exception will likely be ConnectionReset or
520 subprocess.TimeoutExpired. In rare cases, non-graceful termination
521 may result in its own exceptions, likely subprocess.TimeoutExpired.
522 """
523 try:
John Snowb9420e42021-10-26 13:56:05 -0400524 self._soft_shutdown(timeout)
John Snow193bf1c2020-07-10 01:06:47 -0400525 except Exception as exc:
526 self._hard_shutdown()
527 raise AbnormalShutdown("Could not perform graceful shutdown") \
528 from exc
529
John Snowb9420e42021-10-26 13:56:05 -0400530 def shutdown(self,
John Snowc9b30452020-07-10 01:06:43 -0400531 hard: bool = False,
John Snow8226a4b2020-07-20 12:02:52 -0400532 timeout: Optional[int] = 30) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400533 """
John Snow193bf1c2020-07-10 01:06:47 -0400534 Terminate the VM (gracefully if possible) and perform cleanup.
535 Cleanup will always be performed.
536
537 If the VM has not yet been launched, or shutdown(), wait(), or kill()
538 have already been called, this method does nothing.
539
John Snow193bf1c2020-07-10 01:06:47 -0400540 :param hard: When true, do not attempt graceful shutdown, and
541 suppress the SIGKILL warning log message.
542 :param timeout: Optional timeout in seconds for graceful shutdown.
John Snow8226a4b2020-07-20 12:02:52 -0400543 Default 30 seconds, A `None` value is an infinite wait.
John Snowabf0bf92019-06-27 17:28:14 -0400544 """
John Snowa3842cb2020-07-10 01:06:42 -0400545 if not self._launched:
546 return
547
John Snow193bf1c2020-07-10 01:06:47 -0400548 try:
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300549 if hard:
John Snowde6e08b2020-07-10 01:06:48 -0400550 self._user_killed = True
John Snow193bf1c2020-07-10 01:06:47 -0400551 self._hard_shutdown()
552 else:
John Snowb9420e42021-10-26 13:56:05 -0400553 self._do_shutdown(timeout)
John Snow193bf1c2020-07-10 01:06:47 -0400554 finally:
555 self._post_shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400556
John Snowf12a2822020-10-06 19:58:08 -0400557 def kill(self) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400558 """
559 Terminate the VM forcefully, wait for it to exit, and perform cleanup.
560 """
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300561 self.shutdown(hard=True)
562
John Snow8226a4b2020-07-20 12:02:52 -0400563 def wait(self, timeout: Optional[int] = 30) -> None:
John Snow89528052020-07-10 01:06:44 -0400564 """
565 Wait for the VM to power off and perform post-shutdown cleanup.
566
John Snow8226a4b2020-07-20 12:02:52 -0400567 :param timeout: Optional timeout in seconds. Default 30 seconds.
568 A value of `None` is an infinite wait.
John Snow89528052020-07-10 01:06:44 -0400569 """
John Snowb9420e42021-10-26 13:56:05 -0400570 self._quit_issued = True
571 self.shutdown(timeout=timeout)
John Snow89528052020-07-10 01:06:44 -0400572
John Snowf12a2822020-10-06 19:58:08 -0400573 def set_qmp_monitor(self, enabled: bool = True) -> None:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500574 """
575 Set the QMP monitor.
576
577 @param enabled: if False, qmp monitor options will be removed from
578 the base arguments of the resulting QEMU command
579 line. Default is True.
John Snow5c02c862021-06-29 17:43:23 -0400580
581 .. note:: Call this function before launch().
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500582 """
John Snowbe1183e2020-10-06 19:58:04 -0400583 self._qmp_set = enabled
584
585 @property
John Snowbeb6b572021-05-27 17:16:53 -0400586 def _qmp(self) -> QEMUMonitorProtocol:
John Snowbe1183e2020-10-06 19:58:04 -0400587 if self._qmp_connection is None:
588 raise QEMUMachineError("Attempt to access QMP with no connection")
589 return self._qmp_connection
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500590
John Snowaaa81ec2020-10-06 19:58:03 -0400591 @classmethod
Vladimir Sementsov-Ogievskiyc7daa572021-08-24 11:38:45 +0300592 def _qmp_args(cls, conv_keys: bool,
593 args: Dict[str, Any]) -> Dict[str, object]:
594 if conv_keys:
595 return {k.replace('_', '-'): v for k, v in args.items()}
596
597 return args
John Snowabf0bf92019-06-27 17:28:14 -0400598
John Snowaaa81ec2020-10-06 19:58:03 -0400599 def qmp(self, cmd: str,
Vladimir Sementsov-Ogievskiy3f3c9b42021-08-24 11:38:46 +0300600 args_dict: Optional[Dict[str, object]] = None,
601 conv_keys: Optional[bool] = None,
John Snowaaa81ec2020-10-06 19:58:03 -0400602 **args: Any) -> QMPMessage:
603 """
604 Invoke a QMP command and return the response dict
605 """
Vladimir Sementsov-Ogievskiy3f3c9b42021-08-24 11:38:46 +0300606 if args_dict is not None:
607 assert not args
608 assert conv_keys is None
609 args = args_dict
610 conv_keys = False
611
612 if conv_keys is None:
613 conv_keys = True
614
Vladimir Sementsov-Ogievskiyc7daa572021-08-24 11:38:45 +0300615 qmp_args = self._qmp_args(conv_keys, args)
John Snowb9420e42021-10-26 13:56:05 -0400616 ret = self._qmp.cmd(cmd, args=qmp_args)
617 if cmd == 'quit' and 'error' not in ret and 'return' in ret:
618 self._quit_issued = True
619 return ret
John Snowabf0bf92019-06-27 17:28:14 -0400620
John Snowf12a2822020-10-06 19:58:08 -0400621 def command(self, cmd: str,
622 conv_keys: bool = True,
623 **args: Any) -> QMPReturnValue:
John Snowabf0bf92019-06-27 17:28:14 -0400624 """
625 Invoke a QMP command.
626 On success return the response dict.
627 On failure raise an exception.
628 """
Vladimir Sementsov-Ogievskiyc7daa572021-08-24 11:38:45 +0300629 qmp_args = self._qmp_args(conv_keys, args)
John Snowb9420e42021-10-26 13:56:05 -0400630 ret = self._qmp.command(cmd, **qmp_args)
631 if cmd == 'quit':
632 self._quit_issued = True
633 return ret
John Snowabf0bf92019-06-27 17:28:14 -0400634
John Snowf12a2822020-10-06 19:58:08 -0400635 def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400636 """
637 Poll for one queued QMP events and return it
638 """
John Snow306dfcd2019-06-27 17:28:15 -0400639 if self._events:
John Snowabf0bf92019-06-27 17:28:14 -0400640 return self._events.pop(0)
641 return self._qmp.pull_event(wait=wait)
642
John Snowf12a2822020-10-06 19:58:08 -0400643 def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400644 """
645 Poll for queued QMP events and return a list of dicts
646 """
647 events = self._qmp.get_events(wait=wait)
648 events.extend(self._events)
649 del self._events[:]
John Snowabf0bf92019-06-27 17:28:14 -0400650 return events
651
652 @staticmethod
John Snowf12a2822020-10-06 19:58:08 -0400653 def event_match(event: Any, match: Optional[Any]) -> bool:
John Snowabf0bf92019-06-27 17:28:14 -0400654 """
655 Check if an event matches optional match criteria.
656
657 The match criteria takes the form of a matching subdict. The event is
658 checked to be a superset of the subdict, recursively, with matching
659 values whenever the subdict values are not None.
660
661 This has a limitation that you cannot explicitly check for None values.
662
663 Examples, with the subdict queries on the left:
664 - None matches any object.
665 - {"foo": None} matches {"foo": {"bar": 1}}
666 - {"foo": None} matches {"foo": 5}
667 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
668 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
669 """
670 if match is None:
671 return True
672
673 try:
674 for key in match:
675 if key in event:
676 if not QEMUMachine.event_match(event[key], match[key]):
677 return False
678 else:
679 return False
680 return True
681 except TypeError:
682 # either match or event wasn't iterable (not a dict)
John Snowf12a2822020-10-06 19:58:08 -0400683 return bool(match == event)
John Snowabf0bf92019-06-27 17:28:14 -0400684
John Snowf12a2822020-10-06 19:58:08 -0400685 def event_wait(self, name: str,
686 timeout: float = 60.0,
687 match: Optional[QMPMessage] = None) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400688 """
689 event_wait waits for and returns a named event from QMP with a timeout.
690
691 name: The event to wait for.
692 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
693 match: Optional match criteria. See event_match for details.
694 """
695 return self.events_wait([(name, match)], timeout)
696
John Snowf12a2822020-10-06 19:58:08 -0400697 def events_wait(self,
698 events: Sequence[Tuple[str, Any]],
699 timeout: float = 60.0) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400700 """
John Snow1847a4a2020-10-06 19:58:02 -0400701 events_wait waits for and returns a single named event from QMP.
702 In the case of multiple qualifying events, this function returns the
703 first one.
John Snowabf0bf92019-06-27 17:28:14 -0400704
John Snow1847a4a2020-10-06 19:58:02 -0400705 :param events: A sequence of (name, match_criteria) tuples.
706 The match criteria are optional and may be None.
707 See event_match for details.
708 :param timeout: Optional timeout, in seconds.
709 See QEMUMonitorProtocol.pull_event.
710
711 :raise QMPTimeoutError: If timeout was non-zero and no matching events
712 were found.
713 :return: A QMP event matching the filter criteria.
714 If timeout was 0 and no event matched, None.
John Snowabf0bf92019-06-27 17:28:14 -0400715 """
John Snowf12a2822020-10-06 19:58:08 -0400716 def _match(event: QMPMessage) -> bool:
John Snowabf0bf92019-06-27 17:28:14 -0400717 for name, match in events:
John Snow306dfcd2019-06-27 17:28:15 -0400718 if event['event'] == name and self.event_match(event, match):
John Snowabf0bf92019-06-27 17:28:14 -0400719 return True
720 return False
721
John Snow1847a4a2020-10-06 19:58:02 -0400722 event: Optional[QMPMessage]
723
John Snowabf0bf92019-06-27 17:28:14 -0400724 # Search cached events
725 for event in self._events:
726 if _match(event):
727 self._events.remove(event)
728 return event
729
730 # Poll for new events
731 while True:
732 event = self._qmp.pull_event(wait=timeout)
John Snow1847a4a2020-10-06 19:58:02 -0400733 if event is None:
734 # NB: None is only returned when timeout is false-ish.
735 # Timeouts raise QMPTimeoutError instead!
736 break
John Snowabf0bf92019-06-27 17:28:14 -0400737 if _match(event):
738 return event
739 self._events.append(event)
740
741 return None
742
John Snowf12a2822020-10-06 19:58:08 -0400743 def get_log(self) -> Optional[str]:
John Snowabf0bf92019-06-27 17:28:14 -0400744 """
745 After self.shutdown or failed qemu execution, this returns the output
746 of the qemu process.
747 """
748 return self._iolog
749
John Snowf12a2822020-10-06 19:58:08 -0400750 def add_args(self, *args: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400751 """
752 Adds to the list of extra arguments to be given to the QEMU binary
753 """
754 self._args.extend(args)
755
John Snowf12a2822020-10-06 19:58:08 -0400756 def set_machine(self, machine_type: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400757 """
758 Sets the machine type
759
760 If set, the machine type will be added to the base arguments
761 of the resulting QEMU command line.
762 """
763 self._machine = machine_type
764
John Snowf12a2822020-10-06 19:58:08 -0400765 def set_console(self,
766 device_type: Optional[str] = None,
767 console_index: int = 0) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400768 """
769 Sets the device type for a console device
770
771 If set, the console device and a backing character device will
772 be added to the base arguments of the resulting QEMU command
773 line.
774
775 This is a convenience method that will either use the provided
776 device type, or default to a "-serial chardev:console" command
777 line argument.
778
779 The actual setting of command line arguments will be be done at
780 machine launch time, as it depends on the temporary directory
781 to be created.
782
783 @param device_type: the device type, such as "isa-serial". If
784 None is given (the default value) a "-serial
785 chardev:console" command line argument will
786 be used instead, resorting to the machine's
787 default device type.
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100788 @param console_index: the index of the console device to use.
789 If not zero, the command line will create
790 'index - 1' consoles and connect them to
791 the 'null' backing character device.
John Snowabf0bf92019-06-27 17:28:14 -0400792 """
793 self._console_set = True
794 self._console_device_type = device_type
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100795 self._console_index = console_index
John Snowabf0bf92019-06-27 17:28:14 -0400796
797 @property
John Snowf12a2822020-10-06 19:58:08 -0400798 def console_socket(self) -> socket.socket:
John Snowabf0bf92019-06-27 17:28:14 -0400799 """
800 Returns a socket connected to the console
801 """
802 if self._console_socket is None:
Robert Foley80ded8e2020-07-24 07:45:08 +0100803 self._console_socket = console_socket.ConsoleSocket(
804 self._console_address,
805 file=self._console_log_path,
806 drain=self._drain_console)
John Snowabf0bf92019-06-27 17:28:14 -0400807 return self._console_socket
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500808
809 @property
810 def temp_dir(self) -> str:
811 """
812 Returns a temporary directory to be used for this machine
813 """
814 if self._temp_dir is None:
815 self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-",
816 dir=self._base_temp_dir)
817 return self._temp_dir
Cleber Rosab306e262021-02-11 16:55:05 -0500818
819 @property
820 def log_dir(self) -> str:
821 """
822 Returns a directory to be used for writing logs
823 """
824 if self._log_dir is None:
825 return self.temp_dir
826 return self._log_dir