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