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