]> git.proxmox.com Git - mirror_qemu.git/blame - python/qemu/machine/machine.py
python/qemu/machine: QEMUMachine: improve qmp() method
[mirror_qemu.git] / python / qemu / machine / machine.py
CommitLineData
306dfcd6
JS
1"""
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
abf0bf99
JS
8# 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
aad3f3bb 21from itertools import chain
abf0bf99
JS
22import logging
23import os
abf0bf99 24import shutil
de6e08b5 25import signal
f12a282f 26import socket
932ca4bb 27import subprocess
abf0bf99 28import tempfile
1dda0404 29from types import TracebackType
aaa81ec6
JS
30from typing import (
31 Any,
f12a282f 32 BinaryIO,
aaa81ec6
JS
33 Dict,
34 List,
35 Optional,
aad3f3bb
JS
36 Sequence,
37 Tuple,
aaa81ec6
JS
38 Type,
39)
932ca4bb 40
d1e04769 41from qemu.qmp import ( # pylint: disable=import-error
beb6b57b
JS
42 QEMUMonitorProtocol,
43 QMPMessage,
44 QMPReturnValue,
45 SocketAddrT,
46)
47
48from . import console_socket
abf0bf99 49
abf0bf99
JS
50
51LOG = logging.getLogger(__name__)
52
8dfac2ed 53
abf0bf99
JS
54class QEMUMachineError(Exception):
55 """
56 Exception called when an error in QEMUMachine happens.
57 """
58
59
60class QEMUMachineAddDeviceError(QEMUMachineError):
61 """
62 Exception raised when a request to add a device can not be fulfilled
63
64 The failures are caused by limitations, lack of information or conflicting
65 requests on the QEMUMachine methods. This exception does not represent
66 failures reported by the QEMU binary itself.
67 """
68
69
193bf1c0
JS
70class AbnormalShutdown(QEMUMachineError):
71 """
72 Exception raised when a graceful shutdown was requested, but not performed.
73 """
74
75
9b8ccd6d 76class QEMUMachine:
abf0bf99 77 """
f12a282f 78 A QEMU VM.
abf0bf99 79
8dfac2ed
JS
80 Use this object as a context manager to ensure
81 the QEMU process terminates::
abf0bf99
JS
82
83 with VM(binary) as vm:
84 ...
85 # vm is guaranteed to be shut down here
86 """
82e6517d 87 # pylint: disable=too-many-instance-attributes, too-many-public-methods
abf0bf99 88
aad3f3bb
JS
89 def __init__(self,
90 binary: str,
91 args: Sequence[str] = (),
92 wrapper: Sequence[str] = (),
93 name: Optional[str] = None,
2ca6e26c 94 base_temp_dir: str = "/var/tmp",
c4e6023f 95 monitor_address: Optional[SocketAddrT] = None,
f12a282f
JS
96 socket_scm_helper: Optional[str] = None,
97 sock_dir: Optional[str] = None,
98 drain_console: bool = False,
b306e26c 99 console_log: Optional[str] = None,
e2f948a8
EGE
100 log_dir: Optional[str] = None,
101 qmp_timer: Optional[float] = None):
abf0bf99
JS
102 '''
103 Initialize a QEMUMachine
104
105 @param binary: path to the qemu binary
106 @param args: list of extra arguments
107 @param wrapper: list of arguments used as prefix to qemu binary
108 @param name: prefix for socket and log file names (default: qemu-PID)
859aeb67 109 @param base_temp_dir: default location where temp files are created
abf0bf99
JS
110 @param monitor_address: address for QMP monitor
111 @param socket_scm_helper: helper program, required for send_fd_scm()
2ca6e26c 112 @param sock_dir: where to create socket (defaults to base_temp_dir)
0fc8f660 113 @param drain_console: (optional) True to drain console socket to buffer
c5e61a6d 114 @param console_log: (optional) path to console log file
b306e26c 115 @param log_dir: where to create and keep log files
e2f948a8 116 @param qmp_timer: (optional) default QMP socket timeout
abf0bf99
JS
117 @note: Qemu process is not started until launch() is used.
118 '''
82e6517d
JS
119 # pylint: disable=too-many-arguments
120
c5e61a6d
JS
121 # Direct user configuration
122
123 self._binary = binary
c5e61a6d 124 self._args = list(args)
c5e61a6d 125 self._wrapper = wrapper
e2f948a8 126 self._qmp_timer = qmp_timer
c5e61a6d
JS
127
128 self._name = name or "qemu-%d" % os.getpid()
2ca6e26c
CR
129 self._base_temp_dir = base_temp_dir
130 self._sock_dir = sock_dir or self._base_temp_dir
b306e26c 131 self._log_dir = log_dir
c5e61a6d
JS
132 self._socket_scm_helper = socket_scm_helper
133
c4e6023f
JS
134 if monitor_address is not None:
135 self._monitor_address = monitor_address
136 self._remove_monitor_sockfile = False
137 else:
138 self._monitor_address = os.path.join(
c5e61a6d 139 self._sock_dir, f"{self._name}-monitor.sock"
c4e6023f
JS
140 )
141 self._remove_monitor_sockfile = True
c5e61a6d
JS
142
143 self._console_log_path = console_log
144 if self._console_log_path:
145 # In order to log the console, buffering needs to be enabled.
146 self._drain_console = True
147 else:
148 self._drain_console = drain_console
149
150 # Runstate
f12a282f
JS
151 self._qemu_log_path: Optional[str] = None
152 self._qemu_log_file: Optional[BinaryIO] = None
9223fda4 153 self._popen: Optional['subprocess.Popen[bytes]'] = None
f12a282f
JS
154 self._events: List[QMPMessage] = []
155 self._iolog: Optional[str] = None
74b56bb5 156 self._qmp_set = True # Enable QMP monitor by default.
beb6b57b 157 self._qmp_connection: Optional[QEMUMonitorProtocol] = None
aad3f3bb 158 self._qemu_full_args: Tuple[str, ...] = ()
f12a282f 159 self._temp_dir: Optional[str] = None
abf0bf99 160 self._launched = False
f12a282f 161 self._machine: Optional[str] = None
746f244d 162 self._console_index = 0
abf0bf99 163 self._console_set = False
f12a282f 164 self._console_device_type: Optional[str] = None
652809df
JS
165 self._console_address = os.path.join(
166 self._sock_dir, f"{self._name}-console.sock"
167 )
f12a282f
JS
168 self._console_socket: Optional[socket.socket] = None
169 self._remove_files: List[str] = []
de6e08b5 170 self._user_killed = False
abf0bf99 171
f12a282f 172 def __enter__(self) -> 'QEMUMachine':
abf0bf99
JS
173 return self
174
1dda0404
JS
175 def __exit__(self,
176 exc_type: Optional[Type[BaseException]],
177 exc_val: Optional[BaseException],
178 exc_tb: Optional[TracebackType]) -> None:
abf0bf99 179 self.shutdown()
abf0bf99 180
f12a282f 181 def add_monitor_null(self) -> None:
306dfcd6
JS
182 """
183 This can be used to add an unused monitor instance.
184 """
abf0bf99
JS
185 self._args.append('-monitor')
186 self._args.append('null')
187
f12a282f
JS
188 def add_fd(self, fd: int, fdset: int,
189 opaque: str, opts: str = '') -> 'QEMUMachine':
abf0bf99
JS
190 """
191 Pass a file descriptor to the VM
192 """
193 options = ['fd=%d' % fd,
194 'set=%d' % fdset,
195 'opaque=%s' % opaque]
196 if opts:
197 options.append(opts)
198
199 # This did not exist before 3.4, but since then it is
200 # mandatory for our purpose
201 if hasattr(os, 'set_inheritable'):
202 os.set_inheritable(fd, True)
203
204 self._args.append('-add-fd')
205 self._args.append(','.join(options))
206 return self
207
f12a282f
JS
208 def send_fd_scm(self, fd: Optional[int] = None,
209 file_path: Optional[str] = None) -> int:
306dfcd6
JS
210 """
211 Send an fd or file_path to socket_scm_helper.
212
213 Exactly one of fd and file_path must be given.
214 If it is file_path, the helper will open that file and pass its own fd.
215 """
abf0bf99
JS
216 # In iotest.py, the qmp should always use unix socket.
217 assert self._qmp.is_scm_available()
218 if self._socket_scm_helper is None:
219 raise QEMUMachineError("No path to socket_scm_helper set")
220 if not os.path.exists(self._socket_scm_helper):
221 raise QEMUMachineError("%s does not exist" %
222 self._socket_scm_helper)
223
224 # This did not exist before 3.4, but since then it is
225 # mandatory for our purpose
226 if hasattr(os, 'set_inheritable'):
227 os.set_inheritable(self._qmp.get_sock_fd(), True)
228 if fd is not None:
229 os.set_inheritable(fd, True)
230
231 fd_param = ["%s" % self._socket_scm_helper,
232 "%d" % self._qmp.get_sock_fd()]
233
234 if file_path is not None:
235 assert fd is None
236 fd_param.append(file_path)
237 else:
238 assert fd is not None
239 fd_param.append(str(fd))
240
14b41797
JS
241 proc = subprocess.run(
242 fd_param,
243 stdin=subprocess.DEVNULL,
244 stdout=subprocess.PIPE,
245 stderr=subprocess.STDOUT,
246 check=False,
247 close_fds=False,
8dfac2ed 248 )
14b41797
JS
249 if proc.stdout:
250 LOG.debug(proc.stdout)
abf0bf99
JS
251
252 return proc.returncode
253
254 @staticmethod
f12a282f 255 def _remove_if_exists(path: str) -> None:
abf0bf99
JS
256 """
257 Remove file object at path if it exists
258 """
259 try:
260 os.remove(path)
261 except OSError as exception:
262 if exception.errno == errno.ENOENT:
263 return
264 raise
265
f12a282f 266 def is_running(self) -> bool:
306dfcd6 267 """Returns true if the VM is running."""
abf0bf99
JS
268 return self._popen is not None and self._popen.poll() is None
269
9223fda4
JS
270 @property
271 def _subp(self) -> 'subprocess.Popen[bytes]':
272 if self._popen is None:
273 raise QEMUMachineError('Subprocess pipe not present')
274 return self._popen
275
f12a282f 276 def exitcode(self) -> Optional[int]:
306dfcd6 277 """Returns the exit code if possible, or None."""
abf0bf99
JS
278 if self._popen is None:
279 return None
280 return self._popen.poll()
281
f12a282f 282 def get_pid(self) -> Optional[int]:
306dfcd6 283 """Returns the PID of the running process, or None."""
abf0bf99
JS
284 if not self.is_running():
285 return None
9223fda4 286 return self._subp.pid
abf0bf99 287
f12a282f 288 def _load_io_log(self) -> None:
abf0bf99
JS
289 if self._qemu_log_path is not None:
290 with open(self._qemu_log_path, "r") as iolog:
291 self._iolog = iolog.read()
292
652809df
JS
293 @property
294 def _base_args(self) -> List[str]:
74b56bb5 295 args = ['-display', 'none', '-vga', 'none']
c4e6023f 296
74b56bb5
WSM
297 if self._qmp_set:
298 if isinstance(self._monitor_address, tuple):
c4e6023f
JS
299 moncdev = "socket,id=mon,host={},port={}".format(
300 *self._monitor_address
301 )
74b56bb5 302 else:
c4e6023f 303 moncdev = f"socket,id=mon,path={self._monitor_address}"
74b56bb5
WSM
304 args.extend(['-chardev', moncdev, '-mon',
305 'chardev=mon,mode=control'])
c4e6023f 306
abf0bf99
JS
307 if self._machine is not None:
308 args.extend(['-machine', self._machine])
9b8ccd6d 309 for _ in range(self._console_index):
746f244d 310 args.extend(['-serial', 'null'])
abf0bf99 311 if self._console_set:
991c180d 312 chardev = ('socket,id=console,path=%s,server=on,wait=off' %
abf0bf99
JS
313 self._console_address)
314 args.extend(['-chardev', chardev])
315 if self._console_device_type is None:
316 args.extend(['-serial', 'chardev:console'])
317 else:
318 device = '%s,chardev=console' % self._console_device_type
319 args.extend(['-device', device])
320 return args
321
555fe0c2
WSM
322 @property
323 def args(self) -> List[str]:
324 """Returns the list of arguments given to the QEMU binary."""
325 return self._args
326
f12a282f 327 def _pre_launch(self) -> None:
652809df
JS
328 if self._console_set:
329 self._remove_files.append(self._console_address)
330
74b56bb5 331 if self._qmp_set:
c4e6023f
JS
332 if self._remove_monitor_sockfile:
333 assert isinstance(self._monitor_address, str)
334 self._remove_files.append(self._monitor_address)
beb6b57b 335 self._qmp_connection = QEMUMonitorProtocol(
c4e6023f
JS
336 self._monitor_address,
337 server=True,
338 nickname=self._name
339 )
abf0bf99 340
63c33f3c
JS
341 # NOTE: Make sure any opened resources are *definitely* freed in
342 # _post_shutdown()!
343 # pylint: disable=consider-using-with
b306e26c 344 self._qemu_log_path = os.path.join(self.log_dir, self._name + ".log")
63c33f3c
JS
345 self._qemu_log_file = open(self._qemu_log_path, 'wb')
346
f12a282f 347 def _post_launch(self) -> None:
be1183e5 348 if self._qmp_connection:
e2f948a8 349 self._qmp.accept(self._qmp_timer)
abf0bf99 350
eb7a91d0
EGE
351 def _close_qemu_log_file(self) -> None:
352 if self._qemu_log_file is not None:
353 self._qemu_log_file.close()
354 self._qemu_log_file = None
355
f12a282f 356 def _post_shutdown(self) -> None:
a3842cb0
JS
357 """
358 Called to cleanup the VM instance after the process has exited.
359 May also be called after a failed launch.
360 """
361 # Comprehensive reset for the failed launch case:
362 self._early_cleanup()
363
be1183e5 364 if self._qmp_connection:
671940e6 365 self._qmp.close()
be1183e5 366 self._qmp_connection = None
671940e6 367
eb7a91d0 368 self._close_qemu_log_file()
abf0bf99 369
3c1e16c6
CR
370 self._load_io_log()
371
abf0bf99
JS
372 self._qemu_log_path = None
373
abf0bf99
JS
374 if self._temp_dir is not None:
375 shutil.rmtree(self._temp_dir)
376 self._temp_dir = None
377
32558ce7
HR
378 while len(self._remove_files) > 0:
379 self._remove_if_exists(self._remove_files.pop())
380
14661d93 381 exitcode = self.exitcode()
de6e08b5
JS
382 if (exitcode is not None and exitcode < 0
383 and not (self._user_killed and exitcode == -signal.SIGKILL)):
14661d93
JS
384 msg = 'qemu received signal %i; command: "%s"'
385 if self._qemu_full_args:
386 command = ' '.join(self._qemu_full_args)
387 else:
388 command = ''
389 LOG.warning(msg, -int(exitcode), command)
390
de6e08b5 391 self._user_killed = False
14661d93
JS
392 self._launched = False
393
f12a282f 394 def launch(self) -> None:
abf0bf99
JS
395 """
396 Launch the VM and make sure we cleanup and expose the
397 command line/output in case of exception
398 """
399
400 if self._launched:
401 raise QEMUMachineError('VM already launched')
402
403 self._iolog = None
aad3f3bb 404 self._qemu_full_args = ()
abf0bf99
JS
405 try:
406 self._launch()
407 self._launched = True
408 except:
a3842cb0 409 self._post_shutdown()
abf0bf99
JS
410
411 LOG.debug('Error launching VM')
412 if self._qemu_full_args:
413 LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
414 if self._iolog:
415 LOG.debug('Output: %r', self._iolog)
416 raise
417
f12a282f 418 def _launch(self) -> None:
abf0bf99
JS
419 """
420 Launch the VM and establish a QMP connection
421 """
abf0bf99 422 self._pre_launch()
aad3f3bb
JS
423 self._qemu_full_args = tuple(
424 chain(self._wrapper,
425 [self._binary],
426 self._base_args,
427 self._args)
428 )
abf0bf99 429 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
a0eae17a
JS
430
431 # Cleaning up of this subprocess is guaranteed by _do_shutdown.
432 # pylint: disable=consider-using-with
abf0bf99 433 self._popen = subprocess.Popen(self._qemu_full_args,
07b71233 434 stdin=subprocess.DEVNULL,
abf0bf99
JS
435 stdout=self._qemu_log_file,
436 stderr=subprocess.STDOUT,
437 shell=False,
438 close_fds=False)
439 self._post_launch()
440
e2c97f16
JS
441 def _early_cleanup(self) -> None:
442 """
443 Perform any cleanup that needs to happen before the VM exits.
a3842cb0 444
193bf1c0 445 May be invoked by both soft and hard shutdown in failover scenarios.
a3842cb0 446 Called additionally by _post_shutdown for comprehensive cleanup.
e2c97f16
JS
447 """
448 # If we keep the console socket open, we may deadlock waiting
449 # for QEMU to exit, while QEMU is waiting for the socket to
450 # become writeable.
451 if self._console_socket is not None:
452 self._console_socket.close()
453 self._console_socket = None
454
193bf1c0
JS
455 def _hard_shutdown(self) -> None:
456 """
457 Perform early cleanup, kill the VM, and wait for it to terminate.
458
459 :raise subprocess.Timeout: When timeout is exceeds 60 seconds
460 waiting for the QEMU process to terminate.
461 """
462 self._early_cleanup()
9223fda4
JS
463 self._subp.kill()
464 self._subp.wait(timeout=60)
193bf1c0 465
8226a4b8
JS
466 def _soft_shutdown(self, timeout: Optional[int],
467 has_quit: bool = False) -> None:
193bf1c0
JS
468 """
469 Perform early cleanup, attempt to gracefully shut down the VM, and wait
470 for it to terminate.
471
8226a4b8
JS
472 :param timeout: Timeout in seconds for graceful shutdown.
473 A value of None is an infinite wait.
193bf1c0 474 :param has_quit: When True, don't attempt to issue 'quit' QMP command
193bf1c0
JS
475
476 :raise ConnectionReset: On QMP communication errors
477 :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
478 the QEMU process to terminate.
479 """
480 self._early_cleanup()
481
be1183e5 482 if self._qmp_connection:
193bf1c0
JS
483 if not has_quit:
484 # Might raise ConnectionReset
485 self._qmp.cmd('quit')
486
487 # May raise subprocess.TimeoutExpired
9223fda4 488 self._subp.wait(timeout=timeout)
193bf1c0 489
8226a4b8
JS
490 def _do_shutdown(self, timeout: Optional[int],
491 has_quit: bool = False) -> None:
193bf1c0
JS
492 """
493 Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
494
8226a4b8
JS
495 :param timeout: Timeout in seconds for graceful shutdown.
496 A value of None is an infinite wait.
193bf1c0 497 :param has_quit: When True, don't attempt to issue 'quit' QMP command
193bf1c0
JS
498
499 :raise AbnormalShutdown: When the VM could not be shut down gracefully.
500 The inner exception will likely be ConnectionReset or
501 subprocess.TimeoutExpired. In rare cases, non-graceful termination
502 may result in its own exceptions, likely subprocess.TimeoutExpired.
503 """
504 try:
8226a4b8 505 self._soft_shutdown(timeout, has_quit)
193bf1c0
JS
506 except Exception as exc:
507 self._hard_shutdown()
508 raise AbnormalShutdown("Could not perform graceful shutdown") \
509 from exc
510
c9b3045b
JS
511 def shutdown(self, has_quit: bool = False,
512 hard: bool = False,
8226a4b8 513 timeout: Optional[int] = 30) -> None:
abf0bf99 514 """
193bf1c0
JS
515 Terminate the VM (gracefully if possible) and perform cleanup.
516 Cleanup will always be performed.
517
518 If the VM has not yet been launched, or shutdown(), wait(), or kill()
519 have already been called, this method does nothing.
520
521 :param has_quit: When true, do not attempt to issue 'quit' QMP command.
522 :param hard: When true, do not attempt graceful shutdown, and
523 suppress the SIGKILL warning log message.
524 :param timeout: Optional timeout in seconds for graceful shutdown.
8226a4b8 525 Default 30 seconds, A `None` value is an infinite wait.
abf0bf99 526 """
a3842cb0
JS
527 if not self._launched:
528 return
529
193bf1c0 530 try:
e0e925a6 531 if hard:
de6e08b5 532 self._user_killed = True
193bf1c0
JS
533 self._hard_shutdown()
534 else:
8226a4b8 535 self._do_shutdown(timeout, has_quit)
193bf1c0
JS
536 finally:
537 self._post_shutdown()
abf0bf99 538
f12a282f 539 def kill(self) -> None:
193bf1c0
JS
540 """
541 Terminate the VM forcefully, wait for it to exit, and perform cleanup.
542 """
e0e925a6
VSO
543 self.shutdown(hard=True)
544
8226a4b8 545 def wait(self, timeout: Optional[int] = 30) -> None:
89528059
JS
546 """
547 Wait for the VM to power off and perform post-shutdown cleanup.
548
8226a4b8
JS
549 :param timeout: Optional timeout in seconds. Default 30 seconds.
550 A value of `None` is an infinite wait.
89528059
JS
551 """
552 self.shutdown(has_quit=True, timeout=timeout)
553
f12a282f 554 def set_qmp_monitor(self, enabled: bool = True) -> None:
74b56bb5
WSM
555 """
556 Set the QMP monitor.
557
558 @param enabled: if False, qmp monitor options will be removed from
559 the base arguments of the resulting QEMU command
560 line. Default is True.
5c02c865
JS
561
562 .. note:: Call this function before launch().
74b56bb5 563 """
be1183e5
JS
564 self._qmp_set = enabled
565
566 @property
beb6b57b 567 def _qmp(self) -> QEMUMonitorProtocol:
be1183e5
JS
568 if self._qmp_connection is None:
569 raise QEMUMachineError("Attempt to access QMP with no connection")
570 return self._qmp_connection
74b56bb5 571
aaa81ec6 572 @classmethod
c7daa57e
VSO
573 def _qmp_args(cls, conv_keys: bool,
574 args: Dict[str, Any]) -> Dict[str, object]:
575 if conv_keys:
576 return {k.replace('_', '-'): v for k, v in args.items()}
577
578 return args
abf0bf99 579
aaa81ec6 580 def qmp(self, cmd: str,
3f3c9b4c
VSO
581 args_dict: Optional[Dict[str, object]] = None,
582 conv_keys: Optional[bool] = None,
aaa81ec6
JS
583 **args: Any) -> QMPMessage:
584 """
585 Invoke a QMP command and return the response dict
586 """
3f3c9b4c
VSO
587 if args_dict is not None:
588 assert not args
589 assert conv_keys is None
590 args = args_dict
591 conv_keys = False
592
593 if conv_keys is None:
594 conv_keys = True
595
c7daa57e 596 qmp_args = self._qmp_args(conv_keys, args)
abf0bf99
JS
597 return self._qmp.cmd(cmd, args=qmp_args)
598
f12a282f
JS
599 def command(self, cmd: str,
600 conv_keys: bool = True,
601 **args: Any) -> QMPReturnValue:
abf0bf99
JS
602 """
603 Invoke a QMP command.
604 On success return the response dict.
605 On failure raise an exception.
606 """
c7daa57e 607 qmp_args = self._qmp_args(conv_keys, args)
aaa81ec6 608 return self._qmp.command(cmd, **qmp_args)
abf0bf99 609
f12a282f 610 def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
abf0bf99
JS
611 """
612 Poll for one queued QMP events and return it
613 """
306dfcd6 614 if self._events:
abf0bf99
JS
615 return self._events.pop(0)
616 return self._qmp.pull_event(wait=wait)
617
f12a282f 618 def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
abf0bf99
JS
619 """
620 Poll for queued QMP events and return a list of dicts
621 """
622 events = self._qmp.get_events(wait=wait)
623 events.extend(self._events)
624 del self._events[:]
625 self._qmp.clear_events()
626 return events
627
628 @staticmethod
f12a282f 629 def event_match(event: Any, match: Optional[Any]) -> bool:
abf0bf99
JS
630 """
631 Check if an event matches optional match criteria.
632
633 The match criteria takes the form of a matching subdict. The event is
634 checked to be a superset of the subdict, recursively, with matching
635 values whenever the subdict values are not None.
636
637 This has a limitation that you cannot explicitly check for None values.
638
639 Examples, with the subdict queries on the left:
640 - None matches any object.
641 - {"foo": None} matches {"foo": {"bar": 1}}
642 - {"foo": None} matches {"foo": 5}
643 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
644 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
645 """
646 if match is None:
647 return True
648
649 try:
650 for key in match:
651 if key in event:
652 if not QEMUMachine.event_match(event[key], match[key]):
653 return False
654 else:
655 return False
656 return True
657 except TypeError:
658 # either match or event wasn't iterable (not a dict)
f12a282f 659 return bool(match == event)
abf0bf99 660
f12a282f
JS
661 def event_wait(self, name: str,
662 timeout: float = 60.0,
663 match: Optional[QMPMessage] = None) -> Optional[QMPMessage]:
abf0bf99
JS
664 """
665 event_wait waits for and returns a named event from QMP with a timeout.
666
667 name: The event to wait for.
668 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
669 match: Optional match criteria. See event_match for details.
670 """
671 return self.events_wait([(name, match)], timeout)
672
f12a282f
JS
673 def events_wait(self,
674 events: Sequence[Tuple[str, Any]],
675 timeout: float = 60.0) -> Optional[QMPMessage]:
abf0bf99 676 """
1847a4a8
JS
677 events_wait waits for and returns a single named event from QMP.
678 In the case of multiple qualifying events, this function returns the
679 first one.
abf0bf99 680
1847a4a8
JS
681 :param events: A sequence of (name, match_criteria) tuples.
682 The match criteria are optional and may be None.
683 See event_match for details.
684 :param timeout: Optional timeout, in seconds.
685 See QEMUMonitorProtocol.pull_event.
686
687 :raise QMPTimeoutError: If timeout was non-zero and no matching events
688 were found.
689 :return: A QMP event matching the filter criteria.
690 If timeout was 0 and no event matched, None.
abf0bf99 691 """
f12a282f 692 def _match(event: QMPMessage) -> bool:
abf0bf99 693 for name, match in events:
306dfcd6 694 if event['event'] == name and self.event_match(event, match):
abf0bf99
JS
695 return True
696 return False
697
1847a4a8
JS
698 event: Optional[QMPMessage]
699
abf0bf99
JS
700 # Search cached events
701 for event in self._events:
702 if _match(event):
703 self._events.remove(event)
704 return event
705
706 # Poll for new events
707 while True:
708 event = self._qmp.pull_event(wait=timeout)
1847a4a8
JS
709 if event is None:
710 # NB: None is only returned when timeout is false-ish.
711 # Timeouts raise QMPTimeoutError instead!
712 break
abf0bf99
JS
713 if _match(event):
714 return event
715 self._events.append(event)
716
717 return None
718
f12a282f 719 def get_log(self) -> Optional[str]:
abf0bf99
JS
720 """
721 After self.shutdown or failed qemu execution, this returns the output
722 of the qemu process.
723 """
724 return self._iolog
725
f12a282f 726 def add_args(self, *args: str) -> None:
abf0bf99
JS
727 """
728 Adds to the list of extra arguments to be given to the QEMU binary
729 """
730 self._args.extend(args)
731
f12a282f 732 def set_machine(self, machine_type: str) -> None:
abf0bf99
JS
733 """
734 Sets the machine type
735
736 If set, the machine type will be added to the base arguments
737 of the resulting QEMU command line.
738 """
739 self._machine = machine_type
740
f12a282f
JS
741 def set_console(self,
742 device_type: Optional[str] = None,
743 console_index: int = 0) -> None:
abf0bf99
JS
744 """
745 Sets the device type for a console device
746
747 If set, the console device and a backing character device will
748 be added to the base arguments of the resulting QEMU command
749 line.
750
751 This is a convenience method that will either use the provided
752 device type, or default to a "-serial chardev:console" command
753 line argument.
754
755 The actual setting of command line arguments will be be done at
756 machine launch time, as it depends on the temporary directory
757 to be created.
758
759 @param device_type: the device type, such as "isa-serial". If
760 None is given (the default value) a "-serial
761 chardev:console" command line argument will
762 be used instead, resorting to the machine's
763 default device type.
746f244d
PMD
764 @param console_index: the index of the console device to use.
765 If not zero, the command line will create
766 'index - 1' consoles and connect them to
767 the 'null' backing character device.
abf0bf99
JS
768 """
769 self._console_set = True
770 self._console_device_type = device_type
746f244d 771 self._console_index = console_index
abf0bf99
JS
772
773 @property
f12a282f 774 def console_socket(self) -> socket.socket:
abf0bf99
JS
775 """
776 Returns a socket connected to the console
777 """
778 if self._console_socket is None:
80ded8e9
RF
779 self._console_socket = console_socket.ConsoleSocket(
780 self._console_address,
781 file=self._console_log_path,
782 drain=self._drain_console)
abf0bf99 783 return self._console_socket
2ca6e26c
CR
784
785 @property
786 def temp_dir(self) -> str:
787 """
788 Returns a temporary directory to be used for this machine
789 """
790 if self._temp_dir is None:
791 self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-",
792 dir=self._base_temp_dir)
793 return self._temp_dir
b306e26c
CR
794
795 @property
796 def log_dir(self) -> str:
797 """
798 Returns a directory to be used for writing logs
799 """
800 if self._log_dir is None:
801 return self.temp_dir
802 return self._log_dir