blob: b379fcbe726b4213cad562ac50282a6358c15a57 [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
226 devnull = open(os.path.devnull, 'rb')
John Snow8dfac2e2020-05-28 18:21:29 -0400227 proc = subprocess.Popen(
228 fd_param, stdin=devnull, stdout=subprocess.PIPE,
229 stderr=subprocess.STDOUT, close_fds=False
230 )
John Snowabf0bf92019-06-27 17:28:14 -0400231 output = proc.communicate()[0]
232 if output:
233 LOG.debug(output)
234
235 return proc.returncode
236
237 @staticmethod
John Snowf12a2822020-10-06 19:58:08 -0400238 def _remove_if_exists(path: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400239 """
240 Remove file object at path if it exists
241 """
242 try:
243 os.remove(path)
244 except OSError as exception:
245 if exception.errno == errno.ENOENT:
246 return
247 raise
248
John Snowf12a2822020-10-06 19:58:08 -0400249 def is_running(self) -> bool:
John Snow306dfcd2019-06-27 17:28:15 -0400250 """Returns true if the VM is running."""
John Snowabf0bf92019-06-27 17:28:14 -0400251 return self._popen is not None and self._popen.poll() is None
252
John Snow9223fda2020-10-06 19:58:05 -0400253 @property
254 def _subp(self) -> 'subprocess.Popen[bytes]':
255 if self._popen is None:
256 raise QEMUMachineError('Subprocess pipe not present')
257 return self._popen
258
John Snowf12a2822020-10-06 19:58:08 -0400259 def exitcode(self) -> Optional[int]:
John Snow306dfcd2019-06-27 17:28:15 -0400260 """Returns the exit code if possible, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400261 if self._popen is None:
262 return None
263 return self._popen.poll()
264
John Snowf12a2822020-10-06 19:58:08 -0400265 def get_pid(self) -> Optional[int]:
John Snow306dfcd2019-06-27 17:28:15 -0400266 """Returns the PID of the running process, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400267 if not self.is_running():
268 return None
John Snow9223fda2020-10-06 19:58:05 -0400269 return self._subp.pid
John Snowabf0bf92019-06-27 17:28:14 -0400270
John Snowf12a2822020-10-06 19:58:08 -0400271 def _load_io_log(self) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400272 if self._qemu_log_path is not None:
273 with open(self._qemu_log_path, "r") as iolog:
274 self._iolog = iolog.read()
275
John Snow652809d2020-10-06 19:58:01 -0400276 @property
277 def _base_args(self) -> List[str]:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500278 args = ['-display', 'none', '-vga', 'none']
John Snowc4e60232020-10-06 19:57:59 -0400279
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500280 if self._qmp_set:
281 if isinstance(self._monitor_address, tuple):
John Snowc4e60232020-10-06 19:57:59 -0400282 moncdev = "socket,id=mon,host={},port={}".format(
283 *self._monitor_address
284 )
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500285 else:
John Snowc4e60232020-10-06 19:57:59 -0400286 moncdev = f"socket,id=mon,path={self._monitor_address}"
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500287 args.extend(['-chardev', moncdev, '-mon',
288 'chardev=mon,mode=control'])
John Snowc4e60232020-10-06 19:57:59 -0400289
John Snowabf0bf92019-06-27 17:28:14 -0400290 if self._machine is not None:
291 args.extend(['-machine', self._machine])
John Snow9b8ccd62020-05-28 18:21:28 -0400292 for _ in range(self._console_index):
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100293 args.extend(['-serial', 'null'])
John Snowabf0bf92019-06-27 17:28:14 -0400294 if self._console_set:
Paolo Bonzini991c1802020-11-13 03:10:52 -0500295 chardev = ('socket,id=console,path=%s,server=on,wait=off' %
John Snowabf0bf92019-06-27 17:28:14 -0400296 self._console_address)
297 args.extend(['-chardev', chardev])
298 if self._console_device_type is None:
299 args.extend(['-serial', 'chardev:console'])
300 else:
301 device = '%s,chardev=console' % self._console_device_type
302 args.extend(['-device', device])
303 return args
304
John Snowf12a2822020-10-06 19:58:08 -0400305 def _pre_launch(self) -> None:
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500306 self._qemu_log_path = os.path.join(self.temp_dir, self._name + ".log")
John Snowabf0bf92019-06-27 17:28:14 -0400307 self._qemu_log_file = open(self._qemu_log_path, 'wb')
308
John Snow652809d2020-10-06 19:58:01 -0400309 if self._console_set:
310 self._remove_files.append(self._console_address)
311
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500312 if self._qmp_set:
John Snowc4e60232020-10-06 19:57:59 -0400313 if self._remove_monitor_sockfile:
314 assert isinstance(self._monitor_address, str)
315 self._remove_files.append(self._monitor_address)
John Snowbe1183e2020-10-06 19:58:04 -0400316 self._qmp_connection = qmp.QEMUMonitorProtocol(
John Snowc4e60232020-10-06 19:57:59 -0400317 self._monitor_address,
318 server=True,
319 nickname=self._name
320 )
John Snowabf0bf92019-06-27 17:28:14 -0400321
John Snowf12a2822020-10-06 19:58:08 -0400322 def _post_launch(self) -> None:
John Snowbe1183e2020-10-06 19:58:04 -0400323 if self._qmp_connection:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500324 self._qmp.accept()
John Snowabf0bf92019-06-27 17:28:14 -0400325
John Snowf12a2822020-10-06 19:58:08 -0400326 def _post_shutdown(self) -> None:
John Snowa3842cb2020-07-10 01:06:42 -0400327 """
328 Called to cleanup the VM instance after the process has exited.
329 May also be called after a failed launch.
330 """
331 # Comprehensive reset for the failed launch case:
332 self._early_cleanup()
333
John Snowbe1183e2020-10-06 19:58:04 -0400334 if self._qmp_connection:
John Snow671940e2020-07-10 01:06:39 -0400335 self._qmp.close()
John Snowbe1183e2020-10-06 19:58:04 -0400336 self._qmp_connection = None
John Snow671940e2020-07-10 01:06:39 -0400337
John Snowabf0bf92019-06-27 17:28:14 -0400338 if self._qemu_log_file is not None:
339 self._qemu_log_file.close()
340 self._qemu_log_file = None
341
Cleber Rosa3c1e16c2021-02-11 17:01:41 -0500342 self._load_io_log()
343
John Snowabf0bf92019-06-27 17:28:14 -0400344 self._qemu_log_path = None
345
John Snowabf0bf92019-06-27 17:28:14 -0400346 if self._temp_dir is not None:
347 shutil.rmtree(self._temp_dir)
348 self._temp_dir = None
349
Max Reitz32558ce2019-10-17 15:31:34 +0200350 while len(self._remove_files) > 0:
351 self._remove_if_exists(self._remove_files.pop())
352
John Snow14661d92020-07-10 01:06:38 -0400353 exitcode = self.exitcode()
John Snowde6e08b2020-07-10 01:06:48 -0400354 if (exitcode is not None and exitcode < 0
355 and not (self._user_killed and exitcode == -signal.SIGKILL)):
John Snow14661d92020-07-10 01:06:38 -0400356 msg = 'qemu received signal %i; command: "%s"'
357 if self._qemu_full_args:
358 command = ' '.join(self._qemu_full_args)
359 else:
360 command = ''
361 LOG.warning(msg, -int(exitcode), command)
362
John Snowde6e08b2020-07-10 01:06:48 -0400363 self._user_killed = False
John Snow14661d92020-07-10 01:06:38 -0400364 self._launched = False
365
John Snowf12a2822020-10-06 19:58:08 -0400366 def launch(self) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400367 """
368 Launch the VM and make sure we cleanup and expose the
369 command line/output in case of exception
370 """
371
372 if self._launched:
373 raise QEMUMachineError('VM already launched')
374
375 self._iolog = None
John Snowaad3f3b2020-10-06 19:58:06 -0400376 self._qemu_full_args = ()
John Snowabf0bf92019-06-27 17:28:14 -0400377 try:
378 self._launch()
379 self._launched = True
380 except:
John Snowa3842cb2020-07-10 01:06:42 -0400381 self._post_shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400382
383 LOG.debug('Error launching VM')
384 if self._qemu_full_args:
385 LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
386 if self._iolog:
387 LOG.debug('Output: %r', self._iolog)
388 raise
389
John Snowf12a2822020-10-06 19:58:08 -0400390 def _launch(self) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400391 """
392 Launch the VM and establish a QMP connection
393 """
394 devnull = open(os.path.devnull, 'rb')
395 self._pre_launch()
John Snowaad3f3b2020-10-06 19:58:06 -0400396 self._qemu_full_args = tuple(
397 chain(self._wrapper,
398 [self._binary],
399 self._base_args,
400 self._args)
401 )
John Snowabf0bf92019-06-27 17:28:14 -0400402 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
403 self._popen = subprocess.Popen(self._qemu_full_args,
404 stdin=devnull,
405 stdout=self._qemu_log_file,
406 stderr=subprocess.STDOUT,
407 shell=False,
408 close_fds=False)
409 self._post_launch()
410
John Snowe2c97f12020-07-10 01:06:40 -0400411 def _early_cleanup(self) -> None:
412 """
413 Perform any cleanup that needs to happen before the VM exits.
John Snowa3842cb2020-07-10 01:06:42 -0400414
John Snow193bf1c2020-07-10 01:06:47 -0400415 May be invoked by both soft and hard shutdown in failover scenarios.
John Snowa3842cb2020-07-10 01:06:42 -0400416 Called additionally by _post_shutdown for comprehensive cleanup.
John Snowe2c97f12020-07-10 01:06:40 -0400417 """
418 # If we keep the console socket open, we may deadlock waiting
419 # for QEMU to exit, while QEMU is waiting for the socket to
420 # become writeable.
421 if self._console_socket is not None:
422 self._console_socket.close()
423 self._console_socket = None
424
John Snow193bf1c2020-07-10 01:06:47 -0400425 def _hard_shutdown(self) -> None:
426 """
427 Perform early cleanup, kill the VM, and wait for it to terminate.
428
429 :raise subprocess.Timeout: When timeout is exceeds 60 seconds
430 waiting for the QEMU process to terminate.
431 """
432 self._early_cleanup()
John Snow9223fda2020-10-06 19:58:05 -0400433 self._subp.kill()
434 self._subp.wait(timeout=60)
John Snow193bf1c2020-07-10 01:06:47 -0400435
John Snow8226a4b2020-07-20 12:02:52 -0400436 def _soft_shutdown(self, timeout: Optional[int],
437 has_quit: bool = False) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400438 """
439 Perform early cleanup, attempt to gracefully shut down the VM, and wait
440 for it to terminate.
441
John Snow8226a4b2020-07-20 12:02:52 -0400442 :param timeout: Timeout in seconds for graceful shutdown.
443 A value of None is an infinite wait.
John Snow193bf1c2020-07-10 01:06:47 -0400444 :param has_quit: When True, don't attempt to issue 'quit' QMP command
John Snow193bf1c2020-07-10 01:06:47 -0400445
446 :raise ConnectionReset: On QMP communication errors
447 :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
448 the QEMU process to terminate.
449 """
450 self._early_cleanup()
451
John Snowbe1183e2020-10-06 19:58:04 -0400452 if self._qmp_connection:
John Snow193bf1c2020-07-10 01:06:47 -0400453 if not has_quit:
454 # Might raise ConnectionReset
455 self._qmp.cmd('quit')
456
457 # May raise subprocess.TimeoutExpired
John Snow9223fda2020-10-06 19:58:05 -0400458 self._subp.wait(timeout=timeout)
John Snow193bf1c2020-07-10 01:06:47 -0400459
John Snow8226a4b2020-07-20 12:02:52 -0400460 def _do_shutdown(self, timeout: Optional[int],
461 has_quit: bool = False) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400462 """
463 Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
464
John Snow8226a4b2020-07-20 12:02:52 -0400465 :param timeout: Timeout in seconds for graceful shutdown.
466 A value of None is an infinite wait.
John Snow193bf1c2020-07-10 01:06:47 -0400467 :param has_quit: When True, don't attempt to issue 'quit' QMP command
John Snow193bf1c2020-07-10 01:06:47 -0400468
469 :raise AbnormalShutdown: When the VM could not be shut down gracefully.
470 The inner exception will likely be ConnectionReset or
471 subprocess.TimeoutExpired. In rare cases, non-graceful termination
472 may result in its own exceptions, likely subprocess.TimeoutExpired.
473 """
474 try:
John Snow8226a4b2020-07-20 12:02:52 -0400475 self._soft_shutdown(timeout, has_quit)
John Snow193bf1c2020-07-10 01:06:47 -0400476 except Exception as exc:
477 self._hard_shutdown()
478 raise AbnormalShutdown("Could not perform graceful shutdown") \
479 from exc
480
John Snowc9b30452020-07-10 01:06:43 -0400481 def shutdown(self, has_quit: bool = False,
482 hard: bool = False,
John Snow8226a4b2020-07-20 12:02:52 -0400483 timeout: Optional[int] = 30) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400484 """
John Snow193bf1c2020-07-10 01:06:47 -0400485 Terminate the VM (gracefully if possible) and perform cleanup.
486 Cleanup will always be performed.
487
488 If the VM has not yet been launched, or shutdown(), wait(), or kill()
489 have already been called, this method does nothing.
490
491 :param has_quit: When true, do not attempt to issue 'quit' QMP command.
492 :param hard: When true, do not attempt graceful shutdown, and
493 suppress the SIGKILL warning log message.
494 :param timeout: Optional timeout in seconds for graceful shutdown.
John Snow8226a4b2020-07-20 12:02:52 -0400495 Default 30 seconds, A `None` value is an infinite wait.
John Snowabf0bf92019-06-27 17:28:14 -0400496 """
John Snowa3842cb2020-07-10 01:06:42 -0400497 if not self._launched:
498 return
499
John Snow193bf1c2020-07-10 01:06:47 -0400500 try:
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300501 if hard:
John Snowde6e08b2020-07-10 01:06:48 -0400502 self._user_killed = True
John Snow193bf1c2020-07-10 01:06:47 -0400503 self._hard_shutdown()
504 else:
John Snow8226a4b2020-07-20 12:02:52 -0400505 self._do_shutdown(timeout, has_quit)
John Snow193bf1c2020-07-10 01:06:47 -0400506 finally:
507 self._post_shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400508
John Snowf12a2822020-10-06 19:58:08 -0400509 def kill(self) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400510 """
511 Terminate the VM forcefully, wait for it to exit, and perform cleanup.
512 """
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300513 self.shutdown(hard=True)
514
John Snow8226a4b2020-07-20 12:02:52 -0400515 def wait(self, timeout: Optional[int] = 30) -> None:
John Snow89528052020-07-10 01:06:44 -0400516 """
517 Wait for the VM to power off and perform post-shutdown cleanup.
518
John Snow8226a4b2020-07-20 12:02:52 -0400519 :param timeout: Optional timeout in seconds. Default 30 seconds.
520 A value of `None` is an infinite wait.
John Snow89528052020-07-10 01:06:44 -0400521 """
522 self.shutdown(has_quit=True, timeout=timeout)
523
John Snowf12a2822020-10-06 19:58:08 -0400524 def set_qmp_monitor(self, enabled: bool = True) -> None:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500525 """
526 Set the QMP monitor.
527
528 @param enabled: if False, qmp monitor options will be removed from
529 the base arguments of the resulting QEMU command
530 line. Default is True.
531 @note: call this function before launch().
532 """
John Snowbe1183e2020-10-06 19:58:04 -0400533 self._qmp_set = enabled
534
535 @property
536 def _qmp(self) -> qmp.QEMUMonitorProtocol:
537 if self._qmp_connection is None:
538 raise QEMUMachineError("Attempt to access QMP with no connection")
539 return self._qmp_connection
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500540
John Snowaaa81ec2020-10-06 19:58:03 -0400541 @classmethod
542 def _qmp_args(cls, _conv_keys: bool = True, **args: Any) -> Dict[str, Any]:
John Snowabf0bf92019-06-27 17:28:14 -0400543 qmp_args = dict()
544 for key, value in args.items():
John Snowaaa81ec2020-10-06 19:58:03 -0400545 if _conv_keys:
John Snowabf0bf92019-06-27 17:28:14 -0400546 qmp_args[key.replace('_', '-')] = value
547 else:
548 qmp_args[key] = value
John Snowaaa81ec2020-10-06 19:58:03 -0400549 return qmp_args
John Snowabf0bf92019-06-27 17:28:14 -0400550
John Snowaaa81ec2020-10-06 19:58:03 -0400551 def qmp(self, cmd: str,
552 conv_keys: bool = True,
553 **args: Any) -> QMPMessage:
554 """
555 Invoke a QMP command and return the response dict
556 """
557 qmp_args = self._qmp_args(conv_keys, **args)
John Snowabf0bf92019-06-27 17:28:14 -0400558 return self._qmp.cmd(cmd, args=qmp_args)
559
John Snowf12a2822020-10-06 19:58:08 -0400560 def command(self, cmd: str,
561 conv_keys: bool = True,
562 **args: Any) -> QMPReturnValue:
John Snowabf0bf92019-06-27 17:28:14 -0400563 """
564 Invoke a QMP command.
565 On success return the response dict.
566 On failure raise an exception.
567 """
John Snowaaa81ec2020-10-06 19:58:03 -0400568 qmp_args = self._qmp_args(conv_keys, **args)
569 return self._qmp.command(cmd, **qmp_args)
John Snowabf0bf92019-06-27 17:28:14 -0400570
John Snowf12a2822020-10-06 19:58:08 -0400571 def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400572 """
573 Poll for one queued QMP events and return it
574 """
John Snow306dfcd2019-06-27 17:28:15 -0400575 if self._events:
John Snowabf0bf92019-06-27 17:28:14 -0400576 return self._events.pop(0)
577 return self._qmp.pull_event(wait=wait)
578
John Snowf12a2822020-10-06 19:58:08 -0400579 def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400580 """
581 Poll for queued QMP events and return a list of dicts
582 """
583 events = self._qmp.get_events(wait=wait)
584 events.extend(self._events)
585 del self._events[:]
586 self._qmp.clear_events()
587 return events
588
589 @staticmethod
John Snowf12a2822020-10-06 19:58:08 -0400590 def event_match(event: Any, match: Optional[Any]) -> bool:
John Snowabf0bf92019-06-27 17:28:14 -0400591 """
592 Check if an event matches optional match criteria.
593
594 The match criteria takes the form of a matching subdict. The event is
595 checked to be a superset of the subdict, recursively, with matching
596 values whenever the subdict values are not None.
597
598 This has a limitation that you cannot explicitly check for None values.
599
600 Examples, with the subdict queries on the left:
601 - None matches any object.
602 - {"foo": None} matches {"foo": {"bar": 1}}
603 - {"foo": None} matches {"foo": 5}
604 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
605 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
606 """
607 if match is None:
608 return True
609
610 try:
611 for key in match:
612 if key in event:
613 if not QEMUMachine.event_match(event[key], match[key]):
614 return False
615 else:
616 return False
617 return True
618 except TypeError:
619 # either match or event wasn't iterable (not a dict)
John Snowf12a2822020-10-06 19:58:08 -0400620 return bool(match == event)
John Snowabf0bf92019-06-27 17:28:14 -0400621
John Snowf12a2822020-10-06 19:58:08 -0400622 def event_wait(self, name: str,
623 timeout: float = 60.0,
624 match: Optional[QMPMessage] = None) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400625 """
626 event_wait waits for and returns a named event from QMP with a timeout.
627
628 name: The event to wait for.
629 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
630 match: Optional match criteria. See event_match for details.
631 """
632 return self.events_wait([(name, match)], timeout)
633
John Snowf12a2822020-10-06 19:58:08 -0400634 def events_wait(self,
635 events: Sequence[Tuple[str, Any]],
636 timeout: float = 60.0) -> Optional[QMPMessage]:
John Snowabf0bf92019-06-27 17:28:14 -0400637 """
John Snow1847a4a2020-10-06 19:58:02 -0400638 events_wait waits for and returns a single named event from QMP.
639 In the case of multiple qualifying events, this function returns the
640 first one.
John Snowabf0bf92019-06-27 17:28:14 -0400641
John Snow1847a4a2020-10-06 19:58:02 -0400642 :param events: A sequence of (name, match_criteria) tuples.
643 The match criteria are optional and may be None.
644 See event_match for details.
645 :param timeout: Optional timeout, in seconds.
646 See QEMUMonitorProtocol.pull_event.
647
648 :raise QMPTimeoutError: If timeout was non-zero and no matching events
649 were found.
650 :return: A QMP event matching the filter criteria.
651 If timeout was 0 and no event matched, None.
John Snowabf0bf92019-06-27 17:28:14 -0400652 """
John Snowf12a2822020-10-06 19:58:08 -0400653 def _match(event: QMPMessage) -> bool:
John Snowabf0bf92019-06-27 17:28:14 -0400654 for name, match in events:
John Snow306dfcd2019-06-27 17:28:15 -0400655 if event['event'] == name and self.event_match(event, match):
John Snowabf0bf92019-06-27 17:28:14 -0400656 return True
657 return False
658
John Snow1847a4a2020-10-06 19:58:02 -0400659 event: Optional[QMPMessage]
660
John Snowabf0bf92019-06-27 17:28:14 -0400661 # Search cached events
662 for event in self._events:
663 if _match(event):
664 self._events.remove(event)
665 return event
666
667 # Poll for new events
668 while True:
669 event = self._qmp.pull_event(wait=timeout)
John Snow1847a4a2020-10-06 19:58:02 -0400670 if event is None:
671 # NB: None is only returned when timeout is false-ish.
672 # Timeouts raise QMPTimeoutError instead!
673 break
John Snowabf0bf92019-06-27 17:28:14 -0400674 if _match(event):
675 return event
676 self._events.append(event)
677
678 return None
679
John Snowf12a2822020-10-06 19:58:08 -0400680 def get_log(self) -> Optional[str]:
John Snowabf0bf92019-06-27 17:28:14 -0400681 """
682 After self.shutdown or failed qemu execution, this returns the output
683 of the qemu process.
684 """
685 return self._iolog
686
John Snowf12a2822020-10-06 19:58:08 -0400687 def add_args(self, *args: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400688 """
689 Adds to the list of extra arguments to be given to the QEMU binary
690 """
691 self._args.extend(args)
692
John Snowf12a2822020-10-06 19:58:08 -0400693 def set_machine(self, machine_type: str) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400694 """
695 Sets the machine type
696
697 If set, the machine type will be added to the base arguments
698 of the resulting QEMU command line.
699 """
700 self._machine = machine_type
701
John Snowf12a2822020-10-06 19:58:08 -0400702 def set_console(self,
703 device_type: Optional[str] = None,
704 console_index: int = 0) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400705 """
706 Sets the device type for a console device
707
708 If set, the console device and a backing character device will
709 be added to the base arguments of the resulting QEMU command
710 line.
711
712 This is a convenience method that will either use the provided
713 device type, or default to a "-serial chardev:console" command
714 line argument.
715
716 The actual setting of command line arguments will be be done at
717 machine launch time, as it depends on the temporary directory
718 to be created.
719
720 @param device_type: the device type, such as "isa-serial". If
721 None is given (the default value) a "-serial
722 chardev:console" command line argument will
723 be used instead, resorting to the machine's
724 default device type.
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100725 @param console_index: the index of the console device to use.
726 If not zero, the command line will create
727 'index - 1' consoles and connect them to
728 the 'null' backing character device.
John Snowabf0bf92019-06-27 17:28:14 -0400729 """
730 self._console_set = True
731 self._console_device_type = device_type
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100732 self._console_index = console_index
John Snowabf0bf92019-06-27 17:28:14 -0400733
734 @property
John Snowf12a2822020-10-06 19:58:08 -0400735 def console_socket(self) -> socket.socket:
John Snowabf0bf92019-06-27 17:28:14 -0400736 """
737 Returns a socket connected to the console
738 """
739 if self._console_socket is None:
Robert Foley80ded8e2020-07-24 07:45:08 +0100740 self._console_socket = console_socket.ConsoleSocket(
741 self._console_address,
742 file=self._console_log_path,
743 drain=self._drain_console)
John Snowabf0bf92019-06-27 17:28:14 -0400744 return self._console_socket
Cleber Rosa2ca6e262021-02-11 17:01:42 -0500745
746 @property
747 def temp_dir(self) -> str:
748 """
749 Returns a temporary directory to be used for this machine
750 """
751 if self._temp_dir is None:
752 self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-",
753 dir=self._base_temp_dir)
754 return self._temp_dir