xref: /openbmc/qemu/python/qemu/machine/machine.py (revision 33956e47)
1beb6b57bSJohn Snow"""
2beb6b57bSJohn SnowQEMU machine module:
3beb6b57bSJohn Snow
4beb6b57bSJohn SnowThe machine module primarily provides the QEMUMachine class,
5beb6b57bSJohn Snowwhich provides facilities for managing the lifetime of a QEMU VM.
6beb6b57bSJohn Snow"""
7beb6b57bSJohn Snow
8beb6b57bSJohn Snow# Copyright (C) 2015-2016 Red Hat Inc.
9beb6b57bSJohn Snow# Copyright (C) 2012 IBM Corp.
10beb6b57bSJohn Snow#
11beb6b57bSJohn Snow# Authors:
12beb6b57bSJohn Snow#  Fam Zheng <famz@redhat.com>
13beb6b57bSJohn Snow#
14beb6b57bSJohn Snow# This work is licensed under the terms of the GNU GPL, version 2.  See
15beb6b57bSJohn Snow# the COPYING file in the top-level directory.
16beb6b57bSJohn Snow#
17beb6b57bSJohn Snow# Based on qmp.py.
18beb6b57bSJohn Snow#
19beb6b57bSJohn Snow
20beb6b57bSJohn Snowimport errno
21beb6b57bSJohn Snowfrom itertools import chain
225690b437SJohn Snowimport locale
23beb6b57bSJohn Snowimport logging
24beb6b57bSJohn Snowimport os
25beb6b57bSJohn Snowimport shutil
26beb6b57bSJohn Snowimport signal
27beb6b57bSJohn Snowimport socket
28beb6b57bSJohn Snowimport subprocess
29beb6b57bSJohn Snowimport tempfile
30beb6b57bSJohn Snowfrom types import TracebackType
31beb6b57bSJohn Snowfrom typing import (
32beb6b57bSJohn Snow    Any,
33beb6b57bSJohn Snow    BinaryIO,
34beb6b57bSJohn Snow    Dict,
35beb6b57bSJohn Snow    List,
36beb6b57bSJohn Snow    Optional,
37beb6b57bSJohn Snow    Sequence,
38beb6b57bSJohn Snow    Tuple,
39beb6b57bSJohn Snow    Type,
4015c3b863SVladimir Sementsov-Ogievskiy    TypeVar,
41beb6b57bSJohn Snow)
42beb6b57bSJohn Snow
4337094b6dSJohn Snowfrom qemu.qmp import SocketAddrT
4437094b6dSJohn Snowfrom qemu.qmp.legacy import (
45a4225303SJohn Snow    QEMUMonitorProtocol,
46beb6b57bSJohn Snow    QMPMessage,
47beb6b57bSJohn Snow    QMPReturnValue,
48beb6b57bSJohn Snow)
49beb6b57bSJohn Snow
50beb6b57bSJohn Snowfrom . import console_socket
51beb6b57bSJohn Snow
52beb6b57bSJohn Snow
53beb6b57bSJohn SnowLOG = logging.getLogger(__name__)
54beb6b57bSJohn Snow
55beb6b57bSJohn Snow
56beb6b57bSJohn Snowclass QEMUMachineError(Exception):
57beb6b57bSJohn Snow    """
58beb6b57bSJohn Snow    Exception called when an error in QEMUMachine happens.
59beb6b57bSJohn Snow    """
60beb6b57bSJohn Snow
61beb6b57bSJohn Snow
62beb6b57bSJohn Snowclass QEMUMachineAddDeviceError(QEMUMachineError):
63beb6b57bSJohn Snow    """
64beb6b57bSJohn Snow    Exception raised when a request to add a device can not be fulfilled
65beb6b57bSJohn Snow
66beb6b57bSJohn Snow    The failures are caused by limitations, lack of information or conflicting
67beb6b57bSJohn Snow    requests on the QEMUMachine methods.  This exception does not represent
68beb6b57bSJohn Snow    failures reported by the QEMU binary itself.
69beb6b57bSJohn Snow    """
70beb6b57bSJohn Snow
71beb6b57bSJohn Snow
7250465f94SJohn Snowclass VMLaunchFailure(QEMUMachineError):
7350465f94SJohn Snow    """
7450465f94SJohn Snow    Exception raised when a VM launch was attempted, but failed.
7550465f94SJohn Snow    """
7650465f94SJohn Snow    def __init__(self, exitcode: Optional[int],
7750465f94SJohn Snow                 command: str, output: Optional[str]):
7850465f94SJohn Snow        super().__init__(exitcode, command, output)
7950465f94SJohn Snow        self.exitcode = exitcode
8050465f94SJohn Snow        self.command = command
8150465f94SJohn Snow        self.output = output
8250465f94SJohn Snow
8350465f94SJohn Snow    def __str__(self) -> str:
8450465f94SJohn Snow        ret = ''
8550465f94SJohn Snow        if self.__cause__ is not None:
8650465f94SJohn Snow            name = type(self.__cause__).__name__
8750465f94SJohn Snow            reason = str(self.__cause__)
8850465f94SJohn Snow            if reason:
8950465f94SJohn Snow                ret += f"{name}: {reason}"
9050465f94SJohn Snow            else:
9150465f94SJohn Snow                ret += f"{name}"
9250465f94SJohn Snow        ret += '\n'
9350465f94SJohn Snow
9450465f94SJohn Snow        if self.exitcode is not None:
9550465f94SJohn Snow            ret += f"\tExit code: {self.exitcode}\n"
9650465f94SJohn Snow        ret += f"\tCommand: {self.command}\n"
9750465f94SJohn Snow        ret += f"\tOutput: {self.output}\n"
9850465f94SJohn Snow        return ret
9950465f94SJohn Snow
10050465f94SJohn Snow
101beb6b57bSJohn Snowclass AbnormalShutdown(QEMUMachineError):
102beb6b57bSJohn Snow    """
103beb6b57bSJohn Snow    Exception raised when a graceful shutdown was requested, but not performed.
104beb6b57bSJohn Snow    """
105beb6b57bSJohn Snow
106beb6b57bSJohn Snow
10715c3b863SVladimir Sementsov-Ogievskiy_T = TypeVar('_T', bound='QEMUMachine')
10815c3b863SVladimir Sementsov-Ogievskiy
10915c3b863SVladimir Sementsov-Ogievskiy
110beb6b57bSJohn Snowclass QEMUMachine:
111beb6b57bSJohn Snow    """
112beb6b57bSJohn Snow    A QEMU VM.
113beb6b57bSJohn Snow
114beb6b57bSJohn Snow    Use this object as a context manager to ensure
115beb6b57bSJohn Snow    the QEMU process terminates::
116beb6b57bSJohn Snow
117beb6b57bSJohn Snow        with VM(binary) as vm:
118beb6b57bSJohn Snow            ...
119beb6b57bSJohn Snow        # vm is guaranteed to be shut down here
120beb6b57bSJohn Snow    """
12182e6517dSJohn Snow    # pylint: disable=too-many-instance-attributes, too-many-public-methods
122beb6b57bSJohn Snow
123beb6b57bSJohn Snow    def __init__(self,
124beb6b57bSJohn Snow                 binary: str,
125beb6b57bSJohn Snow                 args: Sequence[str] = (),
126beb6b57bSJohn Snow                 wrapper: Sequence[str] = (),
127beb6b57bSJohn Snow                 name: Optional[str] = None,
128beb6b57bSJohn Snow                 base_temp_dir: str = "/var/tmp",
129beb6b57bSJohn Snow                 monitor_address: Optional[SocketAddrT] = None,
130beb6b57bSJohn Snow                 drain_console: bool = False,
131b306e26cSCleber Rosa                 console_log: Optional[str] = None,
132e2f948a8SEmanuele Giuseppe Esposito                 log_dir: Optional[str] = None,
133ada73a49SVladimir Sementsov-Ogievskiy                 qmp_timer: Optional[float] = 30):
134beb6b57bSJohn Snow        '''
135beb6b57bSJohn Snow        Initialize a QEMUMachine
136beb6b57bSJohn Snow
137beb6b57bSJohn Snow        @param binary: path to the qemu binary
138beb6b57bSJohn Snow        @param args: list of extra arguments
139beb6b57bSJohn Snow        @param wrapper: list of arguments used as prefix to qemu binary
140beb6b57bSJohn Snow        @param name: prefix for socket and log file names (default: qemu-PID)
141beb6b57bSJohn Snow        @param base_temp_dir: default location where temp files are created
142beb6b57bSJohn Snow        @param monitor_address: address for QMP monitor
143beb6b57bSJohn Snow        @param drain_console: (optional) True to drain console socket to buffer
144beb6b57bSJohn Snow        @param console_log: (optional) path to console log file
145b306e26cSCleber Rosa        @param log_dir: where to create and keep log files
146e2f948a8SEmanuele Giuseppe Esposito        @param qmp_timer: (optional) default QMP socket timeout
147beb6b57bSJohn Snow        @note: Qemu process is not started until launch() is used.
148beb6b57bSJohn Snow        '''
14982e6517dSJohn Snow        # pylint: disable=too-many-arguments
15082e6517dSJohn Snow
151beb6b57bSJohn Snow        # Direct user configuration
152beb6b57bSJohn Snow
153beb6b57bSJohn Snow        self._binary = binary
154beb6b57bSJohn Snow        self._args = list(args)
155beb6b57bSJohn Snow        self._wrapper = wrapper
156e2f948a8SEmanuele Giuseppe Esposito        self._qmp_timer = qmp_timer
157beb6b57bSJohn Snow
158f9922937SPeter Delevoryas        self._name = name or f"{id(self):x}"
159bd4c0ef4SMarc-André Lureau        self._sock_pair: Optional[Tuple[socket.socket, socket.socket]] = None
1601d4796cdSJohn Snow        self._cons_sock_pair: Optional[
1611d4796cdSJohn Snow            Tuple[socket.socket, socket.socket]] = None
16287bf1fe5SJohn Snow        self._temp_dir: Optional[str] = None
163beb6b57bSJohn Snow        self._base_temp_dir = base_temp_dir
164b306e26cSCleber Rosa        self._log_dir = log_dir
165beb6b57bSJohn Snow
166beb6b57bSJohn Snow        self._monitor_address = monitor_address
167beb6b57bSJohn Snow
168beb6b57bSJohn Snow        self._console_log_path = console_log
169beb6b57bSJohn Snow        if self._console_log_path:
170beb6b57bSJohn Snow            # In order to log the console, buffering needs to be enabled.
171beb6b57bSJohn Snow            self._drain_console = True
172beb6b57bSJohn Snow        else:
173beb6b57bSJohn Snow            self._drain_console = drain_console
174beb6b57bSJohn Snow
175beb6b57bSJohn Snow        # Runstate
176beb6b57bSJohn Snow        self._qemu_log_path: Optional[str] = None
177beb6b57bSJohn Snow        self._qemu_log_file: Optional[BinaryIO] = None
178beb6b57bSJohn Snow        self._popen: Optional['subprocess.Popen[bytes]'] = None
179beb6b57bSJohn Snow        self._events: List[QMPMessage] = []
180beb6b57bSJohn Snow        self._iolog: Optional[str] = None
181beb6b57bSJohn Snow        self._qmp_set = True   # Enable QMP monitor by default.
182beb6b57bSJohn Snow        self._qmp_connection: Optional[QEMUMonitorProtocol] = None
183beb6b57bSJohn Snow        self._qemu_full_args: Tuple[str, ...] = ()
184beb6b57bSJohn Snow        self._launched = False
185beb6b57bSJohn Snow        self._machine: Optional[str] = None
186beb6b57bSJohn Snow        self._console_index = 0
187beb6b57bSJohn Snow        self._console_set = False
188beb6b57bSJohn Snow        self._console_device_type: Optional[str] = None
189beb6b57bSJohn Snow        self._console_socket: Optional[socket.socket] = None
190f0ec14c7SNicholas Piggin        self._console_file: Optional[socket.SocketIO] = None
191beb6b57bSJohn Snow        self._remove_files: List[str] = []
192beb6b57bSJohn Snow        self._user_killed = False
193b9420e4fSJohn Snow        self._quit_issued = False
194beb6b57bSJohn Snow
19515c3b863SVladimir Sementsov-Ogievskiy    def __enter__(self: _T) -> _T:
196beb6b57bSJohn Snow        return self
197beb6b57bSJohn Snow
198beb6b57bSJohn Snow    def __exit__(self,
199beb6b57bSJohn Snow                 exc_type: Optional[Type[BaseException]],
200beb6b57bSJohn Snow                 exc_val: Optional[BaseException],
201beb6b57bSJohn Snow                 exc_tb: Optional[TracebackType]) -> None:
202beb6b57bSJohn Snow        self.shutdown()
203beb6b57bSJohn Snow
204beb6b57bSJohn Snow    def add_monitor_null(self) -> None:
205beb6b57bSJohn Snow        """
206beb6b57bSJohn Snow        This can be used to add an unused monitor instance.
207beb6b57bSJohn Snow        """
208beb6b57bSJohn Snow        self._args.append('-monitor')
209beb6b57bSJohn Snow        self._args.append('null')
210beb6b57bSJohn Snow
21115c3b863SVladimir Sementsov-Ogievskiy    def add_fd(self: _T, fd: int, fdset: int,
21215c3b863SVladimir Sementsov-Ogievskiy               opaque: str, opts: str = '') -> _T:
213beb6b57bSJohn Snow        """
214beb6b57bSJohn Snow        Pass a file descriptor to the VM
215beb6b57bSJohn Snow        """
216beb6b57bSJohn Snow        options = ['fd=%d' % fd,
217beb6b57bSJohn Snow                   'set=%d' % fdset,
218beb6b57bSJohn Snow                   'opaque=%s' % opaque]
219beb6b57bSJohn Snow        if opts:
220beb6b57bSJohn Snow            options.append(opts)
221beb6b57bSJohn Snow
222beb6b57bSJohn Snow        # This did not exist before 3.4, but since then it is
223beb6b57bSJohn Snow        # mandatory for our purpose
224beb6b57bSJohn Snow        if hasattr(os, 'set_inheritable'):
225beb6b57bSJohn Snow            os.set_inheritable(fd, True)
226beb6b57bSJohn Snow
227beb6b57bSJohn Snow        self._args.append('-add-fd')
228beb6b57bSJohn Snow        self._args.append(','.join(options))
229beb6b57bSJohn Snow        return self
230beb6b57bSJohn Snow
231beb6b57bSJohn Snow    def send_fd_scm(self, fd: Optional[int] = None,
232beb6b57bSJohn Snow                    file_path: Optional[str] = None) -> int:
233beb6b57bSJohn Snow        """
234514d00dfSJohn Snow        Send an fd or file_path to the remote via SCM_RIGHTS.
235beb6b57bSJohn Snow
236514d00dfSJohn Snow        Exactly one of fd and file_path must be given.  If it is
237514d00dfSJohn Snow        file_path, the file will be opened read-only and the new file
238514d00dfSJohn Snow        descriptor will be sent to the remote.
239beb6b57bSJohn Snow        """
240beb6b57bSJohn Snow        if file_path is not None:
241beb6b57bSJohn Snow            assert fd is None
242514d00dfSJohn Snow            with open(file_path, "rb") as passfile:
243514d00dfSJohn Snow                fd = passfile.fileno()
244514d00dfSJohn Snow                self._qmp.send_fd_scm(fd)
245beb6b57bSJohn Snow        else:
246beb6b57bSJohn Snow            assert fd is not None
247514d00dfSJohn Snow            self._qmp.send_fd_scm(fd)
248beb6b57bSJohn Snow
249514d00dfSJohn Snow        return 0
250beb6b57bSJohn Snow
251beb6b57bSJohn Snow    @staticmethod
252beb6b57bSJohn Snow    def _remove_if_exists(path: str) -> None:
253beb6b57bSJohn Snow        """
254beb6b57bSJohn Snow        Remove file object at path if it exists
255beb6b57bSJohn Snow        """
256beb6b57bSJohn Snow        try:
257beb6b57bSJohn Snow            os.remove(path)
258beb6b57bSJohn Snow        except OSError as exception:
259beb6b57bSJohn Snow            if exception.errno == errno.ENOENT:
260beb6b57bSJohn Snow                return
261beb6b57bSJohn Snow            raise
262beb6b57bSJohn Snow
263beb6b57bSJohn Snow    def is_running(self) -> bool:
264beb6b57bSJohn Snow        """Returns true if the VM is running."""
265beb6b57bSJohn Snow        return self._popen is not None and self._popen.poll() is None
266beb6b57bSJohn Snow
267beb6b57bSJohn Snow    @property
268beb6b57bSJohn Snow    def _subp(self) -> 'subprocess.Popen[bytes]':
269beb6b57bSJohn Snow        if self._popen is None:
270beb6b57bSJohn Snow            raise QEMUMachineError('Subprocess pipe not present')
271beb6b57bSJohn Snow        return self._popen
272beb6b57bSJohn Snow
273beb6b57bSJohn Snow    def exitcode(self) -> Optional[int]:
274beb6b57bSJohn Snow        """Returns the exit code if possible, or None."""
275beb6b57bSJohn Snow        if self._popen is None:
276beb6b57bSJohn Snow            return None
277beb6b57bSJohn Snow        return self._popen.poll()
278beb6b57bSJohn Snow
279beb6b57bSJohn Snow    def get_pid(self) -> Optional[int]:
280beb6b57bSJohn Snow        """Returns the PID of the running process, or None."""
281beb6b57bSJohn Snow        if not self.is_running():
282beb6b57bSJohn Snow            return None
283beb6b57bSJohn Snow        return self._subp.pid
284beb6b57bSJohn Snow
285beb6b57bSJohn Snow    def _load_io_log(self) -> None:
2865690b437SJohn Snow        # Assume that the output encoding of QEMU's terminal output is
2875690b437SJohn Snow        # defined by our locale. If indeterminate, allow open() to fall
2885690b437SJohn Snow        # back to the platform default.
2895690b437SJohn Snow        _, encoding = locale.getlocale()
290beb6b57bSJohn Snow        if self._qemu_log_path is not None:
2915690b437SJohn Snow            with open(self._qemu_log_path, "r", encoding=encoding) as iolog:
292beb6b57bSJohn Snow                self._iolog = iolog.read()
293beb6b57bSJohn Snow
294beb6b57bSJohn Snow    @property
295beb6b57bSJohn Snow    def _base_args(self) -> List[str]:
296beb6b57bSJohn Snow        args = ['-display', 'none', '-vga', 'none']
297beb6b57bSJohn Snow
298beb6b57bSJohn Snow        if self._qmp_set:
299bd4c0ef4SMarc-André Lureau            if self._sock_pair:
30091e11db7SJohn Snow                moncdev = f"socket,id=mon,fd={self._sock_pair[0].fileno()}"
301bd4c0ef4SMarc-André Lureau            elif isinstance(self._monitor_address, tuple):
302beb6b57bSJohn Snow                moncdev = "socket,id=mon,host={},port={}".format(
303beb6b57bSJohn Snow                    *self._monitor_address
304beb6b57bSJohn Snow                )
305beb6b57bSJohn Snow            else:
306beb6b57bSJohn Snow                moncdev = f"socket,id=mon,path={self._monitor_address}"
307beb6b57bSJohn Snow            args.extend(['-chardev', moncdev, '-mon',
308beb6b57bSJohn Snow                         'chardev=mon,mode=control'])
309beb6b57bSJohn Snow
310beb6b57bSJohn Snow        if self._machine is not None:
311beb6b57bSJohn Snow            args.extend(['-machine', self._machine])
312beb6b57bSJohn Snow        for _ in range(self._console_index):
313beb6b57bSJohn Snow            args.extend(['-serial', 'null'])
314beb6b57bSJohn Snow        if self._console_set:
3151d4796cdSJohn Snow            assert self._cons_sock_pair is not None
3161d4796cdSJohn Snow            fd = self._cons_sock_pair[0].fileno()
3171d4796cdSJohn Snow            chardev = f"socket,id=console,fd={fd}"
318beb6b57bSJohn Snow            args.extend(['-chardev', chardev])
319beb6b57bSJohn Snow            if self._console_device_type is None:
320beb6b57bSJohn Snow                args.extend(['-serial', 'chardev:console'])
321beb6b57bSJohn Snow            else:
322beb6b57bSJohn Snow                device = '%s,chardev=console' % self._console_device_type
323beb6b57bSJohn Snow                args.extend(['-device', device])
324beb6b57bSJohn Snow        return args
325beb6b57bSJohn Snow
326555fe0c2SWainer dos Santos Moschetta    @property
327555fe0c2SWainer dos Santos Moschetta    def args(self) -> List[str]:
328555fe0c2SWainer dos Santos Moschetta        """Returns the list of arguments given to the QEMU binary."""
329555fe0c2SWainer dos Santos Moschetta        return self._args
330555fe0c2SWainer dos Santos Moschetta
331*33956e47SMaksim Davydov    @property
332*33956e47SMaksim Davydov    def binary(self) -> str:
333*33956e47SMaksim Davydov        """Returns path to the QEMU binary"""
334*33956e47SMaksim Davydov        return self._binary
335*33956e47SMaksim Davydov
336beb6b57bSJohn Snow    def _pre_launch(self) -> None:
337beb6b57bSJohn Snow        if self._qmp_set:
338bd4c0ef4SMarc-André Lureau            if self._monitor_address is None:
339bd4c0ef4SMarc-André Lureau                self._sock_pair = socket.socketpair()
34091e11db7SJohn Snow                os.set_inheritable(self._sock_pair[0].fileno(), True)
341bd4c0ef4SMarc-André Lureau                sock = self._sock_pair[1]
3426eeb3de7SJohn Snow            if isinstance(self._monitor_address, str):
343beb6b57bSJohn Snow                self._remove_files.append(self._monitor_address)
3447f5f3ae7SJohn Snow
3455bbc5936SJohn Snow            sock_or_addr = self._monitor_address or sock
3465bbc5936SJohn Snow            assert sock_or_addr is not None
3475bbc5936SJohn Snow
348beb6b57bSJohn Snow            self._qmp_connection = QEMUMonitorProtocol(
3495bbc5936SJohn Snow                sock_or_addr,
3507f5f3ae7SJohn Snow                server=bool(self._monitor_address),
351beb6b57bSJohn Snow                nickname=self._name
352beb6b57bSJohn Snow            )
353beb6b57bSJohn Snow
3541d4796cdSJohn Snow        if self._console_set:
3551d4796cdSJohn Snow            self._cons_sock_pair = socket.socketpair()
3561d4796cdSJohn Snow            os.set_inheritable(self._cons_sock_pair[0].fileno(), True)
3571d4796cdSJohn Snow
358beb6b57bSJohn Snow        # NOTE: Make sure any opened resources are *definitely* freed in
359beb6b57bSJohn Snow        # _post_shutdown()!
360beb6b57bSJohn Snow        # pylint: disable=consider-using-with
361b306e26cSCleber Rosa        self._qemu_log_path = os.path.join(self.log_dir, self._name + ".log")
362beb6b57bSJohn Snow        self._qemu_log_file = open(self._qemu_log_path, 'wb')
363beb6b57bSJohn Snow
364b1ca9919SJohn Snow        self._iolog = None
365b1ca9919SJohn Snow        self._qemu_full_args = tuple(chain(
366b1ca9919SJohn Snow            self._wrapper,
367b1ca9919SJohn Snow            [self._binary],
368b1ca9919SJohn Snow            self._base_args,
369b1ca9919SJohn Snow            self._args
370b1ca9919SJohn Snow        ))
371b1ca9919SJohn Snow
372beb6b57bSJohn Snow    def _post_launch(self) -> None:
373bd4c0ef4SMarc-André Lureau        if self._sock_pair:
374bd4c0ef4SMarc-André Lureau            self._sock_pair[0].close()
3751d4796cdSJohn Snow        if self._cons_sock_pair:
3761d4796cdSJohn Snow            self._cons_sock_pair[0].close()
3771d4796cdSJohn Snow
378beb6b57bSJohn Snow        if self._qmp_connection:
3797f5f3ae7SJohn Snow            if self._sock_pair:
3807f5f3ae7SJohn Snow                self._qmp.connect()
3817f5f3ae7SJohn Snow            else:
382e2f948a8SEmanuele Giuseppe Esposito                self._qmp.accept(self._qmp_timer)
383beb6b57bSJohn Snow
384eb7a91d0SEmanuele Giuseppe Esposito    def _close_qemu_log_file(self) -> None:
385eb7a91d0SEmanuele Giuseppe Esposito        if self._qemu_log_file is not None:
386eb7a91d0SEmanuele Giuseppe Esposito            self._qemu_log_file.close()
387eb7a91d0SEmanuele Giuseppe Esposito            self._qemu_log_file = None
388eb7a91d0SEmanuele Giuseppe Esposito
389beb6b57bSJohn Snow    def _post_shutdown(self) -> None:
390beb6b57bSJohn Snow        """
391beb6b57bSJohn Snow        Called to cleanup the VM instance after the process has exited.
392beb6b57bSJohn Snow        May also be called after a failed launch.
393beb6b57bSJohn Snow        """
3949cccb330SJohn Snow        LOG.debug("Cleaning up after VM process")
39549a608b8SJohn Snow        try:
39649a608b8SJohn Snow            self._close_qmp_connection()
39749a608b8SJohn Snow        except Exception as err:  # pylint: disable=broad-except
39849a608b8SJohn Snow            LOG.warning(
39949a608b8SJohn Snow                "Exception closing QMP connection: %s",
40049a608b8SJohn Snow                str(err) if str(err) else type(err).__name__
40149a608b8SJohn Snow            )
40249a608b8SJohn Snow        finally:
40349a608b8SJohn Snow            assert self._qmp_connection is None
404beb6b57bSJohn Snow
405612b3ba2SJohn Snow        if self._sock_pair:
406612b3ba2SJohn Snow            self._sock_pair[0].close()
407612b3ba2SJohn Snow            self._sock_pair[1].close()
408612b3ba2SJohn Snow            self._sock_pair = None
409612b3ba2SJohn Snow
410eb7a91d0SEmanuele Giuseppe Esposito        self._close_qemu_log_file()
411beb6b57bSJohn Snow
412beb6b57bSJohn Snow        self._load_io_log()
413beb6b57bSJohn Snow
414beb6b57bSJohn Snow        self._qemu_log_path = None
415beb6b57bSJohn Snow
416beb6b57bSJohn Snow        if self._temp_dir is not None:
417beb6b57bSJohn Snow            shutil.rmtree(self._temp_dir)
418beb6b57bSJohn Snow            self._temp_dir = None
419beb6b57bSJohn Snow
420beb6b57bSJohn Snow        while len(self._remove_files) > 0:
421beb6b57bSJohn Snow            self._remove_if_exists(self._remove_files.pop())
422beb6b57bSJohn Snow
423beb6b57bSJohn Snow        exitcode = self.exitcode()
424beb6b57bSJohn Snow        if (exitcode is not None and exitcode < 0
425beb6b57bSJohn Snow                and not (self._user_killed and exitcode == -signal.SIGKILL)):
426beb6b57bSJohn Snow            msg = 'qemu received signal %i; command: "%s"'
427beb6b57bSJohn Snow            if self._qemu_full_args:
428beb6b57bSJohn Snow                command = ' '.join(self._qemu_full_args)
429beb6b57bSJohn Snow            else:
430beb6b57bSJohn Snow                command = ''
431beb6b57bSJohn Snow            LOG.warning(msg, -int(exitcode), command)
432beb6b57bSJohn Snow
433b9420e4fSJohn Snow        self._quit_issued = False
434beb6b57bSJohn Snow        self._user_killed = False
435beb6b57bSJohn Snow        self._launched = False
436beb6b57bSJohn Snow
437beb6b57bSJohn Snow    def launch(self) -> None:
438beb6b57bSJohn Snow        """
439beb6b57bSJohn Snow        Launch the VM and make sure we cleanup and expose the
440beb6b57bSJohn Snow        command line/output in case of exception
441beb6b57bSJohn Snow        """
442beb6b57bSJohn Snow
443beb6b57bSJohn Snow        if self._launched:
444beb6b57bSJohn Snow            raise QEMUMachineError('VM already launched')
445beb6b57bSJohn Snow
446beb6b57bSJohn Snow        try:
447beb6b57bSJohn Snow            self._launch()
44850465f94SJohn Snow        except BaseException as exc:
4491611e6cfSJohn Snow            # We may have launched the process but it may
4501611e6cfSJohn Snow            # have exited before we could connect via QMP.
4511611e6cfSJohn Snow            # Assume the VM didn't launch or is exiting.
4521611e6cfSJohn Snow            # If we don't wait for the process, exitcode() may still be
4531611e6cfSJohn Snow            # 'None' by the time control is ceded back to the caller.
4541611e6cfSJohn Snow            if self._launched:
4551611e6cfSJohn Snow                self.wait()
4561611e6cfSJohn Snow            else:
457beb6b57bSJohn Snow                self._post_shutdown()
458beb6b57bSJohn Snow
45950465f94SJohn Snow            if isinstance(exc, Exception):
46050465f94SJohn Snow                raise VMLaunchFailure(
46150465f94SJohn Snow                    exitcode=self.exitcode(),
46250465f94SJohn Snow                    command=' '.join(self._qemu_full_args),
46350465f94SJohn Snow                    output=self._iolog
46450465f94SJohn Snow                ) from exc
46550465f94SJohn Snow
46650465f94SJohn Snow            # Don't wrap 'BaseException'; doing so would downgrade
46750465f94SJohn Snow            # that exception. However, we still want to clean up.
468beb6b57bSJohn Snow            raise
469beb6b57bSJohn Snow
470beb6b57bSJohn Snow    def _launch(self) -> None:
471beb6b57bSJohn Snow        """
472beb6b57bSJohn Snow        Launch the VM and establish a QMP connection
473beb6b57bSJohn Snow        """
474beb6b57bSJohn Snow        self._pre_launch()
475beb6b57bSJohn Snow        LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
476beb6b57bSJohn Snow
477beb6b57bSJohn Snow        # Cleaning up of this subprocess is guaranteed by _do_shutdown.
478beb6b57bSJohn Snow        # pylint: disable=consider-using-with
479beb6b57bSJohn Snow        self._popen = subprocess.Popen(self._qemu_full_args,
480beb6b57bSJohn Snow                                       stdin=subprocess.DEVNULL,
481beb6b57bSJohn Snow                                       stdout=self._qemu_log_file,
482beb6b57bSJohn Snow                                       stderr=subprocess.STDOUT,
483beb6b57bSJohn Snow                                       shell=False,
484beb6b57bSJohn Snow                                       close_fds=False)
4851611e6cfSJohn Snow        self._launched = True
486beb6b57bSJohn Snow        self._post_launch()
487beb6b57bSJohn Snow
48849a608b8SJohn Snow    def _close_qmp_connection(self) -> None:
48949a608b8SJohn Snow        """
49049a608b8SJohn Snow        Close the underlying QMP connection, if any.
49149a608b8SJohn Snow
49249a608b8SJohn Snow        Dutifully report errors that occurred while closing, but assume
49349a608b8SJohn Snow        that any error encountered indicates an abnormal termination
49449a608b8SJohn Snow        process and not a failure to close.
49549a608b8SJohn Snow        """
49649a608b8SJohn Snow        if self._qmp_connection is None:
49749a608b8SJohn Snow            return
49849a608b8SJohn Snow
49949a608b8SJohn Snow        try:
50049a608b8SJohn Snow            self._qmp.close()
50149a608b8SJohn Snow        except EOFError:
50249a608b8SJohn Snow            # EOF can occur as an Exception here when using the Async
50349a608b8SJohn Snow            # QMP backend. It indicates that the server closed the
50449a608b8SJohn Snow            # stream. If we successfully issued 'quit' at any point,
50549a608b8SJohn Snow            # then this was expected. If the remote went away without
50649a608b8SJohn Snow            # our permission, it's worth reporting that as an abnormal
50749a608b8SJohn Snow            # shutdown case.
50849a608b8SJohn Snow            if not (self._user_killed or self._quit_issued):
50949a608b8SJohn Snow                raise
51049a608b8SJohn Snow        finally:
51149a608b8SJohn Snow            self._qmp_connection = None
51249a608b8SJohn Snow
513beb6b57bSJohn Snow    def _early_cleanup(self) -> None:
514beb6b57bSJohn Snow        """
515beb6b57bSJohn Snow        Perform any cleanup that needs to happen before the VM exits.
516beb6b57bSJohn Snow
5171611e6cfSJohn Snow        This method may be called twice upon shutdown, once each by soft
5181611e6cfSJohn Snow        and hard shutdown in failover scenarios.
519beb6b57bSJohn Snow        """
520beb6b57bSJohn Snow        # If we keep the console socket open, we may deadlock waiting
521beb6b57bSJohn Snow        # for QEMU to exit, while QEMU is waiting for the socket to
5229323e79fSPeter Maydell        # become writable.
523f0ec14c7SNicholas Piggin        if self._console_file is not None:
524f0ec14c7SNicholas Piggin            LOG.debug("Closing console file")
525f0ec14c7SNicholas Piggin            self._console_file.close()
526f0ec14c7SNicholas Piggin            self._console_file = None
527f0ec14c7SNicholas Piggin
528beb6b57bSJohn Snow        if self._console_socket is not None:
5299cccb330SJohn Snow            LOG.debug("Closing console socket")
530beb6b57bSJohn Snow            self._console_socket.close()
531beb6b57bSJohn Snow            self._console_socket = None
532beb6b57bSJohn Snow
5331d4796cdSJohn Snow        if self._cons_sock_pair:
5341d4796cdSJohn Snow            self._cons_sock_pair[0].close()
5351d4796cdSJohn Snow            self._cons_sock_pair[1].close()
5361d4796cdSJohn Snow            self._cons_sock_pair = None
5371d4796cdSJohn Snow
538beb6b57bSJohn Snow    def _hard_shutdown(self) -> None:
539beb6b57bSJohn Snow        """
540beb6b57bSJohn Snow        Perform early cleanup, kill the VM, and wait for it to terminate.
541beb6b57bSJohn Snow
542beb6b57bSJohn Snow        :raise subprocess.Timeout: When timeout is exceeds 60 seconds
543beb6b57bSJohn Snow            waiting for the QEMU process to terminate.
544beb6b57bSJohn Snow        """
5459cccb330SJohn Snow        LOG.debug("Performing hard shutdown")
546beb6b57bSJohn Snow        self._early_cleanup()
547beb6b57bSJohn Snow        self._subp.kill()
548beb6b57bSJohn Snow        self._subp.wait(timeout=60)
549beb6b57bSJohn Snow
550b9420e4fSJohn Snow    def _soft_shutdown(self, timeout: Optional[int]) -> None:
551beb6b57bSJohn Snow        """
552beb6b57bSJohn Snow        Perform early cleanup, attempt to gracefully shut down the VM, and wait
553beb6b57bSJohn Snow        for it to terminate.
554beb6b57bSJohn Snow
555beb6b57bSJohn Snow        :param timeout: Timeout in seconds for graceful shutdown.
556beb6b57bSJohn Snow                        A value of None is an infinite wait.
557beb6b57bSJohn Snow
558beb6b57bSJohn Snow        :raise ConnectionReset: On QMP communication errors
559beb6b57bSJohn Snow        :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
560beb6b57bSJohn Snow            the QEMU process to terminate.
561beb6b57bSJohn Snow        """
5629cccb330SJohn Snow        LOG.debug("Attempting graceful termination")
5639cccb330SJohn Snow
564beb6b57bSJohn Snow        self._early_cleanup()
565beb6b57bSJohn Snow
5669cccb330SJohn Snow        if self._quit_issued:
5679cccb330SJohn Snow            LOG.debug(
5689cccb330SJohn Snow                "Anticipating QEMU termination due to prior 'quit' command, "
5699cccb330SJohn Snow                "or explicit call to wait()"
5709cccb330SJohn Snow            )
5719cccb330SJohn Snow        else:
5729cccb330SJohn Snow            LOG.debug("Politely asking QEMU to terminate")
5739cccb330SJohn Snow
574beb6b57bSJohn Snow        if self._qmp_connection:
57549a608b8SJohn Snow            try:
576b9420e4fSJohn Snow                if not self._quit_issued:
57749a608b8SJohn Snow                    # May raise ExecInterruptedError or StateError if the
57849a608b8SJohn Snow                    # connection dies or has *already* died.
579b9420e4fSJohn Snow                    self.qmp('quit')
58049a608b8SJohn Snow            finally:
58149a608b8SJohn Snow                # Regardless, we want to quiesce the connection.
58249a608b8SJohn Snow                self._close_qmp_connection()
5833c6e5e8cSJohn Snow        elif not self._quit_issued:
5843c6e5e8cSJohn Snow            LOG.debug(
5853c6e5e8cSJohn Snow                "Not anticipating QEMU quit and no QMP connection present, "
5863c6e5e8cSJohn Snow                "issuing SIGTERM"
5873c6e5e8cSJohn Snow            )
5883c6e5e8cSJohn Snow            self._subp.terminate()
589beb6b57bSJohn Snow
590beb6b57bSJohn Snow        # May raise subprocess.TimeoutExpired
5919cccb330SJohn Snow        LOG.debug(
5929cccb330SJohn Snow            "Waiting (timeout=%s) for QEMU process (pid=%s) to terminate",
5939cccb330SJohn Snow            timeout, self._subp.pid
5949cccb330SJohn Snow        )
595beb6b57bSJohn Snow        self._subp.wait(timeout=timeout)
596beb6b57bSJohn Snow
597b9420e4fSJohn Snow    def _do_shutdown(self, timeout: Optional[int]) -> None:
598beb6b57bSJohn Snow        """
599beb6b57bSJohn Snow        Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
600beb6b57bSJohn Snow
601beb6b57bSJohn Snow        :param timeout: Timeout in seconds for graceful shutdown.
602beb6b57bSJohn Snow                        A value of None is an infinite wait.
603beb6b57bSJohn Snow
604beb6b57bSJohn Snow        :raise AbnormalShutdown: When the VM could not be shut down gracefully.
605beb6b57bSJohn Snow            The inner exception will likely be ConnectionReset or
606beb6b57bSJohn Snow            subprocess.TimeoutExpired. In rare cases, non-graceful termination
607beb6b57bSJohn Snow            may result in its own exceptions, likely subprocess.TimeoutExpired.
608beb6b57bSJohn Snow        """
609beb6b57bSJohn Snow        try:
610b9420e4fSJohn Snow            self._soft_shutdown(timeout)
611beb6b57bSJohn Snow        except Exception as exc:
6129cccb330SJohn Snow            if isinstance(exc, subprocess.TimeoutExpired):
6139cccb330SJohn Snow                LOG.debug("Timed out waiting for QEMU process to exit")
6149cccb330SJohn Snow            LOG.debug("Graceful shutdown failed", exc_info=True)
6159cccb330SJohn Snow            LOG.debug("Falling back to hard shutdown")
616beb6b57bSJohn Snow            self._hard_shutdown()
617beb6b57bSJohn Snow            raise AbnormalShutdown("Could not perform graceful shutdown") \
618beb6b57bSJohn Snow                from exc
619beb6b57bSJohn Snow
620b9420e4fSJohn Snow    def shutdown(self,
621beb6b57bSJohn Snow                 hard: bool = False,
622beb6b57bSJohn Snow                 timeout: Optional[int] = 30) -> None:
623beb6b57bSJohn Snow        """
624beb6b57bSJohn Snow        Terminate the VM (gracefully if possible) and perform cleanup.
625beb6b57bSJohn Snow        Cleanup will always be performed.
626beb6b57bSJohn Snow
627beb6b57bSJohn Snow        If the VM has not yet been launched, or shutdown(), wait(), or kill()
628beb6b57bSJohn Snow        have already been called, this method does nothing.
629beb6b57bSJohn Snow
630beb6b57bSJohn Snow        :param hard: When true, do not attempt graceful shutdown, and
631beb6b57bSJohn Snow                     suppress the SIGKILL warning log message.
632beb6b57bSJohn Snow        :param timeout: Optional timeout in seconds for graceful shutdown.
633beb6b57bSJohn Snow                        Default 30 seconds, A `None` value is an infinite wait.
634beb6b57bSJohn Snow        """
635beb6b57bSJohn Snow        if not self._launched:
636beb6b57bSJohn Snow            return
637beb6b57bSJohn Snow
6389cccb330SJohn Snow        LOG.debug("Shutting down VM appliance; timeout=%s", timeout)
6399cccb330SJohn Snow        if hard:
6409cccb330SJohn Snow            LOG.debug("Caller requests immediate termination of QEMU process.")
6419cccb330SJohn Snow
642beb6b57bSJohn Snow        try:
643beb6b57bSJohn Snow            if hard:
644beb6b57bSJohn Snow                self._user_killed = True
645beb6b57bSJohn Snow                self._hard_shutdown()
646beb6b57bSJohn Snow            else:
647b9420e4fSJohn Snow                self._do_shutdown(timeout)
648beb6b57bSJohn Snow        finally:
649beb6b57bSJohn Snow            self._post_shutdown()
650beb6b57bSJohn Snow
651beb6b57bSJohn Snow    def kill(self) -> None:
652beb6b57bSJohn Snow        """
653beb6b57bSJohn Snow        Terminate the VM forcefully, wait for it to exit, and perform cleanup.
654beb6b57bSJohn Snow        """
655beb6b57bSJohn Snow        self.shutdown(hard=True)
656beb6b57bSJohn Snow
657beb6b57bSJohn Snow    def wait(self, timeout: Optional[int] = 30) -> None:
658beb6b57bSJohn Snow        """
659beb6b57bSJohn Snow        Wait for the VM to power off and perform post-shutdown cleanup.
660beb6b57bSJohn Snow
661beb6b57bSJohn Snow        :param timeout: Optional timeout in seconds. Default 30 seconds.
662beb6b57bSJohn Snow                        A value of `None` is an infinite wait.
663beb6b57bSJohn Snow        """
664b9420e4fSJohn Snow        self._quit_issued = True
665b9420e4fSJohn Snow        self.shutdown(timeout=timeout)
666beb6b57bSJohn Snow
667beb6b57bSJohn Snow    def set_qmp_monitor(self, enabled: bool = True) -> None:
668beb6b57bSJohn Snow        """
669beb6b57bSJohn Snow        Set the QMP monitor.
670beb6b57bSJohn Snow
671beb6b57bSJohn Snow        @param enabled: if False, qmp monitor options will be removed from
672beb6b57bSJohn Snow                        the base arguments of the resulting QEMU command
673beb6b57bSJohn Snow                        line. Default is True.
6745c02c865SJohn Snow
6755c02c865SJohn Snow        .. note:: Call this function before launch().
676beb6b57bSJohn Snow        """
677beb6b57bSJohn Snow        self._qmp_set = enabled
678beb6b57bSJohn Snow
679beb6b57bSJohn Snow    @property
680beb6b57bSJohn Snow    def _qmp(self) -> QEMUMonitorProtocol:
681beb6b57bSJohn Snow        if self._qmp_connection is None:
682beb6b57bSJohn Snow            raise QEMUMachineError("Attempt to access QMP with no connection")
683beb6b57bSJohn Snow        return self._qmp_connection
684beb6b57bSJohn Snow
685beb6b57bSJohn Snow    @classmethod
686c7daa57eSVladimir Sementsov-Ogievskiy    def _qmp_args(cls, conv_keys: bool,
687c7daa57eSVladimir Sementsov-Ogievskiy                  args: Dict[str, Any]) -> Dict[str, object]:
688c7daa57eSVladimir Sementsov-Ogievskiy        if conv_keys:
689c7daa57eSVladimir Sementsov-Ogievskiy            return {k.replace('_', '-'): v for k, v in args.items()}
690c7daa57eSVladimir Sementsov-Ogievskiy
691c7daa57eSVladimir Sementsov-Ogievskiy        return args
692beb6b57bSJohn Snow
693beb6b57bSJohn Snow    def qmp(self, cmd: str,
6943f3c9b4cSVladimir Sementsov-Ogievskiy            args_dict: Optional[Dict[str, object]] = None,
6953f3c9b4cSVladimir Sementsov-Ogievskiy            conv_keys: Optional[bool] = None,
696beb6b57bSJohn Snow            **args: Any) -> QMPMessage:
697beb6b57bSJohn Snow        """
698beb6b57bSJohn Snow        Invoke a QMP command and return the response dict
699beb6b57bSJohn Snow        """
7003f3c9b4cSVladimir Sementsov-Ogievskiy        if args_dict is not None:
7013f3c9b4cSVladimir Sementsov-Ogievskiy            assert not args
7023f3c9b4cSVladimir Sementsov-Ogievskiy            assert conv_keys is None
7033f3c9b4cSVladimir Sementsov-Ogievskiy            args = args_dict
7043f3c9b4cSVladimir Sementsov-Ogievskiy            conv_keys = False
7053f3c9b4cSVladimir Sementsov-Ogievskiy
7063f3c9b4cSVladimir Sementsov-Ogievskiy        if conv_keys is None:
7073f3c9b4cSVladimir Sementsov-Ogievskiy            conv_keys = True
7083f3c9b4cSVladimir Sementsov-Ogievskiy
709c7daa57eSVladimir Sementsov-Ogievskiy        qmp_args = self._qmp_args(conv_keys, args)
71037274707SVladimir Sementsov-Ogievskiy        ret = self._qmp.cmd_raw(cmd, args=qmp_args)
711b9420e4fSJohn Snow        if cmd == 'quit' and 'error' not in ret and 'return' in ret:
712b9420e4fSJohn Snow            self._quit_issued = True
713b9420e4fSJohn Snow        return ret
714beb6b57bSJohn Snow
715684750abSVladimir Sementsov-Ogievskiy    def cmd(self, cmd: str,
7164e620ff4SVladimir Sementsov-Ogievskiy            args_dict: Optional[Dict[str, object]] = None,
7174e620ff4SVladimir Sementsov-Ogievskiy            conv_keys: Optional[bool] = None,
718beb6b57bSJohn Snow            **args: Any) -> QMPReturnValue:
719beb6b57bSJohn Snow        """
720beb6b57bSJohn Snow        Invoke a QMP command.
721beb6b57bSJohn Snow        On success return the response dict.
722beb6b57bSJohn Snow        On failure raise an exception.
723beb6b57bSJohn Snow        """
7244e620ff4SVladimir Sementsov-Ogievskiy        if args_dict is not None:
7254e620ff4SVladimir Sementsov-Ogievskiy            assert not args
7264e620ff4SVladimir Sementsov-Ogievskiy            assert conv_keys is None
7274e620ff4SVladimir Sementsov-Ogievskiy            args = args_dict
7284e620ff4SVladimir Sementsov-Ogievskiy            conv_keys = False
7294e620ff4SVladimir Sementsov-Ogievskiy
7304e620ff4SVladimir Sementsov-Ogievskiy        if conv_keys is None:
7314e620ff4SVladimir Sementsov-Ogievskiy            conv_keys = True
7324e620ff4SVladimir Sementsov-Ogievskiy
733c7daa57eSVladimir Sementsov-Ogievskiy        qmp_args = self._qmp_args(conv_keys, args)
734684750abSVladimir Sementsov-Ogievskiy        ret = self._qmp.cmd(cmd, **qmp_args)
735b9420e4fSJohn Snow        if cmd == 'quit':
736b9420e4fSJohn Snow            self._quit_issued = True
737b9420e4fSJohn Snow        return ret
738beb6b57bSJohn Snow
739beb6b57bSJohn Snow    def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
740beb6b57bSJohn Snow        """
741beb6b57bSJohn Snow        Poll for one queued QMP events and return it
742beb6b57bSJohn Snow        """
743beb6b57bSJohn Snow        if self._events:
744beb6b57bSJohn Snow            return self._events.pop(0)
745beb6b57bSJohn Snow        return self._qmp.pull_event(wait=wait)
746beb6b57bSJohn Snow
747beb6b57bSJohn Snow    def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
748beb6b57bSJohn Snow        """
749beb6b57bSJohn Snow        Poll for queued QMP events and return a list of dicts
750beb6b57bSJohn Snow        """
751beb6b57bSJohn Snow        events = self._qmp.get_events(wait=wait)
752beb6b57bSJohn Snow        events.extend(self._events)
753beb6b57bSJohn Snow        del self._events[:]
754beb6b57bSJohn Snow        return events
755beb6b57bSJohn Snow
756beb6b57bSJohn Snow    @staticmethod
757beb6b57bSJohn Snow    def event_match(event: Any, match: Optional[Any]) -> bool:
758beb6b57bSJohn Snow        """
759beb6b57bSJohn Snow        Check if an event matches optional match criteria.
760beb6b57bSJohn Snow
761beb6b57bSJohn Snow        The match criteria takes the form of a matching subdict. The event is
762beb6b57bSJohn Snow        checked to be a superset of the subdict, recursively, with matching
763beb6b57bSJohn Snow        values whenever the subdict values are not None.
764beb6b57bSJohn Snow
765beb6b57bSJohn Snow        This has a limitation that you cannot explicitly check for None values.
766beb6b57bSJohn Snow
767beb6b57bSJohn Snow        Examples, with the subdict queries on the left:
768beb6b57bSJohn Snow         - None matches any object.
769beb6b57bSJohn Snow         - {"foo": None} matches {"foo": {"bar": 1}}
770beb6b57bSJohn Snow         - {"foo": None} matches {"foo": 5}
771beb6b57bSJohn Snow         - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
772beb6b57bSJohn Snow         - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
773beb6b57bSJohn Snow        """
774beb6b57bSJohn Snow        if match is None:
775beb6b57bSJohn Snow            return True
776beb6b57bSJohn Snow
777beb6b57bSJohn Snow        try:
778beb6b57bSJohn Snow            for key in match:
779beb6b57bSJohn Snow                if key in event:
780beb6b57bSJohn Snow                    if not QEMUMachine.event_match(event[key], match[key]):
781beb6b57bSJohn Snow                        return False
782beb6b57bSJohn Snow                else:
783beb6b57bSJohn Snow                    return False
784beb6b57bSJohn Snow            return True
785beb6b57bSJohn Snow        except TypeError:
786beb6b57bSJohn Snow            # either match or event wasn't iterable (not a dict)
787beb6b57bSJohn Snow            return bool(match == event)
788beb6b57bSJohn Snow
789beb6b57bSJohn Snow    def event_wait(self, name: str,
790beb6b57bSJohn Snow                   timeout: float = 60.0,
791beb6b57bSJohn Snow                   match: Optional[QMPMessage] = None) -> Optional[QMPMessage]:
792beb6b57bSJohn Snow        """
793beb6b57bSJohn Snow        event_wait waits for and returns a named event from QMP with a timeout.
794beb6b57bSJohn Snow
795beb6b57bSJohn Snow        name: The event to wait for.
796beb6b57bSJohn Snow        timeout: QEMUMonitorProtocol.pull_event timeout parameter.
797beb6b57bSJohn Snow        match: Optional match criteria. See event_match for details.
798beb6b57bSJohn Snow        """
799beb6b57bSJohn Snow        return self.events_wait([(name, match)], timeout)
800beb6b57bSJohn Snow
801beb6b57bSJohn Snow    def events_wait(self,
802beb6b57bSJohn Snow                    events: Sequence[Tuple[str, Any]],
803beb6b57bSJohn Snow                    timeout: float = 60.0) -> Optional[QMPMessage]:
804beb6b57bSJohn Snow        """
805beb6b57bSJohn Snow        events_wait waits for and returns a single named event from QMP.
806beb6b57bSJohn Snow        In the case of multiple qualifying events, this function returns the
807beb6b57bSJohn Snow        first one.
808beb6b57bSJohn Snow
809beb6b57bSJohn Snow        :param events: A sequence of (name, match_criteria) tuples.
810beb6b57bSJohn Snow                       The match criteria are optional and may be None.
811beb6b57bSJohn Snow                       See event_match for details.
812beb6b57bSJohn Snow        :param timeout: Optional timeout, in seconds.
813beb6b57bSJohn Snow                        See QEMUMonitorProtocol.pull_event.
814beb6b57bSJohn Snow
815a4225303SJohn Snow        :raise asyncio.TimeoutError:
816a4225303SJohn Snow            If timeout was non-zero and no matching events were found.
817a4225303SJohn Snow
818beb6b57bSJohn Snow        :return: A QMP event matching the filter criteria.
819beb6b57bSJohn Snow                 If timeout was 0 and no event matched, None.
820beb6b57bSJohn Snow        """
821beb6b57bSJohn Snow        def _match(event: QMPMessage) -> bool:
822beb6b57bSJohn Snow            for name, match in events:
823beb6b57bSJohn Snow                if event['event'] == name and self.event_match(event, match):
824beb6b57bSJohn Snow                    return True
825beb6b57bSJohn Snow            return False
826beb6b57bSJohn Snow
827beb6b57bSJohn Snow        event: Optional[QMPMessage]
828beb6b57bSJohn Snow
829beb6b57bSJohn Snow        # Search cached events
830beb6b57bSJohn Snow        for event in self._events:
831beb6b57bSJohn Snow            if _match(event):
832beb6b57bSJohn Snow                self._events.remove(event)
833beb6b57bSJohn Snow                return event
834beb6b57bSJohn Snow
835beb6b57bSJohn Snow        # Poll for new events
836beb6b57bSJohn Snow        while True:
837beb6b57bSJohn Snow            event = self._qmp.pull_event(wait=timeout)
838beb6b57bSJohn Snow            if event is None:
839beb6b57bSJohn Snow                # NB: None is only returned when timeout is false-ish.
840a4225303SJohn Snow                # Timeouts raise asyncio.TimeoutError instead!
841beb6b57bSJohn Snow                break
842beb6b57bSJohn Snow            if _match(event):
843beb6b57bSJohn Snow                return event
844beb6b57bSJohn Snow            self._events.append(event)
845beb6b57bSJohn Snow
846beb6b57bSJohn Snow        return None
847beb6b57bSJohn Snow
848beb6b57bSJohn Snow    def get_log(self) -> Optional[str]:
849beb6b57bSJohn Snow        """
850beb6b57bSJohn Snow        After self.shutdown or failed qemu execution, this returns the output
851beb6b57bSJohn Snow        of the qemu process.
852beb6b57bSJohn Snow        """
853beb6b57bSJohn Snow        return self._iolog
854beb6b57bSJohn Snow
855beb6b57bSJohn Snow    def add_args(self, *args: str) -> None:
856beb6b57bSJohn Snow        """
857beb6b57bSJohn Snow        Adds to the list of extra arguments to be given to the QEMU binary
858beb6b57bSJohn Snow        """
859beb6b57bSJohn Snow        self._args.extend(args)
860beb6b57bSJohn Snow
861beb6b57bSJohn Snow    def set_machine(self, machine_type: str) -> None:
862beb6b57bSJohn Snow        """
863beb6b57bSJohn Snow        Sets the machine type
864beb6b57bSJohn Snow
865beb6b57bSJohn Snow        If set, the machine type will be added to the base arguments
866beb6b57bSJohn Snow        of the resulting QEMU command line.
867beb6b57bSJohn Snow        """
868beb6b57bSJohn Snow        self._machine = machine_type
869beb6b57bSJohn Snow
870beb6b57bSJohn Snow    def set_console(self,
871beb6b57bSJohn Snow                    device_type: Optional[str] = None,
872beb6b57bSJohn Snow                    console_index: int = 0) -> None:
873beb6b57bSJohn Snow        """
874beb6b57bSJohn Snow        Sets the device type for a console device
875beb6b57bSJohn Snow
876beb6b57bSJohn Snow        If set, the console device and a backing character device will
877beb6b57bSJohn Snow        be added to the base arguments of the resulting QEMU command
878beb6b57bSJohn Snow        line.
879beb6b57bSJohn Snow
880beb6b57bSJohn Snow        This is a convenience method that will either use the provided
881beb6b57bSJohn Snow        device type, or default to a "-serial chardev:console" command
882beb6b57bSJohn Snow        line argument.
883beb6b57bSJohn Snow
884beb6b57bSJohn Snow        The actual setting of command line arguments will be be done at
885beb6b57bSJohn Snow        machine launch time, as it depends on the temporary directory
886beb6b57bSJohn Snow        to be created.
887beb6b57bSJohn Snow
888beb6b57bSJohn Snow        @param device_type: the device type, such as "isa-serial".  If
889beb6b57bSJohn Snow                            None is given (the default value) a "-serial
890beb6b57bSJohn Snow                            chardev:console" command line argument will
891beb6b57bSJohn Snow                            be used instead, resorting to the machine's
892beb6b57bSJohn Snow                            default device type.
893beb6b57bSJohn Snow        @param console_index: the index of the console device to use.
894beb6b57bSJohn Snow                              If not zero, the command line will create
895beb6b57bSJohn Snow                              'index - 1' consoles and connect them to
896beb6b57bSJohn Snow                              the 'null' backing character device.
897beb6b57bSJohn Snow        """
898beb6b57bSJohn Snow        self._console_set = True
899beb6b57bSJohn Snow        self._console_device_type = device_type
900beb6b57bSJohn Snow        self._console_index = console_index
901beb6b57bSJohn Snow
902beb6b57bSJohn Snow    @property
903beb6b57bSJohn Snow    def console_socket(self) -> socket.socket:
904beb6b57bSJohn Snow        """
905beb6b57bSJohn Snow        Returns a socket connected to the console
906beb6b57bSJohn Snow        """
907beb6b57bSJohn Snow        if self._console_socket is None:
908f0ec14c7SNicholas Piggin            LOG.debug("Opening console socket")
9091d4796cdSJohn Snow            if not self._console_set:
9101d4796cdSJohn Snow                raise QEMUMachineError(
9111d4796cdSJohn Snow                    "Attempt to access console socket with no connection")
9121d4796cdSJohn Snow            assert self._cons_sock_pair is not None
9131d4796cdSJohn Snow            # os.dup() is used here for sock_fd because otherwise we'd
9141d4796cdSJohn Snow            # have two rich python socket objects that would each try to
9151d4796cdSJohn Snow            # close the same underlying fd when either one gets garbage
9161d4796cdSJohn Snow            # collected.
917beb6b57bSJohn Snow            self._console_socket = console_socket.ConsoleSocket(
9181d4796cdSJohn Snow                sock_fd=os.dup(self._cons_sock_pair[1].fileno()),
919beb6b57bSJohn Snow                file=self._console_log_path,
920beb6b57bSJohn Snow                drain=self._drain_console)
9211d4796cdSJohn Snow            self._cons_sock_pair[1].close()
922beb6b57bSJohn Snow        return self._console_socket
923beb6b57bSJohn Snow
924beb6b57bSJohn Snow    @property
925f0ec14c7SNicholas Piggin    def console_file(self) -> socket.SocketIO:
926f0ec14c7SNicholas Piggin        """
927f0ec14c7SNicholas Piggin        Returns a file associated with the console socket
928f0ec14c7SNicholas Piggin        """
929f0ec14c7SNicholas Piggin        if self._console_file is None:
930f0ec14c7SNicholas Piggin            LOG.debug("Opening console file")
931f0ec14c7SNicholas Piggin            self._console_file = self.console_socket.makefile(mode='rb',
932f0ec14c7SNicholas Piggin                                                              buffering=0,
933f0ec14c7SNicholas Piggin                                                              encoding='utf-8')
934f0ec14c7SNicholas Piggin        return self._console_file
935f0ec14c7SNicholas Piggin
936f0ec14c7SNicholas Piggin    @property
937beb6b57bSJohn Snow    def temp_dir(self) -> str:
938beb6b57bSJohn Snow        """
939beb6b57bSJohn Snow        Returns a temporary directory to be used for this machine
940beb6b57bSJohn Snow        """
941beb6b57bSJohn Snow        if self._temp_dir is None:
942beb6b57bSJohn Snow            self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-",
943beb6b57bSJohn Snow                                              dir=self._base_temp_dir)
944beb6b57bSJohn Snow        return self._temp_dir
945b306e26cSCleber Rosa
946b306e26cSCleber Rosa    @property
947b306e26cSCleber Rosa    def log_dir(self) -> str:
948b306e26cSCleber Rosa        """
949b306e26cSCleber Rosa        Returns a directory to be used for writing logs
950b306e26cSCleber Rosa        """
951b306e26cSCleber Rosa        if self._log_dir is None:
952b306e26cSCleber Rosa            return self.temp_dir
953b306e26cSCleber Rosa        return self._log_dir
954