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