]> git.ipfire.org Git - thirdparty/qemu.git/blame - python/qemu/machine/machine.py
python/qemu/machine: add method to retrieve QEMUMachine::binary field
[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
33956e47
MD
331 @property
332 def binary(self) -> str:
333 """Returns path to the QEMU binary"""
334 return self._binary
335
f12a282f 336 def _pre_launch(self) -> None:
74b56bb5 337 if self._qmp_set:
bd4c0ef4
MAL
338 if self._monitor_address is None:
339 self._sock_pair = socket.socketpair()
91e11db7 340 os.set_inheritable(self._sock_pair[0].fileno(), True)
bd4c0ef4 341 sock = self._sock_pair[1]
6eeb3de7 342 if isinstance(self._monitor_address, str):
c4e6023f 343 self._remove_files.append(self._monitor_address)
7f5f3ae7 344
5bbc5936
JS
345 sock_or_addr = self._monitor_address or sock
346 assert sock_or_addr is not None
347
beb6b57b 348 self._qmp_connection = QEMUMonitorProtocol(
5bbc5936 349 sock_or_addr,
7f5f3ae7 350 server=bool(self._monitor_address),
c4e6023f
JS
351 nickname=self._name
352 )
abf0bf99 353
1d4796cd
JS
354 if self._console_set:
355 self._cons_sock_pair = socket.socketpair()
356 os.set_inheritable(self._cons_sock_pair[0].fileno(), True)
357
63c33f3c
JS
358 # NOTE: Make sure any opened resources are *definitely* freed in
359 # _post_shutdown()!
360 # pylint: disable=consider-using-with
b306e26c 361 self._qemu_log_path = os.path.join(self.log_dir, self._name + ".log")
63c33f3c
JS
362 self._qemu_log_file = open(self._qemu_log_path, 'wb')
363
b1ca9919
JS
364 self._iolog = None
365 self._qemu_full_args = tuple(chain(
366 self._wrapper,
367 [self._binary],
368 self._base_args,
369 self._args
370 ))
371
f12a282f 372 def _post_launch(self) -> None:
bd4c0ef4
MAL
373 if self._sock_pair:
374 self._sock_pair[0].close()
1d4796cd
JS
375 if self._cons_sock_pair:
376 self._cons_sock_pair[0].close()
377
be1183e5 378 if self._qmp_connection:
7f5f3ae7
JS
379 if self._sock_pair:
380 self._qmp.connect()
381 else:
382 self._qmp.accept(self._qmp_timer)
abf0bf99 383
eb7a91d0
EGE
384 def _close_qemu_log_file(self) -> None:
385 if self._qemu_log_file is not None:
386 self._qemu_log_file.close()
387 self._qemu_log_file = None
388
f12a282f 389 def _post_shutdown(self) -> None:
a3842cb0
JS
390 """
391 Called to cleanup the VM instance after the process has exited.
392 May also be called after a failed launch.
393 """
9cccb330 394 LOG.debug("Cleaning up after VM process")
49a608b8
JS
395 try:
396 self._close_qmp_connection()
397 except Exception as err: # pylint: disable=broad-except
398 LOG.warning(
399 "Exception closing QMP connection: %s",
400 str(err) if str(err) else type(err).__name__
401 )
402 finally:
403 assert self._qmp_connection is None
671940e6 404
612b3ba2
JS
405 if self._sock_pair:
406 self._sock_pair[0].close()
407 self._sock_pair[1].close()
408 self._sock_pair = None
409
eb7a91d0 410 self._close_qemu_log_file()
abf0bf99 411
3c1e16c6
CR
412 self._load_io_log()
413
abf0bf99
JS
414 self._qemu_log_path = None
415
abf0bf99
JS
416 if self._temp_dir is not None:
417 shutil.rmtree(self._temp_dir)
418 self._temp_dir = None
419
32558ce7
HR
420 while len(self._remove_files) > 0:
421 self._remove_if_exists(self._remove_files.pop())
422
14661d93 423 exitcode = self.exitcode()
de6e08b5
JS
424 if (exitcode is not None and exitcode < 0
425 and not (self._user_killed and exitcode == -signal.SIGKILL)):
14661d93
JS
426 msg = 'qemu received signal %i; command: "%s"'
427 if self._qemu_full_args:
428 command = ' '.join(self._qemu_full_args)
429 else:
430 command = ''
431 LOG.warning(msg, -int(exitcode), command)
432
b9420e4f 433 self._quit_issued = False
de6e08b5 434 self._user_killed = False
14661d93
JS
435 self._launched = False
436
f12a282f 437 def launch(self) -> None:
abf0bf99
JS
438 """
439 Launch the VM and make sure we cleanup and expose the
440 command line/output in case of exception
441 """
442
443 if self._launched:
444 raise QEMUMachineError('VM already launched')
445
abf0bf99
JS
446 try:
447 self._launch()
50465f94 448 except BaseException as exc:
1611e6cf
JS
449 # We may have launched the process but it may
450 # have exited before we could connect via QMP.
451 # Assume the VM didn't launch or is exiting.
452 # If we don't wait for the process, exitcode() may still be
453 # 'None' by the time control is ceded back to the caller.
454 if self._launched:
455 self.wait()
456 else:
457 self._post_shutdown()
abf0bf99 458
50465f94
JS
459 if isinstance(exc, Exception):
460 raise VMLaunchFailure(
461 exitcode=self.exitcode(),
462 command=' '.join(self._qemu_full_args),
463 output=self._iolog
464 ) from exc
465
466 # Don't wrap 'BaseException'; doing so would downgrade
467 # that exception. However, we still want to clean up.
abf0bf99
JS
468 raise
469
f12a282f 470 def _launch(self) -> None:
abf0bf99
JS
471 """
472 Launch the VM and establish a QMP connection
473 """
abf0bf99 474 self._pre_launch()
abf0bf99 475 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
a0eae17a
JS
476
477 # Cleaning up of this subprocess is guaranteed by _do_shutdown.
478 # pylint: disable=consider-using-with
abf0bf99 479 self._popen = subprocess.Popen(self._qemu_full_args,
07b71233 480 stdin=subprocess.DEVNULL,
abf0bf99
JS
481 stdout=self._qemu_log_file,
482 stderr=subprocess.STDOUT,
483 shell=False,
484 close_fds=False)
1611e6cf 485 self._launched = True
abf0bf99
JS
486 self._post_launch()
487
49a608b8
JS
488 def _close_qmp_connection(self) -> None:
489 """
490 Close the underlying QMP connection, if any.
491
492 Dutifully report errors that occurred while closing, but assume
493 that any error encountered indicates an abnormal termination
494 process and not a failure to close.
495 """
496 if self._qmp_connection is None:
497 return
498
499 try:
500 self._qmp.close()
501 except EOFError:
502 # EOF can occur as an Exception here when using the Async
503 # QMP backend. It indicates that the server closed the
504 # stream. If we successfully issued 'quit' at any point,
505 # then this was expected. If the remote went away without
506 # our permission, it's worth reporting that as an abnormal
507 # shutdown case.
508 if not (self._user_killed or self._quit_issued):
509 raise
510 finally:
511 self._qmp_connection = None
512
e2c97f16
JS
513 def _early_cleanup(self) -> None:
514 """
515 Perform any cleanup that needs to happen before the VM exits.
a3842cb0 516
1611e6cf
JS
517 This method may be called twice upon shutdown, once each by soft
518 and hard shutdown in failover scenarios.
e2c97f16
JS
519 """
520 # If we keep the console socket open, we may deadlock waiting
521 # for QEMU to exit, while QEMU is waiting for the socket to
9323e79f 522 # become writable.
f0ec14c7
NP
523 if self._console_file is not None:
524 LOG.debug("Closing console file")
525 self._console_file.close()
526 self._console_file = None
527
e2c97f16 528 if self._console_socket is not None:
9cccb330 529 LOG.debug("Closing console socket")
e2c97f16
JS
530 self._console_socket.close()
531 self._console_socket = None
532
1d4796cd
JS
533 if self._cons_sock_pair:
534 self._cons_sock_pair[0].close()
535 self._cons_sock_pair[1].close()
536 self._cons_sock_pair = None
537
193bf1c0
JS
538 def _hard_shutdown(self) -> None:
539 """
540 Perform early cleanup, kill the VM, and wait for it to terminate.
541
542 :raise subprocess.Timeout: When timeout is exceeds 60 seconds
543 waiting for the QEMU process to terminate.
544 """
9cccb330 545 LOG.debug("Performing hard shutdown")
193bf1c0 546 self._early_cleanup()
9223fda4
JS
547 self._subp.kill()
548 self._subp.wait(timeout=60)
193bf1c0 549
b9420e4f 550 def _soft_shutdown(self, timeout: Optional[int]) -> None:
193bf1c0
JS
551 """
552 Perform early cleanup, attempt to gracefully shut down the VM, and wait
553 for it to terminate.
554
8226a4b8
JS
555 :param timeout: Timeout in seconds for graceful shutdown.
556 A value of None is an infinite wait.
193bf1c0
JS
557
558 :raise ConnectionReset: On QMP communication errors
559 :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
560 the QEMU process to terminate.
561 """
9cccb330
JS
562 LOG.debug("Attempting graceful termination")
563
193bf1c0
JS
564 self._early_cleanup()
565
9cccb330
JS
566 if self._quit_issued:
567 LOG.debug(
568 "Anticipating QEMU termination due to prior 'quit' command, "
569 "or explicit call to wait()"
570 )
571 else:
572 LOG.debug("Politely asking QEMU to terminate")
573
be1183e5 574 if self._qmp_connection:
49a608b8
JS
575 try:
576 if not self._quit_issued:
577 # May raise ExecInterruptedError or StateError if the
578 # connection dies or has *already* died.
579 self.qmp('quit')
580 finally:
581 # Regardless, we want to quiesce the connection.
582 self._close_qmp_connection()
3c6e5e8c
JS
583 elif not self._quit_issued:
584 LOG.debug(
585 "Not anticipating QEMU quit and no QMP connection present, "
586 "issuing SIGTERM"
587 )
588 self._subp.terminate()
193bf1c0
JS
589
590 # May raise subprocess.TimeoutExpired
9cccb330
JS
591 LOG.debug(
592 "Waiting (timeout=%s) for QEMU process (pid=%s) to terminate",
593 timeout, self._subp.pid
594 )
9223fda4 595 self._subp.wait(timeout=timeout)
193bf1c0 596
b9420e4f 597 def _do_shutdown(self, timeout: Optional[int]) -> None:
193bf1c0
JS
598 """
599 Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
600
8226a4b8
JS
601 :param timeout: Timeout in seconds for graceful shutdown.
602 A value of None is an infinite wait.
193bf1c0
JS
603
604 :raise AbnormalShutdown: When the VM could not be shut down gracefully.
605 The inner exception will likely be ConnectionReset or
606 subprocess.TimeoutExpired. In rare cases, non-graceful termination
607 may result in its own exceptions, likely subprocess.TimeoutExpired.
608 """
609 try:
b9420e4f 610 self._soft_shutdown(timeout)
193bf1c0 611 except Exception as exc:
9cccb330
JS
612 if isinstance(exc, subprocess.TimeoutExpired):
613 LOG.debug("Timed out waiting for QEMU process to exit")
614 LOG.debug("Graceful shutdown failed", exc_info=True)
615 LOG.debug("Falling back to hard shutdown")
193bf1c0
JS
616 self._hard_shutdown()
617 raise AbnormalShutdown("Could not perform graceful shutdown") \
618 from exc
619
b9420e4f 620 def shutdown(self,
c9b3045b 621 hard: bool = False,
8226a4b8 622 timeout: Optional[int] = 30) -> None:
abf0bf99 623 """
193bf1c0
JS
624 Terminate the VM (gracefully if possible) and perform cleanup.
625 Cleanup will always be performed.
626
627 If the VM has not yet been launched, or shutdown(), wait(), or kill()
628 have already been called, this method does nothing.
629
193bf1c0
JS
630 :param hard: When true, do not attempt graceful shutdown, and
631 suppress the SIGKILL warning log message.
632 :param timeout: Optional timeout in seconds for graceful shutdown.
8226a4b8 633 Default 30 seconds, A `None` value is an infinite wait.
abf0bf99 634 """
a3842cb0
JS
635 if not self._launched:
636 return
637
9cccb330
JS
638 LOG.debug("Shutting down VM appliance; timeout=%s", timeout)
639 if hard:
640 LOG.debug("Caller requests immediate termination of QEMU process.")
641
193bf1c0 642 try:
e0e925a6 643 if hard:
de6e08b5 644 self._user_killed = True
193bf1c0
JS
645 self._hard_shutdown()
646 else:
b9420e4f 647 self._do_shutdown(timeout)
193bf1c0
JS
648 finally:
649 self._post_shutdown()
abf0bf99 650
f12a282f 651 def kill(self) -> None:
193bf1c0
JS
652 """
653 Terminate the VM forcefully, wait for it to exit, and perform cleanup.
654 """
e0e925a6
VSO
655 self.shutdown(hard=True)
656
8226a4b8 657 def wait(self, timeout: Optional[int] = 30) -> None:
89528059
JS
658 """
659 Wait for the VM to power off and perform post-shutdown cleanup.
660
8226a4b8
JS
661 :param timeout: Optional timeout in seconds. Default 30 seconds.
662 A value of `None` is an infinite wait.
89528059 663 """
b9420e4f
JS
664 self._quit_issued = True
665 self.shutdown(timeout=timeout)
89528059 666
f12a282f 667 def set_qmp_monitor(self, enabled: bool = True) -> None:
74b56bb5
WSM
668 """
669 Set the QMP monitor.
670
671 @param enabled: if False, qmp monitor options will be removed from
672 the base arguments of the resulting QEMU command
673 line. Default is True.
5c02c865
JS
674
675 .. note:: Call this function before launch().
74b56bb5 676 """
be1183e5
JS
677 self._qmp_set = enabled
678
679 @property
beb6b57b 680 def _qmp(self) -> QEMUMonitorProtocol:
be1183e5
JS
681 if self._qmp_connection is None:
682 raise QEMUMachineError("Attempt to access QMP with no connection")
683 return self._qmp_connection
74b56bb5 684
aaa81ec6 685 @classmethod
c7daa57e
VSO
686 def _qmp_args(cls, conv_keys: bool,
687 args: Dict[str, Any]) -> Dict[str, object]:
688 if conv_keys:
689 return {k.replace('_', '-'): v for k, v in args.items()}
690
691 return args
abf0bf99 692
aaa81ec6 693 def qmp(self, cmd: str,
3f3c9b4c
VSO
694 args_dict: Optional[Dict[str, object]] = None,
695 conv_keys: Optional[bool] = None,
aaa81ec6
JS
696 **args: Any) -> QMPMessage:
697 """
698 Invoke a QMP command and return the response dict
699 """
3f3c9b4c
VSO
700 if args_dict is not None:
701 assert not args
702 assert conv_keys is None
703 args = args_dict
704 conv_keys = False
705
706 if conv_keys is None:
707 conv_keys = True
708
c7daa57e 709 qmp_args = self._qmp_args(conv_keys, args)
37274707 710 ret = self._qmp.cmd_raw(cmd, args=qmp_args)
b9420e4f
JS
711 if cmd == 'quit' and 'error' not in ret and 'return' in ret:
712 self._quit_issued = True
713 return ret
abf0bf99 714
684750ab 715 def cmd(self, cmd: str,
4e620ff4
VSO
716 args_dict: Optional[Dict[str, object]] = None,
717 conv_keys: Optional[bool] = None,
684750ab 718 **args: Any) -> QMPReturnValue:
abf0bf99
JS
719 """
720 Invoke a QMP command.
721 On success return the response dict.
722 On failure raise an exception.
723 """
4e620ff4
VSO
724 if args_dict is not None:
725 assert not args
726 assert conv_keys is None
727 args = args_dict
728 conv_keys = False
729
730 if conv_keys is None:
731 conv_keys = True
732
c7daa57e 733 qmp_args = self._qmp_args(conv_keys, args)
684750ab 734 ret = self._qmp.cmd(cmd, **qmp_args)
b9420e4f
JS
735 if cmd == 'quit':
736 self._quit_issued = True
737 return ret
abf0bf99 738
f12a282f 739 def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
abf0bf99
JS
740 """
741 Poll for one queued QMP events and return it
742 """
306dfcd6 743 if self._events:
abf0bf99
JS
744 return self._events.pop(0)
745 return self._qmp.pull_event(wait=wait)
746
f12a282f 747 def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
abf0bf99
JS
748 """
749 Poll for queued QMP events and return a list of dicts
750 """
751 events = self._qmp.get_events(wait=wait)
752 events.extend(self._events)
753 del self._events[:]
abf0bf99
JS
754 return events
755
756 @staticmethod
f12a282f 757 def event_match(event: Any, match: Optional[Any]) -> bool:
abf0bf99
JS
758 """
759 Check if an event matches optional match criteria.
760
761 The match criteria takes the form of a matching subdict. The event is
762 checked to be a superset of the subdict, recursively, with matching
763 values whenever the subdict values are not None.
764
765 This has a limitation that you cannot explicitly check for None values.
766
767 Examples, with the subdict queries on the left:
768 - None matches any object.
769 - {"foo": None} matches {"foo": {"bar": 1}}
770 - {"foo": None} matches {"foo": 5}
771 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
772 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
773 """
774 if match is None:
775 return True
776
777 try:
778 for key in match:
779 if key in event:
780 if not QEMUMachine.event_match(event[key], match[key]):
781 return False
782 else:
783 return False
784 return True
785 except TypeError:
786 # either match or event wasn't iterable (not a dict)
f12a282f 787 return bool(match == event)
abf0bf99 788
f12a282f
JS
789 def event_wait(self, name: str,
790 timeout: float = 60.0,
791 match: Optional[QMPMessage] = None) -> Optional[QMPMessage]:
abf0bf99
JS
792 """
793 event_wait waits for and returns a named event from QMP with a timeout.
794
795 name: The event to wait for.
796 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
797 match: Optional match criteria. See event_match for details.
798 """
799 return self.events_wait([(name, match)], timeout)
800
f12a282f
JS
801 def events_wait(self,
802 events: Sequence[Tuple[str, Any]],
803 timeout: float = 60.0) -> Optional[QMPMessage]:
abf0bf99 804 """
1847a4a8
JS
805 events_wait waits for and returns a single named event from QMP.
806 In the case of multiple qualifying events, this function returns the
807 first one.
abf0bf99 808
1847a4a8
JS
809 :param events: A sequence of (name, match_criteria) tuples.
810 The match criteria are optional and may be None.
811 See event_match for details.
812 :param timeout: Optional timeout, in seconds.
813 See QEMUMonitorProtocol.pull_event.
814
a4225303
JS
815 :raise asyncio.TimeoutError:
816 If timeout was non-zero and no matching events were found.
817
1847a4a8
JS
818 :return: A QMP event matching the filter criteria.
819 If timeout was 0 and no event matched, None.
abf0bf99 820 """
f12a282f 821 def _match(event: QMPMessage) -> bool:
abf0bf99 822 for name, match in events:
306dfcd6 823 if event['event'] == name and self.event_match(event, match):
abf0bf99
JS
824 return True
825 return False
826
1847a4a8
JS
827 event: Optional[QMPMessage]
828
abf0bf99
JS
829 # Search cached events
830 for event in self._events:
831 if _match(event):
832 self._events.remove(event)
833 return event
834
835 # Poll for new events
836 while True:
837 event = self._qmp.pull_event(wait=timeout)
1847a4a8
JS
838 if event is None:
839 # NB: None is only returned when timeout is false-ish.
a4225303 840 # Timeouts raise asyncio.TimeoutError instead!
1847a4a8 841 break
abf0bf99
JS
842 if _match(event):
843 return event
844 self._events.append(event)
845
846 return None
847
f12a282f 848 def get_log(self) -> Optional[str]:
abf0bf99
JS
849 """
850 After self.shutdown or failed qemu execution, this returns the output
851 of the qemu process.
852 """
853 return self._iolog
854
f12a282f 855 def add_args(self, *args: str) -> None:
abf0bf99
JS
856 """
857 Adds to the list of extra arguments to be given to the QEMU binary
858 """
859 self._args.extend(args)
860
f12a282f 861 def set_machine(self, machine_type: str) -> None:
abf0bf99
JS
862 """
863 Sets the machine type
864
865 If set, the machine type will be added to the base arguments
866 of the resulting QEMU command line.
867 """
868 self._machine = machine_type
869
f12a282f
JS
870 def set_console(self,
871 device_type: Optional[str] = None,
872 console_index: int = 0) -> None:
abf0bf99
JS
873 """
874 Sets the device type for a console device
875
876 If set, the console device and a backing character device will
877 be added to the base arguments of the resulting QEMU command
878 line.
879
880 This is a convenience method that will either use the provided
881 device type, or default to a "-serial chardev:console" command
882 line argument.
883
884 The actual setting of command line arguments will be be done at
885 machine launch time, as it depends on the temporary directory
886 to be created.
887
888 @param device_type: the device type, such as "isa-serial". If
889 None is given (the default value) a "-serial
890 chardev:console" command line argument will
891 be used instead, resorting to the machine's
892 default device type.
746f244d
PMD
893 @param console_index: the index of the console device to use.
894 If not zero, the command line will create
895 'index - 1' consoles and connect them to
896 the 'null' backing character device.
abf0bf99
JS
897 """
898 self._console_set = True
899 self._console_device_type = device_type
746f244d 900 self._console_index = console_index
abf0bf99
JS
901
902 @property
f12a282f 903 def console_socket(self) -> socket.socket:
abf0bf99
JS
904 """
905 Returns a socket connected to the console
906 """
907 if self._console_socket is None:
f0ec14c7 908 LOG.debug("Opening console socket")
1d4796cd
JS
909 if not self._console_set:
910 raise QEMUMachineError(
911 "Attempt to access console socket with no connection")
912 assert self._cons_sock_pair is not None
913 # os.dup() is used here for sock_fd because otherwise we'd
914 # have two rich python socket objects that would each try to
915 # close the same underlying fd when either one gets garbage
916 # collected.
80ded8e9 917 self._console_socket = console_socket.ConsoleSocket(
1d4796cd 918 sock_fd=os.dup(self._cons_sock_pair[1].fileno()),
80ded8e9
RF
919 file=self._console_log_path,
920 drain=self._drain_console)
1d4796cd 921 self._cons_sock_pair[1].close()
abf0bf99 922 return self._console_socket
2ca6e26c 923
f0ec14c7
NP
924 @property
925 def console_file(self) -> socket.SocketIO:
926 """
927 Returns a file associated with the console socket
928 """
929 if self._console_file is None:
930 LOG.debug("Opening console file")
931 self._console_file = self.console_socket.makefile(mode='rb',
932 buffering=0,
933 encoding='utf-8')
934 return self._console_file
935
2ca6e26c
CR
936 @property
937 def temp_dir(self) -> str:
938 """
939 Returns a temporary directory to be used for this machine
940 """
941 if self._temp_dir is None:
942 self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-",
943 dir=self._base_temp_dir)
944 return self._temp_dir
b306e26c
CR
945
946 @property
947 def log_dir(self) -> str:
948 """
949 Returns a directory to be used for writing logs
950 """
951 if self._log_dir is None:
952 return self.temp_dir
953 return self._log_dir