blob: 0bd40bc2f76eedf115ab77b6dc5bac035a4a2995 [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 sock_dir: Optional[str] = None,
102 drain_console: bool = False,
Cleber Rosab306e262021-02-11 16:55:05 -0500103 console_log: Optional[str] = None,
Emanuele Giuseppe Espositoe2f948a2021-08-09 11:00:59 +0200104 log_dir: Optional[str] = None,
105 qmp_timer: Optional[float] = None):
John Snowabf0bf92019-06-27 17:28:14 -0400106 '''
107 Initialize a QEMUMachine
108
109 @param binary: path to the qemu binary
110 @param args: list of extra arguments
111 @param wrapper: list of arguments used as prefix to qemu binary
112 @param name: prefix for socket and log file names (default: qemu-PID)
John Snow859aeb62021-05-27 17:16:51 -0400113 @param base_temp_dir: default location where temp files are created
John Snowabf0bf92019-06-27 17:28:14 -0400114 @param monitor_address: address for QMP monitor
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500115 @param sock_dir: where to create socket (defaults to base_temp_dir)
Robert Foley0fc8f662020-07-01 14:56:24 +0100116 @param drain_console: (optional) True to drain console socket to buffer
John Snowc5e61a62020-10-06 19:58:00 -0400117 @param console_log: (optional) path to console log file
Cleber Rosab306e262021-02-11 16:55:05 -0500118 @param log_dir: where to create and keep log files
Emanuele Giuseppe Espositoe2f948a2021-08-09 11:00:59 +0200119 @param qmp_timer: (optional) default QMP socket timeout
John Snowabf0bf92019-06-27 17:28:14 -0400120 @note: Qemu process is not started until launch() is used.
121 '''
John Snow82e65172021-06-29 17:43:11 -0400122 # pylint: disable=too-many-arguments
123
John Snowc5e61a62020-10-06 19:58:00 -0400124 # Direct user configuration
125
126 self._binary = binary
John Snowc5e61a62020-10-06 19:58:00 -0400127 self._args = list(args)
John Snowc5e61a62020-10-06 19:58:00 -0400128 self._wrapper = wrapper
Emanuele Giuseppe Espositoe2f948a2021-08-09 11:00:59 +0200129 self._qmp_timer = qmp_timer
John Snowc5e61a62020-10-06 19:58:00 -0400130
131 self._name = name or "qemu-%d" % os.getpid()
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500132 self._base_temp_dir = base_temp_dir
133 self._sock_dir = sock_dir or self._base_temp_dir
Cleber Rosab306e262021-02-11 16:55:05 -0500134 self._log_dir = log_dir
John Snowc5e61a62020-10-06 19:58:00 -0400135
John Snowc4e60232020-10-06 19:57:59 -0400136 if monitor_address is not None:
137 self._monitor_address = monitor_address
138 self._remove_monitor_sockfile = False
139 else:
140 self._monitor_address = os.path.join(
John Snowc5e61a62020-10-06 19:58:00 -0400141 self._sock_dir, f"{self._name}-monitor.sock"
John Snowc4e60232020-10-06 19:57:59 -0400142 )
143 self._remove_monitor_sockfile = True
John Snowc5e61a62020-10-06 19:58:00 -0400144
145 self._console_log_path = console_log
146 if self._console_log_path:
147 # In order to log the console, buffering needs to be enabled.
148 self._drain_console = True
149 else:
150 self._drain_console = drain_console
151
152 # Runstate
John Snowf12a2822020-10-06 19:58:08 -0400153 self._qemu_log_path: Optional[str] = None
154 self._qemu_log_file: Optional[BinaryIO] = None
John Snow9223fda2020-10-06 19:58:05 -0400155 self._popen: Optional['subprocess.Popen[bytes]'] = None
John Snowf12a2822020-10-06 19:58:08 -0400156 self._events: List[QMPMessage] = []
157 self._iolog: Optional[str] = None
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500158 self._qmp_set = True # Enable QMP monitor by default.
John Snowbeb6b572021-05-27 17:16:53 -0400159 self._qmp_connection: Optional[QEMUMonitorProtocol] = None
John Snowaad3f3b2020-10-06 19:58:06 -0400160 self._qemu_full_args: Tuple[str, ...] = ()
John Snowf12a2822020-10-06 19:58:08 -0400161 self._temp_dir: Optional[str] = None
John Snowabf0bf92019-06-27 17:28:14 -0400162 self._launched = False
John Snowf12a2822020-10-06 19:58:08 -0400163 self._machine: Optional[str] = None
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100164 self._console_index = 0
John Snowabf0bf92019-06-27 17:28:14 -0400165 self._console_set = False
John Snowf12a2822020-10-06 19:58:08 -0400166 self._console_device_type: Optional[str] = None
John Snow652809d2020-10-06 19:58:01 -0400167 self._console_address = os.path.join(
168 self._sock_dir, f"{self._name}-console.sock"
169 )
John Snowf12a2822020-10-06 19:58:08 -0400170 self._console_socket: Optional[socket.socket] = None
171 self._remove_files: List[str] = []
John Snowde6e08b2020-07-10 01:06:48 -0400172 self._user_killed = False
John Snowb9420e42021-10-26 13:56:05 -0400173 self._quit_issued = False
John Snowabf0bf92019-06-27 17:28:14 -0400174
Vladimir Sementsov-Ogievskiy15c3b862021-08-24 11:38:47 +0300175 def __enter__(self: _T) -> _T:
John Snowabf0bf92019-06-27 17:28:14 -0400176 return self
177
John Snow1dda0402020-05-14 01:53:44 -0400178 def __exit__(self,
179 exc_type: Optional[Type[BaseException]],
180 exc_val: Optional[BaseException],
181 exc_tb: Optional[TracebackType]) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400182 self.shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400183
John Snowf12a2822020-10-06 19:58:08 -0400184 def add_monitor_null(self) -> None:
John Snow306dfcd2019-06-27 17:28:15 -0400185 """
186 This can be used to add an unused monitor instance.
187 """
John Snowabf0bf92019-06-27 17:28:14 -0400188 self._args.append('-monitor')
189 self._args.append('null')
190
Vladimir Sementsov-Ogievskiy15c3b862021-08-24 11:38:47 +0300191 def add_fd(self: _T, fd: int, fdset: int,
192 opaque: str, opts: str = '') -> _T:
John Snowabf0bf92019-06-27 17:28:14 -0400193 """
194 Pass a file descriptor to the VM
195 """
196 options = ['fd=%d' % fd,
197 'set=%d' % fdset,
198 'opaque=%s' % opaque]
199 if opts:
200 options.append(opts)
201
202 # This did not exist before 3.4, but since then it is
203 # mandatory for our purpose
204 if hasattr(os, 'set_inheritable'):
205 os.set_inheritable(fd, True)
206
207 self._args.append('-add-fd')
208 self._args.append(','.join(options))
209 return self
210
John Snowf12a2822020-10-06 19:58:08 -0400211 def send_fd_scm(self, fd: Optional[int] = None,
212 file_path: Optional[str] = None) -> int:
John Snow306dfcd2019-06-27 17:28:15 -0400213 """
John Snow514d00d2021-09-22 20:49:30 -0400214 Send an fd or file_path to the remote via SCM_RIGHTS.
John Snow306dfcd2019-06-27 17:28:15 -0400215
John Snow514d00d2021-09-22 20:49:30 -0400216 Exactly one of fd and file_path must be given. If it is
217 file_path, the file will be opened read-only and the new file
218 descriptor will be sent to the remote.
John Snow306dfcd2019-06-27 17:28:15 -0400219 """
John Snowabf0bf92019-06-27 17:28:14 -0400220 if file_path is not None:
221 assert fd is None
John Snow514d00d2021-09-22 20:49:30 -0400222 with open(file_path, "rb") as passfile:
223 fd = passfile.fileno()
224 self._qmp.send_fd_scm(fd)
John Snowabf0bf92019-06-27 17:28:14 -0400225 else:
226 assert fd is not None
John Snow514d00d2021-09-22 20:49:30 -0400227 self._qmp.send_fd_scm(fd)
John Snowabf0bf92019-06-27 17:28:14 -0400228
John Snow514d00d2021-09-22 20:49:30 -0400229 return 0
John Snowabf0bf92019-06-27 17:28:14 -0400230
231 @staticmethod
John Snowf12a2822020-10-06 19:58:08 -0400232 def _remove_if_exists(path: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400233 """
234 Remove file object at path if it exists
235 """
236 try:
237 os.remove(path)
238 except OSError as exception:
239 if exception.errno == errno.ENOENT:
240 return
241 raise
242
John Snowf12a2822020-10-06 19:58:08 -0400243 def is_running(self) -> bool:
John Snow306dfcd2019-06-27 17:28:15 -0400244 """Returns true if the VM is running."""
John Snowabf0bf92019-06-27 17:28:14 -0400245 return self._popen is not None and self._popen.poll() is None
246
John Snow9223fda2020-10-06 19:58:05 -0400247 @property
248 def _subp(self) -> 'subprocess.Popen[bytes]':
249 if self._popen is None:
250 raise QEMUMachineError('Subprocess pipe not present')
251 return self._popen
252
John Snowf12a2822020-10-06 19:58:08 -0400253 def exitcode(self) -> Optional[int]:
John Snow306dfcd2019-06-27 17:28:15 -0400254 """Returns the exit code if possible, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400255 if self._popen is None:
256 return None
257 return self._popen.poll()
258
John Snowf12a2822020-10-06 19:58:08 -0400259 def get_pid(self) -> Optional[int]:
John Snow306dfcd2019-06-27 17:28:15 -0400260 """Returns the PID of the running process, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400261 if not self.is_running():
262 return None
John Snow9223fda2020-10-06 19:58:05 -0400263 return self._subp.pid
John Snowabf0bf92019-06-27 17:28:14 -0400264
John Snowf12a2822020-10-06 19:58:08 -0400265 def _load_io_log(self) -> None:
John Snow5690b432021-09-16 14:22:47 -0400266 # Assume that the output encoding of QEMU's terminal output is
267 # defined by our locale. If indeterminate, allow open() to fall
268 # back to the platform default.
269 _, encoding = locale.getlocale()
John Snowabf0bf92019-06-27 17:28:14 -0400270 if self._qemu_log_path is not None:
John Snow5690b432021-09-16 14:22:47 -0400271 with open(self._qemu_log_path, "r", encoding=encoding) as iolog:
John Snowabf0bf92019-06-27 17:28:14 -0400272 self._iolog = iolog.read()
273
John Snow652809d2020-10-06 19:58:01 -0400274 @property
275 def _base_args(self) -> List[str]:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500276 args = ['-display', 'none', '-vga', 'none']
John Snowc4e60232020-10-06 19:57:59 -0400277
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500278 if self._qmp_set:
279 if isinstance(self._monitor_address, tuple):
John Snowc4e60232020-10-06 19:57:59 -0400280 moncdev = "socket,id=mon,host={},port={}".format(
281 *self._monitor_address
282 )
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500283 else:
John Snowc4e60232020-10-06 19:57:59 -0400284 moncdev = f"socket,id=mon,path={self._monitor_address}"
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500285 args.extend(['-chardev', moncdev, '-mon',
286 'chardev=mon,mode=control'])
John Snowc4e60232020-10-06 19:57:59 -0400287
John Snowabf0bf92019-06-27 17:28:14 -0400288 if self._machine is not None:
289 args.extend(['-machine', self._machine])
John Snow9b8ccd62020-05-28 18:21:28 -0400290 for _ in range(self._console_index):
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100291 args.extend(['-serial', 'null'])
John Snowabf0bf92019-06-27 17:28:14 -0400292 if self._console_set:
Paolo Bonzini991c1802020-11-13 03:10:52 -0500293 chardev = ('socket,id=console,path=%s,server=on,wait=off' %
John Snowabf0bf92019-06-27 17:28:14 -0400294 self._console_address)
295 args.extend(['-chardev', chardev])
296 if self._console_device_type is None:
297 args.extend(['-serial', 'chardev:console'])
298 else:
299 device = '%s,chardev=console' % self._console_device_type
300 args.extend(['-device', device])
301 return args
302
Wainer dos Santos Moschetta555fe0c2021-04-30 10:34:12 -0300303 @property
304 def args(self) -> List[str]:
305 """Returns the list of arguments given to the QEMU binary."""
306 return self._args
307
John Snowf12a2822020-10-06 19:58:08 -0400308 def _pre_launch(self) -> None:
John Snow652809d2020-10-06 19:58:01 -0400309 if self._console_set:
310 self._remove_files.append(self._console_address)
311
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500312 if self._qmp_set:
John Snowc4e60232020-10-06 19:57:59 -0400313 if self._remove_monitor_sockfile:
314 assert isinstance(self._monitor_address, str)
315 self._remove_files.append(self._monitor_address)
John Snowbeb6b572021-05-27 17:16:53 -0400316 self._qmp_connection = QEMUMonitorProtocol(
John Snowc4e60232020-10-06 19:57:59 -0400317 self._monitor_address,
318 server=True,
319 nickname=self._name
320 )
John Snowabf0bf92019-06-27 17:28:14 -0400321
John Snow63c33f32021-05-27 17:16:49 -0400322 # NOTE: Make sure any opened resources are *definitely* freed in
323 # _post_shutdown()!
324 # pylint: disable=consider-using-with
Cleber Rosab306e262021-02-11 16:55:05 -0500325 self._qemu_log_path = os.path.join(self.log_dir, self._name + ".log")
John Snow63c33f32021-05-27 17:16:49 -0400326 self._qemu_log_file = open(self._qemu_log_path, 'wb')
327
John Snowf12a2822020-10-06 19:58:08 -0400328 def _post_launch(self) -> None:
John Snowbe1183e2020-10-06 19:58:04 -0400329 if self._qmp_connection:
Emanuele Giuseppe Espositoe2f948a2021-08-09 11:00:59 +0200330 self._qmp.accept(self._qmp_timer)
John Snowabf0bf92019-06-27 17:28:14 -0400331
Emanuele Giuseppe Espositoeb7a91d2021-08-09 11:01:13 +0200332 def _close_qemu_log_file(self) -> None:
333 if self._qemu_log_file is not None:
334 self._qemu_log_file.close()
335 self._qemu_log_file = None
336
John Snowf12a2822020-10-06 19:58:08 -0400337 def _post_shutdown(self) -> None:
John Snowa3842cb2020-07-10 01:06:42 -0400338 """
339 Called to cleanup the VM instance after the process has exited.
340 May also be called after a failed launch.
341 """
342 # Comprehensive reset for the failed launch case:
343 self._early_cleanup()
344
John Snowbe1183e2020-10-06 19:58:04 -0400345 if self._qmp_connection:
John Snow671940e2020-07-10 01:06:39 -0400346 self._qmp.close()
John Snowbe1183e2020-10-06 19:58:04 -0400347 self._qmp_connection = None
John Snow671940e2020-07-10 01:06:39 -0400348
Emanuele Giuseppe Espositoeb7a91d2021-08-09 11:01:13 +0200349 self._close_qemu_log_file()
John Snowabf0bf92019-06-27 17:28:14 -0400350
Cleber Rosa3c1e16c2021-02-11 17:01:41 -0500351 self._load_io_log()
352
John Snowabf0bf92019-06-27 17:28:14 -0400353 self._qemu_log_path = None
354
John Snowabf0bf92019-06-27 17:28:14 -0400355 if self._temp_dir is not None:
356 shutil.rmtree(self._temp_dir)
357 self._temp_dir = None
358
Max Reitz32558ce2019-10-17 15:31:34 +0200359 while len(self._remove_files) > 0:
360 self._remove_if_exists(self._remove_files.pop())
361
John Snow14661d92020-07-10 01:06:38 -0400362 exitcode = self.exitcode()
John Snowde6e08b2020-07-10 01:06:48 -0400363 if (exitcode is not None and exitcode < 0
364 and not (self._user_killed and exitcode == -signal.SIGKILL)):
John Snow14661d92020-07-10 01:06:38 -0400365 msg = 'qemu received signal %i; command: "%s"'
366 if self._qemu_full_args:
367 command = ' '.join(self._qemu_full_args)
368 else:
369 command = ''
370 LOG.warning(msg, -int(exitcode), command)
371
John Snowb9420e42021-10-26 13:56:05 -0400372 self._quit_issued = False
John Snowde6e08b2020-07-10 01:06:48 -0400373 self._user_killed = False
John Snow14661d92020-07-10 01:06:38 -0400374 self._launched = False
375
John Snowf12a2822020-10-06 19:58:08 -0400376 def launch(self) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400377 """
378 Launch the VM and make sure we cleanup and expose the
379 command line/output in case of exception
380 """
381
382 if self._launched:
383 raise QEMUMachineError('VM already launched')
384
385 self._iolog = None
John Snowaad3f3b2020-10-06 19:58:06 -0400386 self._qemu_full_args = ()
John Snowabf0bf92019-06-27 17:28:14 -0400387 try:
388 self._launch()
389 self._launched = True
390 except:
John Snowa3842cb2020-07-10 01:06:42 -0400391 self._post_shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400392
393 LOG.debug('Error launching VM')
394 if self._qemu_full_args:
395 LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
396 if self._iolog:
397 LOG.debug('Output: %r', self._iolog)
398 raise
399
John Snowf12a2822020-10-06 19:58:08 -0400400 def _launch(self) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400401 """
402 Launch the VM and establish a QMP connection
403 """
John Snowabf0bf92019-06-27 17:28:14 -0400404 self._pre_launch()
John Snowaad3f3b2020-10-06 19:58:06 -0400405 self._qemu_full_args = tuple(
406 chain(self._wrapper,
407 [self._binary],
408 self._base_args,
409 self._args)
410 )
John Snowabf0bf92019-06-27 17:28:14 -0400411 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
John Snowa0eae172021-05-27 17:16:50 -0400412
413 # Cleaning up of this subprocess is guaranteed by _do_shutdown.
414 # pylint: disable=consider-using-with
John Snowabf0bf92019-06-27 17:28:14 -0400415 self._popen = subprocess.Popen(self._qemu_full_args,
John Snow07b71232021-05-27 17:16:46 -0400416 stdin=subprocess.DEVNULL,
John Snowabf0bf92019-06-27 17:28:14 -0400417 stdout=self._qemu_log_file,
418 stderr=subprocess.STDOUT,
419 shell=False,
420 close_fds=False)
421 self._post_launch()
422
John Snowe2c97f12020-07-10 01:06:40 -0400423 def _early_cleanup(self) -> None:
424 """
425 Perform any cleanup that needs to happen before the VM exits.
John Snowa3842cb2020-07-10 01:06:42 -0400426
John Snow193bf1c2020-07-10 01:06:47 -0400427 May be invoked by both soft and hard shutdown in failover scenarios.
John Snowa3842cb2020-07-10 01:06:42 -0400428 Called additionally by _post_shutdown for comprehensive cleanup.
John Snowe2c97f12020-07-10 01:06:40 -0400429 """
430 # If we keep the console socket open, we may deadlock waiting
431 # for QEMU to exit, while QEMU is waiting for the socket to
432 # become writeable.
433 if self._console_socket is not None:
434 self._console_socket.close()
435 self._console_socket = None
436
John Snow193bf1c2020-07-10 01:06:47 -0400437 def _hard_shutdown(self) -> None:
438 """
439 Perform early cleanup, kill the VM, and wait for it to terminate.
440
441 :raise subprocess.Timeout: When timeout is exceeds 60 seconds
442 waiting for the QEMU process to terminate.
443 """
444 self._early_cleanup()
John Snow9223fda2020-10-06 19:58:05 -0400445 self._subp.kill()
446 self._subp.wait(timeout=60)
John Snow193bf1c2020-07-10 01:06:47 -0400447
John Snowb9420e42021-10-26 13:56:05 -0400448 def _soft_shutdown(self, timeout: Optional[int]) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400449 """
450 Perform early cleanup, attempt to gracefully shut down the VM, and wait
451 for it to terminate.
452
John Snow8226a4b2020-07-20 12:02:52 -0400453 :param timeout: Timeout in seconds for graceful shutdown.
454 A value of None is an infinite wait.
John Snow193bf1c2020-07-10 01:06:47 -0400455
456 :raise ConnectionReset: On QMP communication errors
457 :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
458 the QEMU process to terminate.
459 """
460 self._early_cleanup()
461
John Snowbe1183e2020-10-06 19:58:04 -0400462 if self._qmp_connection:
John Snowb9420e42021-10-26 13:56:05 -0400463 if not self._quit_issued:
John Snow193bf1c2020-07-10 01:06:47 -0400464 # Might raise ConnectionReset
John Snowb9420e42021-10-26 13:56:05 -0400465 self.qmp('quit')
John Snow193bf1c2020-07-10 01:06:47 -0400466
467 # May raise subprocess.TimeoutExpired
John Snow9223fda2020-10-06 19:58:05 -0400468 self._subp.wait(timeout=timeout)
John Snow193bf1c2020-07-10 01:06:47 -0400469
John Snowb9420e42021-10-26 13:56:05 -0400470 def _do_shutdown(self, timeout: Optional[int]) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400471 """
472 Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
473
John Snow8226a4b2020-07-20 12:02:52 -0400474 :param timeout: Timeout in seconds for graceful shutdown.
475 A value of None is an infinite wait.
John Snow193bf1c2020-07-10 01:06:47 -0400476
477 :raise AbnormalShutdown: When the VM could not be shut down gracefully.
478 The inner exception will likely be ConnectionReset or
479 subprocess.TimeoutExpired. In rare cases, non-graceful termination
480 may result in its own exceptions, likely subprocess.TimeoutExpired.
481 """
482 try:
John Snowb9420e42021-10-26 13:56:05 -0400483 self._soft_shutdown(timeout)
John Snow193bf1c2020-07-10 01:06:47 -0400484 except Exception as exc:
485 self._hard_shutdown()
486 raise AbnormalShutdown("Could not perform graceful shutdown") \
487 from exc
488
John Snowb9420e42021-10-26 13:56:05 -0400489 def shutdown(self,
John Snowc9b30452020-07-10 01:06:43 -0400490 hard: bool = False,
John Snow8226a4b2020-07-20 12:02:52 -0400491 timeout: Optional[int] = 30) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400492 """
John Snow193bf1c2020-07-10 01:06:47 -0400493 Terminate the VM (gracefully if possible) and perform cleanup.
494 Cleanup will always be performed.
495
496 If the VM has not yet been launched, or shutdown(), wait(), or kill()
497 have already been called, this method does nothing.
498
John Snow193bf1c2020-07-10 01:06:47 -0400499 :param hard: When true, do not attempt graceful shutdown, and
500 suppress the SIGKILL warning log message.
501 :param timeout: Optional timeout in seconds for graceful shutdown.
John Snow8226a4b2020-07-20 12:02:52 -0400502 Default 30 seconds, A `None` value is an infinite wait.
John Snowabf0bf92019-06-27 17:28:14 -0400503 """
John Snowa3842cb2020-07-10 01:06:42 -0400504 if not self._launched:
505 return
506
John Snow193bf1c2020-07-10 01:06:47 -0400507 try:
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300508 if hard:
John Snowde6e08b2020-07-10 01:06:48 -0400509 self._user_killed = True
John Snow193bf1c2020-07-10 01:06:47 -0400510 self._hard_shutdown()
511 else:
John Snowb9420e42021-10-26 13:56:05 -0400512 self._do_shutdown(timeout)
John Snow193bf1c2020-07-10 01:06:47 -0400513 finally:
514 self._post_shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400515
John Snowf12a2822020-10-06 19:58:08 -0400516 def kill(self) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400517 """
518 Terminate the VM forcefully, wait for it to exit, and perform cleanup.
519 """
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300520 self.shutdown(hard=True)
521
John Snow8226a4b2020-07-20 12:02:52 -0400522 def wait(self, timeout: Optional[int] = 30) -> None:
John Snow89528052020-07-10 01:06:44 -0400523 """
524 Wait for the VM to power off and perform post-shutdown cleanup.
525
John Snow8226a4b2020-07-20 12:02:52 -0400526 :param timeout: Optional timeout in seconds. Default 30 seconds.
527 A value of `None` is an infinite wait.
John Snow89528052020-07-10 01:06:44 -0400528 """
John Snowb9420e42021-10-26 13:56:05 -0400529 self._quit_issued = True
530 self.shutdown(timeout=timeout)
John Snow89528052020-07-10 01:06:44 -0400531
John Snowf12a2822020-10-06 19:58:08 -0400532 def set_qmp_monitor(self, enabled: bool = True) -> None:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500533 """
534 Set the QMP monitor.
535
536 @param enabled: if False, qmp monitor options will be removed from
537 the base arguments of the resulting QEMU command
538 line. Default is True.
John Snow5c02c862021-06-29 17:43:23 -0400539
540 .. note:: Call this function before launch().
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500541 """
John Snowbe1183e2020-10-06 19:58:04 -0400542 self._qmp_set = enabled
543
544 @property
John Snowbeb6b572021-05-27 17:16:53 -0400545 def _qmp(self) -> QEMUMonitorProtocol:
John Snowbe1183e2020-10-06 19:58:04 -0400546 if self._qmp_connection is None:
547 raise QEMUMachineError("Attempt to access QMP with no connection")
548 return self._qmp_connection
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500549
John Snowaaa81ec2020-10-06 19:58:03 -0400550 @classmethod
Vladimir Sementsov-Ogievskiyc7daa572021-08-24 11:38:45 +0300551 def _qmp_args(cls, conv_keys: bool,
552 args: Dict[str, Any]) -> Dict[str, object]:
553 if conv_keys:
554 return {k.replace('_', '-'): v for k, v in args.items()}
555
556 return args
John Snowabf0bf92019-06-27 17:28:14 -0400557
John Snowaaa81ec2020-10-06 19:58:03 -0400558 def qmp(self, cmd: str,
Vladimir Sementsov-Ogievskiy3f3c9b42021-08-24 11:38:46 +0300559 args_dict: Optional[Dict[str, object]] = None,
560 conv_keys: Optional[bool] = None,
John Snowaaa81ec2020-10-06 19:58:03 -0400561 **args: Any) -> QMPMessage:
562 """
563 Invoke a QMP command and return the response dict
564 """
Vladimir Sementsov-Ogievskiy3f3c9b42021-08-24 11:38:46 +0300565 if args_dict is not None:
566 assert not args
567 assert conv_keys is None
568 args = args_dict
569 conv_keys = False
570
571 if conv_keys is None:
572 conv_keys = True
573
Vladimir Sementsov-Ogievskiyc7daa572021-08-24 11:38:45 +0300574 qmp_args = self._qmp_args(conv_keys, args)
John Snowb9420e42021-10-26 13:56:05 -0400575 ret = self._qmp.cmd(cmd, args=qmp_args)
576 if cmd == 'quit' and 'error' not in ret and 'return' in ret:
577 self._quit_issued = True
578 return ret
John Snowabf0bf92019-06-27 17:28:14 -0400579
John Snowf12a2822020-10-06 19:58:08 -0400580 def command(self, cmd: str,
581 conv_keys: bool = True,
582 **args: Any) -> QMPReturnValue:
John Snowabf0bf92019-06-27 17:28:14 -0400583 """
584 Invoke a QMP command.
585 On success return the response dict.
586 On failure raise an exception.
587 """
Vladimir Sementsov-Ogievskiyc7daa572021-08-24 11:38:45 +0300588 qmp_args = self._qmp_args(conv_keys, args)
John Snowb9420e42021-10-26 13:56:05 -0400589 ret = self._qmp.command(cmd, **qmp_args)
590 if cmd == 'quit':
591 self._quit_issued = True
592 return ret
John Snowabf0bf92019-06-27 17:28:14 -0400593
John Snowf12a2822020-10-06 19:58:08 -0400594 def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400595 """
596 Poll for one queued QMP events and return it
597 """
John Snow306dfcd2019-06-27 17:28:15 -0400598 if self._events:
John Snowabf0bf92019-06-27 17:28:14 -0400599 return self._events.pop(0)
600 return self._qmp.pull_event(wait=wait)
601
John Snowf12a2822020-10-06 19:58:08 -0400602 def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400603 """
604 Poll for queued QMP events and return a list of dicts
605 """
606 events = self._qmp.get_events(wait=wait)
607 events.extend(self._events)
608 del self._events[:]
John Snowabf0bf92019-06-27 17:28:14 -0400609 return events
610
611 @staticmethod
John Snowf12a2822020-10-06 19:58:08 -0400612 def event_match(event: Any, match: Optional[Any]) -> bool:
John Snowabf0bf92019-06-27 17:28:14 -0400613 """
614 Check if an event matches optional match criteria.
615
616 The match criteria takes the form of a matching subdict. The event is
617 checked to be a superset of the subdict, recursively, with matching
618 values whenever the subdict values are not None.
619
620 This has a limitation that you cannot explicitly check for None values.
621
622 Examples, with the subdict queries on the left:
623 - None matches any object.
624 - {"foo": None} matches {"foo": {"bar": 1}}
625 - {"foo": None} matches {"foo": 5}
626 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
627 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
628 """
629 if match is None:
630 return True
631
632 try:
633 for key in match:
634 if key in event:
635 if not QEMUMachine.event_match(event[key], match[key]):
636 return False
637 else:
638 return False
639 return True
640 except TypeError:
641 # either match or event wasn't iterable (not a dict)
John Snowf12a2822020-10-06 19:58:08 -0400642 return bool(match == event)
John Snowabf0bf92019-06-27 17:28:14 -0400643
John Snowf12a2822020-10-06 19:58:08 -0400644 def event_wait(self, name: str,
645 timeout: float = 60.0,
646 match: Optional[QMPMessage] = None) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400647 """
648 event_wait waits for and returns a named event from QMP with a timeout.
649
650 name: The event to wait for.
651 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
652 match: Optional match criteria. See event_match for details.
653 """
654 return self.events_wait([(name, match)], timeout)
655
John Snowf12a2822020-10-06 19:58:08 -0400656 def events_wait(self,
657 events: Sequence[Tuple[str, Any]],
658 timeout: float = 60.0) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400659 """
John Snow1847a4a2020-10-06 19:58:02 -0400660 events_wait waits for and returns a single named event from QMP.
661 In the case of multiple qualifying events, this function returns the
662 first one.
John Snowabf0bf92019-06-27 17:28:14 -0400663
John Snow1847a4a2020-10-06 19:58:02 -0400664 :param events: A sequence of (name, match_criteria) tuples.
665 The match criteria are optional and may be None.
666 See event_match for details.
667 :param timeout: Optional timeout, in seconds.
668 See QEMUMonitorProtocol.pull_event.
669
670 :raise QMPTimeoutError: If timeout was non-zero and no matching events
671 were found.
672 :return: A QMP event matching the filter criteria.
673 If timeout was 0 and no event matched, None.
John Snowabf0bf92019-06-27 17:28:14 -0400674 """
John Snowf12a2822020-10-06 19:58:08 -0400675 def _match(event: QMPMessage) -> bool:
John Snowabf0bf92019-06-27 17:28:14 -0400676 for name, match in events:
John Snow306dfcd2019-06-27 17:28:15 -0400677 if event['event'] == name and self.event_match(event, match):
John Snowabf0bf92019-06-27 17:28:14 -0400678 return True
679 return False
680
John Snow1847a4a2020-10-06 19:58:02 -0400681 event: Optional[QMPMessage]
682
John Snowabf0bf92019-06-27 17:28:14 -0400683 # Search cached events
684 for event in self._events:
685 if _match(event):
686 self._events.remove(event)
687 return event
688
689 # Poll for new events
690 while True:
691 event = self._qmp.pull_event(wait=timeout)
John Snow1847a4a2020-10-06 19:58:02 -0400692 if event is None:
693 # NB: None is only returned when timeout is false-ish.
694 # Timeouts raise QMPTimeoutError instead!
695 break
John Snowabf0bf92019-06-27 17:28:14 -0400696 if _match(event):
697 return event
698 self._events.append(event)
699
700 return None
701
John Snowf12a2822020-10-06 19:58:08 -0400702 def get_log(self) -> Optional[str]:
John Snowabf0bf92019-06-27 17:28:14 -0400703 """
704 After self.shutdown or failed qemu execution, this returns the output
705 of the qemu process.
706 """
707 return self._iolog
708
John Snowf12a2822020-10-06 19:58:08 -0400709 def add_args(self, *args: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400710 """
711 Adds to the list of extra arguments to be given to the QEMU binary
712 """
713 self._args.extend(args)
714
John Snowf12a2822020-10-06 19:58:08 -0400715 def set_machine(self, machine_type: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400716 """
717 Sets the machine type
718
719 If set, the machine type will be added to the base arguments
720 of the resulting QEMU command line.
721 """
722 self._machine = machine_type
723
John Snowf12a2822020-10-06 19:58:08 -0400724 def set_console(self,
725 device_type: Optional[str] = None,
726 console_index: int = 0) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400727 """
728 Sets the device type for a console device
729
730 If set, the console device and a backing character device will
731 be added to the base arguments of the resulting QEMU command
732 line.
733
734 This is a convenience method that will either use the provided
735 device type, or default to a "-serial chardev:console" command
736 line argument.
737
738 The actual setting of command line arguments will be be done at
739 machine launch time, as it depends on the temporary directory
740 to be created.
741
742 @param device_type: the device type, such as "isa-serial". If
743 None is given (the default value) a "-serial
744 chardev:console" command line argument will
745 be used instead, resorting to the machine's
746 default device type.
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100747 @param console_index: the index of the console device to use.
748 If not zero, the command line will create
749 'index - 1' consoles and connect them to
750 the 'null' backing character device.
John Snowabf0bf92019-06-27 17:28:14 -0400751 """
752 self._console_set = True
753 self._console_device_type = device_type
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100754 self._console_index = console_index
John Snowabf0bf92019-06-27 17:28:14 -0400755
756 @property
John Snowf12a2822020-10-06 19:58:08 -0400757 def console_socket(self) -> socket.socket:
John Snowabf0bf92019-06-27 17:28:14 -0400758 """
759 Returns a socket connected to the console
760 """
761 if self._console_socket is None:
Robert Foley80ded8e2020-07-24 07:45:08 +0100762 self._console_socket = console_socket.ConsoleSocket(
763 self._console_address,
764 file=self._console_log_path,
765 drain=self._drain_console)
John Snowabf0bf92019-06-27 17:28:14 -0400766 return self._console_socket
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500767
768 @property
769 def temp_dir(self) -> str:
770 """
771 Returns a temporary directory to be used for this machine
772 """
773 if self._temp_dir is None:
774 self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-",
775 dir=self._base_temp_dir)
776 return self._temp_dir
Cleber Rosab306e262021-02-11 16:55:05 -0500777
778 @property
779 def log_dir(self) -> str:
780 """
781 Returns a directory to be used for writing logs
782 """
783 if self._log_dir is None:
784 return self.temp_dir
785 return self._log_dir