blob: 4e762fcd529a5ee6ce145177e439ed5202a8c489 [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
21import logging
22import os
John Snowabf0bf92019-06-27 17:28:14 -040023import shutil
John Snowde6e08b2020-07-10 01:06:48 -040024import signal
John Snow932ca4b2020-10-06 19:57:58 -040025import subprocess
John Snowabf0bf92019-06-27 17:28:14 -040026import tempfile
John Snow1dda0402020-05-14 01:53:44 -040027from types import TracebackType
John Snowaaa81ec2020-10-06 19:58:03 -040028from typing import (
29 Any,
30 Dict,
31 List,
32 Optional,
33 Type,
34)
John Snowabf0bf92019-06-27 17:28:14 -040035
John Snow932ca4b2020-10-06 19:57:58 -040036from . import console_socket, qmp
John Snow1847a4a2020-10-06 19:58:02 -040037from .qmp import QMPMessage, SocketAddrT
John Snow932ca4b2020-10-06 19:57:58 -040038
John Snowabf0bf92019-06-27 17:28:14 -040039
40LOG = logging.getLogger(__name__)
41
John Snow8dfac2e2020-05-28 18:21:29 -040042
John Snowabf0bf92019-06-27 17:28:14 -040043class QEMUMachineError(Exception):
44 """
45 Exception called when an error in QEMUMachine happens.
46 """
47
48
49class QEMUMachineAddDeviceError(QEMUMachineError):
50 """
51 Exception raised when a request to add a device can not be fulfilled
52
53 The failures are caused by limitations, lack of information or conflicting
54 requests on the QEMUMachine methods. This exception does not represent
55 failures reported by the QEMU binary itself.
56 """
57
58
John Snow193bf1c2020-07-10 01:06:47 -040059class AbnormalShutdown(QEMUMachineError):
60 """
61 Exception raised when a graceful shutdown was requested, but not performed.
62 """
63
64
John Snow9b8ccd62020-05-28 18:21:28 -040065class QEMUMachine:
John Snowabf0bf92019-06-27 17:28:14 -040066 """
67 A QEMU VM
68
John Snow8dfac2e2020-05-28 18:21:29 -040069 Use this object as a context manager to ensure
70 the QEMU process terminates::
John Snowabf0bf92019-06-27 17:28:14 -040071
72 with VM(binary) as vm:
73 ...
74 # vm is guaranteed to be shut down here
75 """
76
77 def __init__(self, binary, args=None, wrapper=None, name=None,
John Snowc4e60232020-10-06 19:57:59 -040078 test_dir="/var/tmp",
79 monitor_address: Optional[SocketAddrT] = None,
Robert Foley0fc8f662020-07-01 14:56:24 +010080 socket_scm_helper=None, sock_dir=None,
81 drain_console=False, console_log=None):
John Snowabf0bf92019-06-27 17:28:14 -040082 '''
83 Initialize a QEMUMachine
84
85 @param binary: path to the qemu binary
86 @param args: list of extra arguments
87 @param wrapper: list of arguments used as prefix to qemu binary
88 @param name: prefix for socket and log file names (default: qemu-PID)
89 @param test_dir: where to create socket and log file
90 @param monitor_address: address for QMP monitor
91 @param socket_scm_helper: helper program, required for send_fd_scm()
Robert Foley0fc8f662020-07-01 14:56:24 +010092 @param sock_dir: where to create socket (overrides test_dir for sock)
Robert Foley0fc8f662020-07-01 14:56:24 +010093 @param drain_console: (optional) True to drain console socket to buffer
John Snowc5e61a62020-10-06 19:58:00 -040094 @param console_log: (optional) path to console log file
John Snowabf0bf92019-06-27 17:28:14 -040095 @note: Qemu process is not started until launch() is used.
96 '''
John Snowc5e61a62020-10-06 19:58:00 -040097 # Direct user configuration
98
99 self._binary = binary
100
John Snowabf0bf92019-06-27 17:28:14 -0400101 if args is None:
102 args = []
John Snowc5e61a62020-10-06 19:58:00 -0400103 # Copy mutable input: we will be modifying our copy
104 self._args = list(args)
105
John Snowabf0bf92019-06-27 17:28:14 -0400106 if wrapper is None:
107 wrapper = []
John Snowc5e61a62020-10-06 19:58:00 -0400108 self._wrapper = wrapper
109
110 self._name = name or "qemu-%d" % os.getpid()
111 self._test_dir = test_dir
112 self._sock_dir = sock_dir or self._test_dir
113 self._socket_scm_helper = socket_scm_helper
114
John Snowc4e60232020-10-06 19:57:59 -0400115 if monitor_address is not None:
116 self._monitor_address = monitor_address
117 self._remove_monitor_sockfile = False
118 else:
119 self._monitor_address = os.path.join(
John Snowc5e61a62020-10-06 19:58:00 -0400120 self._sock_dir, f"{self._name}-monitor.sock"
John Snowc4e60232020-10-06 19:57:59 -0400121 )
122 self._remove_monitor_sockfile = True
John Snowc5e61a62020-10-06 19:58:00 -0400123
124 self._console_log_path = console_log
125 if self._console_log_path:
126 # In order to log the console, buffering needs to be enabled.
127 self._drain_console = True
128 else:
129 self._drain_console = drain_console
130
131 # Runstate
John Snowabf0bf92019-06-27 17:28:14 -0400132 self._qemu_log_path = None
133 self._qemu_log_file = None
John Snow9223fda2020-10-06 19:58:05 -0400134 self._popen: Optional['subprocess.Popen[bytes]'] = None
John Snowabf0bf92019-06-27 17:28:14 -0400135 self._events = []
136 self._iolog = None
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500137 self._qmp_set = True # Enable QMP monitor by default.
John Snowbe1183e2020-10-06 19:58:04 -0400138 self._qmp_connection: Optional[qmp.QEMUMonitorProtocol] = None
John Snowabf0bf92019-06-27 17:28:14 -0400139 self._qemu_full_args = None
John Snowabf0bf92019-06-27 17:28:14 -0400140 self._temp_dir = None
141 self._launched = False
142 self._machine = None
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100143 self._console_index = 0
John Snowabf0bf92019-06-27 17:28:14 -0400144 self._console_set = False
145 self._console_device_type = None
John Snow652809d2020-10-06 19:58:01 -0400146 self._console_address = os.path.join(
147 self._sock_dir, f"{self._name}-console.sock"
148 )
John Snowabf0bf92019-06-27 17:28:14 -0400149 self._console_socket = None
Max Reitz32558ce2019-10-17 15:31:34 +0200150 self._remove_files = []
John Snowde6e08b2020-07-10 01:06:48 -0400151 self._user_killed = False
John Snowabf0bf92019-06-27 17:28:14 -0400152
John Snowabf0bf92019-06-27 17:28:14 -0400153 def __enter__(self):
154 return self
155
John Snow1dda0402020-05-14 01:53:44 -0400156 def __exit__(self,
157 exc_type: Optional[Type[BaseException]],
158 exc_val: Optional[BaseException],
159 exc_tb: Optional[TracebackType]) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400160 self.shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400161
John Snowabf0bf92019-06-27 17:28:14 -0400162 def add_monitor_null(self):
John Snow306dfcd2019-06-27 17:28:15 -0400163 """
164 This can be used to add an unused monitor instance.
165 """
John Snowabf0bf92019-06-27 17:28:14 -0400166 self._args.append('-monitor')
167 self._args.append('null')
168
169 def add_fd(self, fd, fdset, opaque, opts=''):
170 """
171 Pass a file descriptor to the VM
172 """
173 options = ['fd=%d' % fd,
174 'set=%d' % fdset,
175 'opaque=%s' % opaque]
176 if opts:
177 options.append(opts)
178
179 # This did not exist before 3.4, but since then it is
180 # mandatory for our purpose
181 if hasattr(os, 'set_inheritable'):
182 os.set_inheritable(fd, True)
183
184 self._args.append('-add-fd')
185 self._args.append(','.join(options))
186 return self
187
John Snowabf0bf92019-06-27 17:28:14 -0400188 def send_fd_scm(self, fd=None, file_path=None):
John Snow306dfcd2019-06-27 17:28:15 -0400189 """
190 Send an fd or file_path to socket_scm_helper.
191
192 Exactly one of fd and file_path must be given.
193 If it is file_path, the helper will open that file and pass its own fd.
194 """
John Snowabf0bf92019-06-27 17:28:14 -0400195 # In iotest.py, the qmp should always use unix socket.
196 assert self._qmp.is_scm_available()
197 if self._socket_scm_helper is None:
198 raise QEMUMachineError("No path to socket_scm_helper set")
199 if not os.path.exists(self._socket_scm_helper):
200 raise QEMUMachineError("%s does not exist" %
201 self._socket_scm_helper)
202
203 # This did not exist before 3.4, but since then it is
204 # mandatory for our purpose
205 if hasattr(os, 'set_inheritable'):
206 os.set_inheritable(self._qmp.get_sock_fd(), True)
207 if fd is not None:
208 os.set_inheritable(fd, True)
209
210 fd_param = ["%s" % self._socket_scm_helper,
211 "%d" % self._qmp.get_sock_fd()]
212
213 if file_path is not None:
214 assert fd is None
215 fd_param.append(file_path)
216 else:
217 assert fd is not None
218 fd_param.append(str(fd))
219
220 devnull = open(os.path.devnull, 'rb')
John Snow8dfac2e2020-05-28 18:21:29 -0400221 proc = subprocess.Popen(
222 fd_param, stdin=devnull, stdout=subprocess.PIPE,
223 stderr=subprocess.STDOUT, close_fds=False
224 )
John Snowabf0bf92019-06-27 17:28:14 -0400225 output = proc.communicate()[0]
226 if output:
227 LOG.debug(output)
228
229 return proc.returncode
230
231 @staticmethod
232 def _remove_if_exists(path):
233 """
234 Remove file object at path if it exists
235 """
236 try:
237 os.remove(path)
238 except OSError as exception:
239 if exception.errno == errno.ENOENT:
240 return
241 raise
242
243 def is_running(self):
John Snow306dfcd2019-06-27 17:28:15 -0400244 """Returns true if the VM is running."""
John Snowabf0bf92019-06-27 17:28:14 -0400245 return self._popen is not None and self._popen.poll() is None
246
John Snow9223fda2020-10-06 19:58:05 -0400247 @property
248 def _subp(self) -> 'subprocess.Popen[bytes]':
249 if self._popen is None:
250 raise QEMUMachineError('Subprocess pipe not present')
251 return self._popen
252
John Snowabf0bf92019-06-27 17:28:14 -0400253 def exitcode(self):
John Snow306dfcd2019-06-27 17:28:15 -0400254 """Returns the exit code if possible, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400255 if self._popen is None:
256 return None
257 return self._popen.poll()
258
259 def get_pid(self):
John Snow306dfcd2019-06-27 17:28:15 -0400260 """Returns the PID of the running process, or None."""
John Snowabf0bf92019-06-27 17:28:14 -0400261 if not self.is_running():
262 return None
John Snow9223fda2020-10-06 19:58:05 -0400263 return self._subp.pid
John Snowabf0bf92019-06-27 17:28:14 -0400264
265 def _load_io_log(self):
266 if self._qemu_log_path is not None:
267 with open(self._qemu_log_path, "r") as iolog:
268 self._iolog = iolog.read()
269
John Snow652809d2020-10-06 19:58:01 -0400270 @property
271 def _base_args(self) -> List[str]:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500272 args = ['-display', 'none', '-vga', 'none']
John Snowc4e60232020-10-06 19:57:59 -0400273
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500274 if self._qmp_set:
275 if isinstance(self._monitor_address, tuple):
John Snowc4e60232020-10-06 19:57:59 -0400276 moncdev = "socket,id=mon,host={},port={}".format(
277 *self._monitor_address
278 )
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500279 else:
John Snowc4e60232020-10-06 19:57:59 -0400280 moncdev = f"socket,id=mon,path={self._monitor_address}"
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500281 args.extend(['-chardev', moncdev, '-mon',
282 'chardev=mon,mode=control'])
John Snowc4e60232020-10-06 19:57:59 -0400283
John Snowabf0bf92019-06-27 17:28:14 -0400284 if self._machine is not None:
285 args.extend(['-machine', self._machine])
John Snow9b8ccd62020-05-28 18:21:28 -0400286 for _ in range(self._console_index):
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100287 args.extend(['-serial', 'null'])
John Snowabf0bf92019-06-27 17:28:14 -0400288 if self._console_set:
John Snowabf0bf92019-06-27 17:28:14 -0400289 chardev = ('socket,id=console,path=%s,server,nowait' %
290 self._console_address)
291 args.extend(['-chardev', chardev])
292 if self._console_device_type is None:
293 args.extend(['-serial', 'chardev:console'])
294 else:
295 device = '%s,chardev=console' % self._console_device_type
296 args.extend(['-device', device])
297 return args
298
299 def _pre_launch(self):
300 self._temp_dir = tempfile.mkdtemp(dir=self._test_dir)
John Snowabf0bf92019-06-27 17:28:14 -0400301 self._qemu_log_path = os.path.join(self._temp_dir, self._name + ".log")
302 self._qemu_log_file = open(self._qemu_log_path, 'wb')
303
John Snow652809d2020-10-06 19:58:01 -0400304 if self._console_set:
305 self._remove_files.append(self._console_address)
306
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500307 if self._qmp_set:
John Snowc4e60232020-10-06 19:57:59 -0400308 if self._remove_monitor_sockfile:
309 assert isinstance(self._monitor_address, str)
310 self._remove_files.append(self._monitor_address)
John Snowbe1183e2020-10-06 19:58:04 -0400311 self._qmp_connection = qmp.QEMUMonitorProtocol(
John Snowc4e60232020-10-06 19:57:59 -0400312 self._monitor_address,
313 server=True,
314 nickname=self._name
315 )
John Snowabf0bf92019-06-27 17:28:14 -0400316
317 def _post_launch(self):
John Snowbe1183e2020-10-06 19:58:04 -0400318 if self._qmp_connection:
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500319 self._qmp.accept()
John Snowabf0bf92019-06-27 17:28:14 -0400320
321 def _post_shutdown(self):
John Snowa3842cb2020-07-10 01:06:42 -0400322 """
323 Called to cleanup the VM instance after the process has exited.
324 May also be called after a failed launch.
325 """
326 # Comprehensive reset for the failed launch case:
327 self._early_cleanup()
328
John Snowbe1183e2020-10-06 19:58:04 -0400329 if self._qmp_connection:
John Snow671940e2020-07-10 01:06:39 -0400330 self._qmp.close()
John Snowbe1183e2020-10-06 19:58:04 -0400331 self._qmp_connection = None
John Snow671940e2020-07-10 01:06:39 -0400332
John Snow14661d92020-07-10 01:06:38 -0400333 self._load_io_log()
334
John Snowabf0bf92019-06-27 17:28:14 -0400335 if self._qemu_log_file is not None:
336 self._qemu_log_file.close()
337 self._qemu_log_file = None
338
339 self._qemu_log_path = None
340
John Snowabf0bf92019-06-27 17:28:14 -0400341 if self._temp_dir is not None:
342 shutil.rmtree(self._temp_dir)
343 self._temp_dir = None
344
Max Reitz32558ce2019-10-17 15:31:34 +0200345 while len(self._remove_files) > 0:
346 self._remove_if_exists(self._remove_files.pop())
347
John Snow14661d92020-07-10 01:06:38 -0400348 exitcode = self.exitcode()
John Snowde6e08b2020-07-10 01:06:48 -0400349 if (exitcode is not None and exitcode < 0
350 and not (self._user_killed and exitcode == -signal.SIGKILL)):
John Snow14661d92020-07-10 01:06:38 -0400351 msg = 'qemu received signal %i; command: "%s"'
352 if self._qemu_full_args:
353 command = ' '.join(self._qemu_full_args)
354 else:
355 command = ''
356 LOG.warning(msg, -int(exitcode), command)
357
John Snowde6e08b2020-07-10 01:06:48 -0400358 self._user_killed = False
John Snow14661d92020-07-10 01:06:38 -0400359 self._launched = False
360
John Snowabf0bf92019-06-27 17:28:14 -0400361 def launch(self):
362 """
363 Launch the VM and make sure we cleanup and expose the
364 command line/output in case of exception
365 """
366
367 if self._launched:
368 raise QEMUMachineError('VM already launched')
369
370 self._iolog = None
371 self._qemu_full_args = None
372 try:
373 self._launch()
374 self._launched = True
375 except:
John Snowa3842cb2020-07-10 01:06:42 -0400376 self._post_shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400377
378 LOG.debug('Error launching VM')
379 if self._qemu_full_args:
380 LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
381 if self._iolog:
382 LOG.debug('Output: %r', self._iolog)
383 raise
384
385 def _launch(self):
386 """
387 Launch the VM and establish a QMP connection
388 """
389 devnull = open(os.path.devnull, 'rb')
390 self._pre_launch()
391 self._qemu_full_args = (self._wrapper + [self._binary] +
John Snow652809d2020-10-06 19:58:01 -0400392 self._base_args + self._args)
John Snowabf0bf92019-06-27 17:28:14 -0400393 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
394 self._popen = subprocess.Popen(self._qemu_full_args,
395 stdin=devnull,
396 stdout=self._qemu_log_file,
397 stderr=subprocess.STDOUT,
398 shell=False,
399 close_fds=False)
400 self._post_launch()
401
John Snowe2c97f12020-07-10 01:06:40 -0400402 def _early_cleanup(self) -> None:
403 """
404 Perform any cleanup that needs to happen before the VM exits.
John Snowa3842cb2020-07-10 01:06:42 -0400405
John Snow193bf1c2020-07-10 01:06:47 -0400406 May be invoked by both soft and hard shutdown in failover scenarios.
John Snowa3842cb2020-07-10 01:06:42 -0400407 Called additionally by _post_shutdown for comprehensive cleanup.
John Snowe2c97f12020-07-10 01:06:40 -0400408 """
409 # If we keep the console socket open, we may deadlock waiting
410 # for QEMU to exit, while QEMU is waiting for the socket to
411 # become writeable.
412 if self._console_socket is not None:
413 self._console_socket.close()
414 self._console_socket = None
415
John Snow193bf1c2020-07-10 01:06:47 -0400416 def _hard_shutdown(self) -> None:
417 """
418 Perform early cleanup, kill the VM, and wait for it to terminate.
419
420 :raise subprocess.Timeout: When timeout is exceeds 60 seconds
421 waiting for the QEMU process to terminate.
422 """
423 self._early_cleanup()
John Snow9223fda2020-10-06 19:58:05 -0400424 self._subp.kill()
425 self._subp.wait(timeout=60)
John Snow193bf1c2020-07-10 01:06:47 -0400426
John Snow8226a4b2020-07-20 12:02:52 -0400427 def _soft_shutdown(self, timeout: Optional[int],
428 has_quit: bool = False) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400429 """
430 Perform early cleanup, attempt to gracefully shut down the VM, and wait
431 for it to terminate.
432
John Snow8226a4b2020-07-20 12:02:52 -0400433 :param timeout: Timeout in seconds for graceful shutdown.
434 A value of None is an infinite wait.
John Snow193bf1c2020-07-10 01:06:47 -0400435 :param has_quit: When True, don't attempt to issue 'quit' QMP command
John Snow193bf1c2020-07-10 01:06:47 -0400436
437 :raise ConnectionReset: On QMP communication errors
438 :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
439 the QEMU process to terminate.
440 """
441 self._early_cleanup()
442
John Snowbe1183e2020-10-06 19:58:04 -0400443 if self._qmp_connection:
John Snow193bf1c2020-07-10 01:06:47 -0400444 if not has_quit:
445 # Might raise ConnectionReset
446 self._qmp.cmd('quit')
447
448 # May raise subprocess.TimeoutExpired
John Snow9223fda2020-10-06 19:58:05 -0400449 self._subp.wait(timeout=timeout)
John Snow193bf1c2020-07-10 01:06:47 -0400450
John Snow8226a4b2020-07-20 12:02:52 -0400451 def _do_shutdown(self, timeout: Optional[int],
452 has_quit: bool = False) -> None:
John Snow193bf1c2020-07-10 01:06:47 -0400453 """
454 Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
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 AbnormalShutdown: When the VM could not be shut down gracefully.
461 The inner exception will likely be ConnectionReset or
462 subprocess.TimeoutExpired. In rare cases, non-graceful termination
463 may result in its own exceptions, likely subprocess.TimeoutExpired.
464 """
465 try:
John Snow8226a4b2020-07-20 12:02:52 -0400466 self._soft_shutdown(timeout, has_quit)
John Snow193bf1c2020-07-10 01:06:47 -0400467 except Exception as exc:
468 self._hard_shutdown()
469 raise AbnormalShutdown("Could not perform graceful shutdown") \
470 from exc
471
John Snowc9b30452020-07-10 01:06:43 -0400472 def shutdown(self, has_quit: bool = False,
473 hard: bool = False,
John Snow8226a4b2020-07-20 12:02:52 -0400474 timeout: Optional[int] = 30) -> None:
John Snowabf0bf92019-06-27 17:28:14 -0400475 """
John Snow193bf1c2020-07-10 01:06:47 -0400476 Terminate the VM (gracefully if possible) and perform cleanup.
477 Cleanup will always be performed.
478
479 If the VM has not yet been launched, or shutdown(), wait(), or kill()
480 have already been called, this method does nothing.
481
482 :param has_quit: When true, do not attempt to issue 'quit' QMP command.
483 :param hard: When true, do not attempt graceful shutdown, and
484 suppress the SIGKILL warning log message.
485 :param timeout: Optional timeout in seconds for graceful shutdown.
John Snow8226a4b2020-07-20 12:02:52 -0400486 Default 30 seconds, A `None` value is an infinite wait.
John Snowabf0bf92019-06-27 17:28:14 -0400487 """
John Snowa3842cb2020-07-10 01:06:42 -0400488 if not self._launched:
489 return
490
John Snow193bf1c2020-07-10 01:06:47 -0400491 try:
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300492 if hard:
John Snowde6e08b2020-07-10 01:06:48 -0400493 self._user_killed = True
John Snow193bf1c2020-07-10 01:06:47 -0400494 self._hard_shutdown()
495 else:
John Snow8226a4b2020-07-20 12:02:52 -0400496 self._do_shutdown(timeout, has_quit)
John Snow193bf1c2020-07-10 01:06:47 -0400497 finally:
498 self._post_shutdown()
John Snowabf0bf92019-06-27 17:28:14 -0400499
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300500 def kill(self):
John Snow193bf1c2020-07-10 01:06:47 -0400501 """
502 Terminate the VM forcefully, wait for it to exit, and perform cleanup.
503 """
Vladimir Sementsov-Ogievskiye0e925a2020-02-17 18:02:42 +0300504 self.shutdown(hard=True)
505
John Snow8226a4b2020-07-20 12:02:52 -0400506 def wait(self, timeout: Optional[int] = 30) -> None:
John Snow89528052020-07-10 01:06:44 -0400507 """
508 Wait for the VM to power off and perform post-shutdown cleanup.
509
John Snow8226a4b2020-07-20 12:02:52 -0400510 :param timeout: Optional timeout in seconds. Default 30 seconds.
511 A value of `None` is an infinite wait.
John Snow89528052020-07-10 01:06:44 -0400512 """
513 self.shutdown(has_quit=True, timeout=timeout)
514
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500515 def set_qmp_monitor(self, enabled=True):
516 """
517 Set the QMP monitor.
518
519 @param enabled: if False, qmp monitor options will be removed from
520 the base arguments of the resulting QEMU command
521 line. Default is True.
522 @note: call this function before launch().
523 """
John Snowbe1183e2020-10-06 19:58:04 -0400524 self._qmp_set = enabled
525
526 @property
527 def _qmp(self) -> qmp.QEMUMonitorProtocol:
528 if self._qmp_connection is None:
529 raise QEMUMachineError("Attempt to access QMP with no connection")
530 return self._qmp_connection
Wainer dos Santos Moschetta74b56bb2019-12-11 13:55:35 -0500531
John Snowaaa81ec2020-10-06 19:58:03 -0400532 @classmethod
533 def _qmp_args(cls, _conv_keys: bool = True, **args: Any) -> Dict[str, Any]:
John Snowabf0bf92019-06-27 17:28:14 -0400534 qmp_args = dict()
535 for key, value in args.items():
John Snowaaa81ec2020-10-06 19:58:03 -0400536 if _conv_keys:
John Snowabf0bf92019-06-27 17:28:14 -0400537 qmp_args[key.replace('_', '-')] = value
538 else:
539 qmp_args[key] = value
John Snowaaa81ec2020-10-06 19:58:03 -0400540 return qmp_args
John Snowabf0bf92019-06-27 17:28:14 -0400541
John Snowaaa81ec2020-10-06 19:58:03 -0400542 def qmp(self, cmd: str,
543 conv_keys: bool = True,
544 **args: Any) -> QMPMessage:
545 """
546 Invoke a QMP command and return the response dict
547 """
548 qmp_args = self._qmp_args(conv_keys, **args)
John Snowabf0bf92019-06-27 17:28:14 -0400549 return self._qmp.cmd(cmd, args=qmp_args)
550
551 def command(self, cmd, conv_keys=True, **args):
552 """
553 Invoke a QMP command.
554 On success return the response dict.
555 On failure raise an exception.
556 """
John Snowaaa81ec2020-10-06 19:58:03 -0400557 qmp_args = self._qmp_args(conv_keys, **args)
558 return self._qmp.command(cmd, **qmp_args)
John Snowabf0bf92019-06-27 17:28:14 -0400559
560 def get_qmp_event(self, wait=False):
561 """
562 Poll for one queued QMP events and return it
563 """
John Snow306dfcd2019-06-27 17:28:15 -0400564 if self._events:
John Snowabf0bf92019-06-27 17:28:14 -0400565 return self._events.pop(0)
566 return self._qmp.pull_event(wait=wait)
567
568 def get_qmp_events(self, wait=False):
569 """
570 Poll for queued QMP events and return a list of dicts
571 """
572 events = self._qmp.get_events(wait=wait)
573 events.extend(self._events)
574 del self._events[:]
575 self._qmp.clear_events()
576 return events
577
578 @staticmethod
579 def event_match(event, match=None):
580 """
581 Check if an event matches optional match criteria.
582
583 The match criteria takes the form of a matching subdict. The event is
584 checked to be a superset of the subdict, recursively, with matching
585 values whenever the subdict values are not None.
586
587 This has a limitation that you cannot explicitly check for None values.
588
589 Examples, with the subdict queries on the left:
590 - None matches any object.
591 - {"foo": None} matches {"foo": {"bar": 1}}
592 - {"foo": None} matches {"foo": 5}
593 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
594 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
595 """
596 if match is None:
597 return True
598
599 try:
600 for key in match:
601 if key in event:
602 if not QEMUMachine.event_match(event[key], match[key]):
603 return False
604 else:
605 return False
606 return True
607 except TypeError:
608 # either match or event wasn't iterable (not a dict)
609 return match == event
610
611 def event_wait(self, name, timeout=60.0, match=None):
612 """
613 event_wait waits for and returns a named event from QMP with a timeout.
614
615 name: The event to wait for.
616 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
617 match: Optional match criteria. See event_match for details.
618 """
619 return self.events_wait([(name, match)], timeout)
620
621 def events_wait(self, events, timeout=60.0):
622 """
John Snow1847a4a2020-10-06 19:58:02 -0400623 events_wait waits for and returns a single named event from QMP.
624 In the case of multiple qualifying events, this function returns the
625 first one.
John Snowabf0bf92019-06-27 17:28:14 -0400626
John Snow1847a4a2020-10-06 19:58:02 -0400627 :param events: A sequence of (name, match_criteria) tuples.
628 The match criteria are optional and may be None.
629 See event_match for details.
630 :param timeout: Optional timeout, in seconds.
631 See QEMUMonitorProtocol.pull_event.
632
633 :raise QMPTimeoutError: If timeout was non-zero and no matching events
634 were found.
635 :return: A QMP event matching the filter criteria.
636 If timeout was 0 and no event matched, None.
John Snowabf0bf92019-06-27 17:28:14 -0400637 """
638 def _match(event):
639 for name, match in events:
John Snow306dfcd2019-06-27 17:28:15 -0400640 if event['event'] == name and self.event_match(event, match):
John Snowabf0bf92019-06-27 17:28:14 -0400641 return True
642 return False
643
John Snow1847a4a2020-10-06 19:58:02 -0400644 event: Optional[QMPMessage]
645
John Snowabf0bf92019-06-27 17:28:14 -0400646 # Search cached events
647 for event in self._events:
648 if _match(event):
649 self._events.remove(event)
650 return event
651
652 # Poll for new events
653 while True:
654 event = self._qmp.pull_event(wait=timeout)
John Snow1847a4a2020-10-06 19:58:02 -0400655 if event is None:
656 # NB: None is only returned when timeout is false-ish.
657 # Timeouts raise QMPTimeoutError instead!
658 break
John Snowabf0bf92019-06-27 17:28:14 -0400659 if _match(event):
660 return event
661 self._events.append(event)
662
663 return None
664
665 def get_log(self):
666 """
667 After self.shutdown or failed qemu execution, this returns the output
668 of the qemu process.
669 """
670 return self._iolog
671
672 def add_args(self, *args):
673 """
674 Adds to the list of extra arguments to be given to the QEMU binary
675 """
676 self._args.extend(args)
677
678 def set_machine(self, machine_type):
679 """
680 Sets the machine type
681
682 If set, the machine type will be added to the base arguments
683 of the resulting QEMU command line.
684 """
685 self._machine = machine_type
686
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100687 def set_console(self, device_type=None, console_index=0):
John Snowabf0bf92019-06-27 17:28:14 -0400688 """
689 Sets the device type for a console device
690
691 If set, the console device and a backing character device will
692 be added to the base arguments of the resulting QEMU command
693 line.
694
695 This is a convenience method that will either use the provided
696 device type, or default to a "-serial chardev:console" command
697 line argument.
698
699 The actual setting of command line arguments will be be done at
700 machine launch time, as it depends on the temporary directory
701 to be created.
702
703 @param device_type: the device type, such as "isa-serial". If
704 None is given (the default value) a "-serial
705 chardev:console" command line argument will
706 be used instead, resorting to the machine's
707 default device type.
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100708 @param console_index: the index of the console device to use.
709 If not zero, the command line will create
710 'index - 1' consoles and connect them to
711 the 'null' backing character device.
John Snowabf0bf92019-06-27 17:28:14 -0400712 """
713 self._console_set = True
714 self._console_device_type = device_type
Philippe Mathieu-Daudé746f2442020-01-21 00:51:56 +0100715 self._console_index = console_index
John Snowabf0bf92019-06-27 17:28:14 -0400716
717 @property
718 def console_socket(self):
719 """
720 Returns a socket connected to the console
721 """
722 if self._console_socket is None:
Robert Foley80ded8e2020-07-24 07:45:08 +0100723 self._console_socket = console_socket.ConsoleSocket(
724 self._console_address,
725 file=self._console_log_path,
726 drain=self._drain_console)
John Snowabf0bf92019-06-27 17:28:14 -0400727 return self._console_socket