blob: 3fde73cf108e5ea76741753f815a62c68db98fa7 [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 Snowabf0bf92019-06-27 17:28:14 -040022import logging
23import os
John Snowabf0bf92019-06-27 17:28:14 -040024import shutil
John Snowde6e08b2020-07-10 01:06:48 -040025import signal
John Snowf12a2822020-10-06 19:58:08 -040026import socket
John Snow932ca4b2020-10-06 19:57:58 -040027import subprocess
John Snowabf0bf92019-06-27 17:28:14 -040028import tempfile
John Snow1dda0402020-05-14 01:53:44 -040029from types import TracebackType
John Snowaaa81ec2020-10-06 19:58:03 -040030from typing import (
31 Any,
John Snowf12a2822020-10-06 19:58:08 -040032 BinaryIO,
John Snowaaa81ec2020-10-06 19:58:03 -040033 Dict,
34 List,
35 Optional,
John Snowaad3f3b2020-10-06 19:58:06 -040036 Sequence,
37 Tuple,
John Snowaaa81ec2020-10-06 19:58:03 -040038 Type,
39)
John Snowabf0bf92019-06-27 17:28:14 -040040
John Snowd1e04762021-05-27 17:16:59 -040041from qemu.qmp import ( # pylint: disable=import-error
John Snowbeb6b572021-05-27 17:16:53 -040042 QEMUMonitorProtocol,
43 QMPMessage,
44 QMPReturnValue,
45 SocketAddrT,
46)
47
48from . import console_socket
John Snow932ca4b2020-10-06 19:57:58 -040049
John Snowabf0bf92019-06-27 17:28:14 -040050
51LOG = logging.getLogger(__name__)
52
John Snow8dfac2e2020-05-28 18:21:29 -040053
John Snowabf0bf92019-06-27 17:28:14 -040054class QEMUMachineError(Exception):
55 """
56 Exception called when an error in QEMUMachine happens.
57 """
58
59
60class QEMUMachineAddDeviceError(QEMUMachineError):
61 """
62 Exception raised when a request to add a device can not be fulfilled
63
64 The failures are caused by limitations, lack of information or conflicting
65 requests on the QEMUMachine methods. This exception does not represent
66 failures reported by the QEMU binary itself.
67 """
68
69
John Snow193bf1c2020-07-10 01:06:47 -040070class AbnormalShutdown(QEMUMachineError):
71 """
72 Exception raised when a graceful shutdown was requested, but not performed.
73 """
74
75
John Snow9b8ccd62020-05-28 18:21:28 -040076class QEMUMachine:
John Snowabf0bf92019-06-27 17:28:14 -040077 """
John Snowf12a2822020-10-06 19:58:08 -040078 A QEMU VM.
John Snowabf0bf92019-06-27 17:28:14 -040079
John Snow8dfac2e2020-05-28 18:21:29 -040080 Use this object as a context manager to ensure
81 the QEMU process terminates::
John Snowabf0bf92019-06-27 17:28:14 -040082
83 with VM(binary) as vm:
84 ...
85 # vm is guaranteed to be shut down here
86 """
John Snow82e65172021-06-29 17:43:11 -040087 # pylint: disable=too-many-instance-attributes, too-many-public-methods
John Snowabf0bf92019-06-27 17:28:14 -040088
John Snowaad3f3b2020-10-06 19:58:06 -040089 def __init__(self,
90 binary: str,
91 args: Sequence[str] = (),
92 wrapper: Sequence[str] = (),
93 name: Optional[str] = None,
Cleber Rosa2ca6e262021-02-11 17:01:42 -050094 base_temp_dir: str = "/var/tmp",
John Snowc4e60232020-10-06 19:57:59 -040095 monitor_address: Optional[SocketAddrT] = None,
John Snowf12a2822020-10-06 19:58:08 -040096 socket_scm_helper: Optional[str] = None,
97 sock_dir: Optional[str] = None,
98 drain_console: bool = False,
Cleber Rosab306e262021-02-11 16:55:05 -050099 console_log: Optional[str] = None,
Emanuele Giuseppe Espositoe2f948a2021-08-09 11:00:59 +0200100 log_dir: Optional[str] = None,
101 qmp_timer: Optional[float] = None):
John Snowabf0bf92019-06-27 17:28:14 -0400102 '''
103 Initialize a QEMUMachine
104
105 @param binary: path to the qemu binary
106 @param args: list of extra arguments
107 @param wrapper: list of arguments used as prefix to qemu binary
108 @param name: prefix for socket and log file names (default: qemu-PID)
John Snow859aeb62021-05-27 17:16:51 -0400109 @param base_temp_dir: default location where temp files are created
John Snowabf0bf92019-06-27 17:28:14 -0400110 @param monitor_address: address for QMP monitor
111 @param socket_scm_helper: helper program, required for send_fd_scm()
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500112 @param sock_dir: where to create socket (defaults to base_temp_dir)
Robert Foley0fc8f662020-07-01 14:56:24 +0100113 @param drain_console: (optional) True to drain console socket to buffer
John Snowc5e61a62020-10-06 19:58:00 -0400114 @param console_log: (optional) path to console log file
Cleber Rosab306e262021-02-11 16:55:05 -0500115 @param log_dir: where to create and keep log files
Emanuele Giuseppe Espositoe2f948a2021-08-09 11:00:59 +0200116 @param qmp_timer: (optional) default QMP socket timeout
John Snowabf0bf92019-06-27 17:28:14 -0400117 @note: Qemu process is not started until launch() is used.
118 '''
John Snow82e65172021-06-29 17:43:11 -0400119 # pylint: disable=too-many-arguments
120
John Snowc5e61a62020-10-06 19:58:00 -0400121 # Direct user configuration
122
123 self._binary = binary
John Snowc5e61a62020-10-06 19:58:00 -0400124 self._args = list(args)
John Snowc5e61a62020-10-06 19:58:00 -0400125 self._wrapper = wrapper
Emanuele Giuseppe Espositoe2f948a2021-08-09 11:00:59 +0200126 self._qmp_timer = qmp_timer
John Snowc5e61a62020-10-06 19:58:00 -0400127
128 self._name = name or "qemu-%d" % os.getpid()
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500129 self._base_temp_dir = base_temp_dir
130 self._sock_dir = sock_dir or self._base_temp_dir
Cleber Rosab306e262021-02-11 16:55:05 -0500131 self._log_dir = log_dir
John Snowc5e61a62020-10-06 19:58:00 -0400132 self._socket_scm_helper = socket_scm_helper
133
John Snowc4e60232020-10-06 19:57:59 -0400134 if monitor_address is not None:
135 self._monitor_address = monitor_address
136 self._remove_monitor_sockfile = False
137 else:
138 self._monitor_address = os.path.join(
John Snowc5e61a62020-10-06 19:58:00 -0400139 self._sock_dir, f"{self._name}-monitor.sock"
John Snowc4e60232020-10-06 19:57:59 -0400140 )
141 self._remove_monitor_sockfile = True
John Snowc5e61a62020-10-06 19:58:00 -0400142
143 self._console_log_path = console_log
144 if self._console_log_path:
145 # In order to log the console, buffering needs to be enabled.
146 self._drain_console = True
147 else:
148 self._drain_console = drain_console
149
150 # Runstate
John Snowf12a2822020-10-06 19:58:08 -0400151 self._qemu_log_path: Optional[str] = None
152 self._qemu_log_file: Optional[BinaryIO] = None
John Snow9223fda2020-10-06 19:58:05 -0400153 self._popen: Optional['subprocess.Popen[bytes]'] = None
John Snowf12a2822020-10-06 19:58:08 -0400154 self._events: List[QMPMessage] = []
155 self._iolog: Optional[str] = None
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500156 self._qmp_set = True # Enable QMP monitor by default.
John Snowbeb6b572021-05-27 17:16:53 -0400157 self._qmp_connection: Optional[QEMUMonitorProtocol] = None
John Snowaad3f3b2020-10-06 19:58:06 -0400158 self._qemu_full_args: Tuple[str, ...] = ()
John Snowf12a2822020-10-06 19:58:08 -0400159 self._temp_dir: Optional[str] = None
John Snowabf0bf92019-06-27 17:28:14 -0400160 self._launched = False
John Snowf12a2822020-10-06 19:58:08 -0400161 self._machine: Optional[str] = None
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100162 self._console_index = 0
John Snowabf0bf92019-06-27 17:28:14 -0400163 self._console_set = False
John Snowf12a2822020-10-06 19:58:08 -0400164 self._console_device_type: Optional[str] = None
John Snow652809d2020-10-06 19:58:01 -0400165 self._console_address = os.path.join(
166 self._sock_dir, f"{self._name}-console.sock"
167 )
John Snowf12a2822020-10-06 19:58:08 -0400168 self._console_socket: Optional[socket.socket] = None
169 self._remove_files: List[str] = []
John Snowde6e08b2020-07-10 01:06:48 -0400170 self._user_killed = False
John Snowabf0bf92019-06-27 17:28:14 -0400171
John Snowf12a2822020-10-06 19:58:08 -0400172 def __enter__(self) -> 'QEMUMachine':
John Snowabf0bf92019-06-27 17:28:14 -0400173 return self
174
John Snow1dda0402020-05-14 01:53:44 -0400175 def __exit__(self,
176 exc_type: Optional[Type[BaseException]],
177 exc_val: Optional[BaseException],
178 exc_tb: Optional[TracebackType]) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400179 self.shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400180
John Snowf12a2822020-10-06 19:58:08 -0400181 def add_monitor_null(self) -> None:
John Snow306dfcd2019-06-27 17:28:15 -0400182 """
183 This can be used to add an unused monitor instance.
184 """
John Snowabf0bf92019-06-27 17:28:14 -0400185 self._args.append('-monitor')
186 self._args.append('null')
187
John Snowf12a2822020-10-06 19:58:08 -0400188 def add_fd(self, fd: int, fdset: int,
189 opaque: str, opts: str = '') -> 'QEMUMachine':
John Snowabf0bf92019-06-27 17:28:14 -0400190 """
191 Pass a file descriptor to the VM
192 """
193 options = ['fd=%d' % fd,
194 'set=%d' % fdset,
195 'opaque=%s' % opaque]
196 if opts:
197 options.append(opts)
198
199 # This did not exist before 3.4, but since then it is
200 # mandatory for our purpose
201 if hasattr(os, 'set_inheritable'):
202 os.set_inheritable(fd, True)
203
204 self._args.append('-add-fd')
205 self._args.append(','.join(options))
206 return self
207
John Snowf12a2822020-10-06 19:58:08 -0400208 def send_fd_scm(self, fd: Optional[int] = None,
209 file_path: Optional[str] = None) -> int:
John Snow306dfcd2019-06-27 17:28:15 -0400210 """
211 Send an fd or file_path to socket_scm_helper.
212
213 Exactly one of fd and file_path must be given.
214 If it is file_path, the helper will open that file and pass its own fd.
215 """
John Snowabf0bf92019-06-27 17:28:14 -0400216 # In iotest.py, the qmp should always use unix socket.
217 assert self._qmp.is_scm_available()
218 if self._socket_scm_helper is None:
219 raise QEMUMachineError("No path to socket_scm_helper set")
220 if not os.path.exists(self._socket_scm_helper):
221 raise QEMUMachineError("%s does not exist" %
222 self._socket_scm_helper)
223
224 # This did not exist before 3.4, but since then it is
225 # mandatory for our purpose
226 if hasattr(os, 'set_inheritable'):
227 os.set_inheritable(self._qmp.get_sock_fd(), True)
228 if fd is not None:
229 os.set_inheritable(fd, True)
230
231 fd_param = ["%s" % self._socket_scm_helper,
232 "%d" % self._qmp.get_sock_fd()]
233
234 if file_path is not None:
235 assert fd is None
236 fd_param.append(file_path)
237 else:
238 assert fd is not None
239 fd_param.append(str(fd))
240
John Snow14b41792021-05-27 17:16:47 -0400241 proc = subprocess.run(
242 fd_param,
243 stdin=subprocess.DEVNULL,
244 stdout=subprocess.PIPE,
245 stderr=subprocess.STDOUT,
246 check=False,
247 close_fds=False,
John Snow8dfac2e2020-05-28 18:21:29 -0400248 )
John Snow14b41792021-05-27 17:16:47 -0400249 if proc.stdout:
250 LOG.debug(proc.stdout)
John Snowabf0bf92019-06-27 17:28:14 -0400251
252 return proc.returncode
253
254 @staticmethod
John Snowf12a2822020-10-06 19:58:08 -0400255 def _remove_if_exists(path: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400256 """
257 Remove file object at path if it exists
258 """
259 try:
260 os.remove(path)
261 except OSError as exception:
262 if exception.errno == errno.ENOENT:
263 return
264 raise
265
John Snowf12a2822020-10-06 19:58:08 -0400266 def is_running(self) -> bool:
John Snow306dfcd2019-06-27 17:28:15 -0400267 """Returns true if the VM is running."""
John Snowabf0bf92019-06-27 17:28:14 -0400268 return self._popen is not None and self._popen.poll() is None
269
John Snow9223fda2020-10-06 19:58:05 -0400270 @property
271 def _subp(self) -> 'subprocess.Popen[bytes]':
272 if self._popen is None:
273 raise QEMUMachineError('Subprocess pipe not present')
274 return self._popen
275
John Snowf12a2822020-10-06 19:58:08 -0400276 def exitcode(self) -> Optional[int]:
John Snow306dfcd2019-06-27 17:28:15 -0400277 """Returns the exit code if possible, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400278 if self._popen is None:
279 return None
280 return self._popen.poll()
281
John Snowf12a2822020-10-06 19:58:08 -0400282 def get_pid(self) -> Optional[int]:
John Snow306dfcd2019-06-27 17:28:15 -0400283 """Returns the PID of the running process, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400284 if not self.is_running():
285 return None
John Snow9223fda2020-10-06 19:58:05 -0400286 return self._subp.pid
John Snowabf0bf92019-06-27 17:28:14 -0400287
John Snowf12a2822020-10-06 19:58:08 -0400288 def _load_io_log(self) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400289 if self._qemu_log_path is not None:
290 with open(self._qemu_log_path, "r") as iolog:
291 self._iolog = iolog.read()
292
John Snow652809d2020-10-06 19:58:01 -0400293 @property
294 def _base_args(self) -> List[str]:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500295 args = ['-display', 'none', '-vga', 'none']
John Snowc4e60232020-10-06 19:57:59 -0400296
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500297 if self._qmp_set:
298 if isinstance(self._monitor_address, tuple):
John Snowc4e60232020-10-06 19:57:59 -0400299 moncdev = "socket,id=mon,host={},port={}".format(
300 *self._monitor_address
301 )
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500302 else:
John Snowc4e60232020-10-06 19:57:59 -0400303 moncdev = f"socket,id=mon,path={self._monitor_address}"
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500304 args.extend(['-chardev', moncdev, '-mon',
305 'chardev=mon,mode=control'])
John Snowc4e60232020-10-06 19:57:59 -0400306
John Snowabf0bf92019-06-27 17:28:14 -0400307 if self._machine is not None:
308 args.extend(['-machine', self._machine])
John Snow9b8ccd62020-05-28 18:21:28 -0400309 for _ in range(self._console_index):
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100310 args.extend(['-serial', 'null'])
John Snowabf0bf92019-06-27 17:28:14 -0400311 if self._console_set:
Paolo Bonzini991c1802020-11-13 03:10:52 -0500312 chardev = ('socket,id=console,path=%s,server=on,wait=off' %
John Snowabf0bf92019-06-27 17:28:14 -0400313 self._console_address)
314 args.extend(['-chardev', chardev])
315 if self._console_device_type is None:
316 args.extend(['-serial', 'chardev:console'])
317 else:
318 device = '%s,chardev=console' % self._console_device_type
319 args.extend(['-device', device])
320 return args
321
Wainer dos Santos Moschetta555fe0c2021-04-30 10:34:12 -0300322 @property
323 def args(self) -> List[str]:
324 """Returns the list of arguments given to the QEMU binary."""
325 return self._args
326
John Snowf12a2822020-10-06 19:58:08 -0400327 def _pre_launch(self) -> None:
John Snow652809d2020-10-06 19:58:01 -0400328 if self._console_set:
329 self._remove_files.append(self._console_address)
330
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500331 if self._qmp_set:
John Snowc4e60232020-10-06 19:57:59 -0400332 if self._remove_monitor_sockfile:
333 assert isinstance(self._monitor_address, str)
334 self._remove_files.append(self._monitor_address)
John Snowbeb6b572021-05-27 17:16:53 -0400335 self._qmp_connection = QEMUMonitorProtocol(
John Snowc4e60232020-10-06 19:57:59 -0400336 self._monitor_address,
337 server=True,
338 nickname=self._name
339 )
John Snowabf0bf92019-06-27 17:28:14 -0400340
John Snow63c33f32021-05-27 17:16:49 -0400341 # NOTE: Make sure any opened resources are *definitely* freed in
342 # _post_shutdown()!
343 # pylint: disable=consider-using-with
Cleber Rosab306e262021-02-11 16:55:05 -0500344 self._qemu_log_path = os.path.join(self.log_dir, self._name + ".log")
John Snow63c33f32021-05-27 17:16:49 -0400345 self._qemu_log_file = open(self._qemu_log_path, 'wb')
346
John Snowf12a2822020-10-06 19:58:08 -0400347 def _post_launch(self) -> None:
John Snowbe1183e2020-10-06 19:58:04 -0400348 if self._qmp_connection:
Emanuele Giuseppe Espositoe2f948a2021-08-09 11:00:59 +0200349 self._qmp.accept(self._qmp_timer)
John Snowabf0bf92019-06-27 17:28:14 -0400350
Emanuele Giuseppe Espositoeb7a91d2021-08-09 11:01:13 +0200351 def _close_qemu_log_file(self) -> None:
352 if self._qemu_log_file is not None:
353 self._qemu_log_file.close()
354 self._qemu_log_file = None
355
John Snowf12a2822020-10-06 19:58:08 -0400356 def _post_shutdown(self) -> None:
John Snowa3842cb2020-07-10 01:06:42 -0400357 """
358 Called to cleanup the VM instance after the process has exited.
359 May also be called after a failed launch.
360 """
361 # Comprehensive reset for the failed launch case:
362 self._early_cleanup()
363
John Snowbe1183e2020-10-06 19:58:04 -0400364 if self._qmp_connection:
John Snow671940e2020-07-10 01:06:39 -0400365 self._qmp.close()
John Snowbe1183e2020-10-06 19:58:04 -0400366 self._qmp_connection = None
John Snow671940e2020-07-10 01:06:39 -0400367
Emanuele Giuseppe Espositoeb7a91d2021-08-09 11:01:13 +0200368 self._close_qemu_log_file()
John Snowabf0bf92019-06-27 17:28:14 -0400369
Cleber Rosa3c1e16c2021-02-11 17:01:41 -0500370 self._load_io_log()
371
John Snowabf0bf92019-06-27 17:28:14 -0400372 self._qemu_log_path = None
373
John Snowabf0bf92019-06-27 17:28:14 -0400374 if self._temp_dir is not None:
375 shutil.rmtree(self._temp_dir)
376 self._temp_dir = None
377
Max Reitz32558ce2019-10-17 15:31:34 +0200378 while len(self._remove_files) > 0:
379 self._remove_if_exists(self._remove_files.pop())
380
John Snow14661d92020-07-10 01:06:38 -0400381 exitcode = self.exitcode()
John Snowde6e08b2020-07-10 01:06:48 -0400382 if (exitcode is not None and exitcode < 0
383 and not (self._user_killed and exitcode == -signal.SIGKILL)):
John Snow14661d92020-07-10 01:06:38 -0400384 msg = 'qemu received signal %i; command: "%s"'
385 if self._qemu_full_args:
386 command = ' '.join(self._qemu_full_args)
387 else:
388 command = ''
389 LOG.warning(msg, -int(exitcode), command)
390
John Snowde6e08b2020-07-10 01:06:48 -0400391 self._user_killed = False
John Snow14661d92020-07-10 01:06:38 -0400392 self._launched = False
393
John Snowf12a2822020-10-06 19:58:08 -0400394 def launch(self) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400395 """
396 Launch the VM and make sure we cleanup and expose the
397 command line/output in case of exception
398 """
399
400 if self._launched:
401 raise QEMUMachineError('VM already launched')
402
403 self._iolog = None
John Snowaad3f3b2020-10-06 19:58:06 -0400404 self._qemu_full_args = ()
John Snowabf0bf92019-06-27 17:28:14 -0400405 try:
406 self._launch()
407 self._launched = True
408 except:
John Snowa3842cb2020-07-10 01:06:42 -0400409 self._post_shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400410
411 LOG.debug('Error launching VM')
412 if self._qemu_full_args:
413 LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
414 if self._iolog:
415 LOG.debug('Output: %r', self._iolog)
416 raise
417
John Snowf12a2822020-10-06 19:58:08 -0400418 def _launch(self) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400419 """
420 Launch the VM and establish a QMP connection
421 """
John Snowabf0bf92019-06-27 17:28:14 -0400422 self._pre_launch()
John Snowaad3f3b2020-10-06 19:58:06 -0400423 self._qemu_full_args = tuple(
424 chain(self._wrapper,
425 [self._binary],
426 self._base_args,
427 self._args)
428 )
John Snowabf0bf92019-06-27 17:28:14 -0400429 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
John Snowa0eae172021-05-27 17:16:50 -0400430
431 # Cleaning up of this subprocess is guaranteed by _do_shutdown.
432 # pylint: disable=consider-using-with
John Snowabf0bf92019-06-27 17:28:14 -0400433 self._popen = subprocess.Popen(self._qemu_full_args,
John Snow07b71232021-05-27 17:16:46 -0400434 stdin=subprocess.DEVNULL,
John Snowabf0bf92019-06-27 17:28:14 -0400435 stdout=self._qemu_log_file,
436 stderr=subprocess.STDOUT,
437 shell=False,
438 close_fds=False)
439 self._post_launch()
440
John Snowe2c97f12020-07-10 01:06:40 -0400441 def _early_cleanup(self) -> None:
442 """
443 Perform any cleanup that needs to happen before the VM exits.
John Snowa3842cb2020-07-10 01:06:42 -0400444
John Snow193bf1c2020-07-10 01:06:47 -0400445 May be invoked by both soft and hard shutdown in failover scenarios.
John Snowa3842cb2020-07-10 01:06:42 -0400446 Called additionally by _post_shutdown for comprehensive cleanup.
John Snowe2c97f12020-07-10 01:06:40 -0400447 """
448 # If we keep the console socket open, we may deadlock waiting
449 # for QEMU to exit, while QEMU is waiting for the socket to
450 # become writeable.
451 if self._console_socket is not None:
452 self._console_socket.close()
453 self._console_socket = None
454
John Snow193bf1c2020-07-10 01:06:47 -0400455 def _hard_shutdown(self) -> None:
456 """
457 Perform early cleanup, kill the VM, and wait for it to terminate.
458
459 :raise subprocess.Timeout: When timeout is exceeds 60 seconds
460 waiting for the QEMU process to terminate.
461 """
462 self._early_cleanup()
John Snow9223fda2020-10-06 19:58:05 -0400463 self._subp.kill()
464 self._subp.wait(timeout=60)
John Snow193bf1c2020-07-10 01:06:47 -0400465
John Snow8226a4b2020-07-20 12:02:52 -0400466 def _soft_shutdown(self, timeout: Optional[int],
467 has_quit: bool = False) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400468 """
469 Perform early cleanup, attempt to gracefully shut down the VM, and wait
470 for it to terminate.
471
John Snow8226a4b2020-07-20 12:02:52 -0400472 :param timeout: Timeout in seconds for graceful shutdown.
473 A value of None is an infinite wait.
John Snow193bf1c2020-07-10 01:06:47 -0400474 :param has_quit: When True, don't attempt to issue 'quit' QMP command
John Snow193bf1c2020-07-10 01:06:47 -0400475
476 :raise ConnectionReset: On QMP communication errors
477 :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
478 the QEMU process to terminate.
479 """
480 self._early_cleanup()
481
John Snowbe1183e2020-10-06 19:58:04 -0400482 if self._qmp_connection:
John Snow193bf1c2020-07-10 01:06:47 -0400483 if not has_quit:
484 # Might raise ConnectionReset
485 self._qmp.cmd('quit')
486
487 # May raise subprocess.TimeoutExpired
John Snow9223fda2020-10-06 19:58:05 -0400488 self._subp.wait(timeout=timeout)
John Snow193bf1c2020-07-10 01:06:47 -0400489
John Snow8226a4b2020-07-20 12:02:52 -0400490 def _do_shutdown(self, timeout: Optional[int],
491 has_quit: bool = False) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400492 """
493 Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
494
John Snow8226a4b2020-07-20 12:02:52 -0400495 :param timeout: Timeout in seconds for graceful shutdown.
496 A value of None is an infinite wait.
John Snow193bf1c2020-07-10 01:06:47 -0400497 :param has_quit: When True, don't attempt to issue 'quit' QMP command
John Snow193bf1c2020-07-10 01:06:47 -0400498
499 :raise AbnormalShutdown: When the VM could not be shut down gracefully.
500 The inner exception will likely be ConnectionReset or
501 subprocess.TimeoutExpired. In rare cases, non-graceful termination
502 may result in its own exceptions, likely subprocess.TimeoutExpired.
503 """
504 try:
John Snow8226a4b2020-07-20 12:02:52 -0400505 self._soft_shutdown(timeout, has_quit)
John Snow193bf1c2020-07-10 01:06:47 -0400506 except Exception as exc:
507 self._hard_shutdown()
508 raise AbnormalShutdown("Could not perform graceful shutdown") \
509 from exc
510
John Snowc9b30452020-07-10 01:06:43 -0400511 def shutdown(self, has_quit: bool = False,
512 hard: bool = False,
John Snow8226a4b2020-07-20 12:02:52 -0400513 timeout: Optional[int] = 30) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400514 """
John Snow193bf1c2020-07-10 01:06:47 -0400515 Terminate the VM (gracefully if possible) and perform cleanup.
516 Cleanup will always be performed.
517
518 If the VM has not yet been launched, or shutdown(), wait(), or kill()
519 have already been called, this method does nothing.
520
521 :param has_quit: When true, do not attempt to issue 'quit' QMP command.
522 :param hard: When true, do not attempt graceful shutdown, and
523 suppress the SIGKILL warning log message.
524 :param timeout: Optional timeout in seconds for graceful shutdown.
John Snow8226a4b2020-07-20 12:02:52 -0400525 Default 30 seconds, A `None` value is an infinite wait.
John Snowabf0bf92019-06-27 17:28:14 -0400526 """
John Snowa3842cb2020-07-10 01:06:42 -0400527 if not self._launched:
528 return
529
John Snow193bf1c2020-07-10 01:06:47 -0400530 try:
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300531 if hard:
John Snowde6e08b2020-07-10 01:06:48 -0400532 self._user_killed = True
John Snow193bf1c2020-07-10 01:06:47 -0400533 self._hard_shutdown()
534 else:
John Snow8226a4b2020-07-20 12:02:52 -0400535 self._do_shutdown(timeout, has_quit)
John Snow193bf1c2020-07-10 01:06:47 -0400536 finally:
537 self._post_shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400538
John Snowf12a2822020-10-06 19:58:08 -0400539 def kill(self) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400540 """
541 Terminate the VM forcefully, wait for it to exit, and perform cleanup.
542 """
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300543 self.shutdown(hard=True)
544
John Snow8226a4b2020-07-20 12:02:52 -0400545 def wait(self, timeout: Optional[int] = 30) -> None:
John Snow89528052020-07-10 01:06:44 -0400546 """
547 Wait for the VM to power off and perform post-shutdown cleanup.
548
John Snow8226a4b2020-07-20 12:02:52 -0400549 :param timeout: Optional timeout in seconds. Default 30 seconds.
550 A value of `None` is an infinite wait.
John Snow89528052020-07-10 01:06:44 -0400551 """
552 self.shutdown(has_quit=True, timeout=timeout)
553
John Snowf12a2822020-10-06 19:58:08 -0400554 def set_qmp_monitor(self, enabled: bool = True) -> None:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500555 """
556 Set the QMP monitor.
557
558 @param enabled: if False, qmp monitor options will be removed from
559 the base arguments of the resulting QEMU command
560 line. Default is True.
John Snow5c02c862021-06-29 17:43:23 -0400561
562 .. note:: Call this function before launch().
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500563 """
John Snowbe1183e2020-10-06 19:58:04 -0400564 self._qmp_set = enabled
565
566 @property
John Snowbeb6b572021-05-27 17:16:53 -0400567 def _qmp(self) -> QEMUMonitorProtocol:
John Snowbe1183e2020-10-06 19:58:04 -0400568 if self._qmp_connection is None:
569 raise QEMUMachineError("Attempt to access QMP with no connection")
570 return self._qmp_connection
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500571
John Snowaaa81ec2020-10-06 19:58:03 -0400572 @classmethod
Vladimir Sementsov-Ogievskiyc7daa572021-08-24 11:38:45 +0300573 def _qmp_args(cls, conv_keys: bool,
574 args: Dict[str, Any]) -> Dict[str, object]:
575 if conv_keys:
576 return {k.replace('_', '-'): v for k, v in args.items()}
577
578 return args
John Snowabf0bf92019-06-27 17:28:14 -0400579
John Snowaaa81ec2020-10-06 19:58:03 -0400580 def qmp(self, cmd: str,
581 conv_keys: bool = True,
582 **args: Any) -> QMPMessage:
583 """
584 Invoke a QMP command and return the response dict
585 """
Vladimir Sementsov-Ogievskiyc7daa572021-08-24 11:38:45 +0300586 qmp_args = self._qmp_args(conv_keys, args)
John Snowabf0bf92019-06-27 17:28:14 -0400587 return self._qmp.cmd(cmd, args=qmp_args)
588
John Snowf12a2822020-10-06 19:58:08 -0400589 def command(self, cmd: str,
590 conv_keys: bool = True,
591 **args: Any) -> QMPReturnValue:
John Snowabf0bf92019-06-27 17:28:14 -0400592 """
593 Invoke a QMP command.
594 On success return the response dict.
595 On failure raise an exception.
596 """
Vladimir Sementsov-Ogievskiyc7daa572021-08-24 11:38:45 +0300597 qmp_args = self._qmp_args(conv_keys, args)
John Snowaaa81ec2020-10-06 19:58:03 -0400598 return self._qmp.command(cmd, **qmp_args)
John Snowabf0bf92019-06-27 17:28:14 -0400599
John Snowf12a2822020-10-06 19:58:08 -0400600 def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400601 """
602 Poll for one queued QMP events and return it
603 """
John Snow306dfcd2019-06-27 17:28:15 -0400604 if self._events:
John Snowabf0bf92019-06-27 17:28:14 -0400605 return self._events.pop(0)
606 return self._qmp.pull_event(wait=wait)
607
John Snowf12a2822020-10-06 19:58:08 -0400608 def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400609 """
610 Poll for queued QMP events and return a list of dicts
611 """
612 events = self._qmp.get_events(wait=wait)
613 events.extend(self._events)
614 del self._events[:]
615 self._qmp.clear_events()
616 return events
617
618 @staticmethod
John Snowf12a2822020-10-06 19:58:08 -0400619 def event_match(event: Any, match: Optional[Any]) -> bool:
John Snowabf0bf92019-06-27 17:28:14 -0400620 """
621 Check if an event matches optional match criteria.
622
623 The match criteria takes the form of a matching subdict. The event is
624 checked to be a superset of the subdict, recursively, with matching
625 values whenever the subdict values are not None.
626
627 This has a limitation that you cannot explicitly check for None values.
628
629 Examples, with the subdict queries on the left:
630 - None matches any object.
631 - {"foo": None} matches {"foo": {"bar": 1}}
632 - {"foo": None} matches {"foo": 5}
633 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
634 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
635 """
636 if match is None:
637 return True
638
639 try:
640 for key in match:
641 if key in event:
642 if not QEMUMachine.event_match(event[key], match[key]):
643 return False
644 else:
645 return False
646 return True
647 except TypeError:
648 # either match or event wasn't iterable (not a dict)
John Snowf12a2822020-10-06 19:58:08 -0400649 return bool(match == event)
John Snowabf0bf92019-06-27 17:28:14 -0400650
John Snowf12a2822020-10-06 19:58:08 -0400651 def event_wait(self, name: str,
652 timeout: float = 60.0,
653 match: Optional[QMPMessage] = None) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400654 """
655 event_wait waits for and returns a named event from QMP with a timeout.
656
657 name: The event to wait for.
658 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
659 match: Optional match criteria. See event_match for details.
660 """
661 return self.events_wait([(name, match)], timeout)
662
John Snowf12a2822020-10-06 19:58:08 -0400663 def events_wait(self,
664 events: Sequence[Tuple[str, Any]],
665 timeout: float = 60.0) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400666 """
John Snow1847a4a2020-10-06 19:58:02 -0400667 events_wait waits for and returns a single named event from QMP.
668 In the case of multiple qualifying events, this function returns the
669 first one.
John Snowabf0bf92019-06-27 17:28:14 -0400670
John Snow1847a4a2020-10-06 19:58:02 -0400671 :param events: A sequence of (name, match_criteria) tuples.
672 The match criteria are optional and may be None.
673 See event_match for details.
674 :param timeout: Optional timeout, in seconds.
675 See QEMUMonitorProtocol.pull_event.
676
677 :raise QMPTimeoutError: If timeout was non-zero and no matching events
678 were found.
679 :return: A QMP event matching the filter criteria.
680 If timeout was 0 and no event matched, None.
John Snowabf0bf92019-06-27 17:28:14 -0400681 """
John Snowf12a2822020-10-06 19:58:08 -0400682 def _match(event: QMPMessage) -> bool:
John Snowabf0bf92019-06-27 17:28:14 -0400683 for name, match in events:
John Snow306dfcd2019-06-27 17:28:15 -0400684 if event['event'] == name and self.event_match(event, match):
John Snowabf0bf92019-06-27 17:28:14 -0400685 return True
686 return False
687
John Snow1847a4a2020-10-06 19:58:02 -0400688 event: Optional[QMPMessage]
689
John Snowabf0bf92019-06-27 17:28:14 -0400690 # Search cached events
691 for event in self._events:
692 if _match(event):
693 self._events.remove(event)
694 return event
695
696 # Poll for new events
697 while True:
698 event = self._qmp.pull_event(wait=timeout)
John Snow1847a4a2020-10-06 19:58:02 -0400699 if event is None:
700 # NB: None is only returned when timeout is false-ish.
701 # Timeouts raise QMPTimeoutError instead!
702 break
John Snowabf0bf92019-06-27 17:28:14 -0400703 if _match(event):
704 return event
705 self._events.append(event)
706
707 return None
708
John Snowf12a2822020-10-06 19:58:08 -0400709 def get_log(self) -> Optional[str]:
John Snowabf0bf92019-06-27 17:28:14 -0400710 """
711 After self.shutdown or failed qemu execution, this returns the output
712 of the qemu process.
713 """
714 return self._iolog
715
John Snowf12a2822020-10-06 19:58:08 -0400716 def add_args(self, *args: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400717 """
718 Adds to the list of extra arguments to be given to the QEMU binary
719 """
720 self._args.extend(args)
721
John Snowf12a2822020-10-06 19:58:08 -0400722 def set_machine(self, machine_type: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400723 """
724 Sets the machine type
725
726 If set, the machine type will be added to the base arguments
727 of the resulting QEMU command line.
728 """
729 self._machine = machine_type
730
John Snowf12a2822020-10-06 19:58:08 -0400731 def set_console(self,
732 device_type: Optional[str] = None,
733 console_index: int = 0) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400734 """
735 Sets the device type for a console device
736
737 If set, the console device and a backing character device will
738 be added to the base arguments of the resulting QEMU command
739 line.
740
741 This is a convenience method that will either use the provided
742 device type, or default to a "-serial chardev:console" command
743 line argument.
744
745 The actual setting of command line arguments will be be done at
746 machine launch time, as it depends on the temporary directory
747 to be created.
748
749 @param device_type: the device type, such as "isa-serial". If
750 None is given (the default value) a "-serial
751 chardev:console" command line argument will
752 be used instead, resorting to the machine's
753 default device type.
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100754 @param console_index: the index of the console device to use.
755 If not zero, the command line will create
756 'index - 1' consoles and connect them to
757 the 'null' backing character device.
John Snowabf0bf92019-06-27 17:28:14 -0400758 """
759 self._console_set = True
760 self._console_device_type = device_type
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100761 self._console_index = console_index
John Snowabf0bf92019-06-27 17:28:14 -0400762
763 @property
John Snowf12a2822020-10-06 19:58:08 -0400764 def console_socket(self) -> socket.socket:
John Snowabf0bf92019-06-27 17:28:14 -0400765 """
766 Returns a socket connected to the console
767 """
768 if self._console_socket is None:
Robert Foley80ded8e2020-07-24 07:45:08 +0100769 self._console_socket = console_socket.ConsoleSocket(
770 self._console_address,
771 file=self._console_log_path,
772 drain=self._drain_console)
John Snowabf0bf92019-06-27 17:28:14 -0400773 return self._console_socket
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500774
775 @property
776 def temp_dir(self) -> str:
777 """
778 Returns a temporary directory to be used for this machine
779 """
780 if self._temp_dir is None:
781 self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-",
782 dir=self._base_temp_dir)
783 return self._temp_dir
Cleber Rosab306e262021-02-11 16:55:05 -0500784
785 @property
786 def log_dir(self) -> str:
787 """
788 Returns a directory to be used for writing logs
789 """
790 if self._log_dir is None:
791 return self.temp_dir
792 return self._log_dir