blob: a5972fab4d2b1fddfa2d5a7db882f8ca66b334a1 [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 Snow50465f92022-01-31 23:11:32 -050077class VMLaunchFailure(QEMUMachineError):
78 """
79 Exception raised when a VM launch was attempted, but failed.
80 """
81 def __init__(self, exitcode: Optional[int],
82 command: str, output: Optional[str]):
83 super().__init__(exitcode, command, output)
84 self.exitcode = exitcode
85 self.command = command
86 self.output = output
87
88 def __str__(self) -> str:
89 ret = ''
90 if self.__cause__ is not None:
91 name = type(self.__cause__).__name__
92 reason = str(self.__cause__)
93 if reason:
94 ret += f"{name}: {reason}"
95 else:
96 ret += f"{name}"
97 ret += '\n'
98
99 if self.exitcode is not None:
100 ret += f"\tExit code: {self.exitcode}\n"
101 ret += f"\tCommand: {self.command}\n"
102 ret += f"\tOutput: {self.output}\n"
103 return ret
104
105
John Snow193bf1c2020-07-10 01:06:47 -0400106class AbnormalShutdown(QEMUMachineError):
107 """
108 Exception raised when a graceful shutdown was requested, but not performed.
109 """
110
111
Vladimir Sementsov-Ogievskiy15c3b862021-08-24 11:38:47 +0300112_T = TypeVar('_T', bound='QEMUMachine')
113
114
John Snow9b8ccd62020-05-28 18:21:28 -0400115class QEMUMachine:
John Snowabf0bf92019-06-27 17:28:14 -0400116 """
John Snowf12a2822020-10-06 19:58:08 -0400117 A QEMU VM.
John Snowabf0bf92019-06-27 17:28:14 -0400118
John Snow8dfac2e2020-05-28 18:21:29 -0400119 Use this object as a context manager to ensure
120 the QEMU process terminates::
John Snowabf0bf92019-06-27 17:28:14 -0400121
122 with VM(binary) as vm:
123 ...
124 # vm is guaranteed to be shut down here
125 """
John Snow82e65172021-06-29 17:43:11 -0400126 # pylint: disable=too-many-instance-attributes, too-many-public-methods
John Snowabf0bf92019-06-27 17:28:14 -0400127
John Snowaad3f3b2020-10-06 19:58:06 -0400128 def __init__(self,
129 binary: str,
130 args: Sequence[str] = (),
131 wrapper: Sequence[str] = (),
132 name: Optional[str] = None,
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500133 base_temp_dir: str = "/var/tmp",
John Snowc4e60232020-10-06 19:57:59 -0400134 monitor_address: Optional[SocketAddrT] = None,
John Snowf12a2822020-10-06 19:58:08 -0400135 sock_dir: Optional[str] = None,
136 drain_console: bool = False,
Cleber Rosab306e262021-02-11 16:55:05 -0500137 console_log: Optional[str] = None,
Emanuele Giuseppe Espositoe2f948a2021-08-09 11:00:59 +0200138 log_dir: Optional[str] = None,
139 qmp_timer: Optional[float] = None):
John Snowabf0bf92019-06-27 17:28:14 -0400140 '''
141 Initialize a QEMUMachine
142
143 @param binary: path to the qemu binary
144 @param args: list of extra arguments
145 @param wrapper: list of arguments used as prefix to qemu binary
146 @param name: prefix for socket and log file names (default: qemu-PID)
John Snow859aeb62021-05-27 17:16:51 -0400147 @param base_temp_dir: default location where temp files are created
John Snowabf0bf92019-06-27 17:28:14 -0400148 @param monitor_address: address for QMP monitor
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500149 @param sock_dir: where to create socket (defaults to base_temp_dir)
Robert Foley0fc8f662020-07-01 14:56:24 +0100150 @param drain_console: (optional) True to drain console socket to buffer
John Snowc5e61a62020-10-06 19:58:00 -0400151 @param console_log: (optional) path to console log file
Cleber Rosab306e262021-02-11 16:55:05 -0500152 @param log_dir: where to create and keep log files
Emanuele Giuseppe Espositoe2f948a2021-08-09 11:00:59 +0200153 @param qmp_timer: (optional) default QMP socket timeout
John Snowabf0bf92019-06-27 17:28:14 -0400154 @note: Qemu process is not started until launch() is used.
155 '''
John Snow82e65172021-06-29 17:43:11 -0400156 # pylint: disable=too-many-arguments
157
John Snowc5e61a62020-10-06 19:58:00 -0400158 # Direct user configuration
159
160 self._binary = binary
John Snowc5e61a62020-10-06 19:58:00 -0400161 self._args = list(args)
John Snowc5e61a62020-10-06 19:58:00 -0400162 self._wrapper = wrapper
Emanuele Giuseppe Espositoe2f948a2021-08-09 11:00:59 +0200163 self._qmp_timer = qmp_timer
John Snowc5e61a62020-10-06 19:58:00 -0400164
John Snow72b17fe2021-11-18 15:46:16 -0500165 self._name = name or f"qemu-{os.getpid()}-{id(self):02x}"
John Snow87bf1fe2021-11-18 15:46:14 -0500166 self._temp_dir: Optional[str] = None
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500167 self._base_temp_dir = base_temp_dir
John Snow87bf1fe2021-11-18 15:46:14 -0500168 self._sock_dir = sock_dir
Cleber Rosab306e262021-02-11 16:55:05 -0500169 self._log_dir = log_dir
John Snowc5e61a62020-10-06 19:58:00 -0400170
John Snowc4e60232020-10-06 19:57:59 -0400171 if monitor_address is not None:
172 self._monitor_address = monitor_address
John Snowc4e60232020-10-06 19:57:59 -0400173 else:
174 self._monitor_address = os.path.join(
John Snow87bf1fe2021-11-18 15:46:14 -0500175 self.sock_dir, f"{self._name}-monitor.sock"
John Snowc4e60232020-10-06 19:57:59 -0400176 )
John Snowc5e61a62020-10-06 19:58:00 -0400177
178 self._console_log_path = console_log
179 if self._console_log_path:
180 # In order to log the console, buffering needs to be enabled.
181 self._drain_console = True
182 else:
183 self._drain_console = drain_console
184
185 # Runstate
John Snowf12a2822020-10-06 19:58:08 -0400186 self._qemu_log_path: Optional[str] = None
187 self._qemu_log_file: Optional[BinaryIO] = None
John Snow9223fda2020-10-06 19:58:05 -0400188 self._popen: Optional['subprocess.Popen[bytes]'] = None
John Snowf12a2822020-10-06 19:58:08 -0400189 self._events: List[QMPMessage] = []
190 self._iolog: Optional[str] = None
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500191 self._qmp_set = True # Enable QMP monitor by default.
John Snowbeb6b572021-05-27 17:16:53 -0400192 self._qmp_connection: Optional[QEMUMonitorProtocol] = None
John Snowaad3f3b2020-10-06 19:58:06 -0400193 self._qemu_full_args: Tuple[str, ...] = ()
John Snowabf0bf92019-06-27 17:28:14 -0400194 self._launched = False
John Snowf12a2822020-10-06 19:58:08 -0400195 self._machine: Optional[str] = None
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100196 self._console_index = 0
John Snowabf0bf92019-06-27 17:28:14 -0400197 self._console_set = False
John Snowf12a2822020-10-06 19:58:08 -0400198 self._console_device_type: Optional[str] = None
John Snow652809d2020-10-06 19:58:01 -0400199 self._console_address = os.path.join(
John Snow87bf1fe2021-11-18 15:46:14 -0500200 self.sock_dir, f"{self._name}-console.sock"
John Snow652809d2020-10-06 19:58:01 -0400201 )
John Snowf12a2822020-10-06 19:58:08 -0400202 self._console_socket: Optional[socket.socket] = None
203 self._remove_files: List[str] = []
John Snowde6e08b2020-07-10 01:06:48 -0400204 self._user_killed = False
John Snowb9420e42021-10-26 13:56:05 -0400205 self._quit_issued = False
John Snowabf0bf92019-06-27 17:28:14 -0400206
Vladimir Sementsov-Ogievskiy15c3b862021-08-24 11:38:47 +0300207 def __enter__(self: _T) -> _T:
John Snowabf0bf92019-06-27 17:28:14 -0400208 return self
209
John Snow1dda0402020-05-14 01:53:44 -0400210 def __exit__(self,
211 exc_type: Optional[Type[BaseException]],
212 exc_val: Optional[BaseException],
213 exc_tb: Optional[TracebackType]) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400214 self.shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400215
John Snowf12a2822020-10-06 19:58:08 -0400216 def add_monitor_null(self) -> None:
John Snow306dfcd2019-06-27 17:28:15 -0400217 """
218 This can be used to add an unused monitor instance.
219 """
John Snowabf0bf92019-06-27 17:28:14 -0400220 self._args.append('-monitor')
221 self._args.append('null')
222
Vladimir Sementsov-Ogievskiy15c3b862021-08-24 11:38:47 +0300223 def add_fd(self: _T, fd: int, fdset: int,
224 opaque: str, opts: str = '') -> _T:
John Snowabf0bf92019-06-27 17:28:14 -0400225 """
226 Pass a file descriptor to the VM
227 """
228 options = ['fd=%d' % fd,
229 'set=%d' % fdset,
230 'opaque=%s' % opaque]
231 if opts:
232 options.append(opts)
233
234 # This did not exist before 3.4, but since then it is
235 # mandatory for our purpose
236 if hasattr(os, 'set_inheritable'):
237 os.set_inheritable(fd, True)
238
239 self._args.append('-add-fd')
240 self._args.append(','.join(options))
241 return self
242
John Snowf12a2822020-10-06 19:58:08 -0400243 def send_fd_scm(self, fd: Optional[int] = None,
244 file_path: Optional[str] = None) -> int:
John Snow306dfcd2019-06-27 17:28:15 -0400245 """
John Snow514d00d2021-09-22 20:49:30 -0400246 Send an fd or file_path to the remote via SCM_RIGHTS.
John Snow306dfcd2019-06-27 17:28:15 -0400247
John Snow514d00d2021-09-22 20:49:30 -0400248 Exactly one of fd and file_path must be given. If it is
249 file_path, the file will be opened read-only and the new file
250 descriptor will be sent to the remote.
John Snow306dfcd2019-06-27 17:28:15 -0400251 """
John Snowabf0bf92019-06-27 17:28:14 -0400252 if file_path is not None:
253 assert fd is None
John Snow514d00d2021-09-22 20:49:30 -0400254 with open(file_path, "rb") as passfile:
255 fd = passfile.fileno()
256 self._qmp.send_fd_scm(fd)
John Snowabf0bf92019-06-27 17:28:14 -0400257 else:
258 assert fd is not None
John Snow514d00d2021-09-22 20:49:30 -0400259 self._qmp.send_fd_scm(fd)
John Snowabf0bf92019-06-27 17:28:14 -0400260
John Snow514d00d2021-09-22 20:49:30 -0400261 return 0
John Snowabf0bf92019-06-27 17:28:14 -0400262
263 @staticmethod
John Snowf12a2822020-10-06 19:58:08 -0400264 def _remove_if_exists(path: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400265 """
266 Remove file object at path if it exists
267 """
268 try:
269 os.remove(path)
270 except OSError as exception:
271 if exception.errno == errno.ENOENT:
272 return
273 raise
274
John Snowf12a2822020-10-06 19:58:08 -0400275 def is_running(self) -> bool:
John Snow306dfcd2019-06-27 17:28:15 -0400276 """Returns true if the VM is running."""
John Snowabf0bf92019-06-27 17:28:14 -0400277 return self._popen is not None and self._popen.poll() is None
278
John Snow9223fda2020-10-06 19:58:05 -0400279 @property
280 def _subp(self) -> 'subprocess.Popen[bytes]':
281 if self._popen is None:
282 raise QEMUMachineError('Subprocess pipe not present')
283 return self._popen
284
John Snowf12a2822020-10-06 19:58:08 -0400285 def exitcode(self) -> Optional[int]:
John Snow306dfcd2019-06-27 17:28:15 -0400286 """Returns the exit code if possible, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400287 if self._popen is None:
288 return None
289 return self._popen.poll()
290
John Snowf12a2822020-10-06 19:58:08 -0400291 def get_pid(self) -> Optional[int]:
John Snow306dfcd2019-06-27 17:28:15 -0400292 """Returns the PID of the running process, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400293 if not self.is_running():
294 return None
John Snow9223fda2020-10-06 19:58:05 -0400295 return self._subp.pid
John Snowabf0bf92019-06-27 17:28:14 -0400296
John Snowf12a2822020-10-06 19:58:08 -0400297 def _load_io_log(self) -> None:
John Snow5690b432021-09-16 14:22:47 -0400298 # Assume that the output encoding of QEMU's terminal output is
299 # defined by our locale. If indeterminate, allow open() to fall
300 # back to the platform default.
301 _, encoding = locale.getlocale()
John Snowabf0bf92019-06-27 17:28:14 -0400302 if self._qemu_log_path is not None:
John Snow5690b432021-09-16 14:22:47 -0400303 with open(self._qemu_log_path, "r", encoding=encoding) as iolog:
John Snowabf0bf92019-06-27 17:28:14 -0400304 self._iolog = iolog.read()
305
John Snow652809d2020-10-06 19:58:01 -0400306 @property
307 def _base_args(self) -> List[str]:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500308 args = ['-display', 'none', '-vga', 'none']
John Snowc4e60232020-10-06 19:57:59 -0400309
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500310 if self._qmp_set:
311 if isinstance(self._monitor_address, tuple):
John Snowc4e60232020-10-06 19:57:59 -0400312 moncdev = "socket,id=mon,host={},port={}".format(
313 *self._monitor_address
314 )
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500315 else:
John Snowc4e60232020-10-06 19:57:59 -0400316 moncdev = f"socket,id=mon,path={self._monitor_address}"
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500317 args.extend(['-chardev', moncdev, '-mon',
318 'chardev=mon,mode=control'])
John Snowc4e60232020-10-06 19:57:59 -0400319
John Snowabf0bf92019-06-27 17:28:14 -0400320 if self._machine is not None:
321 args.extend(['-machine', self._machine])
John Snow9b8ccd62020-05-28 18:21:28 -0400322 for _ in range(self._console_index):
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100323 args.extend(['-serial', 'null'])
John Snowabf0bf92019-06-27 17:28:14 -0400324 if self._console_set:
Paolo Bonzini991c1802020-11-13 03:10:52 -0500325 chardev = ('socket,id=console,path=%s,server=on,wait=off' %
John Snowabf0bf92019-06-27 17:28:14 -0400326 self._console_address)
327 args.extend(['-chardev', chardev])
328 if self._console_device_type is None:
329 args.extend(['-serial', 'chardev:console'])
330 else:
331 device = '%s,chardev=console' % self._console_device_type
332 args.extend(['-device', device])
333 return args
334
Wainer dos Santos Moschetta555fe0c2021-04-30 10:34:12 -0300335 @property
336 def args(self) -> List[str]:
337 """Returns the list of arguments given to the QEMU binary."""
338 return self._args
339
John Snowf12a2822020-10-06 19:58:08 -0400340 def _pre_launch(self) -> None:
John Snow652809d2020-10-06 19:58:01 -0400341 if self._console_set:
342 self._remove_files.append(self._console_address)
343
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500344 if self._qmp_set:
John Snow6eeb3de2021-11-18 15:46:15 -0500345 if isinstance(self._monitor_address, str):
John Snowc4e60232020-10-06 19:57:59 -0400346 self._remove_files.append(self._monitor_address)
John Snowbeb6b572021-05-27 17:16:53 -0400347 self._qmp_connection = QEMUMonitorProtocol(
John Snowc4e60232020-10-06 19:57:59 -0400348 self._monitor_address,
349 server=True,
350 nickname=self._name
351 )
John Snowabf0bf92019-06-27 17:28:14 -0400352
John Snow63c33f32021-05-27 17:16:49 -0400353 # NOTE: Make sure any opened resources are *definitely* freed in
354 # _post_shutdown()!
355 # pylint: disable=consider-using-with
Cleber Rosab306e262021-02-11 16:55:05 -0500356 self._qemu_log_path = os.path.join(self.log_dir, self._name + ".log")
John Snow63c33f32021-05-27 17:16:49 -0400357 self._qemu_log_file = open(self._qemu_log_path, 'wb')
358
John Snowb1ca9912021-11-18 15:46:17 -0500359 self._iolog = None
360 self._qemu_full_args = tuple(chain(
361 self._wrapper,
362 [self._binary],
363 self._base_args,
364 self._args
365 ))
366
John Snowf12a2822020-10-06 19:58:08 -0400367 def _post_launch(self) -> None:
John Snowbe1183e2020-10-06 19:58:04 -0400368 if self._qmp_connection:
Emanuele Giuseppe Espositoe2f948a2021-08-09 11:00:59 +0200369 self._qmp.accept(self._qmp_timer)
John Snowabf0bf92019-06-27 17:28:14 -0400370
Emanuele Giuseppe Espositoeb7a91d2021-08-09 11:01:13 +0200371 def _close_qemu_log_file(self) -> None:
372 if self._qemu_log_file is not None:
373 self._qemu_log_file.close()
374 self._qemu_log_file = None
375
John Snowf12a2822020-10-06 19:58:08 -0400376 def _post_shutdown(self) -> None:
John Snowa3842cb2020-07-10 01:06:42 -0400377 """
378 Called to cleanup the VM instance after the process has exited.
379 May also be called after a failed launch.
380 """
John Snow49a608b2021-10-26 13:56:06 -0400381 try:
382 self._close_qmp_connection()
383 except Exception as err: # pylint: disable=broad-except
384 LOG.warning(
385 "Exception closing QMP connection: %s",
386 str(err) if str(err) else type(err).__name__
387 )
388 finally:
389 assert self._qmp_connection is None
John Snow671940e2020-07-10 01:06:39 -0400390
Emanuele Giuseppe Espositoeb7a91d2021-08-09 11:01:13 +0200391 self._close_qemu_log_file()
John Snowabf0bf92019-06-27 17:28:14 -0400392
Cleber Rosa3c1e16c2021-02-11 17:01:41 -0500393 self._load_io_log()
394
John Snowabf0bf92019-06-27 17:28:14 -0400395 self._qemu_log_path = None
396
John Snowabf0bf92019-06-27 17:28:14 -0400397 if self._temp_dir is not None:
398 shutil.rmtree(self._temp_dir)
399 self._temp_dir = None
400
Max Reitz32558ce2019-10-17 15:31:34 +0200401 while len(self._remove_files) > 0:
402 self._remove_if_exists(self._remove_files.pop())
403
John Snow14661d92020-07-10 01:06:38 -0400404 exitcode = self.exitcode()
John Snowde6e08b2020-07-10 01:06:48 -0400405 if (exitcode is not None and exitcode < 0
406 and not (self._user_killed and exitcode == -signal.SIGKILL)):
John Snow14661d92020-07-10 01:06:38 -0400407 msg = 'qemu received signal %i; command: "%s"'
408 if self._qemu_full_args:
409 command = ' '.join(self._qemu_full_args)
410 else:
411 command = ''
412 LOG.warning(msg, -int(exitcode), command)
413
John Snowb9420e42021-10-26 13:56:05 -0400414 self._quit_issued = False
John Snowde6e08b2020-07-10 01:06:48 -0400415 self._user_killed = False
John Snow14661d92020-07-10 01:06:38 -0400416 self._launched = False
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 make sure we cleanup and expose the
421 command line/output in case of exception
422 """
423
424 if self._launched:
425 raise QEMUMachineError('VM already launched')
426
John Snowabf0bf92019-06-27 17:28:14 -0400427 try:
428 self._launch()
John Snow50465f92022-01-31 23:11:32 -0500429 except BaseException as exc:
John Snow1611e6c2021-11-18 15:46:18 -0500430 # We may have launched the process but it may
431 # have exited before we could connect via QMP.
432 # Assume the VM didn't launch or is exiting.
433 # If we don't wait for the process, exitcode() may still be
434 # 'None' by the time control is ceded back to the caller.
435 if self._launched:
436 self.wait()
437 else:
438 self._post_shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400439
John Snow50465f92022-01-31 23:11:32 -0500440 if isinstance(exc, Exception):
441 raise VMLaunchFailure(
442 exitcode=self.exitcode(),
443 command=' '.join(self._qemu_full_args),
444 output=self._iolog
445 ) from exc
446
447 # Don't wrap 'BaseException'; doing so would downgrade
448 # that exception. However, we still want to clean up.
John Snowabf0bf92019-06-27 17:28:14 -0400449 raise
450
John Snowf12a2822020-10-06 19:58:08 -0400451 def _launch(self) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400452 """
453 Launch the VM and establish a QMP connection
454 """
John Snowabf0bf92019-06-27 17:28:14 -0400455 self._pre_launch()
John Snowabf0bf92019-06-27 17:28:14 -0400456 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
John Snowa0eae172021-05-27 17:16:50 -0400457
458 # Cleaning up of this subprocess is guaranteed by _do_shutdown.
459 # pylint: disable=consider-using-with
John Snowabf0bf92019-06-27 17:28:14 -0400460 self._popen = subprocess.Popen(self._qemu_full_args,
John Snow07b71232021-05-27 17:16:46 -0400461 stdin=subprocess.DEVNULL,
John Snowabf0bf92019-06-27 17:28:14 -0400462 stdout=self._qemu_log_file,
463 stderr=subprocess.STDOUT,
464 shell=False,
465 close_fds=False)
John Snow1611e6c2021-11-18 15:46:18 -0500466 self._launched = True
John Snowabf0bf92019-06-27 17:28:14 -0400467 self._post_launch()
468
John Snow49a608b2021-10-26 13:56:06 -0400469 def _close_qmp_connection(self) -> None:
470 """
471 Close the underlying QMP connection, if any.
472
473 Dutifully report errors that occurred while closing, but assume
474 that any error encountered indicates an abnormal termination
475 process and not a failure to close.
476 """
477 if self._qmp_connection is None:
478 return
479
480 try:
481 self._qmp.close()
482 except EOFError:
483 # EOF can occur as an Exception here when using the Async
484 # QMP backend. It indicates that the server closed the
485 # stream. If we successfully issued 'quit' at any point,
486 # then this was expected. If the remote went away without
487 # our permission, it's worth reporting that as an abnormal
488 # shutdown case.
489 if not (self._user_killed or self._quit_issued):
490 raise
491 finally:
492 self._qmp_connection = None
493
John Snowe2c97f12020-07-10 01:06:40 -0400494 def _early_cleanup(self) -> None:
495 """
496 Perform any cleanup that needs to happen before the VM exits.
John Snowa3842cb2020-07-10 01:06:42 -0400497
John Snow1611e6c2021-11-18 15:46:18 -0500498 This method may be called twice upon shutdown, once each by soft
499 and hard shutdown in failover scenarios.
John Snowe2c97f12020-07-10 01:06:40 -0400500 """
501 # If we keep the console socket open, we may deadlock waiting
502 # for QEMU to exit, while QEMU is waiting for the socket to
503 # become writeable.
504 if self._console_socket is not None:
505 self._console_socket.close()
506 self._console_socket = None
507
John Snow193bf1c2020-07-10 01:06:47 -0400508 def _hard_shutdown(self) -> None:
509 """
510 Perform early cleanup, kill the VM, and wait for it to terminate.
511
512 :raise subprocess.Timeout: When timeout is exceeds 60 seconds
513 waiting for the QEMU process to terminate.
514 """
515 self._early_cleanup()
John Snow9223fda2020-10-06 19:58:05 -0400516 self._subp.kill()
517 self._subp.wait(timeout=60)
John Snow193bf1c2020-07-10 01:06:47 -0400518
John Snowb9420e42021-10-26 13:56:05 -0400519 def _soft_shutdown(self, timeout: Optional[int]) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400520 """
521 Perform early cleanup, attempt to gracefully shut down the VM, and wait
522 for it to terminate.
523
John Snow8226a4b2020-07-20 12:02:52 -0400524 :param timeout: Timeout in seconds for graceful shutdown.
525 A value of None is an infinite wait.
John Snow193bf1c2020-07-10 01:06:47 -0400526
527 :raise ConnectionReset: On QMP communication errors
528 :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
529 the QEMU process to terminate.
530 """
531 self._early_cleanup()
532
John Snowbe1183e2020-10-06 19:58:04 -0400533 if self._qmp_connection:
John Snow49a608b2021-10-26 13:56:06 -0400534 try:
535 if not self._quit_issued:
536 # May raise ExecInterruptedError or StateError if the
537 # connection dies or has *already* died.
538 self.qmp('quit')
539 finally:
540 # Regardless, we want to quiesce the connection.
541 self._close_qmp_connection()
John Snow193bf1c2020-07-10 01:06:47 -0400542
543 # May raise subprocess.TimeoutExpired
John Snow9223fda2020-10-06 19:58:05 -0400544 self._subp.wait(timeout=timeout)
John Snow193bf1c2020-07-10 01:06:47 -0400545
John Snowb9420e42021-10-26 13:56:05 -0400546 def _do_shutdown(self, timeout: Optional[int]) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400547 """
548 Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
549
John Snow8226a4b2020-07-20 12:02:52 -0400550 :param timeout: Timeout in seconds for graceful shutdown.
551 A value of None is an infinite wait.
John Snow193bf1c2020-07-10 01:06:47 -0400552
553 :raise AbnormalShutdown: When the VM could not be shut down gracefully.
554 The inner exception will likely be ConnectionReset or
555 subprocess.TimeoutExpired. In rare cases, non-graceful termination
556 may result in its own exceptions, likely subprocess.TimeoutExpired.
557 """
558 try:
John Snowb9420e42021-10-26 13:56:05 -0400559 self._soft_shutdown(timeout)
John Snow193bf1c2020-07-10 01:06:47 -0400560 except Exception as exc:
561 self._hard_shutdown()
562 raise AbnormalShutdown("Could not perform graceful shutdown") \
563 from exc
564
John Snowb9420e42021-10-26 13:56:05 -0400565 def shutdown(self,
John Snowc9b30452020-07-10 01:06:43 -0400566 hard: bool = False,
John Snow8226a4b2020-07-20 12:02:52 -0400567 timeout: Optional[int] = 30) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400568 """
John Snow193bf1c2020-07-10 01:06:47 -0400569 Terminate the VM (gracefully if possible) and perform cleanup.
570 Cleanup will always be performed.
571
572 If the VM has not yet been launched, or shutdown(), wait(), or kill()
573 have already been called, this method does nothing.
574
John Snow193bf1c2020-07-10 01:06:47 -0400575 :param hard: When true, do not attempt graceful shutdown, and
576 suppress the SIGKILL warning log message.
577 :param timeout: Optional timeout in seconds for graceful shutdown.
John Snow8226a4b2020-07-20 12:02:52 -0400578 Default 30 seconds, A `None` value is an infinite wait.
John Snowabf0bf92019-06-27 17:28:14 -0400579 """
John Snowa3842cb2020-07-10 01:06:42 -0400580 if not self._launched:
581 return
582
John Snow193bf1c2020-07-10 01:06:47 -0400583 try:
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300584 if hard:
John Snowde6e08b2020-07-10 01:06:48 -0400585 self._user_killed = True
John Snow193bf1c2020-07-10 01:06:47 -0400586 self._hard_shutdown()
587 else:
John Snowb9420e42021-10-26 13:56:05 -0400588 self._do_shutdown(timeout)
John Snow193bf1c2020-07-10 01:06:47 -0400589 finally:
590 self._post_shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400591
John Snowf12a2822020-10-06 19:58:08 -0400592 def kill(self) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400593 """
594 Terminate the VM forcefully, wait for it to exit, and perform cleanup.
595 """
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300596 self.shutdown(hard=True)
597
John Snow8226a4b2020-07-20 12:02:52 -0400598 def wait(self, timeout: Optional[int] = 30) -> None:
John Snow89528052020-07-10 01:06:44 -0400599 """
600 Wait for the VM to power off and perform post-shutdown cleanup.
601
John Snow8226a4b2020-07-20 12:02:52 -0400602 :param timeout: Optional timeout in seconds. Default 30 seconds.
603 A value of `None` is an infinite wait.
John Snow89528052020-07-10 01:06:44 -0400604 """
John Snowb9420e42021-10-26 13:56:05 -0400605 self._quit_issued = True
606 self.shutdown(timeout=timeout)
John Snow89528052020-07-10 01:06:44 -0400607
John Snowf12a2822020-10-06 19:58:08 -0400608 def set_qmp_monitor(self, enabled: bool = True) -> None:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500609 """
610 Set the QMP monitor.
611
612 @param enabled: if False, qmp monitor options will be removed from
613 the base arguments of the resulting QEMU command
614 line. Default is True.
John Snow5c02c862021-06-29 17:43:23 -0400615
616 .. note:: Call this function before launch().
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500617 """
John Snowbe1183e2020-10-06 19:58:04 -0400618 self._qmp_set = enabled
619
620 @property
John Snowbeb6b572021-05-27 17:16:53 -0400621 def _qmp(self) -> QEMUMonitorProtocol:
John Snowbe1183e2020-10-06 19:58:04 -0400622 if self._qmp_connection is None:
623 raise QEMUMachineError("Attempt to access QMP with no connection")
624 return self._qmp_connection
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500625
John Snowaaa81ec2020-10-06 19:58:03 -0400626 @classmethod
Vladimir Sementsov-Ogievskiyc7daa572021-08-24 11:38:45 +0300627 def _qmp_args(cls, conv_keys: bool,
628 args: Dict[str, Any]) -> Dict[str, object]:
629 if conv_keys:
630 return {k.replace('_', '-'): v for k, v in args.items()}
631
632 return args
John Snowabf0bf92019-06-27 17:28:14 -0400633
John Snowaaa81ec2020-10-06 19:58:03 -0400634 def qmp(self, cmd: str,
Vladimir Sementsov-Ogievskiy3f3c9b42021-08-24 11:38:46 +0300635 args_dict: Optional[Dict[str, object]] = None,
636 conv_keys: Optional[bool] = None,
John Snowaaa81ec2020-10-06 19:58:03 -0400637 **args: Any) -> QMPMessage:
638 """
639 Invoke a QMP command and return the response dict
640 """
Vladimir Sementsov-Ogievskiy3f3c9b42021-08-24 11:38:46 +0300641 if args_dict is not None:
642 assert not args
643 assert conv_keys is None
644 args = args_dict
645 conv_keys = False
646
647 if conv_keys is None:
648 conv_keys = True
649
Vladimir Sementsov-Ogievskiyc7daa572021-08-24 11:38:45 +0300650 qmp_args = self._qmp_args(conv_keys, args)
John Snowb9420e42021-10-26 13:56:05 -0400651 ret = self._qmp.cmd(cmd, args=qmp_args)
652 if cmd == 'quit' and 'error' not in ret and 'return' in ret:
653 self._quit_issued = True
654 return ret
John Snowabf0bf92019-06-27 17:28:14 -0400655
John Snowf12a2822020-10-06 19:58:08 -0400656 def command(self, cmd: str,
657 conv_keys: bool = True,
658 **args: Any) -> QMPReturnValue:
John Snowabf0bf92019-06-27 17:28:14 -0400659 """
660 Invoke a QMP command.
661 On success return the response dict.
662 On failure raise an exception.
663 """
Vladimir Sementsov-Ogievskiyc7daa572021-08-24 11:38:45 +0300664 qmp_args = self._qmp_args(conv_keys, args)
John Snowb9420e42021-10-26 13:56:05 -0400665 ret = self._qmp.command(cmd, **qmp_args)
666 if cmd == 'quit':
667 self._quit_issued = True
668 return ret
John Snowabf0bf92019-06-27 17:28:14 -0400669
John Snowf12a2822020-10-06 19:58:08 -0400670 def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400671 """
672 Poll for one queued QMP events and return it
673 """
John Snow306dfcd2019-06-27 17:28:15 -0400674 if self._events:
John Snowabf0bf92019-06-27 17:28:14 -0400675 return self._events.pop(0)
676 return self._qmp.pull_event(wait=wait)
677
John Snowf12a2822020-10-06 19:58:08 -0400678 def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400679 """
680 Poll for queued QMP events and return a list of dicts
681 """
682 events = self._qmp.get_events(wait=wait)
683 events.extend(self._events)
684 del self._events[:]
John Snowabf0bf92019-06-27 17:28:14 -0400685 return events
686
687 @staticmethod
John Snowf12a2822020-10-06 19:58:08 -0400688 def event_match(event: Any, match: Optional[Any]) -> bool:
John Snowabf0bf92019-06-27 17:28:14 -0400689 """
690 Check if an event matches optional match criteria.
691
692 The match criteria takes the form of a matching subdict. The event is
693 checked to be a superset of the subdict, recursively, with matching
694 values whenever the subdict values are not None.
695
696 This has a limitation that you cannot explicitly check for None values.
697
698 Examples, with the subdict queries on the left:
699 - None matches any object.
700 - {"foo": None} matches {"foo": {"bar": 1}}
701 - {"foo": None} matches {"foo": 5}
702 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
703 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
704 """
705 if match is None:
706 return True
707
708 try:
709 for key in match:
710 if key in event:
711 if not QEMUMachine.event_match(event[key], match[key]):
712 return False
713 else:
714 return False
715 return True
716 except TypeError:
717 # either match or event wasn't iterable (not a dict)
John Snowf12a2822020-10-06 19:58:08 -0400718 return bool(match == event)
John Snowabf0bf92019-06-27 17:28:14 -0400719
John Snowf12a2822020-10-06 19:58:08 -0400720 def event_wait(self, name: str,
721 timeout: float = 60.0,
722 match: Optional[QMPMessage] = None) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400723 """
724 event_wait waits for and returns a named event from QMP with a timeout.
725
726 name: The event to wait for.
727 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
728 match: Optional match criteria. See event_match for details.
729 """
730 return self.events_wait([(name, match)], timeout)
731
John Snowf12a2822020-10-06 19:58:08 -0400732 def events_wait(self,
733 events: Sequence[Tuple[str, Any]],
734 timeout: float = 60.0) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400735 """
John Snow1847a4a2020-10-06 19:58:02 -0400736 events_wait waits for and returns a single named event from QMP.
737 In the case of multiple qualifying events, this function returns the
738 first one.
John Snowabf0bf92019-06-27 17:28:14 -0400739
John Snow1847a4a2020-10-06 19:58:02 -0400740 :param events: A sequence of (name, match_criteria) tuples.
741 The match criteria are optional and may be None.
742 See event_match for details.
743 :param timeout: Optional timeout, in seconds.
744 See QEMUMonitorProtocol.pull_event.
745
746 :raise QMPTimeoutError: If timeout was non-zero and no matching events
747 were found.
748 :return: A QMP event matching the filter criteria.
749 If timeout was 0 and no event matched, None.
John Snowabf0bf92019-06-27 17:28:14 -0400750 """
John Snowf12a2822020-10-06 19:58:08 -0400751 def _match(event: QMPMessage) -> bool:
John Snowabf0bf92019-06-27 17:28:14 -0400752 for name, match in events:
John Snow306dfcd2019-06-27 17:28:15 -0400753 if event['event'] == name and self.event_match(event, match):
John Snowabf0bf92019-06-27 17:28:14 -0400754 return True
755 return False
756
John Snow1847a4a2020-10-06 19:58:02 -0400757 event: Optional[QMPMessage]
758
John Snowabf0bf92019-06-27 17:28:14 -0400759 # Search cached events
760 for event in self._events:
761 if _match(event):
762 self._events.remove(event)
763 return event
764
765 # Poll for new events
766 while True:
767 event = self._qmp.pull_event(wait=timeout)
John Snow1847a4a2020-10-06 19:58:02 -0400768 if event is None:
769 # NB: None is only returned when timeout is false-ish.
770 # Timeouts raise QMPTimeoutError instead!
771 break
John Snowabf0bf92019-06-27 17:28:14 -0400772 if _match(event):
773 return event
774 self._events.append(event)
775
776 return None
777
John Snowf12a2822020-10-06 19:58:08 -0400778 def get_log(self) -> Optional[str]:
John Snowabf0bf92019-06-27 17:28:14 -0400779 """
780 After self.shutdown or failed qemu execution, this returns the output
781 of the qemu process.
782 """
783 return self._iolog
784
John Snowf12a2822020-10-06 19:58:08 -0400785 def add_args(self, *args: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400786 """
787 Adds to the list of extra arguments to be given to the QEMU binary
788 """
789 self._args.extend(args)
790
John Snowf12a2822020-10-06 19:58:08 -0400791 def set_machine(self, machine_type: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400792 """
793 Sets the machine type
794
795 If set, the machine type will be added to the base arguments
796 of the resulting QEMU command line.
797 """
798 self._machine = machine_type
799
John Snowf12a2822020-10-06 19:58:08 -0400800 def set_console(self,
801 device_type: Optional[str] = None,
802 console_index: int = 0) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400803 """
804 Sets the device type for a console device
805
806 If set, the console device and a backing character device will
807 be added to the base arguments of the resulting QEMU command
808 line.
809
810 This is a convenience method that will either use the provided
811 device type, or default to a "-serial chardev:console" command
812 line argument.
813
814 The actual setting of command line arguments will be be done at
815 machine launch time, as it depends on the temporary directory
816 to be created.
817
818 @param device_type: the device type, such as "isa-serial". If
819 None is given (the default value) a "-serial
820 chardev:console" command line argument will
821 be used instead, resorting to the machine's
822 default device type.
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100823 @param console_index: the index of the console device to use.
824 If not zero, the command line will create
825 'index - 1' consoles and connect them to
826 the 'null' backing character device.
John Snowabf0bf92019-06-27 17:28:14 -0400827 """
828 self._console_set = True
829 self._console_device_type = device_type
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100830 self._console_index = console_index
John Snowabf0bf92019-06-27 17:28:14 -0400831
832 @property
John Snowf12a2822020-10-06 19:58:08 -0400833 def console_socket(self) -> socket.socket:
John Snowabf0bf92019-06-27 17:28:14 -0400834 """
835 Returns a socket connected to the console
836 """
837 if self._console_socket is None:
Robert Foley80ded8e2020-07-24 07:45:08 +0100838 self._console_socket = console_socket.ConsoleSocket(
839 self._console_address,
840 file=self._console_log_path,
841 drain=self._drain_console)
John Snowabf0bf92019-06-27 17:28:14 -0400842 return self._console_socket
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500843
844 @property
845 def temp_dir(self) -> str:
846 """
847 Returns a temporary directory to be used for this machine
848 """
849 if self._temp_dir is None:
850 self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-",
851 dir=self._base_temp_dir)
852 return self._temp_dir
Cleber Rosab306e262021-02-11 16:55:05 -0500853
854 @property
John Snow87bf1fe2021-11-18 15:46:14 -0500855 def sock_dir(self) -> str:
856 """
857 Returns the directory used for sockfiles by this machine.
858 """
859 if self._sock_dir:
860 return self._sock_dir
861 return self.temp_dir
862
863 @property
Cleber Rosab306e262021-02-11 16:55:05 -0500864 def log_dir(self) -> str:
865 """
866 Returns a directory to be used for writing logs
867 """
868 if self._log_dir is None:
869 return self.temp_dir
870 return self._log_dir