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