xref: /openbmc/qemu/python/qemu/machine/machine.py (revision f0ec14c7)
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                 sock_dir: Optional[str] = None,
131beb6b57bSJohn Snow                 drain_console: bool = False,
132b306e26cSCleber Rosa                 console_log: Optional[str] = None,
133e2f948a8SEmanuele Giuseppe Esposito                 log_dir: Optional[str] = None,
134ada73a49SVladimir Sementsov-Ogievskiy                 qmp_timer: Optional[float] = 30):
135beb6b57bSJohn Snow        '''
136beb6b57bSJohn Snow        Initialize a QEMUMachine
137beb6b57bSJohn Snow
138beb6b57bSJohn Snow        @param binary: path to the qemu binary
139beb6b57bSJohn Snow        @param args: list of extra arguments
140beb6b57bSJohn Snow        @param wrapper: list of arguments used as prefix to qemu binary
141beb6b57bSJohn Snow        @param name: prefix for socket and log file names (default: qemu-PID)
142beb6b57bSJohn Snow        @param base_temp_dir: default location where temp files are created
143beb6b57bSJohn Snow        @param monitor_address: address for QMP monitor
144beb6b57bSJohn Snow        @param sock_dir: where to create socket (defaults to base_temp_dir)
145beb6b57bSJohn Snow        @param drain_console: (optional) True to drain console socket to buffer
146beb6b57bSJohn Snow        @param console_log: (optional) path to console log file
147b306e26cSCleber Rosa        @param log_dir: where to create and keep log files
148e2f948a8SEmanuele Giuseppe Esposito        @param qmp_timer: (optional) default QMP socket timeout
149beb6b57bSJohn Snow        @note: Qemu process is not started until launch() is used.
150beb6b57bSJohn Snow        '''
15182e6517dSJohn Snow        # pylint: disable=too-many-arguments
15282e6517dSJohn Snow
153beb6b57bSJohn Snow        # Direct user configuration
154beb6b57bSJohn Snow
155beb6b57bSJohn Snow        self._binary = binary
156beb6b57bSJohn Snow        self._args = list(args)
157beb6b57bSJohn Snow        self._wrapper = wrapper
158e2f948a8SEmanuele Giuseppe Esposito        self._qmp_timer = qmp_timer
159beb6b57bSJohn Snow
160f9922937SPeter Delevoryas        self._name = name or f"{id(self):x}"
161bd4c0ef4SMarc-André Lureau        self._sock_pair: Optional[Tuple[socket.socket, socket.socket]] = None
16287bf1fe5SJohn Snow        self._temp_dir: Optional[str] = None
163beb6b57bSJohn Snow        self._base_temp_dir = base_temp_dir
16487bf1fe5SJohn Snow        self._sock_dir = sock_dir
165b306e26cSCleber Rosa        self._log_dir = log_dir
166beb6b57bSJohn Snow
167beb6b57bSJohn Snow        self._monitor_address = monitor_address
168beb6b57bSJohn Snow
169beb6b57bSJohn Snow        self._console_log_path = console_log
170beb6b57bSJohn Snow        if self._console_log_path:
171beb6b57bSJohn Snow            # In order to log the console, buffering needs to be enabled.
172beb6b57bSJohn Snow            self._drain_console = True
173beb6b57bSJohn Snow        else:
174beb6b57bSJohn Snow            self._drain_console = drain_console
175beb6b57bSJohn Snow
176beb6b57bSJohn Snow        # Runstate
177beb6b57bSJohn Snow        self._qemu_log_path: Optional[str] = None
178beb6b57bSJohn Snow        self._qemu_log_file: Optional[BinaryIO] = None
179beb6b57bSJohn Snow        self._popen: Optional['subprocess.Popen[bytes]'] = None
180beb6b57bSJohn Snow        self._events: List[QMPMessage] = []
181beb6b57bSJohn Snow        self._iolog: Optional[str] = None
182beb6b57bSJohn Snow        self._qmp_set = True   # Enable QMP monitor by default.
183beb6b57bSJohn Snow        self._qmp_connection: Optional[QEMUMonitorProtocol] = None
184beb6b57bSJohn Snow        self._qemu_full_args: Tuple[str, ...] = ()
185beb6b57bSJohn Snow        self._launched = False
186beb6b57bSJohn Snow        self._machine: Optional[str] = None
187beb6b57bSJohn Snow        self._console_index = 0
188beb6b57bSJohn Snow        self._console_set = False
189beb6b57bSJohn Snow        self._console_device_type: Optional[str] = None
190beb6b57bSJohn Snow        self._console_address = os.path.join(
191f9922937SPeter Delevoryas            self.sock_dir, f"{self._name}.con"
192beb6b57bSJohn Snow        )
193beb6b57bSJohn Snow        self._console_socket: Optional[socket.socket] = None
194*f0ec14c7SNicholas Piggin        self._console_file: Optional[socket.SocketIO] = None
195beb6b57bSJohn Snow        self._remove_files: List[str] = []
196beb6b57bSJohn Snow        self._user_killed = False
197b9420e4fSJohn Snow        self._quit_issued = False
198beb6b57bSJohn Snow
19915c3b863SVladimir Sementsov-Ogievskiy    def __enter__(self: _T) -> _T:
200beb6b57bSJohn Snow        return self
201beb6b57bSJohn Snow
202beb6b57bSJohn Snow    def __exit__(self,
203beb6b57bSJohn Snow                 exc_type: Optional[Type[BaseException]],
204beb6b57bSJohn Snow                 exc_val: Optional[BaseException],
205beb6b57bSJohn Snow                 exc_tb: Optional[TracebackType]) -> None:
206beb6b57bSJohn Snow        self.shutdown()
207beb6b57bSJohn Snow
208beb6b57bSJohn Snow    def add_monitor_null(self) -> None:
209beb6b57bSJohn Snow        """
210beb6b57bSJohn Snow        This can be used to add an unused monitor instance.
211beb6b57bSJohn Snow        """
212beb6b57bSJohn Snow        self._args.append('-monitor')
213beb6b57bSJohn Snow        self._args.append('null')
214beb6b57bSJohn Snow
21515c3b863SVladimir Sementsov-Ogievskiy    def add_fd(self: _T, fd: int, fdset: int,
21615c3b863SVladimir Sementsov-Ogievskiy               opaque: str, opts: str = '') -> _T:
217beb6b57bSJohn Snow        """
218beb6b57bSJohn Snow        Pass a file descriptor to the VM
219beb6b57bSJohn Snow        """
220beb6b57bSJohn Snow        options = ['fd=%d' % fd,
221beb6b57bSJohn Snow                   'set=%d' % fdset,
222beb6b57bSJohn Snow                   'opaque=%s' % opaque]
223beb6b57bSJohn Snow        if opts:
224beb6b57bSJohn Snow            options.append(opts)
225beb6b57bSJohn Snow
226beb6b57bSJohn Snow        # This did not exist before 3.4, but since then it is
227beb6b57bSJohn Snow        # mandatory for our purpose
228beb6b57bSJohn Snow        if hasattr(os, 'set_inheritable'):
229beb6b57bSJohn Snow            os.set_inheritable(fd, True)
230beb6b57bSJohn Snow
231beb6b57bSJohn Snow        self._args.append('-add-fd')
232beb6b57bSJohn Snow        self._args.append(','.join(options))
233beb6b57bSJohn Snow        return self
234beb6b57bSJohn Snow
235beb6b57bSJohn Snow    def send_fd_scm(self, fd: Optional[int] = None,
236beb6b57bSJohn Snow                    file_path: Optional[str] = None) -> int:
237beb6b57bSJohn Snow        """
238514d00dfSJohn Snow        Send an fd or file_path to the remote via SCM_RIGHTS.
239beb6b57bSJohn Snow
240514d00dfSJohn Snow        Exactly one of fd and file_path must be given.  If it is
241514d00dfSJohn Snow        file_path, the file will be opened read-only and the new file
242514d00dfSJohn Snow        descriptor will be sent to the remote.
243beb6b57bSJohn Snow        """
244beb6b57bSJohn Snow        if file_path is not None:
245beb6b57bSJohn Snow            assert fd is None
246514d00dfSJohn Snow            with open(file_path, "rb") as passfile:
247514d00dfSJohn Snow                fd = passfile.fileno()
248514d00dfSJohn Snow                self._qmp.send_fd_scm(fd)
249beb6b57bSJohn Snow        else:
250beb6b57bSJohn Snow            assert fd is not None
251514d00dfSJohn Snow            self._qmp.send_fd_scm(fd)
252beb6b57bSJohn Snow
253514d00dfSJohn Snow        return 0
254beb6b57bSJohn Snow
255beb6b57bSJohn Snow    @staticmethod
256beb6b57bSJohn Snow    def _remove_if_exists(path: str) -> None:
257beb6b57bSJohn Snow        """
258beb6b57bSJohn Snow        Remove file object at path if it exists
259beb6b57bSJohn Snow        """
260beb6b57bSJohn Snow        try:
261beb6b57bSJohn Snow            os.remove(path)
262beb6b57bSJohn Snow        except OSError as exception:
263beb6b57bSJohn Snow            if exception.errno == errno.ENOENT:
264beb6b57bSJohn Snow                return
265beb6b57bSJohn Snow            raise
266beb6b57bSJohn Snow
267beb6b57bSJohn Snow    def is_running(self) -> bool:
268beb6b57bSJohn Snow        """Returns true if the VM is running."""
269beb6b57bSJohn Snow        return self._popen is not None and self._popen.poll() is None
270beb6b57bSJohn Snow
271beb6b57bSJohn Snow    @property
272beb6b57bSJohn Snow    def _subp(self) -> 'subprocess.Popen[bytes]':
273beb6b57bSJohn Snow        if self._popen is None:
274beb6b57bSJohn Snow            raise QEMUMachineError('Subprocess pipe not present')
275beb6b57bSJohn Snow        return self._popen
276beb6b57bSJohn Snow
277beb6b57bSJohn Snow    def exitcode(self) -> Optional[int]:
278beb6b57bSJohn Snow        """Returns the exit code if possible, or None."""
279beb6b57bSJohn Snow        if self._popen is None:
280beb6b57bSJohn Snow            return None
281beb6b57bSJohn Snow        return self._popen.poll()
282beb6b57bSJohn Snow
283beb6b57bSJohn Snow    def get_pid(self) -> Optional[int]:
284beb6b57bSJohn Snow        """Returns the PID of the running process, or None."""
285beb6b57bSJohn Snow        if not self.is_running():
286beb6b57bSJohn Snow            return None
287beb6b57bSJohn Snow        return self._subp.pid
288beb6b57bSJohn Snow
289beb6b57bSJohn Snow    def _load_io_log(self) -> None:
2905690b437SJohn Snow        # Assume that the output encoding of QEMU's terminal output is
2915690b437SJohn Snow        # defined by our locale. If indeterminate, allow open() to fall
2925690b437SJohn Snow        # back to the platform default.
2935690b437SJohn Snow        _, encoding = locale.getlocale()
294beb6b57bSJohn Snow        if self._qemu_log_path is not None:
2955690b437SJohn Snow            with open(self._qemu_log_path, "r", encoding=encoding) as iolog:
296beb6b57bSJohn Snow                self._iolog = iolog.read()
297beb6b57bSJohn Snow
298beb6b57bSJohn Snow    @property
299beb6b57bSJohn Snow    def _base_args(self) -> List[str]:
300beb6b57bSJohn Snow        args = ['-display', 'none', '-vga', 'none']
301beb6b57bSJohn Snow
302beb6b57bSJohn Snow        if self._qmp_set:
303bd4c0ef4SMarc-André Lureau            if self._sock_pair:
304bd4c0ef4SMarc-André Lureau                fd = self._sock_pair[0].fileno()
305bd4c0ef4SMarc-André Lureau                os.set_inheritable(fd, True)
306bd4c0ef4SMarc-André Lureau                moncdev = f"socket,id=mon,fd={fd}"
307bd4c0ef4SMarc-André Lureau            elif isinstance(self._monitor_address, tuple):
308beb6b57bSJohn Snow                moncdev = "socket,id=mon,host={},port={}".format(
309beb6b57bSJohn Snow                    *self._monitor_address
310beb6b57bSJohn Snow                )
311beb6b57bSJohn Snow            else:
312beb6b57bSJohn Snow                moncdev = f"socket,id=mon,path={self._monitor_address}"
313beb6b57bSJohn Snow            args.extend(['-chardev', moncdev, '-mon',
314beb6b57bSJohn Snow                         'chardev=mon,mode=control'])
315beb6b57bSJohn Snow
316beb6b57bSJohn Snow        if self._machine is not None:
317beb6b57bSJohn Snow            args.extend(['-machine', self._machine])
318beb6b57bSJohn Snow        for _ in range(self._console_index):
319beb6b57bSJohn Snow            args.extend(['-serial', 'null'])
320beb6b57bSJohn Snow        if self._console_set:
321beb6b57bSJohn Snow            chardev = ('socket,id=console,path=%s,server=on,wait=off' %
322beb6b57bSJohn Snow                       self._console_address)
323beb6b57bSJohn Snow            args.extend(['-chardev', chardev])
324beb6b57bSJohn Snow            if self._console_device_type is None:
325beb6b57bSJohn Snow                args.extend(['-serial', 'chardev:console'])
326beb6b57bSJohn Snow            else:
327beb6b57bSJohn Snow                device = '%s,chardev=console' % self._console_device_type
328beb6b57bSJohn Snow                args.extend(['-device', device])
329beb6b57bSJohn Snow        return args
330beb6b57bSJohn Snow
331555fe0c2SWainer dos Santos Moschetta    @property
332555fe0c2SWainer dos Santos Moschetta    def args(self) -> List[str]:
333555fe0c2SWainer dos Santos Moschetta        """Returns the list of arguments given to the QEMU binary."""
334555fe0c2SWainer dos Santos Moschetta        return self._args
335555fe0c2SWainer dos Santos Moschetta
336beb6b57bSJohn Snow    def _pre_launch(self) -> None:
337beb6b57bSJohn Snow        if self._console_set:
338beb6b57bSJohn Snow            self._remove_files.append(self._console_address)
339beb6b57bSJohn Snow
340beb6b57bSJohn Snow        if self._qmp_set:
341bd4c0ef4SMarc-André Lureau            if self._monitor_address is None:
342bd4c0ef4SMarc-André Lureau                self._sock_pair = socket.socketpair()
343bd4c0ef4SMarc-André Lureau                sock = self._sock_pair[1]
3446eeb3de7SJohn Snow            if isinstance(self._monitor_address, str):
345beb6b57bSJohn Snow                self._remove_files.append(self._monitor_address)
3467f5f3ae7SJohn Snow
3475bbc5936SJohn Snow            sock_or_addr = self._monitor_address or sock
3485bbc5936SJohn Snow            assert sock_or_addr is not None
3495bbc5936SJohn Snow
350beb6b57bSJohn Snow            self._qmp_connection = QEMUMonitorProtocol(
3515bbc5936SJohn Snow                sock_or_addr,
3527f5f3ae7SJohn Snow                server=bool(self._monitor_address),
353beb6b57bSJohn Snow                nickname=self._name
354beb6b57bSJohn Snow            )
355beb6b57bSJohn Snow
356beb6b57bSJohn Snow        # NOTE: Make sure any opened resources are *definitely* freed in
357beb6b57bSJohn Snow        # _post_shutdown()!
358beb6b57bSJohn Snow        # pylint: disable=consider-using-with
359b306e26cSCleber Rosa        self._qemu_log_path = os.path.join(self.log_dir, self._name + ".log")
360beb6b57bSJohn Snow        self._qemu_log_file = open(self._qemu_log_path, 'wb')
361beb6b57bSJohn Snow
362b1ca9919SJohn Snow        self._iolog = None
363b1ca9919SJohn Snow        self._qemu_full_args = tuple(chain(
364b1ca9919SJohn Snow            self._wrapper,
365b1ca9919SJohn Snow            [self._binary],
366b1ca9919SJohn Snow            self._base_args,
367b1ca9919SJohn Snow            self._args
368b1ca9919SJohn Snow        ))
369b1ca9919SJohn Snow
370beb6b57bSJohn Snow    def _post_launch(self) -> None:
371bd4c0ef4SMarc-André Lureau        if self._sock_pair:
372bd4c0ef4SMarc-André Lureau            self._sock_pair[0].close()
373beb6b57bSJohn Snow        if self._qmp_connection:
3747f5f3ae7SJohn Snow            if self._sock_pair:
3757f5f3ae7SJohn Snow                self._qmp.connect()
3767f5f3ae7SJohn Snow            else:
377e2f948a8SEmanuele Giuseppe Esposito                self._qmp.accept(self._qmp_timer)
378beb6b57bSJohn Snow
379eb7a91d0SEmanuele Giuseppe Esposito    def _close_qemu_log_file(self) -> None:
380eb7a91d0SEmanuele Giuseppe Esposito        if self._qemu_log_file is not None:
381eb7a91d0SEmanuele Giuseppe Esposito            self._qemu_log_file.close()
382eb7a91d0SEmanuele Giuseppe Esposito            self._qemu_log_file = None
383eb7a91d0SEmanuele Giuseppe Esposito
384beb6b57bSJohn Snow    def _post_shutdown(self) -> None:
385beb6b57bSJohn Snow        """
386beb6b57bSJohn Snow        Called to cleanup the VM instance after the process has exited.
387beb6b57bSJohn Snow        May also be called after a failed launch.
388beb6b57bSJohn Snow        """
3899cccb330SJohn Snow        LOG.debug("Cleaning up after VM process")
39049a608b8SJohn Snow        try:
39149a608b8SJohn Snow            self._close_qmp_connection()
39249a608b8SJohn Snow        except Exception as err:  # pylint: disable=broad-except
39349a608b8SJohn Snow            LOG.warning(
39449a608b8SJohn Snow                "Exception closing QMP connection: %s",
39549a608b8SJohn Snow                str(err) if str(err) else type(err).__name__
39649a608b8SJohn Snow            )
39749a608b8SJohn Snow        finally:
39849a608b8SJohn Snow            assert self._qmp_connection is None
399beb6b57bSJohn Snow
400eb7a91d0SEmanuele Giuseppe Esposito        self._close_qemu_log_file()
401beb6b57bSJohn Snow
402beb6b57bSJohn Snow        self._load_io_log()
403beb6b57bSJohn Snow
404beb6b57bSJohn Snow        self._qemu_log_path = None
405beb6b57bSJohn Snow
406beb6b57bSJohn Snow        if self._temp_dir is not None:
407beb6b57bSJohn Snow            shutil.rmtree(self._temp_dir)
408beb6b57bSJohn Snow            self._temp_dir = None
409beb6b57bSJohn Snow
410beb6b57bSJohn Snow        while len(self._remove_files) > 0:
411beb6b57bSJohn Snow            self._remove_if_exists(self._remove_files.pop())
412beb6b57bSJohn Snow
413beb6b57bSJohn Snow        exitcode = self.exitcode()
414beb6b57bSJohn Snow        if (exitcode is not None and exitcode < 0
415beb6b57bSJohn Snow                and not (self._user_killed and exitcode == -signal.SIGKILL)):
416beb6b57bSJohn Snow            msg = 'qemu received signal %i; command: "%s"'
417beb6b57bSJohn Snow            if self._qemu_full_args:
418beb6b57bSJohn Snow                command = ' '.join(self._qemu_full_args)
419beb6b57bSJohn Snow            else:
420beb6b57bSJohn Snow                command = ''
421beb6b57bSJohn Snow            LOG.warning(msg, -int(exitcode), command)
422beb6b57bSJohn Snow
423b9420e4fSJohn Snow        self._quit_issued = False
424beb6b57bSJohn Snow        self._user_killed = False
425beb6b57bSJohn Snow        self._launched = False
426beb6b57bSJohn Snow
427beb6b57bSJohn Snow    def launch(self) -> None:
428beb6b57bSJohn Snow        """
429beb6b57bSJohn Snow        Launch the VM and make sure we cleanup and expose the
430beb6b57bSJohn Snow        command line/output in case of exception
431beb6b57bSJohn Snow        """
432beb6b57bSJohn Snow
433beb6b57bSJohn Snow        if self._launched:
434beb6b57bSJohn Snow            raise QEMUMachineError('VM already launched')
435beb6b57bSJohn Snow
436beb6b57bSJohn Snow        try:
437beb6b57bSJohn Snow            self._launch()
43850465f94SJohn Snow        except BaseException as exc:
4391611e6cfSJohn Snow            # We may have launched the process but it may
4401611e6cfSJohn Snow            # have exited before we could connect via QMP.
4411611e6cfSJohn Snow            # Assume the VM didn't launch or is exiting.
4421611e6cfSJohn Snow            # If we don't wait for the process, exitcode() may still be
4431611e6cfSJohn Snow            # 'None' by the time control is ceded back to the caller.
4441611e6cfSJohn Snow            if self._launched:
4451611e6cfSJohn Snow                self.wait()
4461611e6cfSJohn Snow            else:
447beb6b57bSJohn Snow                self._post_shutdown()
448beb6b57bSJohn Snow
44950465f94SJohn Snow            if isinstance(exc, Exception):
45050465f94SJohn Snow                raise VMLaunchFailure(
45150465f94SJohn Snow                    exitcode=self.exitcode(),
45250465f94SJohn Snow                    command=' '.join(self._qemu_full_args),
45350465f94SJohn Snow                    output=self._iolog
45450465f94SJohn Snow                ) from exc
45550465f94SJohn Snow
45650465f94SJohn Snow            # Don't wrap 'BaseException'; doing so would downgrade
45750465f94SJohn Snow            # that exception. However, we still want to clean up.
458beb6b57bSJohn Snow            raise
459beb6b57bSJohn Snow
460beb6b57bSJohn Snow    def _launch(self) -> None:
461beb6b57bSJohn Snow        """
462beb6b57bSJohn Snow        Launch the VM and establish a QMP connection
463beb6b57bSJohn Snow        """
464beb6b57bSJohn Snow        self._pre_launch()
465beb6b57bSJohn Snow        LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
466beb6b57bSJohn Snow
467beb6b57bSJohn Snow        # Cleaning up of this subprocess is guaranteed by _do_shutdown.
468beb6b57bSJohn Snow        # pylint: disable=consider-using-with
469beb6b57bSJohn Snow        self._popen = subprocess.Popen(self._qemu_full_args,
470beb6b57bSJohn Snow                                       stdin=subprocess.DEVNULL,
471beb6b57bSJohn Snow                                       stdout=self._qemu_log_file,
472beb6b57bSJohn Snow                                       stderr=subprocess.STDOUT,
473beb6b57bSJohn Snow                                       shell=False,
474beb6b57bSJohn Snow                                       close_fds=False)
4751611e6cfSJohn Snow        self._launched = True
476beb6b57bSJohn Snow        self._post_launch()
477beb6b57bSJohn Snow
47849a608b8SJohn Snow    def _close_qmp_connection(self) -> None:
47949a608b8SJohn Snow        """
48049a608b8SJohn Snow        Close the underlying QMP connection, if any.
48149a608b8SJohn Snow
48249a608b8SJohn Snow        Dutifully report errors that occurred while closing, but assume
48349a608b8SJohn Snow        that any error encountered indicates an abnormal termination
48449a608b8SJohn Snow        process and not a failure to close.
48549a608b8SJohn Snow        """
48649a608b8SJohn Snow        if self._qmp_connection is None:
48749a608b8SJohn Snow            return
48849a608b8SJohn Snow
48949a608b8SJohn Snow        try:
49049a608b8SJohn Snow            self._qmp.close()
49149a608b8SJohn Snow        except EOFError:
49249a608b8SJohn Snow            # EOF can occur as an Exception here when using the Async
49349a608b8SJohn Snow            # QMP backend. It indicates that the server closed the
49449a608b8SJohn Snow            # stream. If we successfully issued 'quit' at any point,
49549a608b8SJohn Snow            # then this was expected. If the remote went away without
49649a608b8SJohn Snow            # our permission, it's worth reporting that as an abnormal
49749a608b8SJohn Snow            # shutdown case.
49849a608b8SJohn Snow            if not (self._user_killed or self._quit_issued):
49949a608b8SJohn Snow                raise
50049a608b8SJohn Snow        finally:
50149a608b8SJohn Snow            self._qmp_connection = None
50249a608b8SJohn Snow
503beb6b57bSJohn Snow    def _early_cleanup(self) -> None:
504beb6b57bSJohn Snow        """
505beb6b57bSJohn Snow        Perform any cleanup that needs to happen before the VM exits.
506beb6b57bSJohn Snow
5071611e6cfSJohn Snow        This method may be called twice upon shutdown, once each by soft
5081611e6cfSJohn Snow        and hard shutdown in failover scenarios.
509beb6b57bSJohn Snow        """
510beb6b57bSJohn Snow        # If we keep the console socket open, we may deadlock waiting
511beb6b57bSJohn Snow        # for QEMU to exit, while QEMU is waiting for the socket to
5129323e79fSPeter Maydell        # become writable.
513*f0ec14c7SNicholas Piggin        if self._console_file is not None:
514*f0ec14c7SNicholas Piggin            LOG.debug("Closing console file")
515*f0ec14c7SNicholas Piggin            self._console_file.close()
516*f0ec14c7SNicholas Piggin            self._console_file = None
517*f0ec14c7SNicholas Piggin
518beb6b57bSJohn Snow        if self._console_socket is not None:
5199cccb330SJohn Snow            LOG.debug("Closing console socket")
520beb6b57bSJohn Snow            self._console_socket.close()
521beb6b57bSJohn Snow            self._console_socket = None
522beb6b57bSJohn Snow
523beb6b57bSJohn Snow    def _hard_shutdown(self) -> None:
524beb6b57bSJohn Snow        """
525beb6b57bSJohn Snow        Perform early cleanup, kill the VM, and wait for it to terminate.
526beb6b57bSJohn Snow
527beb6b57bSJohn Snow        :raise subprocess.Timeout: When timeout is exceeds 60 seconds
528beb6b57bSJohn Snow            waiting for the QEMU process to terminate.
529beb6b57bSJohn Snow        """
5309cccb330SJohn Snow        LOG.debug("Performing hard shutdown")
531beb6b57bSJohn Snow        self._early_cleanup()
532beb6b57bSJohn Snow        self._subp.kill()
533beb6b57bSJohn Snow        self._subp.wait(timeout=60)
534beb6b57bSJohn Snow
535b9420e4fSJohn Snow    def _soft_shutdown(self, timeout: Optional[int]) -> None:
536beb6b57bSJohn Snow        """
537beb6b57bSJohn Snow        Perform early cleanup, attempt to gracefully shut down the VM, and wait
538beb6b57bSJohn Snow        for it to terminate.
539beb6b57bSJohn Snow
540beb6b57bSJohn Snow        :param timeout: Timeout in seconds for graceful shutdown.
541beb6b57bSJohn Snow                        A value of None is an infinite wait.
542beb6b57bSJohn Snow
543beb6b57bSJohn Snow        :raise ConnectionReset: On QMP communication errors
544beb6b57bSJohn Snow        :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
545beb6b57bSJohn Snow            the QEMU process to terminate.
546beb6b57bSJohn Snow        """
5479cccb330SJohn Snow        LOG.debug("Attempting graceful termination")
5489cccb330SJohn Snow
549beb6b57bSJohn Snow        self._early_cleanup()
550beb6b57bSJohn Snow
5519cccb330SJohn Snow        if self._quit_issued:
5529cccb330SJohn Snow            LOG.debug(
5539cccb330SJohn Snow                "Anticipating QEMU termination due to prior 'quit' command, "
5549cccb330SJohn Snow                "or explicit call to wait()"
5559cccb330SJohn Snow            )
5569cccb330SJohn Snow        else:
5579cccb330SJohn Snow            LOG.debug("Politely asking QEMU to terminate")
5589cccb330SJohn Snow
559beb6b57bSJohn Snow        if self._qmp_connection:
56049a608b8SJohn Snow            try:
561b9420e4fSJohn Snow                if not self._quit_issued:
56249a608b8SJohn Snow                    # May raise ExecInterruptedError or StateError if the
56349a608b8SJohn Snow                    # connection dies or has *already* died.
564b9420e4fSJohn Snow                    self.qmp('quit')
56549a608b8SJohn Snow            finally:
56649a608b8SJohn Snow                # Regardless, we want to quiesce the connection.
56749a608b8SJohn Snow                self._close_qmp_connection()
5683c6e5e8cSJohn Snow        elif not self._quit_issued:
5693c6e5e8cSJohn Snow            LOG.debug(
5703c6e5e8cSJohn Snow                "Not anticipating QEMU quit and no QMP connection present, "
5713c6e5e8cSJohn Snow                "issuing SIGTERM"
5723c6e5e8cSJohn Snow            )
5733c6e5e8cSJohn Snow            self._subp.terminate()
574beb6b57bSJohn Snow
575beb6b57bSJohn Snow        # May raise subprocess.TimeoutExpired
5769cccb330SJohn Snow        LOG.debug(
5779cccb330SJohn Snow            "Waiting (timeout=%s) for QEMU process (pid=%s) to terminate",
5789cccb330SJohn Snow            timeout, self._subp.pid
5799cccb330SJohn Snow        )
580beb6b57bSJohn Snow        self._subp.wait(timeout=timeout)
581beb6b57bSJohn Snow
582b9420e4fSJohn Snow    def _do_shutdown(self, timeout: Optional[int]) -> None:
583beb6b57bSJohn Snow        """
584beb6b57bSJohn Snow        Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
585beb6b57bSJohn Snow
586beb6b57bSJohn Snow        :param timeout: Timeout in seconds for graceful shutdown.
587beb6b57bSJohn Snow                        A value of None is an infinite wait.
588beb6b57bSJohn Snow
589beb6b57bSJohn Snow        :raise AbnormalShutdown: When the VM could not be shut down gracefully.
590beb6b57bSJohn Snow            The inner exception will likely be ConnectionReset or
591beb6b57bSJohn Snow            subprocess.TimeoutExpired. In rare cases, non-graceful termination
592beb6b57bSJohn Snow            may result in its own exceptions, likely subprocess.TimeoutExpired.
593beb6b57bSJohn Snow        """
594beb6b57bSJohn Snow        try:
595b9420e4fSJohn Snow            self._soft_shutdown(timeout)
596beb6b57bSJohn Snow        except Exception as exc:
5979cccb330SJohn Snow            if isinstance(exc, subprocess.TimeoutExpired):
5989cccb330SJohn Snow                LOG.debug("Timed out waiting for QEMU process to exit")
5999cccb330SJohn Snow            LOG.debug("Graceful shutdown failed", exc_info=True)
6009cccb330SJohn Snow            LOG.debug("Falling back to hard shutdown")
601beb6b57bSJohn Snow            self._hard_shutdown()
602beb6b57bSJohn Snow            raise AbnormalShutdown("Could not perform graceful shutdown") \
603beb6b57bSJohn Snow                from exc
604beb6b57bSJohn Snow
605b9420e4fSJohn Snow    def shutdown(self,
606beb6b57bSJohn Snow                 hard: bool = False,
607beb6b57bSJohn Snow                 timeout: Optional[int] = 30) -> None:
608beb6b57bSJohn Snow        """
609beb6b57bSJohn Snow        Terminate the VM (gracefully if possible) and perform cleanup.
610beb6b57bSJohn Snow        Cleanup will always be performed.
611beb6b57bSJohn Snow
612beb6b57bSJohn Snow        If the VM has not yet been launched, or shutdown(), wait(), or kill()
613beb6b57bSJohn Snow        have already been called, this method does nothing.
614beb6b57bSJohn Snow
615beb6b57bSJohn Snow        :param hard: When true, do not attempt graceful shutdown, and
616beb6b57bSJohn Snow                     suppress the SIGKILL warning log message.
617beb6b57bSJohn Snow        :param timeout: Optional timeout in seconds for graceful shutdown.
618beb6b57bSJohn Snow                        Default 30 seconds, A `None` value is an infinite wait.
619beb6b57bSJohn Snow        """
620beb6b57bSJohn Snow        if not self._launched:
621beb6b57bSJohn Snow            return
622beb6b57bSJohn Snow
6239cccb330SJohn Snow        LOG.debug("Shutting down VM appliance; timeout=%s", timeout)
6249cccb330SJohn Snow        if hard:
6259cccb330SJohn Snow            LOG.debug("Caller requests immediate termination of QEMU process.")
6269cccb330SJohn Snow
627beb6b57bSJohn Snow        try:
628beb6b57bSJohn Snow            if hard:
629beb6b57bSJohn Snow                self._user_killed = True
630beb6b57bSJohn Snow                self._hard_shutdown()
631beb6b57bSJohn Snow            else:
632b9420e4fSJohn Snow                self._do_shutdown(timeout)
633beb6b57bSJohn Snow        finally:
634beb6b57bSJohn Snow            self._post_shutdown()
635beb6b57bSJohn Snow
636beb6b57bSJohn Snow    def kill(self) -> None:
637beb6b57bSJohn Snow        """
638beb6b57bSJohn Snow        Terminate the VM forcefully, wait for it to exit, and perform cleanup.
639beb6b57bSJohn Snow        """
640beb6b57bSJohn Snow        self.shutdown(hard=True)
641beb6b57bSJohn Snow
642beb6b57bSJohn Snow    def wait(self, timeout: Optional[int] = 30) -> None:
643beb6b57bSJohn Snow        """
644beb6b57bSJohn Snow        Wait for the VM to power off and perform post-shutdown cleanup.
645beb6b57bSJohn Snow
646beb6b57bSJohn Snow        :param timeout: Optional timeout in seconds. Default 30 seconds.
647beb6b57bSJohn Snow                        A value of `None` is an infinite wait.
648beb6b57bSJohn Snow        """
649b9420e4fSJohn Snow        self._quit_issued = True
650b9420e4fSJohn Snow        self.shutdown(timeout=timeout)
651beb6b57bSJohn Snow
652beb6b57bSJohn Snow    def set_qmp_monitor(self, enabled: bool = True) -> None:
653beb6b57bSJohn Snow        """
654beb6b57bSJohn Snow        Set the QMP monitor.
655beb6b57bSJohn Snow
656beb6b57bSJohn Snow        @param enabled: if False, qmp monitor options will be removed from
657beb6b57bSJohn Snow                        the base arguments of the resulting QEMU command
658beb6b57bSJohn Snow                        line. Default is True.
6595c02c865SJohn Snow
6605c02c865SJohn Snow        .. note:: Call this function before launch().
661beb6b57bSJohn Snow        """
662beb6b57bSJohn Snow        self._qmp_set = enabled
663beb6b57bSJohn Snow
664beb6b57bSJohn Snow    @property
665beb6b57bSJohn Snow    def _qmp(self) -> QEMUMonitorProtocol:
666beb6b57bSJohn Snow        if self._qmp_connection is None:
667beb6b57bSJohn Snow            raise QEMUMachineError("Attempt to access QMP with no connection")
668beb6b57bSJohn Snow        return self._qmp_connection
669beb6b57bSJohn Snow
670beb6b57bSJohn Snow    @classmethod
671c7daa57eSVladimir Sementsov-Ogievskiy    def _qmp_args(cls, conv_keys: bool,
672c7daa57eSVladimir Sementsov-Ogievskiy                  args: Dict[str, Any]) -> Dict[str, object]:
673c7daa57eSVladimir Sementsov-Ogievskiy        if conv_keys:
674c7daa57eSVladimir Sementsov-Ogievskiy            return {k.replace('_', '-'): v for k, v in args.items()}
675c7daa57eSVladimir Sementsov-Ogievskiy
676c7daa57eSVladimir Sementsov-Ogievskiy        return args
677beb6b57bSJohn Snow
678beb6b57bSJohn Snow    def qmp(self, cmd: str,
6793f3c9b4cSVladimir Sementsov-Ogievskiy            args_dict: Optional[Dict[str, object]] = None,
6803f3c9b4cSVladimir Sementsov-Ogievskiy            conv_keys: Optional[bool] = None,
681beb6b57bSJohn Snow            **args: Any) -> QMPMessage:
682beb6b57bSJohn Snow        """
683beb6b57bSJohn Snow        Invoke a QMP command and return the response dict
684beb6b57bSJohn Snow        """
6853f3c9b4cSVladimir Sementsov-Ogievskiy        if args_dict is not None:
6863f3c9b4cSVladimir Sementsov-Ogievskiy            assert not args
6873f3c9b4cSVladimir Sementsov-Ogievskiy            assert conv_keys is None
6883f3c9b4cSVladimir Sementsov-Ogievskiy            args = args_dict
6893f3c9b4cSVladimir Sementsov-Ogievskiy            conv_keys = False
6903f3c9b4cSVladimir Sementsov-Ogievskiy
6913f3c9b4cSVladimir Sementsov-Ogievskiy        if conv_keys is None:
6923f3c9b4cSVladimir Sementsov-Ogievskiy            conv_keys = True
6933f3c9b4cSVladimir Sementsov-Ogievskiy
694c7daa57eSVladimir Sementsov-Ogievskiy        qmp_args = self._qmp_args(conv_keys, args)
695b9420e4fSJohn Snow        ret = self._qmp.cmd(cmd, args=qmp_args)
696b9420e4fSJohn Snow        if cmd == 'quit' and 'error' not in ret and 'return' in ret:
697b9420e4fSJohn Snow            self._quit_issued = True
698b9420e4fSJohn Snow        return ret
699beb6b57bSJohn Snow
700beb6b57bSJohn Snow    def command(self, cmd: str,
701beb6b57bSJohn Snow                conv_keys: bool = True,
702beb6b57bSJohn Snow                **args: Any) -> QMPReturnValue:
703beb6b57bSJohn Snow        """
704beb6b57bSJohn Snow        Invoke a QMP command.
705beb6b57bSJohn Snow        On success return the response dict.
706beb6b57bSJohn Snow        On failure raise an exception.
707beb6b57bSJohn Snow        """
708c7daa57eSVladimir Sementsov-Ogievskiy        qmp_args = self._qmp_args(conv_keys, args)
709b9420e4fSJohn Snow        ret = self._qmp.command(cmd, **qmp_args)
710b9420e4fSJohn Snow        if cmd == 'quit':
711b9420e4fSJohn Snow            self._quit_issued = True
712b9420e4fSJohn Snow        return ret
713beb6b57bSJohn Snow
714beb6b57bSJohn Snow    def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
715beb6b57bSJohn Snow        """
716beb6b57bSJohn Snow        Poll for one queued QMP events and return it
717beb6b57bSJohn Snow        """
718beb6b57bSJohn Snow        if self._events:
719beb6b57bSJohn Snow            return self._events.pop(0)
720beb6b57bSJohn Snow        return self._qmp.pull_event(wait=wait)
721beb6b57bSJohn Snow
722beb6b57bSJohn Snow    def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
723beb6b57bSJohn Snow        """
724beb6b57bSJohn Snow        Poll for queued QMP events and return a list of dicts
725beb6b57bSJohn Snow        """
726beb6b57bSJohn Snow        events = self._qmp.get_events(wait=wait)
727beb6b57bSJohn Snow        events.extend(self._events)
728beb6b57bSJohn Snow        del self._events[:]
729beb6b57bSJohn Snow        return events
730beb6b57bSJohn Snow
731beb6b57bSJohn Snow    @staticmethod
732beb6b57bSJohn Snow    def event_match(event: Any, match: Optional[Any]) -> bool:
733beb6b57bSJohn Snow        """
734beb6b57bSJohn Snow        Check if an event matches optional match criteria.
735beb6b57bSJohn Snow
736beb6b57bSJohn Snow        The match criteria takes the form of a matching subdict. The event is
737beb6b57bSJohn Snow        checked to be a superset of the subdict, recursively, with matching
738beb6b57bSJohn Snow        values whenever the subdict values are not None.
739beb6b57bSJohn Snow
740beb6b57bSJohn Snow        This has a limitation that you cannot explicitly check for None values.
741beb6b57bSJohn Snow
742beb6b57bSJohn Snow        Examples, with the subdict queries on the left:
743beb6b57bSJohn Snow         - None matches any object.
744beb6b57bSJohn Snow         - {"foo": None} matches {"foo": {"bar": 1}}
745beb6b57bSJohn Snow         - {"foo": None} matches {"foo": 5}
746beb6b57bSJohn Snow         - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
747beb6b57bSJohn Snow         - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
748beb6b57bSJohn Snow        """
749beb6b57bSJohn Snow        if match is None:
750beb6b57bSJohn Snow            return True
751beb6b57bSJohn Snow
752beb6b57bSJohn Snow        try:
753beb6b57bSJohn Snow            for key in match:
754beb6b57bSJohn Snow                if key in event:
755beb6b57bSJohn Snow                    if not QEMUMachine.event_match(event[key], match[key]):
756beb6b57bSJohn Snow                        return False
757beb6b57bSJohn Snow                else:
758beb6b57bSJohn Snow                    return False
759beb6b57bSJohn Snow            return True
760beb6b57bSJohn Snow        except TypeError:
761beb6b57bSJohn Snow            # either match or event wasn't iterable (not a dict)
762beb6b57bSJohn Snow            return bool(match == event)
763beb6b57bSJohn Snow
764beb6b57bSJohn Snow    def event_wait(self, name: str,
765beb6b57bSJohn Snow                   timeout: float = 60.0,
766beb6b57bSJohn Snow                   match: Optional[QMPMessage] = None) -> Optional[QMPMessage]:
767beb6b57bSJohn Snow        """
768beb6b57bSJohn Snow        event_wait waits for and returns a named event from QMP with a timeout.
769beb6b57bSJohn Snow
770beb6b57bSJohn Snow        name: The event to wait for.
771beb6b57bSJohn Snow        timeout: QEMUMonitorProtocol.pull_event timeout parameter.
772beb6b57bSJohn Snow        match: Optional match criteria. See event_match for details.
773beb6b57bSJohn Snow        """
774beb6b57bSJohn Snow        return self.events_wait([(name, match)], timeout)
775beb6b57bSJohn Snow
776beb6b57bSJohn Snow    def events_wait(self,
777beb6b57bSJohn Snow                    events: Sequence[Tuple[str, Any]],
778beb6b57bSJohn Snow                    timeout: float = 60.0) -> Optional[QMPMessage]:
779beb6b57bSJohn Snow        """
780beb6b57bSJohn Snow        events_wait waits for and returns a single named event from QMP.
781beb6b57bSJohn Snow        In the case of multiple qualifying events, this function returns the
782beb6b57bSJohn Snow        first one.
783beb6b57bSJohn Snow
784beb6b57bSJohn Snow        :param events: A sequence of (name, match_criteria) tuples.
785beb6b57bSJohn Snow                       The match criteria are optional and may be None.
786beb6b57bSJohn Snow                       See event_match for details.
787beb6b57bSJohn Snow        :param timeout: Optional timeout, in seconds.
788beb6b57bSJohn Snow                        See QEMUMonitorProtocol.pull_event.
789beb6b57bSJohn Snow
790a4225303SJohn Snow        :raise asyncio.TimeoutError:
791a4225303SJohn Snow            If timeout was non-zero and no matching events were found.
792a4225303SJohn Snow
793beb6b57bSJohn Snow        :return: A QMP event matching the filter criteria.
794beb6b57bSJohn Snow                 If timeout was 0 and no event matched, None.
795beb6b57bSJohn Snow        """
796beb6b57bSJohn Snow        def _match(event: QMPMessage) -> bool:
797beb6b57bSJohn Snow            for name, match in events:
798beb6b57bSJohn Snow                if event['event'] == name and self.event_match(event, match):
799beb6b57bSJohn Snow                    return True
800beb6b57bSJohn Snow            return False
801beb6b57bSJohn Snow
802beb6b57bSJohn Snow        event: Optional[QMPMessage]
803beb6b57bSJohn Snow
804beb6b57bSJohn Snow        # Search cached events
805beb6b57bSJohn Snow        for event in self._events:
806beb6b57bSJohn Snow            if _match(event):
807beb6b57bSJohn Snow                self._events.remove(event)
808beb6b57bSJohn Snow                return event
809beb6b57bSJohn Snow
810beb6b57bSJohn Snow        # Poll for new events
811beb6b57bSJohn Snow        while True:
812beb6b57bSJohn Snow            event = self._qmp.pull_event(wait=timeout)
813beb6b57bSJohn Snow            if event is None:
814beb6b57bSJohn Snow                # NB: None is only returned when timeout is false-ish.
815a4225303SJohn Snow                # Timeouts raise asyncio.TimeoutError instead!
816beb6b57bSJohn Snow                break
817beb6b57bSJohn Snow            if _match(event):
818beb6b57bSJohn Snow                return event
819beb6b57bSJohn Snow            self._events.append(event)
820beb6b57bSJohn Snow
821beb6b57bSJohn Snow        return None
822beb6b57bSJohn Snow
823beb6b57bSJohn Snow    def get_log(self) -> Optional[str]:
824beb6b57bSJohn Snow        """
825beb6b57bSJohn Snow        After self.shutdown or failed qemu execution, this returns the output
826beb6b57bSJohn Snow        of the qemu process.
827beb6b57bSJohn Snow        """
828beb6b57bSJohn Snow        return self._iolog
829beb6b57bSJohn Snow
830beb6b57bSJohn Snow    def add_args(self, *args: str) -> None:
831beb6b57bSJohn Snow        """
832beb6b57bSJohn Snow        Adds to the list of extra arguments to be given to the QEMU binary
833beb6b57bSJohn Snow        """
834beb6b57bSJohn Snow        self._args.extend(args)
835beb6b57bSJohn Snow
836beb6b57bSJohn Snow    def set_machine(self, machine_type: str) -> None:
837beb6b57bSJohn Snow        """
838beb6b57bSJohn Snow        Sets the machine type
839beb6b57bSJohn Snow
840beb6b57bSJohn Snow        If set, the machine type will be added to the base arguments
841beb6b57bSJohn Snow        of the resulting QEMU command line.
842beb6b57bSJohn Snow        """
843beb6b57bSJohn Snow        self._machine = machine_type
844beb6b57bSJohn Snow
845beb6b57bSJohn Snow    def set_console(self,
846beb6b57bSJohn Snow                    device_type: Optional[str] = None,
847beb6b57bSJohn Snow                    console_index: int = 0) -> None:
848beb6b57bSJohn Snow        """
849beb6b57bSJohn Snow        Sets the device type for a console device
850beb6b57bSJohn Snow
851beb6b57bSJohn Snow        If set, the console device and a backing character device will
852beb6b57bSJohn Snow        be added to the base arguments of the resulting QEMU command
853beb6b57bSJohn Snow        line.
854beb6b57bSJohn Snow
855beb6b57bSJohn Snow        This is a convenience method that will either use the provided
856beb6b57bSJohn Snow        device type, or default to a "-serial chardev:console" command
857beb6b57bSJohn Snow        line argument.
858beb6b57bSJohn Snow
859beb6b57bSJohn Snow        The actual setting of command line arguments will be be done at
860beb6b57bSJohn Snow        machine launch time, as it depends on the temporary directory
861beb6b57bSJohn Snow        to be created.
862beb6b57bSJohn Snow
863beb6b57bSJohn Snow        @param device_type: the device type, such as "isa-serial".  If
864beb6b57bSJohn Snow                            None is given (the default value) a "-serial
865beb6b57bSJohn Snow                            chardev:console" command line argument will
866beb6b57bSJohn Snow                            be used instead, resorting to the machine's
867beb6b57bSJohn Snow                            default device type.
868beb6b57bSJohn Snow        @param console_index: the index of the console device to use.
869beb6b57bSJohn Snow                              If not zero, the command line will create
870beb6b57bSJohn Snow                              'index - 1' consoles and connect them to
871beb6b57bSJohn Snow                              the 'null' backing character device.
872beb6b57bSJohn Snow        """
873beb6b57bSJohn Snow        self._console_set = True
874beb6b57bSJohn Snow        self._console_device_type = device_type
875beb6b57bSJohn Snow        self._console_index = console_index
876beb6b57bSJohn Snow
877beb6b57bSJohn Snow    @property
878beb6b57bSJohn Snow    def console_socket(self) -> socket.socket:
879beb6b57bSJohn Snow        """
880beb6b57bSJohn Snow        Returns a socket connected to the console
881beb6b57bSJohn Snow        """
882beb6b57bSJohn Snow        if self._console_socket is None:
883*f0ec14c7SNicholas Piggin            LOG.debug("Opening console socket")
884beb6b57bSJohn Snow            self._console_socket = console_socket.ConsoleSocket(
885beb6b57bSJohn Snow                self._console_address,
886beb6b57bSJohn Snow                file=self._console_log_path,
887beb6b57bSJohn Snow                drain=self._drain_console)
888beb6b57bSJohn Snow        return self._console_socket
889beb6b57bSJohn Snow
890beb6b57bSJohn Snow    @property
891*f0ec14c7SNicholas Piggin    def console_file(self) -> socket.SocketIO:
892*f0ec14c7SNicholas Piggin        """
893*f0ec14c7SNicholas Piggin        Returns a file associated with the console socket
894*f0ec14c7SNicholas Piggin        """
895*f0ec14c7SNicholas Piggin        if self._console_file is None:
896*f0ec14c7SNicholas Piggin            LOG.debug("Opening console file")
897*f0ec14c7SNicholas Piggin            self._console_file = self.console_socket.makefile(mode='rb',
898*f0ec14c7SNicholas Piggin                                                              buffering=0,
899*f0ec14c7SNicholas Piggin                                                              encoding='utf-8')
900*f0ec14c7SNicholas Piggin        return self._console_file
901*f0ec14c7SNicholas Piggin
902*f0ec14c7SNicholas Piggin    @property
903beb6b57bSJohn Snow    def temp_dir(self) -> str:
904beb6b57bSJohn Snow        """
905beb6b57bSJohn Snow        Returns a temporary directory to be used for this machine
906beb6b57bSJohn Snow        """
907beb6b57bSJohn Snow        if self._temp_dir is None:
908beb6b57bSJohn Snow            self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-",
909beb6b57bSJohn Snow                                              dir=self._base_temp_dir)
910beb6b57bSJohn Snow        return self._temp_dir
911b306e26cSCleber Rosa
912b306e26cSCleber Rosa    @property
91387bf1fe5SJohn Snow    def sock_dir(self) -> str:
91487bf1fe5SJohn Snow        """
91587bf1fe5SJohn Snow        Returns the directory used for sockfiles by this machine.
91687bf1fe5SJohn Snow        """
91787bf1fe5SJohn Snow        if self._sock_dir:
91887bf1fe5SJohn Snow            return self._sock_dir
91987bf1fe5SJohn Snow        return self.temp_dir
92087bf1fe5SJohn Snow
92187bf1fe5SJohn Snow    @property
922b306e26cSCleber Rosa    def log_dir(self) -> str:
923b306e26cSCleber Rosa        """
924b306e26cSCleber Rosa        Returns a directory to be used for writing logs
925b306e26cSCleber Rosa        """
926b306e26cSCleber Rosa        if self._log_dir is None:
927b306e26cSCleber Rosa            return self.temp_dir
928b306e26cSCleber Rosa        return self._log_dir
929