blob: d33b02d2ce6c98eb6dbe85c07cd08051eafcf4e1 [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 Snowbeb6b572021-05-27 17:16:53 -040041from qemu.qmp import (
42 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 """
87
John Snowaad3f3b2020-10-06 19:58:06 -040088 def __init__(self,
89 binary: str,
90 args: Sequence[str] = (),
91 wrapper: Sequence[str] = (),
92 name: Optional[str] = None,
Cleber Rosa2ca6e262021-02-11 17:01:42 -050093 base_temp_dir: str = "/var/tmp",
John Snowc4e60232020-10-06 19:57:59 -040094 monitor_address: Optional[SocketAddrT] = None,
John Snowf12a2822020-10-06 19:58:08 -040095 socket_scm_helper: Optional[str] = None,
96 sock_dir: Optional[str] = None,
97 drain_console: bool = False,
98 console_log: Optional[str] = None):
John Snowabf0bf92019-06-27 17:28:14 -040099 '''
100 Initialize a QEMUMachine
101
102 @param binary: path to the qemu binary
103 @param args: list of extra arguments
104 @param wrapper: list of arguments used as prefix to qemu binary
105 @param name: prefix for socket and log file names (default: qemu-PID)
John Snow859aeb62021-05-27 17:16:51 -0400106 @param base_temp_dir: default location where temp files are created
John Snowabf0bf92019-06-27 17:28:14 -0400107 @param monitor_address: address for QMP monitor
108 @param socket_scm_helper: helper program, required for send_fd_scm()
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500109 @param sock_dir: where to create socket (defaults to base_temp_dir)
Robert Foley0fc8f662020-07-01 14:56:24 +0100110 @param drain_console: (optional) True to drain console socket to buffer
John Snowc5e61a62020-10-06 19:58:00 -0400111 @param console_log: (optional) path to console log file
John Snowabf0bf92019-06-27 17:28:14 -0400112 @note: Qemu process is not started until launch() is used.
113 '''
John Snowc5e61a62020-10-06 19:58:00 -0400114 # Direct user configuration
115
116 self._binary = binary
John Snowc5e61a62020-10-06 19:58:00 -0400117 self._args = list(args)
John Snowc5e61a62020-10-06 19:58:00 -0400118 self._wrapper = wrapper
119
120 self._name = name or "qemu-%d" % os.getpid()
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500121 self._base_temp_dir = base_temp_dir
122 self._sock_dir = sock_dir or self._base_temp_dir
John Snowc5e61a62020-10-06 19:58:00 -0400123 self._socket_scm_helper = socket_scm_helper
124
John Snowc4e60232020-10-06 19:57:59 -0400125 if monitor_address is not None:
126 self._monitor_address = monitor_address
127 self._remove_monitor_sockfile = False
128 else:
129 self._monitor_address = os.path.join(
John Snowc5e61a62020-10-06 19:58:00 -0400130 self._sock_dir, f"{self._name}-monitor.sock"
John Snowc4e60232020-10-06 19:57:59 -0400131 )
132 self._remove_monitor_sockfile = True
John Snowc5e61a62020-10-06 19:58:00 -0400133
134 self._console_log_path = console_log
135 if self._console_log_path:
136 # In order to log the console, buffering needs to be enabled.
137 self._drain_console = True
138 else:
139 self._drain_console = drain_console
140
141 # Runstate
John Snowf12a2822020-10-06 19:58:08 -0400142 self._qemu_log_path: Optional[str] = None
143 self._qemu_log_file: Optional[BinaryIO] = None
John Snow9223fda2020-10-06 19:58:05 -0400144 self._popen: Optional['subprocess.Popen[bytes]'] = None
John Snowf12a2822020-10-06 19:58:08 -0400145 self._events: List[QMPMessage] = []
146 self._iolog: Optional[str] = None
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500147 self._qmp_set = True # Enable QMP monitor by default.
John Snowbeb6b572021-05-27 17:16:53 -0400148 self._qmp_connection: Optional[QEMUMonitorProtocol] = None
John Snowaad3f3b2020-10-06 19:58:06 -0400149 self._qemu_full_args: Tuple[str, ...] = ()
John Snowf12a2822020-10-06 19:58:08 -0400150 self._temp_dir: Optional[str] = None
John Snowabf0bf92019-06-27 17:28:14 -0400151 self._launched = False
John Snowf12a2822020-10-06 19:58:08 -0400152 self._machine: Optional[str] = None
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100153 self._console_index = 0
John Snowabf0bf92019-06-27 17:28:14 -0400154 self._console_set = False
John Snowf12a2822020-10-06 19:58:08 -0400155 self._console_device_type: Optional[str] = None
John Snow652809d2020-10-06 19:58:01 -0400156 self._console_address = os.path.join(
157 self._sock_dir, f"{self._name}-console.sock"
158 )
John Snowf12a2822020-10-06 19:58:08 -0400159 self._console_socket: Optional[socket.socket] = None
160 self._remove_files: List[str] = []
John Snowde6e08b2020-07-10 01:06:48 -0400161 self._user_killed = False
John Snowabf0bf92019-06-27 17:28:14 -0400162
John Snowf12a2822020-10-06 19:58:08 -0400163 def __enter__(self) -> 'QEMUMachine':
John Snowabf0bf92019-06-27 17:28:14 -0400164 return self
165
John Snow1dda0402020-05-14 01:53:44 -0400166 def __exit__(self,
167 exc_type: Optional[Type[BaseException]],
168 exc_val: Optional[BaseException],
169 exc_tb: Optional[TracebackType]) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400170 self.shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400171
John Snowf12a2822020-10-06 19:58:08 -0400172 def add_monitor_null(self) -> None:
John Snow306dfcd2019-06-27 17:28:15 -0400173 """
174 This can be used to add an unused monitor instance.
175 """
John Snowabf0bf92019-06-27 17:28:14 -0400176 self._args.append('-monitor')
177 self._args.append('null')
178
John Snowf12a2822020-10-06 19:58:08 -0400179 def add_fd(self, fd: int, fdset: int,
180 opaque: str, opts: str = '') -> 'QEMUMachine':
John Snowabf0bf92019-06-27 17:28:14 -0400181 """
182 Pass a file descriptor to the VM
183 """
184 options = ['fd=%d' % fd,
185 'set=%d' % fdset,
186 'opaque=%s' % opaque]
187 if opts:
188 options.append(opts)
189
190 # This did not exist before 3.4, but since then it is
191 # mandatory for our purpose
192 if hasattr(os, 'set_inheritable'):
193 os.set_inheritable(fd, True)
194
195 self._args.append('-add-fd')
196 self._args.append(','.join(options))
197 return self
198
John Snowf12a2822020-10-06 19:58:08 -0400199 def send_fd_scm(self, fd: Optional[int] = None,
200 file_path: Optional[str] = None) -> int:
John Snow306dfcd2019-06-27 17:28:15 -0400201 """
202 Send an fd or file_path to socket_scm_helper.
203
204 Exactly one of fd and file_path must be given.
205 If it is file_path, the helper will open that file and pass its own fd.
206 """
John Snowabf0bf92019-06-27 17:28:14 -0400207 # In iotest.py, the qmp should always use unix socket.
208 assert self._qmp.is_scm_available()
209 if self._socket_scm_helper is None:
210 raise QEMUMachineError("No path to socket_scm_helper set")
211 if not os.path.exists(self._socket_scm_helper):
212 raise QEMUMachineError("%s does not exist" %
213 self._socket_scm_helper)
214
215 # This did not exist before 3.4, but since then it is
216 # mandatory for our purpose
217 if hasattr(os, 'set_inheritable'):
218 os.set_inheritable(self._qmp.get_sock_fd(), True)
219 if fd is not None:
220 os.set_inheritable(fd, True)
221
222 fd_param = ["%s" % self._socket_scm_helper,
223 "%d" % self._qmp.get_sock_fd()]
224
225 if file_path is not None:
226 assert fd is None
227 fd_param.append(file_path)
228 else:
229 assert fd is not None
230 fd_param.append(str(fd))
231
John Snow14b41792021-05-27 17:16:47 -0400232 proc = subprocess.run(
233 fd_param,
234 stdin=subprocess.DEVNULL,
235 stdout=subprocess.PIPE,
236 stderr=subprocess.STDOUT,
237 check=False,
238 close_fds=False,
John Snow8dfac2e2020-05-28 18:21:29 -0400239 )
John Snow14b41792021-05-27 17:16:47 -0400240 if proc.stdout:
241 LOG.debug(proc.stdout)
John Snowabf0bf92019-06-27 17:28:14 -0400242
243 return proc.returncode
244
245 @staticmethod
John Snowf12a2822020-10-06 19:58:08 -0400246 def _remove_if_exists(path: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400247 """
248 Remove file object at path if it exists
249 """
250 try:
251 os.remove(path)
252 except OSError as exception:
253 if exception.errno == errno.ENOENT:
254 return
255 raise
256
John Snowf12a2822020-10-06 19:58:08 -0400257 def is_running(self) -> bool:
John Snow306dfcd2019-06-27 17:28:15 -0400258 """Returns true if the VM is running."""
John Snowabf0bf92019-06-27 17:28:14 -0400259 return self._popen is not None and self._popen.poll() is None
260
John Snow9223fda2020-10-06 19:58:05 -0400261 @property
262 def _subp(self) -> 'subprocess.Popen[bytes]':
263 if self._popen is None:
264 raise QEMUMachineError('Subprocess pipe not present')
265 return self._popen
266
John Snowf12a2822020-10-06 19:58:08 -0400267 def exitcode(self) -> Optional[int]:
John Snow306dfcd2019-06-27 17:28:15 -0400268 """Returns the exit code if possible, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400269 if self._popen is None:
270 return None
271 return self._popen.poll()
272
John Snowf12a2822020-10-06 19:58:08 -0400273 def get_pid(self) -> Optional[int]:
John Snow306dfcd2019-06-27 17:28:15 -0400274 """Returns the PID of the running process, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400275 if not self.is_running():
276 return None
John Snow9223fda2020-10-06 19:58:05 -0400277 return self._subp.pid
John Snowabf0bf92019-06-27 17:28:14 -0400278
John Snowf12a2822020-10-06 19:58:08 -0400279 def _load_io_log(self) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400280 if self._qemu_log_path is not None:
281 with open(self._qemu_log_path, "r") as iolog:
282 self._iolog = iolog.read()
283
John Snow652809d2020-10-06 19:58:01 -0400284 @property
285 def _base_args(self) -> List[str]:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500286 args = ['-display', 'none', '-vga', 'none']
John Snowc4e60232020-10-06 19:57:59 -0400287
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500288 if self._qmp_set:
289 if isinstance(self._monitor_address, tuple):
John Snowc4e60232020-10-06 19:57:59 -0400290 moncdev = "socket,id=mon,host={},port={}".format(
291 *self._monitor_address
292 )
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500293 else:
John Snowc4e60232020-10-06 19:57:59 -0400294 moncdev = f"socket,id=mon,path={self._monitor_address}"
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500295 args.extend(['-chardev', moncdev, '-mon',
296 'chardev=mon,mode=control'])
John Snowc4e60232020-10-06 19:57:59 -0400297
John Snowabf0bf92019-06-27 17:28:14 -0400298 if self._machine is not None:
299 args.extend(['-machine', self._machine])
John Snow9b8ccd62020-05-28 18:21:28 -0400300 for _ in range(self._console_index):
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100301 args.extend(['-serial', 'null'])
John Snowabf0bf92019-06-27 17:28:14 -0400302 if self._console_set:
Paolo Bonzini991c1802020-11-13 03:10:52 -0500303 chardev = ('socket,id=console,path=%s,server=on,wait=off' %
John Snowabf0bf92019-06-27 17:28:14 -0400304 self._console_address)
305 args.extend(['-chardev', chardev])
306 if self._console_device_type is None:
307 args.extend(['-serial', 'chardev:console'])
308 else:
309 device = '%s,chardev=console' % self._console_device_type
310 args.extend(['-device', device])
311 return args
312
John Snowf12a2822020-10-06 19:58:08 -0400313 def _pre_launch(self) -> None:
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500314 self._qemu_log_path = os.path.join(self.temp_dir, self._name + ".log")
John Snowabf0bf92019-06-27 17:28:14 -0400315
John Snow652809d2020-10-06 19:58:01 -0400316 if self._console_set:
317 self._remove_files.append(self._console_address)
318
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500319 if self._qmp_set:
John Snowc4e60232020-10-06 19:57:59 -0400320 if self._remove_monitor_sockfile:
321 assert isinstance(self._monitor_address, str)
322 self._remove_files.append(self._monitor_address)
John Snowbeb6b572021-05-27 17:16:53 -0400323 self._qmp_connection = QEMUMonitorProtocol(
John Snowc4e60232020-10-06 19:57:59 -0400324 self._monitor_address,
325 server=True,
326 nickname=self._name
327 )
John Snowabf0bf92019-06-27 17:28:14 -0400328
John Snow63c33f32021-05-27 17:16:49 -0400329 # NOTE: Make sure any opened resources are *definitely* freed in
330 # _post_shutdown()!
331 # pylint: disable=consider-using-with
332 self._qemu_log_file = open(self._qemu_log_path, 'wb')
333
John Snowf12a2822020-10-06 19:58:08 -0400334 def _post_launch(self) -> None:
John Snowbe1183e2020-10-06 19:58:04 -0400335 if self._qmp_connection:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500336 self._qmp.accept()
John Snowabf0bf92019-06-27 17:28:14 -0400337
John Snowf12a2822020-10-06 19:58:08 -0400338 def _post_shutdown(self) -> None:
John Snowa3842cb2020-07-10 01:06:42 -0400339 """
340 Called to cleanup the VM instance after the process has exited.
341 May also be called after a failed launch.
342 """
343 # Comprehensive reset for the failed launch case:
344 self._early_cleanup()
345
John Snowbe1183e2020-10-06 19:58:04 -0400346 if self._qmp_connection:
John Snow671940e2020-07-10 01:06:39 -0400347 self._qmp.close()
John Snowbe1183e2020-10-06 19:58:04 -0400348 self._qmp_connection = None
John Snow671940e2020-07-10 01:06:39 -0400349
John Snowabf0bf92019-06-27 17:28:14 -0400350 if self._qemu_log_file is not None:
351 self._qemu_log_file.close()
352 self._qemu_log_file = None
353
Cleber Rosa3c1e16c2021-02-11 17:01:41 -0500354 self._load_io_log()
355
John Snowabf0bf92019-06-27 17:28:14 -0400356 self._qemu_log_path = None
357
John Snowabf0bf92019-06-27 17:28:14 -0400358 if self._temp_dir is not None:
359 shutil.rmtree(self._temp_dir)
360 self._temp_dir = None
361
Max Reitz32558ce2019-10-17 15:31:34 +0200362 while len(self._remove_files) > 0:
363 self._remove_if_exists(self._remove_files.pop())
364
John Snow14661d92020-07-10 01:06:38 -0400365 exitcode = self.exitcode()
John Snowde6e08b2020-07-10 01:06:48 -0400366 if (exitcode is not None and exitcode < 0
367 and not (self._user_killed and exitcode == -signal.SIGKILL)):
John Snow14661d92020-07-10 01:06:38 -0400368 msg = 'qemu received signal %i; command: "%s"'
369 if self._qemu_full_args:
370 command = ' '.join(self._qemu_full_args)
371 else:
372 command = ''
373 LOG.warning(msg, -int(exitcode), command)
374
John Snowde6e08b2020-07-10 01:06:48 -0400375 self._user_killed = False
John Snow14661d92020-07-10 01:06:38 -0400376 self._launched = False
377
John Snowf12a2822020-10-06 19:58:08 -0400378 def launch(self) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400379 """
380 Launch the VM and make sure we cleanup and expose the
381 command line/output in case of exception
382 """
383
384 if self._launched:
385 raise QEMUMachineError('VM already launched')
386
387 self._iolog = None
John Snowaad3f3b2020-10-06 19:58:06 -0400388 self._qemu_full_args = ()
John Snowabf0bf92019-06-27 17:28:14 -0400389 try:
390 self._launch()
391 self._launched = True
392 except:
John Snowa3842cb2020-07-10 01:06:42 -0400393 self._post_shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400394
395 LOG.debug('Error launching VM')
396 if self._qemu_full_args:
397 LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
398 if self._iolog:
399 LOG.debug('Output: %r', self._iolog)
400 raise
401
John Snowf12a2822020-10-06 19:58:08 -0400402 def _launch(self) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400403 """
404 Launch the VM and establish a QMP connection
405 """
John Snowabf0bf92019-06-27 17:28:14 -0400406 self._pre_launch()
John Snowaad3f3b2020-10-06 19:58:06 -0400407 self._qemu_full_args = tuple(
408 chain(self._wrapper,
409 [self._binary],
410 self._base_args,
411 self._args)
412 )
John Snowabf0bf92019-06-27 17:28:14 -0400413 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
John Snowa0eae172021-05-27 17:16:50 -0400414
415 # Cleaning up of this subprocess is guaranteed by _do_shutdown.
416 # pylint: disable=consider-using-with
John Snowabf0bf92019-06-27 17:28:14 -0400417 self._popen = subprocess.Popen(self._qemu_full_args,
John Snow07b71232021-05-27 17:16:46 -0400418 stdin=subprocess.DEVNULL,
John Snowabf0bf92019-06-27 17:28:14 -0400419 stdout=self._qemu_log_file,
420 stderr=subprocess.STDOUT,
421 shell=False,
422 close_fds=False)
423 self._post_launch()
424
John Snowe2c97f12020-07-10 01:06:40 -0400425 def _early_cleanup(self) -> None:
426 """
427 Perform any cleanup that needs to happen before the VM exits.
John Snowa3842cb2020-07-10 01:06:42 -0400428
John Snow193bf1c2020-07-10 01:06:47 -0400429 May be invoked by both soft and hard shutdown in failover scenarios.
John Snowa3842cb2020-07-10 01:06:42 -0400430 Called additionally by _post_shutdown for comprehensive cleanup.
John Snowe2c97f12020-07-10 01:06:40 -0400431 """
432 # If we keep the console socket open, we may deadlock waiting
433 # for QEMU to exit, while QEMU is waiting for the socket to
434 # become writeable.
435 if self._console_socket is not None:
436 self._console_socket.close()
437 self._console_socket = None
438
John Snow193bf1c2020-07-10 01:06:47 -0400439 def _hard_shutdown(self) -> None:
440 """
441 Perform early cleanup, kill the VM, and wait for it to terminate.
442
443 :raise subprocess.Timeout: When timeout is exceeds 60 seconds
444 waiting for the QEMU process to terminate.
445 """
446 self._early_cleanup()
John Snow9223fda2020-10-06 19:58:05 -0400447 self._subp.kill()
448 self._subp.wait(timeout=60)
John Snow193bf1c2020-07-10 01:06:47 -0400449
John Snow8226a4b2020-07-20 12:02:52 -0400450 def _soft_shutdown(self, timeout: Optional[int],
451 has_quit: bool = False) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400452 """
453 Perform early cleanup, attempt to gracefully shut down the VM, and wait
454 for it to terminate.
455
John Snow8226a4b2020-07-20 12:02:52 -0400456 :param timeout: Timeout in seconds for graceful shutdown.
457 A value of None is an infinite wait.
John Snow193bf1c2020-07-10 01:06:47 -0400458 :param has_quit: When True, don't attempt to issue 'quit' QMP command
John Snow193bf1c2020-07-10 01:06:47 -0400459
460 :raise ConnectionReset: On QMP communication errors
461 :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
462 the QEMU process to terminate.
463 """
464 self._early_cleanup()
465
John Snowbe1183e2020-10-06 19:58:04 -0400466 if self._qmp_connection:
John Snow193bf1c2020-07-10 01:06:47 -0400467 if not has_quit:
468 # Might raise ConnectionReset
469 self._qmp.cmd('quit')
470
471 # May raise subprocess.TimeoutExpired
John Snow9223fda2020-10-06 19:58:05 -0400472 self._subp.wait(timeout=timeout)
John Snow193bf1c2020-07-10 01:06:47 -0400473
John Snow8226a4b2020-07-20 12:02:52 -0400474 def _do_shutdown(self, timeout: Optional[int],
475 has_quit: bool = False) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400476 """
477 Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
478
John Snow8226a4b2020-07-20 12:02:52 -0400479 :param timeout: Timeout in seconds for graceful shutdown.
480 A value of None is an infinite wait.
John Snow193bf1c2020-07-10 01:06:47 -0400481 :param has_quit: When True, don't attempt to issue 'quit' QMP command
John Snow193bf1c2020-07-10 01:06:47 -0400482
483 :raise AbnormalShutdown: When the VM could not be shut down gracefully.
484 The inner exception will likely be ConnectionReset or
485 subprocess.TimeoutExpired. In rare cases, non-graceful termination
486 may result in its own exceptions, likely subprocess.TimeoutExpired.
487 """
488 try:
John Snow8226a4b2020-07-20 12:02:52 -0400489 self._soft_shutdown(timeout, has_quit)
John Snow193bf1c2020-07-10 01:06:47 -0400490 except Exception as exc:
491 self._hard_shutdown()
492 raise AbnormalShutdown("Could not perform graceful shutdown") \
493 from exc
494
John Snowc9b30452020-07-10 01:06:43 -0400495 def shutdown(self, has_quit: bool = False,
496 hard: bool = False,
John Snow8226a4b2020-07-20 12:02:52 -0400497 timeout: Optional[int] = 30) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400498 """
John Snow193bf1c2020-07-10 01:06:47 -0400499 Terminate the VM (gracefully if possible) and perform cleanup.
500 Cleanup will always be performed.
501
502 If the VM has not yet been launched, or shutdown(), wait(), or kill()
503 have already been called, this method does nothing.
504
505 :param has_quit: When true, do not attempt to issue 'quit' QMP command.
506 :param hard: When true, do not attempt graceful shutdown, and
507 suppress the SIGKILL warning log message.
508 :param timeout: Optional timeout in seconds for graceful shutdown.
John Snow8226a4b2020-07-20 12:02:52 -0400509 Default 30 seconds, A `None` value is an infinite wait.
John Snowabf0bf92019-06-27 17:28:14 -0400510 """
John Snowa3842cb2020-07-10 01:06:42 -0400511 if not self._launched:
512 return
513
John Snow193bf1c2020-07-10 01:06:47 -0400514 try:
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300515 if hard:
John Snowde6e08b2020-07-10 01:06:48 -0400516 self._user_killed = True
John Snow193bf1c2020-07-10 01:06:47 -0400517 self._hard_shutdown()
518 else:
John Snow8226a4b2020-07-20 12:02:52 -0400519 self._do_shutdown(timeout, has_quit)
John Snow193bf1c2020-07-10 01:06:47 -0400520 finally:
521 self._post_shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400522
John Snowf12a2822020-10-06 19:58:08 -0400523 def kill(self) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400524 """
525 Terminate the VM forcefully, wait for it to exit, and perform cleanup.
526 """
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300527 self.shutdown(hard=True)
528
John Snow8226a4b2020-07-20 12:02:52 -0400529 def wait(self, timeout: Optional[int] = 30) -> None:
John Snow89528052020-07-10 01:06:44 -0400530 """
531 Wait for the VM to power off and perform post-shutdown cleanup.
532
John Snow8226a4b2020-07-20 12:02:52 -0400533 :param timeout: Optional timeout in seconds. Default 30 seconds.
534 A value of `None` is an infinite wait.
John Snow89528052020-07-10 01:06:44 -0400535 """
536 self.shutdown(has_quit=True, timeout=timeout)
537
John Snowf12a2822020-10-06 19:58:08 -0400538 def set_qmp_monitor(self, enabled: bool = True) -> None:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500539 """
540 Set the QMP monitor.
541
542 @param enabled: if False, qmp monitor options will be removed from
543 the base arguments of the resulting QEMU command
544 line. Default is True.
545 @note: call this function before launch().
546 """
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
556 def _qmp_args(cls, _conv_keys: bool = True, **args: Any) -> Dict[str, Any]:
John Snowabf0bf92019-06-27 17:28:14 -0400557 qmp_args = dict()
558 for key, value in args.items():
John Snowaaa81ec2020-10-06 19:58:03 -0400559 if _conv_keys:
John Snowabf0bf92019-06-27 17:28:14 -0400560 qmp_args[key.replace('_', '-')] = value
561 else:
562 qmp_args[key] = value
John Snowaaa81ec2020-10-06 19:58:03 -0400563 return qmp_args
John Snowabf0bf92019-06-27 17:28:14 -0400564
John Snowaaa81ec2020-10-06 19:58:03 -0400565 def qmp(self, cmd: str,
566 conv_keys: bool = True,
567 **args: Any) -> QMPMessage:
568 """
569 Invoke a QMP command and return the response dict
570 """
571 qmp_args = self._qmp_args(conv_keys, **args)
John Snowabf0bf92019-06-27 17:28:14 -0400572 return self._qmp.cmd(cmd, args=qmp_args)
573
John Snowf12a2822020-10-06 19:58:08 -0400574 def command(self, cmd: str,
575 conv_keys: bool = True,
576 **args: Any) -> QMPReturnValue:
John Snowabf0bf92019-06-27 17:28:14 -0400577 """
578 Invoke a QMP command.
579 On success return the response dict.
580 On failure raise an exception.
581 """
John Snowaaa81ec2020-10-06 19:58:03 -0400582 qmp_args = self._qmp_args(conv_keys, **args)
583 return self._qmp.command(cmd, **qmp_args)
John Snowabf0bf92019-06-27 17:28:14 -0400584
John Snowf12a2822020-10-06 19:58:08 -0400585 def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400586 """
587 Poll for one queued QMP events and return it
588 """
John Snow306dfcd2019-06-27 17:28:15 -0400589 if self._events:
John Snowabf0bf92019-06-27 17:28:14 -0400590 return self._events.pop(0)
591 return self._qmp.pull_event(wait=wait)
592
John Snowf12a2822020-10-06 19:58:08 -0400593 def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400594 """
595 Poll for queued QMP events and return a list of dicts
596 """
597 events = self._qmp.get_events(wait=wait)
598 events.extend(self._events)
599 del self._events[:]
600 self._qmp.clear_events()
601 return events
602
603 @staticmethod
John Snowf12a2822020-10-06 19:58:08 -0400604 def event_match(event: Any, match: Optional[Any]) -> bool:
John Snowabf0bf92019-06-27 17:28:14 -0400605 """
606 Check if an event matches optional match criteria.
607
608 The match criteria takes the form of a matching subdict. The event is
609 checked to be a superset of the subdict, recursively, with matching
610 values whenever the subdict values are not None.
611
612 This has a limitation that you cannot explicitly check for None values.
613
614 Examples, with the subdict queries on the left:
615 - None matches any object.
616 - {"foo": None} matches {"foo": {"bar": 1}}
617 - {"foo": None} matches {"foo": 5}
618 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
619 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
620 """
621 if match is None:
622 return True
623
624 try:
625 for key in match:
626 if key in event:
627 if not QEMUMachine.event_match(event[key], match[key]):
628 return False
629 else:
630 return False
631 return True
632 except TypeError:
633 # either match or event wasn't iterable (not a dict)
John Snowf12a2822020-10-06 19:58:08 -0400634 return bool(match == event)
John Snowabf0bf92019-06-27 17:28:14 -0400635
John Snowf12a2822020-10-06 19:58:08 -0400636 def event_wait(self, name: str,
637 timeout: float = 60.0,
638 match: Optional[QMPMessage] = None) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400639 """
640 event_wait waits for and returns a named event from QMP with a timeout.
641
642 name: The event to wait for.
643 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
644 match: Optional match criteria. See event_match for details.
645 """
646 return self.events_wait([(name, match)], timeout)
647
John Snowf12a2822020-10-06 19:58:08 -0400648 def events_wait(self,
649 events: Sequence[Tuple[str, Any]],
650 timeout: float = 60.0) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400651 """
John Snow1847a4a2020-10-06 19:58:02 -0400652 events_wait waits for and returns a single named event from QMP.
653 In the case of multiple qualifying events, this function returns the
654 first one.
John Snowabf0bf92019-06-27 17:28:14 -0400655
John Snow1847a4a2020-10-06 19:58:02 -0400656 :param events: A sequence of (name, match_criteria) tuples.
657 The match criteria are optional and may be None.
658 See event_match for details.
659 :param timeout: Optional timeout, in seconds.
660 See QEMUMonitorProtocol.pull_event.
661
662 :raise QMPTimeoutError: If timeout was non-zero and no matching events
663 were found.
664 :return: A QMP event matching the filter criteria.
665 If timeout was 0 and no event matched, None.
John Snowabf0bf92019-06-27 17:28:14 -0400666 """
John Snowf12a2822020-10-06 19:58:08 -0400667 def _match(event: QMPMessage) -> bool:
John Snowabf0bf92019-06-27 17:28:14 -0400668 for name, match in events:
John Snow306dfcd2019-06-27 17:28:15 -0400669 if event['event'] == name and self.event_match(event, match):
John Snowabf0bf92019-06-27 17:28:14 -0400670 return True
671 return False
672
John Snow1847a4a2020-10-06 19:58:02 -0400673 event: Optional[QMPMessage]
674
John Snowabf0bf92019-06-27 17:28:14 -0400675 # Search cached events
676 for event in self._events:
677 if _match(event):
678 self._events.remove(event)
679 return event
680
681 # Poll for new events
682 while True:
683 event = self._qmp.pull_event(wait=timeout)
John Snow1847a4a2020-10-06 19:58:02 -0400684 if event is None:
685 # NB: None is only returned when timeout is false-ish.
686 # Timeouts raise QMPTimeoutError instead!
687 break
John Snowabf0bf92019-06-27 17:28:14 -0400688 if _match(event):
689 return event
690 self._events.append(event)
691
692 return None
693
John Snowf12a2822020-10-06 19:58:08 -0400694 def get_log(self) -> Optional[str]:
John Snowabf0bf92019-06-27 17:28:14 -0400695 """
696 After self.shutdown or failed qemu execution, this returns the output
697 of the qemu process.
698 """
699 return self._iolog
700
John Snowf12a2822020-10-06 19:58:08 -0400701 def add_args(self, *args: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400702 """
703 Adds to the list of extra arguments to be given to the QEMU binary
704 """
705 self._args.extend(args)
706
John Snowf12a2822020-10-06 19:58:08 -0400707 def set_machine(self, machine_type: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400708 """
709 Sets the machine type
710
711 If set, the machine type will be added to the base arguments
712 of the resulting QEMU command line.
713 """
714 self._machine = machine_type
715
John Snowf12a2822020-10-06 19:58:08 -0400716 def set_console(self,
717 device_type: Optional[str] = None,
718 console_index: int = 0) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400719 """
720 Sets the device type for a console device
721
722 If set, the console device and a backing character device will
723 be added to the base arguments of the resulting QEMU command
724 line.
725
726 This is a convenience method that will either use the provided
727 device type, or default to a "-serial chardev:console" command
728 line argument.
729
730 The actual setting of command line arguments will be be done at
731 machine launch time, as it depends on the temporary directory
732 to be created.
733
734 @param device_type: the device type, such as "isa-serial". If
735 None is given (the default value) a "-serial
736 chardev:console" command line argument will
737 be used instead, resorting to the machine's
738 default device type.
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100739 @param console_index: the index of the console device to use.
740 If not zero, the command line will create
741 'index - 1' consoles and connect them to
742 the 'null' backing character device.
John Snowabf0bf92019-06-27 17:28:14 -0400743 """
744 self._console_set = True
745 self._console_device_type = device_type
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100746 self._console_index = console_index
John Snowabf0bf92019-06-27 17:28:14 -0400747
748 @property
John Snowf12a2822020-10-06 19:58:08 -0400749 def console_socket(self) -> socket.socket:
John Snowabf0bf92019-06-27 17:28:14 -0400750 """
751 Returns a socket connected to the console
752 """
753 if self._console_socket is None:
Robert Foley80ded8e2020-07-24 07:45:08 +0100754 self._console_socket = console_socket.ConsoleSocket(
755 self._console_address,
756 file=self._console_log_path,
757 drain=self._drain_console)
John Snowabf0bf92019-06-27 17:28:14 -0400758 return self._console_socket
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500759
760 @property
761 def temp_dir(self) -> str:
762 """
763 Returns a temporary directory to be used for this machine
764 """
765 if self._temp_dir is None:
766 self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-",
767 dir=self._base_temp_dir)
768 return self._temp_dir