blob: 1c6532a3d68726a1e3f65d66b8bdc4aea1daecb5 [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 QEMUMonitorProtocol,
45 QMPMessage,
46 QMPReturnValue,
47 SocketAddrT,
48)
49
50from . import console_socket
John Snow932ca4b2020-10-06 19:57:58 -040051
John Snowabf0bf92019-06-27 17:28:14 -040052
53LOG = logging.getLogger(__name__)
54
John Snow8dfac2e2020-05-28 18:21:29 -040055
John Snowabf0bf92019-06-27 17:28:14 -040056class QEMUMachineError(Exception):
57 """
58 Exception called when an error in QEMUMachine happens.
59 """
60
61
62class QEMUMachineAddDeviceError(QEMUMachineError):
63 """
64 Exception raised when a request to add a device can not be fulfilled
65
66 The failures are caused by limitations, lack of information or conflicting
67 requests on the QEMUMachine methods. This exception does not represent
68 failures reported by the QEMU binary itself.
69 """
70
71
John Snow193bf1c2020-07-10 01:06:47 -040072class AbnormalShutdown(QEMUMachineError):
73 """
74 Exception raised when a graceful shutdown was requested, but not performed.
75 """
76
77
Vladimir Sementsov-Ogievskiy15c3b862021-08-24 11:38:47 +030078_T = TypeVar('_T', bound='QEMUMachine')
79
80
John Snow9b8ccd62020-05-28 18:21:28 -040081class QEMUMachine:
John Snowabf0bf92019-06-27 17:28:14 -040082 """
John Snowf12a2822020-10-06 19:58:08 -040083 A QEMU VM.
John Snowabf0bf92019-06-27 17:28:14 -040084
John Snow8dfac2e2020-05-28 18:21:29 -040085 Use this object as a context manager to ensure
86 the QEMU process terminates::
John Snowabf0bf92019-06-27 17:28:14 -040087
88 with VM(binary) as vm:
89 ...
90 # vm is guaranteed to be shut down here
91 """
John Snow82e65172021-06-29 17:43:11 -040092 # pylint: disable=too-many-instance-attributes, too-many-public-methods
John Snowabf0bf92019-06-27 17:28:14 -040093
John Snowaad3f3b2020-10-06 19:58:06 -040094 def __init__(self,
95 binary: str,
96 args: Sequence[str] = (),
97 wrapper: Sequence[str] = (),
98 name: Optional[str] = None,
Cleber Rosa2ca6e262021-02-11 17:01:42 -050099 base_temp_dir: str = "/var/tmp",
John Snowc4e60232020-10-06 19:57:59 -0400100 monitor_address: Optional[SocketAddrT] = None,
John Snowf12a2822020-10-06 19:58:08 -0400101 socket_scm_helper: Optional[str] = None,
102 sock_dir: Optional[str] = None,
103 drain_console: bool = False,
Cleber Rosab306e262021-02-11 16:55:05 -0500104 console_log: Optional[str] = None,
Emanuele Giuseppe Espositoe2f948a2021-08-09 11:00:59 +0200105 log_dir: Optional[str] = None,
106 qmp_timer: Optional[float] = None):
John Snowabf0bf92019-06-27 17:28:14 -0400107 '''
108 Initialize a QEMUMachine
109
110 @param binary: path to the qemu binary
111 @param args: list of extra arguments
112 @param wrapper: list of arguments used as prefix to qemu binary
113 @param name: prefix for socket and log file names (default: qemu-PID)
John Snow859aeb62021-05-27 17:16:51 -0400114 @param base_temp_dir: default location where temp files are created
John Snowabf0bf92019-06-27 17:28:14 -0400115 @param monitor_address: address for QMP monitor
116 @param socket_scm_helper: helper program, required for send_fd_scm()
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500117 @param sock_dir: where to create socket (defaults to base_temp_dir)
Robert Foley0fc8f662020-07-01 14:56:24 +0100118 @param drain_console: (optional) True to drain console socket to buffer
John Snowc5e61a62020-10-06 19:58:00 -0400119 @param console_log: (optional) path to console log file
Cleber Rosab306e262021-02-11 16:55:05 -0500120 @param log_dir: where to create and keep log files
Emanuele Giuseppe Espositoe2f948a2021-08-09 11:00:59 +0200121 @param qmp_timer: (optional) default QMP socket timeout
John Snowabf0bf92019-06-27 17:28:14 -0400122 @note: Qemu process is not started until launch() is used.
123 '''
John Snow82e65172021-06-29 17:43:11 -0400124 # pylint: disable=too-many-arguments
125
John Snowc5e61a62020-10-06 19:58:00 -0400126 # Direct user configuration
127
128 self._binary = binary
John Snowc5e61a62020-10-06 19:58:00 -0400129 self._args = list(args)
John Snowc5e61a62020-10-06 19:58:00 -0400130 self._wrapper = wrapper
Emanuele Giuseppe Espositoe2f948a2021-08-09 11:00:59 +0200131 self._qmp_timer = qmp_timer
John Snowc5e61a62020-10-06 19:58:00 -0400132
133 self._name = name or "qemu-%d" % os.getpid()
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500134 self._base_temp_dir = base_temp_dir
135 self._sock_dir = sock_dir or self._base_temp_dir
Cleber Rosab306e262021-02-11 16:55:05 -0500136 self._log_dir = log_dir
John Snowc5e61a62020-10-06 19:58:00 -0400137 self._socket_scm_helper = socket_scm_helper
138
John Snowc4e60232020-10-06 19:57:59 -0400139 if monitor_address is not None:
140 self._monitor_address = monitor_address
141 self._remove_monitor_sockfile = False
142 else:
143 self._monitor_address = os.path.join(
John Snowc5e61a62020-10-06 19:58:00 -0400144 self._sock_dir, f"{self._name}-monitor.sock"
John Snowc4e60232020-10-06 19:57:59 -0400145 )
146 self._remove_monitor_sockfile = True
John Snowc5e61a62020-10-06 19:58:00 -0400147
148 self._console_log_path = console_log
149 if self._console_log_path:
150 # In order to log the console, buffering needs to be enabled.
151 self._drain_console = True
152 else:
153 self._drain_console = drain_console
154
155 # Runstate
John Snowf12a2822020-10-06 19:58:08 -0400156 self._qemu_log_path: Optional[str] = None
157 self._qemu_log_file: Optional[BinaryIO] = None
John Snow9223fda2020-10-06 19:58:05 -0400158 self._popen: Optional['subprocess.Popen[bytes]'] = None
John Snowf12a2822020-10-06 19:58:08 -0400159 self._events: List[QMPMessage] = []
160 self._iolog: Optional[str] = None
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500161 self._qmp_set = True # Enable QMP monitor by default.
John Snowbeb6b572021-05-27 17:16:53 -0400162 self._qmp_connection: Optional[QEMUMonitorProtocol] = None
John Snowaad3f3b2020-10-06 19:58:06 -0400163 self._qemu_full_args: Tuple[str, ...] = ()
John Snowf12a2822020-10-06 19:58:08 -0400164 self._temp_dir: Optional[str] = None
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(
171 self._sock_dir, f"{self._name}-console.sock"
172 )
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 Snowabf0bf92019-06-27 17:28:14 -0400176
Vladimir Sementsov-Ogievskiy15c3b862021-08-24 11:38:47 +0300177 def __enter__(self: _T) -> _T:
John Snowabf0bf92019-06-27 17:28:14 -0400178 return self
179
John Snow1dda0402020-05-14 01:53:44 -0400180 def __exit__(self,
181 exc_type: Optional[Type[BaseException]],
182 exc_val: Optional[BaseException],
183 exc_tb: Optional[TracebackType]) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400184 self.shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400185
John Snowf12a2822020-10-06 19:58:08 -0400186 def add_monitor_null(self) -> None:
John Snow306dfcd2019-06-27 17:28:15 -0400187 """
188 This can be used to add an unused monitor instance.
189 """
John Snowabf0bf92019-06-27 17:28:14 -0400190 self._args.append('-monitor')
191 self._args.append('null')
192
Vladimir Sementsov-Ogievskiy15c3b862021-08-24 11:38:47 +0300193 def add_fd(self: _T, fd: int, fdset: int,
194 opaque: str, opts: str = '') -> _T:
John Snowabf0bf92019-06-27 17:28:14 -0400195 """
196 Pass a file descriptor to the VM
197 """
198 options = ['fd=%d' % fd,
199 'set=%d' % fdset,
200 'opaque=%s' % opaque]
201 if opts:
202 options.append(opts)
203
204 # This did not exist before 3.4, but since then it is
205 # mandatory for our purpose
206 if hasattr(os, 'set_inheritable'):
207 os.set_inheritable(fd, True)
208
209 self._args.append('-add-fd')
210 self._args.append(','.join(options))
211 return self
212
John Snowf12a2822020-10-06 19:58:08 -0400213 def send_fd_scm(self, fd: Optional[int] = None,
214 file_path: Optional[str] = None) -> int:
John Snow306dfcd2019-06-27 17:28:15 -0400215 """
John Snow514d00d2021-09-22 20:49:30 -0400216 Send an fd or file_path to the remote via SCM_RIGHTS.
John Snow306dfcd2019-06-27 17:28:15 -0400217
John Snow514d00d2021-09-22 20:49:30 -0400218 Exactly one of fd and file_path must be given. If it is
219 file_path, the file will be opened read-only and the new file
220 descriptor will be sent to the remote.
John Snow306dfcd2019-06-27 17:28:15 -0400221 """
John Snowabf0bf92019-06-27 17:28:14 -0400222 if file_path is not None:
223 assert fd is None
John Snow514d00d2021-09-22 20:49:30 -0400224 with open(file_path, "rb") as passfile:
225 fd = passfile.fileno()
226 self._qmp.send_fd_scm(fd)
John Snowabf0bf92019-06-27 17:28:14 -0400227 else:
228 assert fd is not None
John Snow514d00d2021-09-22 20:49:30 -0400229 self._qmp.send_fd_scm(fd)
John Snowabf0bf92019-06-27 17:28:14 -0400230
John Snow514d00d2021-09-22 20:49:30 -0400231 return 0
John Snowabf0bf92019-06-27 17:28:14 -0400232
233 @staticmethod
John Snowf12a2822020-10-06 19:58:08 -0400234 def _remove_if_exists(path: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400235 """
236 Remove file object at path if it exists
237 """
238 try:
239 os.remove(path)
240 except OSError as exception:
241 if exception.errno == errno.ENOENT:
242 return
243 raise
244
John Snowf12a2822020-10-06 19:58:08 -0400245 def is_running(self) -> bool:
John Snow306dfcd2019-06-27 17:28:15 -0400246 """Returns true if the VM is running."""
John Snowabf0bf92019-06-27 17:28:14 -0400247 return self._popen is not None and self._popen.poll() is None
248
John Snow9223fda2020-10-06 19:58:05 -0400249 @property
250 def _subp(self) -> 'subprocess.Popen[bytes]':
251 if self._popen is None:
252 raise QEMUMachineError('Subprocess pipe not present')
253 return self._popen
254
John Snowf12a2822020-10-06 19:58:08 -0400255 def exitcode(self) -> Optional[int]:
John Snow306dfcd2019-06-27 17:28:15 -0400256 """Returns the exit code if possible, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400257 if self._popen is None:
258 return None
259 return self._popen.poll()
260
John Snowf12a2822020-10-06 19:58:08 -0400261 def get_pid(self) -> Optional[int]:
John Snow306dfcd2019-06-27 17:28:15 -0400262 """Returns the PID of the running process, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400263 if not self.is_running():
264 return None
John Snow9223fda2020-10-06 19:58:05 -0400265 return self._subp.pid
John Snowabf0bf92019-06-27 17:28:14 -0400266
John Snowf12a2822020-10-06 19:58:08 -0400267 def _load_io_log(self) -> None:
John Snow5690b432021-09-16 14:22:47 -0400268 # Assume that the output encoding of QEMU's terminal output is
269 # defined by our locale. If indeterminate, allow open() to fall
270 # back to the platform default.
271 _, encoding = locale.getlocale()
John Snowabf0bf92019-06-27 17:28:14 -0400272 if self._qemu_log_path is not None:
John Snow5690b432021-09-16 14:22:47 -0400273 with open(self._qemu_log_path, "r", encoding=encoding) as iolog:
John Snowabf0bf92019-06-27 17:28:14 -0400274 self._iolog = iolog.read()
275
John Snow652809d2020-10-06 19:58:01 -0400276 @property
277 def _base_args(self) -> List[str]:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500278 args = ['-display', 'none', '-vga', 'none']
John Snowc4e60232020-10-06 19:57:59 -0400279
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500280 if self._qmp_set:
281 if isinstance(self._monitor_address, tuple):
John Snowc4e60232020-10-06 19:57:59 -0400282 moncdev = "socket,id=mon,host={},port={}".format(
283 *self._monitor_address
284 )
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500285 else:
John Snowc4e60232020-10-06 19:57:59 -0400286 moncdev = f"socket,id=mon,path={self._monitor_address}"
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500287 args.extend(['-chardev', moncdev, '-mon',
288 'chardev=mon,mode=control'])
John Snowc4e60232020-10-06 19:57:59 -0400289
John Snowabf0bf92019-06-27 17:28:14 -0400290 if self._machine is not None:
291 args.extend(['-machine', self._machine])
John Snow9b8ccd62020-05-28 18:21:28 -0400292 for _ in range(self._console_index):
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100293 args.extend(['-serial', 'null'])
John Snowabf0bf92019-06-27 17:28:14 -0400294 if self._console_set:
Paolo Bonzini991c1802020-11-13 03:10:52 -0500295 chardev = ('socket,id=console,path=%s,server=on,wait=off' %
John Snowabf0bf92019-06-27 17:28:14 -0400296 self._console_address)
297 args.extend(['-chardev', chardev])
298 if self._console_device_type is None:
299 args.extend(['-serial', 'chardev:console'])
300 else:
301 device = '%s,chardev=console' % self._console_device_type
302 args.extend(['-device', device])
303 return args
304
Wainer dos Santos Moschetta555fe0c2021-04-30 10:34:12 -0300305 @property
306 def args(self) -> List[str]:
307 """Returns the list of arguments given to the QEMU binary."""
308 return self._args
309
John Snowf12a2822020-10-06 19:58:08 -0400310 def _pre_launch(self) -> None:
John Snow652809d2020-10-06 19:58:01 -0400311 if self._console_set:
312 self._remove_files.append(self._console_address)
313
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500314 if self._qmp_set:
John Snowc4e60232020-10-06 19:57:59 -0400315 if self._remove_monitor_sockfile:
316 assert isinstance(self._monitor_address, str)
317 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 Snowf12a2822020-10-06 19:58:08 -0400330 def _post_launch(self) -> None:
John Snowbe1183e2020-10-06 19:58:04 -0400331 if self._qmp_connection:
Emanuele Giuseppe Espositoe2f948a2021-08-09 11:00:59 +0200332 self._qmp.accept(self._qmp_timer)
John Snowabf0bf92019-06-27 17:28:14 -0400333
Emanuele Giuseppe Espositoeb7a91d2021-08-09 11:01:13 +0200334 def _close_qemu_log_file(self) -> None:
335 if self._qemu_log_file is not None:
336 self._qemu_log_file.close()
337 self._qemu_log_file = None
338
John Snowf12a2822020-10-06 19:58:08 -0400339 def _post_shutdown(self) -> None:
John Snowa3842cb2020-07-10 01:06:42 -0400340 """
341 Called to cleanup the VM instance after the process has exited.
342 May also be called after a failed launch.
343 """
344 # Comprehensive reset for the failed launch case:
345 self._early_cleanup()
346
John Snowbe1183e2020-10-06 19:58:04 -0400347 if self._qmp_connection:
John Snow671940e2020-07-10 01:06:39 -0400348 self._qmp.close()
John Snowbe1183e2020-10-06 19:58:04 -0400349 self._qmp_connection = None
John Snow671940e2020-07-10 01:06:39 -0400350
Emanuele Giuseppe Espositoeb7a91d2021-08-09 11:01:13 +0200351 self._close_qemu_log_file()
John Snowabf0bf92019-06-27 17:28:14 -0400352
Cleber Rosa3c1e16c2021-02-11 17:01:41 -0500353 self._load_io_log()
354
John Snowabf0bf92019-06-27 17:28:14 -0400355 self._qemu_log_path = None
356
John Snowabf0bf92019-06-27 17:28:14 -0400357 if self._temp_dir is not None:
358 shutil.rmtree(self._temp_dir)
359 self._temp_dir = None
360
Max Reitz32558ce2019-10-17 15:31:34 +0200361 while len(self._remove_files) > 0:
362 self._remove_if_exists(self._remove_files.pop())
363
John Snow14661d92020-07-10 01:06:38 -0400364 exitcode = self.exitcode()
John Snowde6e08b2020-07-10 01:06:48 -0400365 if (exitcode is not None and exitcode < 0
366 and not (self._user_killed and exitcode == -signal.SIGKILL)):
John Snow14661d92020-07-10 01:06:38 -0400367 msg = 'qemu received signal %i; command: "%s"'
368 if self._qemu_full_args:
369 command = ' '.join(self._qemu_full_args)
370 else:
371 command = ''
372 LOG.warning(msg, -int(exitcode), command)
373
John Snowde6e08b2020-07-10 01:06:48 -0400374 self._user_killed = False
John Snow14661d92020-07-10 01:06:38 -0400375 self._launched = False
376
John Snowf12a2822020-10-06 19:58:08 -0400377 def launch(self) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400378 """
379 Launch the VM and make sure we cleanup and expose the
380 command line/output in case of exception
381 """
382
383 if self._launched:
384 raise QEMUMachineError('VM already launched')
385
386 self._iolog = None
John Snowaad3f3b2020-10-06 19:58:06 -0400387 self._qemu_full_args = ()
John Snowabf0bf92019-06-27 17:28:14 -0400388 try:
389 self._launch()
390 self._launched = True
391 except:
John Snowa3842cb2020-07-10 01:06:42 -0400392 self._post_shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400393
394 LOG.debug('Error launching VM')
395 if self._qemu_full_args:
396 LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
397 if self._iolog:
398 LOG.debug('Output: %r', self._iolog)
399 raise
400
John Snowf12a2822020-10-06 19:58:08 -0400401 def _launch(self) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400402 """
403 Launch the VM and establish a QMP connection
404 """
John Snowabf0bf92019-06-27 17:28:14 -0400405 self._pre_launch()
John Snowaad3f3b2020-10-06 19:58:06 -0400406 self._qemu_full_args = tuple(
407 chain(self._wrapper,
408 [self._binary],
409 self._base_args,
410 self._args)
411 )
John Snowabf0bf92019-06-27 17:28:14 -0400412 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
John Snowa0eae172021-05-27 17:16:50 -0400413
414 # Cleaning up of this subprocess is guaranteed by _do_shutdown.
415 # pylint: disable=consider-using-with
John Snowabf0bf92019-06-27 17:28:14 -0400416 self._popen = subprocess.Popen(self._qemu_full_args,
John Snow07b71232021-05-27 17:16:46 -0400417 stdin=subprocess.DEVNULL,
John Snowabf0bf92019-06-27 17:28:14 -0400418 stdout=self._qemu_log_file,
419 stderr=subprocess.STDOUT,
420 shell=False,
421 close_fds=False)
422 self._post_launch()
423
John Snowe2c97f12020-07-10 01:06:40 -0400424 def _early_cleanup(self) -> None:
425 """
426 Perform any cleanup that needs to happen before the VM exits.
John Snowa3842cb2020-07-10 01:06:42 -0400427
John Snow193bf1c2020-07-10 01:06:47 -0400428 May be invoked by both soft and hard shutdown in failover scenarios.
John Snowa3842cb2020-07-10 01:06:42 -0400429 Called additionally by _post_shutdown for comprehensive cleanup.
John Snowe2c97f12020-07-10 01:06:40 -0400430 """
431 # If we keep the console socket open, we may deadlock waiting
432 # for QEMU to exit, while QEMU is waiting for the socket to
433 # become writeable.
434 if self._console_socket is not None:
435 self._console_socket.close()
436 self._console_socket = None
437
John Snow193bf1c2020-07-10 01:06:47 -0400438 def _hard_shutdown(self) -> None:
439 """
440 Perform early cleanup, kill the VM, and wait for it to terminate.
441
442 :raise subprocess.Timeout: When timeout is exceeds 60 seconds
443 waiting for the QEMU process to terminate.
444 """
445 self._early_cleanup()
John Snow9223fda2020-10-06 19:58:05 -0400446 self._subp.kill()
447 self._subp.wait(timeout=60)
John Snow193bf1c2020-07-10 01:06:47 -0400448
John Snow8226a4b2020-07-20 12:02:52 -0400449 def _soft_shutdown(self, timeout: Optional[int],
450 has_quit: bool = False) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400451 """
452 Perform early cleanup, attempt to gracefully shut down the VM, and wait
453 for it to terminate.
454
John Snow8226a4b2020-07-20 12:02:52 -0400455 :param timeout: Timeout in seconds for graceful shutdown.
456 A value of None is an infinite wait.
John Snow193bf1c2020-07-10 01:06:47 -0400457 :param has_quit: When True, don't attempt to issue 'quit' QMP command
John Snow193bf1c2020-07-10 01:06:47 -0400458
459 :raise ConnectionReset: On QMP communication errors
460 :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
461 the QEMU process to terminate.
462 """
463 self._early_cleanup()
464
John Snowbe1183e2020-10-06 19:58:04 -0400465 if self._qmp_connection:
John Snow193bf1c2020-07-10 01:06:47 -0400466 if not has_quit:
467 # Might raise ConnectionReset
468 self._qmp.cmd('quit')
469
470 # May raise subprocess.TimeoutExpired
John Snow9223fda2020-10-06 19:58:05 -0400471 self._subp.wait(timeout=timeout)
John Snow193bf1c2020-07-10 01:06:47 -0400472
John Snow8226a4b2020-07-20 12:02:52 -0400473 def _do_shutdown(self, timeout: Optional[int],
474 has_quit: bool = False) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400475 """
476 Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
477
John Snow8226a4b2020-07-20 12:02:52 -0400478 :param timeout: Timeout in seconds for graceful shutdown.
479 A value of None is an infinite wait.
John Snow193bf1c2020-07-10 01:06:47 -0400480 :param has_quit: When True, don't attempt to issue 'quit' QMP command
John Snow193bf1c2020-07-10 01:06:47 -0400481
482 :raise AbnormalShutdown: When the VM could not be shut down gracefully.
483 The inner exception will likely be ConnectionReset or
484 subprocess.TimeoutExpired. In rare cases, non-graceful termination
485 may result in its own exceptions, likely subprocess.TimeoutExpired.
486 """
487 try:
John Snow8226a4b2020-07-20 12:02:52 -0400488 self._soft_shutdown(timeout, has_quit)
John Snow193bf1c2020-07-10 01:06:47 -0400489 except Exception as exc:
490 self._hard_shutdown()
491 raise AbnormalShutdown("Could not perform graceful shutdown") \
492 from exc
493
John Snowc9b30452020-07-10 01:06:43 -0400494 def shutdown(self, has_quit: bool = False,
495 hard: bool = False,
John Snow8226a4b2020-07-20 12:02:52 -0400496 timeout: Optional[int] = 30) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400497 """
John Snow193bf1c2020-07-10 01:06:47 -0400498 Terminate the VM (gracefully if possible) and perform cleanup.
499 Cleanup will always be performed.
500
501 If the VM has not yet been launched, or shutdown(), wait(), or kill()
502 have already been called, this method does nothing.
503
504 :param has_quit: When true, do not attempt to issue 'quit' QMP command.
505 :param hard: When true, do not attempt graceful shutdown, and
506 suppress the SIGKILL warning log message.
507 :param timeout: Optional timeout in seconds for graceful shutdown.
John Snow8226a4b2020-07-20 12:02:52 -0400508 Default 30 seconds, A `None` value is an infinite wait.
John Snowabf0bf92019-06-27 17:28:14 -0400509 """
John Snowa3842cb2020-07-10 01:06:42 -0400510 if not self._launched:
511 return
512
John Snow193bf1c2020-07-10 01:06:47 -0400513 try:
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300514 if hard:
John Snowde6e08b2020-07-10 01:06:48 -0400515 self._user_killed = True
John Snow193bf1c2020-07-10 01:06:47 -0400516 self._hard_shutdown()
517 else:
John Snow8226a4b2020-07-20 12:02:52 -0400518 self._do_shutdown(timeout, has_quit)
John Snow193bf1c2020-07-10 01:06:47 -0400519 finally:
520 self._post_shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400521
John Snowf12a2822020-10-06 19:58:08 -0400522 def kill(self) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400523 """
524 Terminate the VM forcefully, wait for it to exit, and perform cleanup.
525 """
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300526 self.shutdown(hard=True)
527
John Snow8226a4b2020-07-20 12:02:52 -0400528 def wait(self, timeout: Optional[int] = 30) -> None:
John Snow89528052020-07-10 01:06:44 -0400529 """
530 Wait for the VM to power off and perform post-shutdown cleanup.
531
John Snow8226a4b2020-07-20 12:02:52 -0400532 :param timeout: Optional timeout in seconds. Default 30 seconds.
533 A value of `None` is an infinite wait.
John Snow89528052020-07-10 01:06:44 -0400534 """
535 self.shutdown(has_quit=True, timeout=timeout)
536
John Snowf12a2822020-10-06 19:58:08 -0400537 def set_qmp_monitor(self, enabled: bool = True) -> None:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500538 """
539 Set the QMP monitor.
540
541 @param enabled: if False, qmp monitor options will be removed from
542 the base arguments of the resulting QEMU command
543 line. Default is True.
John Snow5c02c862021-06-29 17:43:23 -0400544
545 .. note:: Call this function before launch().
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500546 """
John Snowbe1183e2020-10-06 19:58:04 -0400547 self._qmp_set = enabled
548
549 @property
John Snowbeb6b572021-05-27 17:16:53 -0400550 def _qmp(self) -> QEMUMonitorProtocol:
John Snowbe1183e2020-10-06 19:58:04 -0400551 if self._qmp_connection is None:
552 raise QEMUMachineError("Attempt to access QMP with no connection")
553 return self._qmp_connection
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500554
John Snowaaa81ec2020-10-06 19:58:03 -0400555 @classmethod
Vladimir Sementsov-Ogievskiyc7daa572021-08-24 11:38:45 +0300556 def _qmp_args(cls, conv_keys: bool,
557 args: Dict[str, Any]) -> Dict[str, object]:
558 if conv_keys:
559 return {k.replace('_', '-'): v for k, v in args.items()}
560
561 return args
John Snowabf0bf92019-06-27 17:28:14 -0400562
John Snowaaa81ec2020-10-06 19:58:03 -0400563 def qmp(self, cmd: str,
Vladimir Sementsov-Ogievskiy3f3c9b42021-08-24 11:38:46 +0300564 args_dict: Optional[Dict[str, object]] = None,
565 conv_keys: Optional[bool] = None,
John Snowaaa81ec2020-10-06 19:58:03 -0400566 **args: Any) -> QMPMessage:
567 """
568 Invoke a QMP command and return the response dict
569 """
Vladimir Sementsov-Ogievskiy3f3c9b42021-08-24 11:38:46 +0300570 if args_dict is not None:
571 assert not args
572 assert conv_keys is None
573 args = args_dict
574 conv_keys = False
575
576 if conv_keys is None:
577 conv_keys = True
578
Vladimir Sementsov-Ogievskiyc7daa572021-08-24 11:38:45 +0300579 qmp_args = self._qmp_args(conv_keys, args)
John Snowabf0bf92019-06-27 17:28:14 -0400580 return self._qmp.cmd(cmd, args=qmp_args)
581
John Snowf12a2822020-10-06 19:58:08 -0400582 def command(self, cmd: str,
583 conv_keys: bool = True,
584 **args: Any) -> QMPReturnValue:
John Snowabf0bf92019-06-27 17:28:14 -0400585 """
586 Invoke a QMP command.
587 On success return the response dict.
588 On failure raise an exception.
589 """
Vladimir Sementsov-Ogievskiyc7daa572021-08-24 11:38:45 +0300590 qmp_args = self._qmp_args(conv_keys, args)
John Snowaaa81ec2020-10-06 19:58:03 -0400591 return self._qmp.command(cmd, **qmp_args)
John Snowabf0bf92019-06-27 17:28:14 -0400592
John Snowf12a2822020-10-06 19:58:08 -0400593 def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400594 """
595 Poll for one queued QMP events and return it
596 """
John Snow306dfcd2019-06-27 17:28:15 -0400597 if self._events:
John Snowabf0bf92019-06-27 17:28:14 -0400598 return self._events.pop(0)
599 return self._qmp.pull_event(wait=wait)
600
John Snowf12a2822020-10-06 19:58:08 -0400601 def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400602 """
603 Poll for queued QMP events and return a list of dicts
604 """
605 events = self._qmp.get_events(wait=wait)
606 events.extend(self._events)
607 del self._events[:]
John Snowabf0bf92019-06-27 17:28:14 -0400608 return events
609
610 @staticmethod
John Snowf12a2822020-10-06 19:58:08 -0400611 def event_match(event: Any, match: Optional[Any]) -> bool:
John Snowabf0bf92019-06-27 17:28:14 -0400612 """
613 Check if an event matches optional match criteria.
614
615 The match criteria takes the form of a matching subdict. The event is
616 checked to be a superset of the subdict, recursively, with matching
617 values whenever the subdict values are not None.
618
619 This has a limitation that you cannot explicitly check for None values.
620
621 Examples, with the subdict queries on the left:
622 - None matches any object.
623 - {"foo": None} matches {"foo": {"bar": 1}}
624 - {"foo": None} matches {"foo": 5}
625 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
626 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
627 """
628 if match is None:
629 return True
630
631 try:
632 for key in match:
633 if key in event:
634 if not QEMUMachine.event_match(event[key], match[key]):
635 return False
636 else:
637 return False
638 return True
639 except TypeError:
640 # either match or event wasn't iterable (not a dict)
John Snowf12a2822020-10-06 19:58:08 -0400641 return bool(match == event)
John Snowabf0bf92019-06-27 17:28:14 -0400642
John Snowf12a2822020-10-06 19:58:08 -0400643 def event_wait(self, name: str,
644 timeout: float = 60.0,
645 match: Optional[QMPMessage] = None) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400646 """
647 event_wait waits for and returns a named event from QMP with a timeout.
648
649 name: The event to wait for.
650 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
651 match: Optional match criteria. See event_match for details.
652 """
653 return self.events_wait([(name, match)], timeout)
654
John Snowf12a2822020-10-06 19:58:08 -0400655 def events_wait(self,
656 events: Sequence[Tuple[str, Any]],
657 timeout: float = 60.0) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400658 """
John Snow1847a4a2020-10-06 19:58:02 -0400659 events_wait waits for and returns a single named event from QMP.
660 In the case of multiple qualifying events, this function returns the
661 first one.
John Snowabf0bf92019-06-27 17:28:14 -0400662
John Snow1847a4a2020-10-06 19:58:02 -0400663 :param events: A sequence of (name, match_criteria) tuples.
664 The match criteria are optional and may be None.
665 See event_match for details.
666 :param timeout: Optional timeout, in seconds.
667 See QEMUMonitorProtocol.pull_event.
668
669 :raise QMPTimeoutError: If timeout was non-zero and no matching events
670 were found.
671 :return: A QMP event matching the filter criteria.
672 If timeout was 0 and no event matched, None.
John Snowabf0bf92019-06-27 17:28:14 -0400673 """
John Snowf12a2822020-10-06 19:58:08 -0400674 def _match(event: QMPMessage) -> bool:
John Snowabf0bf92019-06-27 17:28:14 -0400675 for name, match in events:
John Snow306dfcd2019-06-27 17:28:15 -0400676 if event['event'] == name and self.event_match(event, match):
John Snowabf0bf92019-06-27 17:28:14 -0400677 return True
678 return False
679
John Snow1847a4a2020-10-06 19:58:02 -0400680 event: Optional[QMPMessage]
681
John Snowabf0bf92019-06-27 17:28:14 -0400682 # Search cached events
683 for event in self._events:
684 if _match(event):
685 self._events.remove(event)
686 return event
687
688 # Poll for new events
689 while True:
690 event = self._qmp.pull_event(wait=timeout)
John Snow1847a4a2020-10-06 19:58:02 -0400691 if event is None:
692 # NB: None is only returned when timeout is false-ish.
693 # Timeouts raise QMPTimeoutError instead!
694 break
John Snowabf0bf92019-06-27 17:28:14 -0400695 if _match(event):
696 return event
697 self._events.append(event)
698
699 return None
700
John Snowf12a2822020-10-06 19:58:08 -0400701 def get_log(self) -> Optional[str]:
John Snowabf0bf92019-06-27 17:28:14 -0400702 """
703 After self.shutdown or failed qemu execution, this returns the output
704 of the qemu process.
705 """
706 return self._iolog
707
John Snowf12a2822020-10-06 19:58:08 -0400708 def add_args(self, *args: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400709 """
710 Adds to the list of extra arguments to be given to the QEMU binary
711 """
712 self._args.extend(args)
713
John Snowf12a2822020-10-06 19:58:08 -0400714 def set_machine(self, machine_type: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400715 """
716 Sets the machine type
717
718 If set, the machine type will be added to the base arguments
719 of the resulting QEMU command line.
720 """
721 self._machine = machine_type
722
John Snowf12a2822020-10-06 19:58:08 -0400723 def set_console(self,
724 device_type: Optional[str] = None,
725 console_index: int = 0) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400726 """
727 Sets the device type for a console device
728
729 If set, the console device and a backing character device will
730 be added to the base arguments of the resulting QEMU command
731 line.
732
733 This is a convenience method that will either use the provided
734 device type, or default to a "-serial chardev:console" command
735 line argument.
736
737 The actual setting of command line arguments will be be done at
738 machine launch time, as it depends on the temporary directory
739 to be created.
740
741 @param device_type: the device type, such as "isa-serial". If
742 None is given (the default value) a "-serial
743 chardev:console" command line argument will
744 be used instead, resorting to the machine's
745 default device type.
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100746 @param console_index: the index of the console device to use.
747 If not zero, the command line will create
748 'index - 1' consoles and connect them to
749 the 'null' backing character device.
John Snowabf0bf92019-06-27 17:28:14 -0400750 """
751 self._console_set = True
752 self._console_device_type = device_type
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100753 self._console_index = console_index
John Snowabf0bf92019-06-27 17:28:14 -0400754
755 @property
John Snowf12a2822020-10-06 19:58:08 -0400756 def console_socket(self) -> socket.socket:
John Snowabf0bf92019-06-27 17:28:14 -0400757 """
758 Returns a socket connected to the console
759 """
760 if self._console_socket is None:
Robert Foley80ded8e2020-07-24 07:45:08 +0100761 self._console_socket = console_socket.ConsoleSocket(
762 self._console_address,
763 file=self._console_log_path,
764 drain=self._drain_console)
John Snowabf0bf92019-06-27 17:28:14 -0400765 return self._console_socket
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500766
767 @property
768 def temp_dir(self) -> str:
769 """
770 Returns a temporary directory to be used for this machine
771 """
772 if self._temp_dir is None:
773 self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-",
774 dir=self._base_temp_dir)
775 return self._temp_dir
Cleber Rosab306e262021-02-11 16:55:05 -0500776
777 @property
778 def log_dir(self) -> str:
779 """
780 Returns a directory to be used for writing logs
781 """
782 if self._log_dir is None:
783 return self.temp_dir
784 return self._log_dir