blob: 5b87e9ce0241a52210d7e3884562ed6cf2f58a8c [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 Snow932ca4b2020-10-06 19:57:58 -040041from . import console_socket, qmp
John Snowf12a2822020-10-06 19:58:08 -040042from .qmp import QMPMessage, QMPReturnValue, SocketAddrT
John Snow932ca4b2020-10-06 19:57:58 -040043
John Snowabf0bf92019-06-27 17:28:14 -040044
45LOG = logging.getLogger(__name__)
46
John Snow8dfac2e2020-05-28 18:21:29 -040047
John Snowabf0bf92019-06-27 17:28:14 -040048class QEMUMachineError(Exception):
49 """
50 Exception called when an error in QEMUMachine happens.
51 """
52
53
54class QEMUMachineAddDeviceError(QEMUMachineError):
55 """
56 Exception raised when a request to add a device can not be fulfilled
57
58 The failures are caused by limitations, lack of information or conflicting
59 requests on the QEMUMachine methods. This exception does not represent
60 failures reported by the QEMU binary itself.
61 """
62
63
John Snow193bf1c2020-07-10 01:06:47 -040064class AbnormalShutdown(QEMUMachineError):
65 """
66 Exception raised when a graceful shutdown was requested, but not performed.
67 """
68
69
John Snow9b8ccd62020-05-28 18:21:28 -040070class QEMUMachine:
John Snowabf0bf92019-06-27 17:28:14 -040071 """
John Snowf12a2822020-10-06 19:58:08 -040072 A QEMU VM.
John Snowabf0bf92019-06-27 17:28:14 -040073
John Snow8dfac2e2020-05-28 18:21:29 -040074 Use this object as a context manager to ensure
75 the QEMU process terminates::
John Snowabf0bf92019-06-27 17:28:14 -040076
77 with VM(binary) as vm:
78 ...
79 # vm is guaranteed to be shut down here
80 """
81
John Snowaad3f3b2020-10-06 19:58:06 -040082 def __init__(self,
83 binary: str,
84 args: Sequence[str] = (),
85 wrapper: Sequence[str] = (),
86 name: Optional[str] = None,
Cleber Rosa2ca6e262021-02-11 17:01:42 -050087 base_temp_dir: str = "/var/tmp",
John Snowc4e60232020-10-06 19:57:59 -040088 monitor_address: Optional[SocketAddrT] = None,
John Snowf12a2822020-10-06 19:58:08 -040089 socket_scm_helper: Optional[str] = None,
90 sock_dir: Optional[str] = None,
91 drain_console: bool = False,
92 console_log: Optional[str] = None):
John Snowabf0bf92019-06-27 17:28:14 -040093 '''
94 Initialize a QEMUMachine
95
96 @param binary: path to the qemu binary
97 @param args: list of extra arguments
98 @param wrapper: list of arguments used as prefix to qemu binary
99 @param name: prefix for socket and log file names (default: qemu-PID)
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500100 @param base_temp_dir: default location where temporary files are created
John Snowabf0bf92019-06-27 17:28:14 -0400101 @param monitor_address: address for QMP monitor
102 @param socket_scm_helper: helper program, required for send_fd_scm()
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500103 @param sock_dir: where to create socket (defaults to base_temp_dir)
Robert Foley0fc8f662020-07-01 14:56:24 +0100104 @param drain_console: (optional) True to drain console socket to buffer
John Snowc5e61a62020-10-06 19:58:00 -0400105 @param console_log: (optional) path to console log file
John Snowabf0bf92019-06-27 17:28:14 -0400106 @note: Qemu process is not started until launch() is used.
107 '''
John Snowc5e61a62020-10-06 19:58:00 -0400108 # Direct user configuration
109
110 self._binary = binary
John Snowc5e61a62020-10-06 19:58:00 -0400111 self._args = list(args)
John Snowc5e61a62020-10-06 19:58:00 -0400112 self._wrapper = wrapper
113
114 self._name = name or "qemu-%d" % os.getpid()
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500115 self._base_temp_dir = base_temp_dir
116 self._sock_dir = sock_dir or self._base_temp_dir
John Snowc5e61a62020-10-06 19:58:00 -0400117 self._socket_scm_helper = socket_scm_helper
118
John Snowc4e60232020-10-06 19:57:59 -0400119 if monitor_address is not None:
120 self._monitor_address = monitor_address
121 self._remove_monitor_sockfile = False
122 else:
123 self._monitor_address = os.path.join(
John Snowc5e61a62020-10-06 19:58:00 -0400124 self._sock_dir, f"{self._name}-monitor.sock"
John Snowc4e60232020-10-06 19:57:59 -0400125 )
126 self._remove_monitor_sockfile = True
John Snowc5e61a62020-10-06 19:58:00 -0400127
128 self._console_log_path = console_log
129 if self._console_log_path:
130 # In order to log the console, buffering needs to be enabled.
131 self._drain_console = True
132 else:
133 self._drain_console = drain_console
134
135 # Runstate
John Snowf12a2822020-10-06 19:58:08 -0400136 self._qemu_log_path: Optional[str] = None
137 self._qemu_log_file: Optional[BinaryIO] = None
John Snow9223fda2020-10-06 19:58:05 -0400138 self._popen: Optional['subprocess.Popen[bytes]'] = None
John Snowf12a2822020-10-06 19:58:08 -0400139 self._events: List[QMPMessage] = []
140 self._iolog: Optional[str] = None
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500141 self._qmp_set = True # Enable QMP monitor by default.
John Snowbe1183e2020-10-06 19:58:04 -0400142 self._qmp_connection: Optional[qmp.QEMUMonitorProtocol] = None
John Snowaad3f3b2020-10-06 19:58:06 -0400143 self._qemu_full_args: Tuple[str, ...] = ()
John Snowf12a2822020-10-06 19:58:08 -0400144 self._temp_dir: Optional[str] = None
John Snowabf0bf92019-06-27 17:28:14 -0400145 self._launched = False
John Snowf12a2822020-10-06 19:58:08 -0400146 self._machine: Optional[str] = None
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100147 self._console_index = 0
John Snowabf0bf92019-06-27 17:28:14 -0400148 self._console_set = False
John Snowf12a2822020-10-06 19:58:08 -0400149 self._console_device_type: Optional[str] = None
John Snow652809d2020-10-06 19:58:01 -0400150 self._console_address = os.path.join(
151 self._sock_dir, f"{self._name}-console.sock"
152 )
John Snowf12a2822020-10-06 19:58:08 -0400153 self._console_socket: Optional[socket.socket] = None
154 self._remove_files: List[str] = []
John Snowde6e08b2020-07-10 01:06:48 -0400155 self._user_killed = False
John Snowabf0bf92019-06-27 17:28:14 -0400156
John Snowf12a2822020-10-06 19:58:08 -0400157 def __enter__(self) -> 'QEMUMachine':
John Snowabf0bf92019-06-27 17:28:14 -0400158 return self
159
John Snow1dda0402020-05-14 01:53:44 -0400160 def __exit__(self,
161 exc_type: Optional[Type[BaseException]],
162 exc_val: Optional[BaseException],
163 exc_tb: Optional[TracebackType]) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400164 self.shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400165
John Snowf12a2822020-10-06 19:58:08 -0400166 def add_monitor_null(self) -> None:
John Snow306dfcd2019-06-27 17:28:15 -0400167 """
168 This can be used to add an unused monitor instance.
169 """
John Snowabf0bf92019-06-27 17:28:14 -0400170 self._args.append('-monitor')
171 self._args.append('null')
172
John Snowf12a2822020-10-06 19:58:08 -0400173 def add_fd(self, fd: int, fdset: int,
174 opaque: str, opts: str = '') -> 'QEMUMachine':
John Snowabf0bf92019-06-27 17:28:14 -0400175 """
176 Pass a file descriptor to the VM
177 """
178 options = ['fd=%d' % fd,
179 'set=%d' % fdset,
180 'opaque=%s' % opaque]
181 if opts:
182 options.append(opts)
183
184 # This did not exist before 3.4, but since then it is
185 # mandatory for our purpose
186 if hasattr(os, 'set_inheritable'):
187 os.set_inheritable(fd, True)
188
189 self._args.append('-add-fd')
190 self._args.append(','.join(options))
191 return self
192
John Snowf12a2822020-10-06 19:58:08 -0400193 def send_fd_scm(self, fd: Optional[int] = None,
194 file_path: Optional[str] = None) -> int:
John Snow306dfcd2019-06-27 17:28:15 -0400195 """
196 Send an fd or file_path to socket_scm_helper.
197
198 Exactly one of fd and file_path must be given.
199 If it is file_path, the helper will open that file and pass its own fd.
200 """
John Snowabf0bf92019-06-27 17:28:14 -0400201 # In iotest.py, the qmp should always use unix socket.
202 assert self._qmp.is_scm_available()
203 if self._socket_scm_helper is None:
204 raise QEMUMachineError("No path to socket_scm_helper set")
205 if not os.path.exists(self._socket_scm_helper):
206 raise QEMUMachineError("%s does not exist" %
207 self._socket_scm_helper)
208
209 # This did not exist before 3.4, but since then it is
210 # mandatory for our purpose
211 if hasattr(os, 'set_inheritable'):
212 os.set_inheritable(self._qmp.get_sock_fd(), True)
213 if fd is not None:
214 os.set_inheritable(fd, True)
215
216 fd_param = ["%s" % self._socket_scm_helper,
217 "%d" % self._qmp.get_sock_fd()]
218
219 if file_path is not None:
220 assert fd is None
221 fd_param.append(file_path)
222 else:
223 assert fd is not None
224 fd_param.append(str(fd))
225
John Snow8dfac2e2020-05-28 18:21:29 -0400226 proc = subprocess.Popen(
John Snow07b71232021-05-27 17:16:46 -0400227 fd_param, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
John Snow8dfac2e2020-05-28 18:21:29 -0400228 stderr=subprocess.STDOUT, close_fds=False
229 )
John Snowabf0bf92019-06-27 17:28:14 -0400230 output = proc.communicate()[0]
231 if output:
232 LOG.debug(output)
233
234 return proc.returncode
235
236 @staticmethod
John Snowf12a2822020-10-06 19:58:08 -0400237 def _remove_if_exists(path: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400238 """
239 Remove file object at path if it exists
240 """
241 try:
242 os.remove(path)
243 except OSError as exception:
244 if exception.errno == errno.ENOENT:
245 return
246 raise
247
John Snowf12a2822020-10-06 19:58:08 -0400248 def is_running(self) -> bool:
John Snow306dfcd2019-06-27 17:28:15 -0400249 """Returns true if the VM is running."""
John Snowabf0bf92019-06-27 17:28:14 -0400250 return self._popen is not None and self._popen.poll() is None
251
John Snow9223fda2020-10-06 19:58:05 -0400252 @property
253 def _subp(self) -> 'subprocess.Popen[bytes]':
254 if self._popen is None:
255 raise QEMUMachineError('Subprocess pipe not present')
256 return self._popen
257
John Snowf12a2822020-10-06 19:58:08 -0400258 def exitcode(self) -> Optional[int]:
John Snow306dfcd2019-06-27 17:28:15 -0400259 """Returns the exit code if possible, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400260 if self._popen is None:
261 return None
262 return self._popen.poll()
263
John Snowf12a2822020-10-06 19:58:08 -0400264 def get_pid(self) -> Optional[int]:
John Snow306dfcd2019-06-27 17:28:15 -0400265 """Returns the PID of the running process, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400266 if not self.is_running():
267 return None
John Snow9223fda2020-10-06 19:58:05 -0400268 return self._subp.pid
John Snowabf0bf92019-06-27 17:28:14 -0400269
John Snowf12a2822020-10-06 19:58:08 -0400270 def _load_io_log(self) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400271 if self._qemu_log_path is not None:
272 with open(self._qemu_log_path, "r") as iolog:
273 self._iolog = iolog.read()
274
John Snow652809d2020-10-06 19:58:01 -0400275 @property
276 def _base_args(self) -> List[str]:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500277 args = ['-display', 'none', '-vga', 'none']
John Snowc4e60232020-10-06 19:57:59 -0400278
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500279 if self._qmp_set:
280 if isinstance(self._monitor_address, tuple):
John Snowc4e60232020-10-06 19:57:59 -0400281 moncdev = "socket,id=mon,host={},port={}".format(
282 *self._monitor_address
283 )
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500284 else:
John Snowc4e60232020-10-06 19:57:59 -0400285 moncdev = f"socket,id=mon,path={self._monitor_address}"
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500286 args.extend(['-chardev', moncdev, '-mon',
287 'chardev=mon,mode=control'])
John Snowc4e60232020-10-06 19:57:59 -0400288
John Snowabf0bf92019-06-27 17:28:14 -0400289 if self._machine is not None:
290 args.extend(['-machine', self._machine])
John Snow9b8ccd62020-05-28 18:21:28 -0400291 for _ in range(self._console_index):
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100292 args.extend(['-serial', 'null'])
John Snowabf0bf92019-06-27 17:28:14 -0400293 if self._console_set:
Paolo Bonzini991c1802020-11-13 03:10:52 -0500294 chardev = ('socket,id=console,path=%s,server=on,wait=off' %
John Snowabf0bf92019-06-27 17:28:14 -0400295 self._console_address)
296 args.extend(['-chardev', chardev])
297 if self._console_device_type is None:
298 args.extend(['-serial', 'chardev:console'])
299 else:
300 device = '%s,chardev=console' % self._console_device_type
301 args.extend(['-device', device])
302 return args
303
John Snowf12a2822020-10-06 19:58:08 -0400304 def _pre_launch(self) -> None:
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500305 self._qemu_log_path = os.path.join(self.temp_dir, self._name + ".log")
John Snowabf0bf92019-06-27 17:28:14 -0400306 self._qemu_log_file = open(self._qemu_log_path, 'wb')
307
John Snow652809d2020-10-06 19:58:01 -0400308 if self._console_set:
309 self._remove_files.append(self._console_address)
310
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500311 if self._qmp_set:
John Snowc4e60232020-10-06 19:57:59 -0400312 if self._remove_monitor_sockfile:
313 assert isinstance(self._monitor_address, str)
314 self._remove_files.append(self._monitor_address)
John Snowbe1183e2020-10-06 19:58:04 -0400315 self._qmp_connection = qmp.QEMUMonitorProtocol(
John Snowc4e60232020-10-06 19:57:59 -0400316 self._monitor_address,
317 server=True,
318 nickname=self._name
319 )
John Snowabf0bf92019-06-27 17:28:14 -0400320
John Snowf12a2822020-10-06 19:58:08 -0400321 def _post_launch(self) -> None:
John Snowbe1183e2020-10-06 19:58:04 -0400322 if self._qmp_connection:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500323 self._qmp.accept()
John Snowabf0bf92019-06-27 17:28:14 -0400324
John Snowf12a2822020-10-06 19:58:08 -0400325 def _post_shutdown(self) -> None:
John Snowa3842cb2020-07-10 01:06:42 -0400326 """
327 Called to cleanup the VM instance after the process has exited.
328 May also be called after a failed launch.
329 """
330 # Comprehensive reset for the failed launch case:
331 self._early_cleanup()
332
John Snowbe1183e2020-10-06 19:58:04 -0400333 if self._qmp_connection:
John Snow671940e2020-07-10 01:06:39 -0400334 self._qmp.close()
John Snowbe1183e2020-10-06 19:58:04 -0400335 self._qmp_connection = None
John Snow671940e2020-07-10 01:06:39 -0400336
John Snowabf0bf92019-06-27 17:28:14 -0400337 if self._qemu_log_file is not None:
338 self._qemu_log_file.close()
339 self._qemu_log_file = None
340
Cleber Rosa3c1e16c2021-02-11 17:01:41 -0500341 self._load_io_log()
342
John Snowabf0bf92019-06-27 17:28:14 -0400343 self._qemu_log_path = None
344
John Snowabf0bf92019-06-27 17:28:14 -0400345 if self._temp_dir is not None:
346 shutil.rmtree(self._temp_dir)
347 self._temp_dir = None
348
Max Reitz32558ce2019-10-17 15:31:34 +0200349 while len(self._remove_files) > 0:
350 self._remove_if_exists(self._remove_files.pop())
351
John Snow14661d92020-07-10 01:06:38 -0400352 exitcode = self.exitcode()
John Snowde6e08b2020-07-10 01:06:48 -0400353 if (exitcode is not None and exitcode < 0
354 and not (self._user_killed and exitcode == -signal.SIGKILL)):
John Snow14661d92020-07-10 01:06:38 -0400355 msg = 'qemu received signal %i; command: "%s"'
356 if self._qemu_full_args:
357 command = ' '.join(self._qemu_full_args)
358 else:
359 command = ''
360 LOG.warning(msg, -int(exitcode), command)
361
John Snowde6e08b2020-07-10 01:06:48 -0400362 self._user_killed = False
John Snow14661d92020-07-10 01:06:38 -0400363 self._launched = False
364
John Snowf12a2822020-10-06 19:58:08 -0400365 def launch(self) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400366 """
367 Launch the VM and make sure we cleanup and expose the
368 command line/output in case of exception
369 """
370
371 if self._launched:
372 raise QEMUMachineError('VM already launched')
373
374 self._iolog = None
John Snowaad3f3b2020-10-06 19:58:06 -0400375 self._qemu_full_args = ()
John Snowabf0bf92019-06-27 17:28:14 -0400376 try:
377 self._launch()
378 self._launched = True
379 except:
John Snowa3842cb2020-07-10 01:06:42 -0400380 self._post_shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400381
382 LOG.debug('Error launching VM')
383 if self._qemu_full_args:
384 LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
385 if self._iolog:
386 LOG.debug('Output: %r', self._iolog)
387 raise
388
John Snowf12a2822020-10-06 19:58:08 -0400389 def _launch(self) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400390 """
391 Launch the VM and establish a QMP connection
392 """
John Snowabf0bf92019-06-27 17:28:14 -0400393 self._pre_launch()
John Snowaad3f3b2020-10-06 19:58:06 -0400394 self._qemu_full_args = tuple(
395 chain(self._wrapper,
396 [self._binary],
397 self._base_args,
398 self._args)
399 )
John Snowabf0bf92019-06-27 17:28:14 -0400400 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
401 self._popen = subprocess.Popen(self._qemu_full_args,
John Snow07b71232021-05-27 17:16:46 -0400402 stdin=subprocess.DEVNULL,
John Snowabf0bf92019-06-27 17:28:14 -0400403 stdout=self._qemu_log_file,
404 stderr=subprocess.STDOUT,
405 shell=False,
406 close_fds=False)
407 self._post_launch()
408
John Snowe2c97f12020-07-10 01:06:40 -0400409 def _early_cleanup(self) -> None:
410 """
411 Perform any cleanup that needs to happen before the VM exits.
John Snowa3842cb2020-07-10 01:06:42 -0400412
John Snow193bf1c2020-07-10 01:06:47 -0400413 May be invoked by both soft and hard shutdown in failover scenarios.
John Snowa3842cb2020-07-10 01:06:42 -0400414 Called additionally by _post_shutdown for comprehensive cleanup.
John Snowe2c97f12020-07-10 01:06:40 -0400415 """
416 # If we keep the console socket open, we may deadlock waiting
417 # for QEMU to exit, while QEMU is waiting for the socket to
418 # become writeable.
419 if self._console_socket is not None:
420 self._console_socket.close()
421 self._console_socket = None
422
John Snow193bf1c2020-07-10 01:06:47 -0400423 def _hard_shutdown(self) -> None:
424 """
425 Perform early cleanup, kill the VM, and wait for it to terminate.
426
427 :raise subprocess.Timeout: When timeout is exceeds 60 seconds
428 waiting for the QEMU process to terminate.
429 """
430 self._early_cleanup()
John Snow9223fda2020-10-06 19:58:05 -0400431 self._subp.kill()
432 self._subp.wait(timeout=60)
John Snow193bf1c2020-07-10 01:06:47 -0400433
John Snow8226a4b2020-07-20 12:02:52 -0400434 def _soft_shutdown(self, timeout: Optional[int],
435 has_quit: bool = False) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400436 """
437 Perform early cleanup, attempt to gracefully shut down the VM, and wait
438 for it to terminate.
439
John Snow8226a4b2020-07-20 12:02:52 -0400440 :param timeout: Timeout in seconds for graceful shutdown.
441 A value of None is an infinite wait.
John Snow193bf1c2020-07-10 01:06:47 -0400442 :param has_quit: When True, don't attempt to issue 'quit' QMP command
John Snow193bf1c2020-07-10 01:06:47 -0400443
444 :raise ConnectionReset: On QMP communication errors
445 :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
446 the QEMU process to terminate.
447 """
448 self._early_cleanup()
449
John Snowbe1183e2020-10-06 19:58:04 -0400450 if self._qmp_connection:
John Snow193bf1c2020-07-10 01:06:47 -0400451 if not has_quit:
452 # Might raise ConnectionReset
453 self._qmp.cmd('quit')
454
455 # May raise subprocess.TimeoutExpired
John Snow9223fda2020-10-06 19:58:05 -0400456 self._subp.wait(timeout=timeout)
John Snow193bf1c2020-07-10 01:06:47 -0400457
John Snow8226a4b2020-07-20 12:02:52 -0400458 def _do_shutdown(self, timeout: Optional[int],
459 has_quit: bool = False) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400460 """
461 Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
462
John Snow8226a4b2020-07-20 12:02:52 -0400463 :param timeout: Timeout in seconds for graceful shutdown.
464 A value of None is an infinite wait.
John Snow193bf1c2020-07-10 01:06:47 -0400465 :param has_quit: When True, don't attempt to issue 'quit' QMP command
John Snow193bf1c2020-07-10 01:06:47 -0400466
467 :raise AbnormalShutdown: When the VM could not be shut down gracefully.
468 The inner exception will likely be ConnectionReset or
469 subprocess.TimeoutExpired. In rare cases, non-graceful termination
470 may result in its own exceptions, likely subprocess.TimeoutExpired.
471 """
472 try:
John Snow8226a4b2020-07-20 12:02:52 -0400473 self._soft_shutdown(timeout, has_quit)
John Snow193bf1c2020-07-10 01:06:47 -0400474 except Exception as exc:
475 self._hard_shutdown()
476 raise AbnormalShutdown("Could not perform graceful shutdown") \
477 from exc
478
John Snowc9b30452020-07-10 01:06:43 -0400479 def shutdown(self, has_quit: bool = False,
480 hard: bool = False,
John Snow8226a4b2020-07-20 12:02:52 -0400481 timeout: Optional[int] = 30) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400482 """
John Snow193bf1c2020-07-10 01:06:47 -0400483 Terminate the VM (gracefully if possible) and perform cleanup.
484 Cleanup will always be performed.
485
486 If the VM has not yet been launched, or shutdown(), wait(), or kill()
487 have already been called, this method does nothing.
488
489 :param has_quit: When true, do not attempt to issue 'quit' QMP command.
490 :param hard: When true, do not attempt graceful shutdown, and
491 suppress the SIGKILL warning log message.
492 :param timeout: Optional timeout in seconds for graceful shutdown.
John Snow8226a4b2020-07-20 12:02:52 -0400493 Default 30 seconds, A `None` value is an infinite wait.
John Snowabf0bf92019-06-27 17:28:14 -0400494 """
John Snowa3842cb2020-07-10 01:06:42 -0400495 if not self._launched:
496 return
497
John Snow193bf1c2020-07-10 01:06:47 -0400498 try:
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300499 if hard:
John Snowde6e08b2020-07-10 01:06:48 -0400500 self._user_killed = True
John Snow193bf1c2020-07-10 01:06:47 -0400501 self._hard_shutdown()
502 else:
John Snow8226a4b2020-07-20 12:02:52 -0400503 self._do_shutdown(timeout, has_quit)
John Snow193bf1c2020-07-10 01:06:47 -0400504 finally:
505 self._post_shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400506
John Snowf12a2822020-10-06 19:58:08 -0400507 def kill(self) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400508 """
509 Terminate the VM forcefully, wait for it to exit, and perform cleanup.
510 """
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300511 self.shutdown(hard=True)
512
John Snow8226a4b2020-07-20 12:02:52 -0400513 def wait(self, timeout: Optional[int] = 30) -> None:
John Snow89528052020-07-10 01:06:44 -0400514 """
515 Wait for the VM to power off and perform post-shutdown cleanup.
516
John Snow8226a4b2020-07-20 12:02:52 -0400517 :param timeout: Optional timeout in seconds. Default 30 seconds.
518 A value of `None` is an infinite wait.
John Snow89528052020-07-10 01:06:44 -0400519 """
520 self.shutdown(has_quit=True, timeout=timeout)
521
John Snowf12a2822020-10-06 19:58:08 -0400522 def set_qmp_monitor(self, enabled: bool = True) -> None:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500523 """
524 Set the QMP monitor.
525
526 @param enabled: if False, qmp monitor options will be removed from
527 the base arguments of the resulting QEMU command
528 line. Default is True.
529 @note: call this function before launch().
530 """
John Snowbe1183e2020-10-06 19:58:04 -0400531 self._qmp_set = enabled
532
533 @property
534 def _qmp(self) -> qmp.QEMUMonitorProtocol:
535 if self._qmp_connection is None:
536 raise QEMUMachineError("Attempt to access QMP with no connection")
537 return self._qmp_connection
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500538
John Snowaaa81ec2020-10-06 19:58:03 -0400539 @classmethod
540 def _qmp_args(cls, _conv_keys: bool = True, **args: Any) -> Dict[str, Any]:
John Snowabf0bf92019-06-27 17:28:14 -0400541 qmp_args = dict()
542 for key, value in args.items():
John Snowaaa81ec2020-10-06 19:58:03 -0400543 if _conv_keys:
John Snowabf0bf92019-06-27 17:28:14 -0400544 qmp_args[key.replace('_', '-')] = value
545 else:
546 qmp_args[key] = value
John Snowaaa81ec2020-10-06 19:58:03 -0400547 return qmp_args
John Snowabf0bf92019-06-27 17:28:14 -0400548
John Snowaaa81ec2020-10-06 19:58:03 -0400549 def qmp(self, cmd: str,
550 conv_keys: bool = True,
551 **args: Any) -> QMPMessage:
552 """
553 Invoke a QMP command and return the response dict
554 """
555 qmp_args = self._qmp_args(conv_keys, **args)
John Snowabf0bf92019-06-27 17:28:14 -0400556 return self._qmp.cmd(cmd, args=qmp_args)
557
John Snowf12a2822020-10-06 19:58:08 -0400558 def command(self, cmd: str,
559 conv_keys: bool = True,
560 **args: Any) -> QMPReturnValue:
John Snowabf0bf92019-06-27 17:28:14 -0400561 """
562 Invoke a QMP command.
563 On success return the response dict.
564 On failure raise an exception.
565 """
John Snowaaa81ec2020-10-06 19:58:03 -0400566 qmp_args = self._qmp_args(conv_keys, **args)
567 return self._qmp.command(cmd, **qmp_args)
John Snowabf0bf92019-06-27 17:28:14 -0400568
John Snowf12a2822020-10-06 19:58:08 -0400569 def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400570 """
571 Poll for one queued QMP events and return it
572 """
John Snow306dfcd2019-06-27 17:28:15 -0400573 if self._events:
John Snowabf0bf92019-06-27 17:28:14 -0400574 return self._events.pop(0)
575 return self._qmp.pull_event(wait=wait)
576
John Snowf12a2822020-10-06 19:58:08 -0400577 def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400578 """
579 Poll for queued QMP events and return a list of dicts
580 """
581 events = self._qmp.get_events(wait=wait)
582 events.extend(self._events)
583 del self._events[:]
584 self._qmp.clear_events()
585 return events
586
587 @staticmethod
John Snowf12a2822020-10-06 19:58:08 -0400588 def event_match(event: Any, match: Optional[Any]) -> bool:
John Snowabf0bf92019-06-27 17:28:14 -0400589 """
590 Check if an event matches optional match criteria.
591
592 The match criteria takes the form of a matching subdict. The event is
593 checked to be a superset of the subdict, recursively, with matching
594 values whenever the subdict values are not None.
595
596 This has a limitation that you cannot explicitly check for None values.
597
598 Examples, with the subdict queries on the left:
599 - None matches any object.
600 - {"foo": None} matches {"foo": {"bar": 1}}
601 - {"foo": None} matches {"foo": 5}
602 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
603 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
604 """
605 if match is None:
606 return True
607
608 try:
609 for key in match:
610 if key in event:
611 if not QEMUMachine.event_match(event[key], match[key]):
612 return False
613 else:
614 return False
615 return True
616 except TypeError:
617 # either match or event wasn't iterable (not a dict)
John Snowf12a2822020-10-06 19:58:08 -0400618 return bool(match == event)
John Snowabf0bf92019-06-27 17:28:14 -0400619
John Snowf12a2822020-10-06 19:58:08 -0400620 def event_wait(self, name: str,
621 timeout: float = 60.0,
622 match: Optional[QMPMessage] = None) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400623 """
624 event_wait waits for and returns a named event from QMP with a timeout.
625
626 name: The event to wait for.
627 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
628 match: Optional match criteria. See event_match for details.
629 """
630 return self.events_wait([(name, match)], timeout)
631
John Snowf12a2822020-10-06 19:58:08 -0400632 def events_wait(self,
633 events: Sequence[Tuple[str, Any]],
634 timeout: float = 60.0) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400635 """
John Snow1847a4a2020-10-06 19:58:02 -0400636 events_wait waits for and returns a single named event from QMP.
637 In the case of multiple qualifying events, this function returns the
638 first one.
John Snowabf0bf92019-06-27 17:28:14 -0400639
John Snow1847a4a2020-10-06 19:58:02 -0400640 :param events: A sequence of (name, match_criteria) tuples.
641 The match criteria are optional and may be None.
642 See event_match for details.
643 :param timeout: Optional timeout, in seconds.
644 See QEMUMonitorProtocol.pull_event.
645
646 :raise QMPTimeoutError: If timeout was non-zero and no matching events
647 were found.
648 :return: A QMP event matching the filter criteria.
649 If timeout was 0 and no event matched, None.
John Snowabf0bf92019-06-27 17:28:14 -0400650 """
John Snowf12a2822020-10-06 19:58:08 -0400651 def _match(event: QMPMessage) -> bool:
John Snowabf0bf92019-06-27 17:28:14 -0400652 for name, match in events:
John Snow306dfcd2019-06-27 17:28:15 -0400653 if event['event'] == name and self.event_match(event, match):
John Snowabf0bf92019-06-27 17:28:14 -0400654 return True
655 return False
656
John Snow1847a4a2020-10-06 19:58:02 -0400657 event: Optional[QMPMessage]
658
John Snowabf0bf92019-06-27 17:28:14 -0400659 # Search cached events
660 for event in self._events:
661 if _match(event):
662 self._events.remove(event)
663 return event
664
665 # Poll for new events
666 while True:
667 event = self._qmp.pull_event(wait=timeout)
John Snow1847a4a2020-10-06 19:58:02 -0400668 if event is None:
669 # NB: None is only returned when timeout is false-ish.
670 # Timeouts raise QMPTimeoutError instead!
671 break
John Snowabf0bf92019-06-27 17:28:14 -0400672 if _match(event):
673 return event
674 self._events.append(event)
675
676 return None
677
John Snowf12a2822020-10-06 19:58:08 -0400678 def get_log(self) -> Optional[str]:
John Snowabf0bf92019-06-27 17:28:14 -0400679 """
680 After self.shutdown or failed qemu execution, this returns the output
681 of the qemu process.
682 """
683 return self._iolog
684
John Snowf12a2822020-10-06 19:58:08 -0400685 def add_args(self, *args: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400686 """
687 Adds to the list of extra arguments to be given to the QEMU binary
688 """
689 self._args.extend(args)
690
John Snowf12a2822020-10-06 19:58:08 -0400691 def set_machine(self, machine_type: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400692 """
693 Sets the machine type
694
695 If set, the machine type will be added to the base arguments
696 of the resulting QEMU command line.
697 """
698 self._machine = machine_type
699
John Snowf12a2822020-10-06 19:58:08 -0400700 def set_console(self,
701 device_type: Optional[str] = None,
702 console_index: int = 0) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400703 """
704 Sets the device type for a console device
705
706 If set, the console device and a backing character device will
707 be added to the base arguments of the resulting QEMU command
708 line.
709
710 This is a convenience method that will either use the provided
711 device type, or default to a "-serial chardev:console" command
712 line argument.
713
714 The actual setting of command line arguments will be be done at
715 machine launch time, as it depends on the temporary directory
716 to be created.
717
718 @param device_type: the device type, such as "isa-serial". If
719 None is given (the default value) a "-serial
720 chardev:console" command line argument will
721 be used instead, resorting to the machine's
722 default device type.
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100723 @param console_index: the index of the console device to use.
724 If not zero, the command line will create
725 'index - 1' consoles and connect them to
726 the 'null' backing character device.
John Snowabf0bf92019-06-27 17:28:14 -0400727 """
728 self._console_set = True
729 self._console_device_type = device_type
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100730 self._console_index = console_index
John Snowabf0bf92019-06-27 17:28:14 -0400731
732 @property
John Snowf12a2822020-10-06 19:58:08 -0400733 def console_socket(self) -> socket.socket:
John Snowabf0bf92019-06-27 17:28:14 -0400734 """
735 Returns a socket connected to the console
736 """
737 if self._console_socket is None:
Robert Foley80ded8e2020-07-24 07:45:08 +0100738 self._console_socket = console_socket.ConsoleSocket(
739 self._console_address,
740 file=self._console_log_path,
741 drain=self._drain_console)
John Snowabf0bf92019-06-27 17:28:14 -0400742 return self._console_socket
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500743
744 @property
745 def temp_dir(self) -> str:
746 """
747 Returns a temporary directory to be used for this machine
748 """
749 if self._temp_dir is None:
750 self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-",
751 dir=self._base_temp_dir)
752 return self._temp_dir