xref: /openbmc/qemu/python/qemu/machine/machine.py (revision 9323e79f)
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,
134e2f948a8SEmanuele Giuseppe Esposito                 qmp_timer: Optional[float] = None):
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
16072b17fe7SJohn Snow        self._name = name or f"qemu-{os.getpid()}-{id(self):02x}"
16187bf1fe5SJohn Snow        self._temp_dir: Optional[str] = None
162beb6b57bSJohn Snow        self._base_temp_dir = base_temp_dir
16387bf1fe5SJohn Snow        self._sock_dir = sock_dir
164b306e26cSCleber Rosa        self._log_dir = log_dir
165beb6b57bSJohn Snow
166beb6b57bSJohn Snow        if monitor_address is not None:
167beb6b57bSJohn Snow            self._monitor_address = monitor_address
168beb6b57bSJohn Snow        else:
169beb6b57bSJohn Snow            self._monitor_address = os.path.join(
17087bf1fe5SJohn Snow                self.sock_dir, f"{self._name}-monitor.sock"
171beb6b57bSJohn Snow            )
172beb6b57bSJohn Snow
173beb6b57bSJohn Snow        self._console_log_path = console_log
174beb6b57bSJohn Snow        if self._console_log_path:
175beb6b57bSJohn Snow            # In order to log the console, buffering needs to be enabled.
176beb6b57bSJohn Snow            self._drain_console = True
177beb6b57bSJohn Snow        else:
178beb6b57bSJohn Snow            self._drain_console = drain_console
179beb6b57bSJohn Snow
180beb6b57bSJohn Snow        # Runstate
181beb6b57bSJohn Snow        self._qemu_log_path: Optional[str] = None
182beb6b57bSJohn Snow        self._qemu_log_file: Optional[BinaryIO] = None
183beb6b57bSJohn Snow        self._popen: Optional['subprocess.Popen[bytes]'] = None
184beb6b57bSJohn Snow        self._events: List[QMPMessage] = []
185beb6b57bSJohn Snow        self._iolog: Optional[str] = None
186beb6b57bSJohn Snow        self._qmp_set = True   # Enable QMP monitor by default.
187beb6b57bSJohn Snow        self._qmp_connection: Optional[QEMUMonitorProtocol] = None
188beb6b57bSJohn Snow        self._qemu_full_args: Tuple[str, ...] = ()
189beb6b57bSJohn Snow        self._launched = False
190beb6b57bSJohn Snow        self._machine: Optional[str] = None
191beb6b57bSJohn Snow        self._console_index = 0
192beb6b57bSJohn Snow        self._console_set = False
193beb6b57bSJohn Snow        self._console_device_type: Optional[str] = None
194beb6b57bSJohn Snow        self._console_address = os.path.join(
19587bf1fe5SJohn Snow            self.sock_dir, f"{self._name}-console.sock"
196beb6b57bSJohn Snow        )
197beb6b57bSJohn Snow        self._console_socket: Optional[socket.socket] = None
198beb6b57bSJohn Snow        self._remove_files: List[str] = []
199beb6b57bSJohn Snow        self._user_killed = False
200b9420e4fSJohn Snow        self._quit_issued = False
201beb6b57bSJohn Snow
20215c3b863SVladimir Sementsov-Ogievskiy    def __enter__(self: _T) -> _T:
203beb6b57bSJohn Snow        return self
204beb6b57bSJohn Snow
205beb6b57bSJohn Snow    def __exit__(self,
206beb6b57bSJohn Snow                 exc_type: Optional[Type[BaseException]],
207beb6b57bSJohn Snow                 exc_val: Optional[BaseException],
208beb6b57bSJohn Snow                 exc_tb: Optional[TracebackType]) -> None:
209beb6b57bSJohn Snow        self.shutdown()
210beb6b57bSJohn Snow
211beb6b57bSJohn Snow    def add_monitor_null(self) -> None:
212beb6b57bSJohn Snow        """
213beb6b57bSJohn Snow        This can be used to add an unused monitor instance.
214beb6b57bSJohn Snow        """
215beb6b57bSJohn Snow        self._args.append('-monitor')
216beb6b57bSJohn Snow        self._args.append('null')
217beb6b57bSJohn Snow
21815c3b863SVladimir Sementsov-Ogievskiy    def add_fd(self: _T, fd: int, fdset: int,
21915c3b863SVladimir Sementsov-Ogievskiy               opaque: str, opts: str = '') -> _T:
220beb6b57bSJohn Snow        """
221beb6b57bSJohn Snow        Pass a file descriptor to the VM
222beb6b57bSJohn Snow        """
223beb6b57bSJohn Snow        options = ['fd=%d' % fd,
224beb6b57bSJohn Snow                   'set=%d' % fdset,
225beb6b57bSJohn Snow                   'opaque=%s' % opaque]
226beb6b57bSJohn Snow        if opts:
227beb6b57bSJohn Snow            options.append(opts)
228beb6b57bSJohn Snow
229beb6b57bSJohn Snow        # This did not exist before 3.4, but since then it is
230beb6b57bSJohn Snow        # mandatory for our purpose
231beb6b57bSJohn Snow        if hasattr(os, 'set_inheritable'):
232beb6b57bSJohn Snow            os.set_inheritable(fd, True)
233beb6b57bSJohn Snow
234beb6b57bSJohn Snow        self._args.append('-add-fd')
235beb6b57bSJohn Snow        self._args.append(','.join(options))
236beb6b57bSJohn Snow        return self
237beb6b57bSJohn Snow
238beb6b57bSJohn Snow    def send_fd_scm(self, fd: Optional[int] = None,
239beb6b57bSJohn Snow                    file_path: Optional[str] = None) -> int:
240beb6b57bSJohn Snow        """
241514d00dfSJohn Snow        Send an fd or file_path to the remote via SCM_RIGHTS.
242beb6b57bSJohn Snow
243514d00dfSJohn Snow        Exactly one of fd and file_path must be given.  If it is
244514d00dfSJohn Snow        file_path, the file will be opened read-only and the new file
245514d00dfSJohn Snow        descriptor will be sent to the remote.
246beb6b57bSJohn Snow        """
247beb6b57bSJohn Snow        if file_path is not None:
248beb6b57bSJohn Snow            assert fd is None
249514d00dfSJohn Snow            with open(file_path, "rb") as passfile:
250514d00dfSJohn Snow                fd = passfile.fileno()
251514d00dfSJohn Snow                self._qmp.send_fd_scm(fd)
252beb6b57bSJohn Snow        else:
253beb6b57bSJohn Snow            assert fd is not None
254514d00dfSJohn Snow            self._qmp.send_fd_scm(fd)
255beb6b57bSJohn Snow
256514d00dfSJohn Snow        return 0
257beb6b57bSJohn Snow
258beb6b57bSJohn Snow    @staticmethod
259beb6b57bSJohn Snow    def _remove_if_exists(path: str) -> None:
260beb6b57bSJohn Snow        """
261beb6b57bSJohn Snow        Remove file object at path if it exists
262beb6b57bSJohn Snow        """
263beb6b57bSJohn Snow        try:
264beb6b57bSJohn Snow            os.remove(path)
265beb6b57bSJohn Snow        except OSError as exception:
266beb6b57bSJohn Snow            if exception.errno == errno.ENOENT:
267beb6b57bSJohn Snow                return
268beb6b57bSJohn Snow            raise
269beb6b57bSJohn Snow
270beb6b57bSJohn Snow    def is_running(self) -> bool:
271beb6b57bSJohn Snow        """Returns true if the VM is running."""
272beb6b57bSJohn Snow        return self._popen is not None and self._popen.poll() is None
273beb6b57bSJohn Snow
274beb6b57bSJohn Snow    @property
275beb6b57bSJohn Snow    def _subp(self) -> 'subprocess.Popen[bytes]':
276beb6b57bSJohn Snow        if self._popen is None:
277beb6b57bSJohn Snow            raise QEMUMachineError('Subprocess pipe not present')
278beb6b57bSJohn Snow        return self._popen
279beb6b57bSJohn Snow
280beb6b57bSJohn Snow    def exitcode(self) -> Optional[int]:
281beb6b57bSJohn Snow        """Returns the exit code if possible, or None."""
282beb6b57bSJohn Snow        if self._popen is None:
283beb6b57bSJohn Snow            return None
284beb6b57bSJohn Snow        return self._popen.poll()
285beb6b57bSJohn Snow
286beb6b57bSJohn Snow    def get_pid(self) -> Optional[int]:
287beb6b57bSJohn Snow        """Returns the PID of the running process, or None."""
288beb6b57bSJohn Snow        if not self.is_running():
289beb6b57bSJohn Snow            return None
290beb6b57bSJohn Snow        return self._subp.pid
291beb6b57bSJohn Snow
292beb6b57bSJohn Snow    def _load_io_log(self) -> None:
2935690b437SJohn Snow        # Assume that the output encoding of QEMU's terminal output is
2945690b437SJohn Snow        # defined by our locale. If indeterminate, allow open() to fall
2955690b437SJohn Snow        # back to the platform default.
2965690b437SJohn Snow        _, encoding = locale.getlocale()
297beb6b57bSJohn Snow        if self._qemu_log_path is not None:
2985690b437SJohn Snow            with open(self._qemu_log_path, "r", encoding=encoding) as iolog:
299beb6b57bSJohn Snow                self._iolog = iolog.read()
300beb6b57bSJohn Snow
301beb6b57bSJohn Snow    @property
302beb6b57bSJohn Snow    def _base_args(self) -> List[str]:
303beb6b57bSJohn Snow        args = ['-display', 'none', '-vga', 'none']
304beb6b57bSJohn Snow
305beb6b57bSJohn Snow        if self._qmp_set:
306beb6b57bSJohn Snow            if isinstance(self._monitor_address, tuple):
307beb6b57bSJohn Snow                moncdev = "socket,id=mon,host={},port={}".format(
308beb6b57bSJohn Snow                    *self._monitor_address
309beb6b57bSJohn Snow                )
310beb6b57bSJohn Snow            else:
311beb6b57bSJohn Snow                moncdev = f"socket,id=mon,path={self._monitor_address}"
312beb6b57bSJohn Snow            args.extend(['-chardev', moncdev, '-mon',
313beb6b57bSJohn Snow                         'chardev=mon,mode=control'])
314beb6b57bSJohn Snow
315beb6b57bSJohn Snow        if self._machine is not None:
316beb6b57bSJohn Snow            args.extend(['-machine', self._machine])
317beb6b57bSJohn Snow        for _ in range(self._console_index):
318beb6b57bSJohn Snow            args.extend(['-serial', 'null'])
319beb6b57bSJohn Snow        if self._console_set:
320beb6b57bSJohn Snow            chardev = ('socket,id=console,path=%s,server=on,wait=off' %
321beb6b57bSJohn Snow                       self._console_address)
322beb6b57bSJohn Snow            args.extend(['-chardev', chardev])
323beb6b57bSJohn Snow            if self._console_device_type is None:
324beb6b57bSJohn Snow                args.extend(['-serial', 'chardev:console'])
325beb6b57bSJohn Snow            else:
326beb6b57bSJohn Snow                device = '%s,chardev=console' % self._console_device_type
327beb6b57bSJohn Snow                args.extend(['-device', device])
328beb6b57bSJohn Snow        return args
329beb6b57bSJohn Snow
330555fe0c2SWainer dos Santos Moschetta    @property
331555fe0c2SWainer dos Santos Moschetta    def args(self) -> List[str]:
332555fe0c2SWainer dos Santos Moschetta        """Returns the list of arguments given to the QEMU binary."""
333555fe0c2SWainer dos Santos Moschetta        return self._args
334555fe0c2SWainer dos Santos Moschetta
335beb6b57bSJohn Snow    def _pre_launch(self) -> None:
336beb6b57bSJohn Snow        if self._console_set:
337beb6b57bSJohn Snow            self._remove_files.append(self._console_address)
338beb6b57bSJohn Snow
339beb6b57bSJohn Snow        if self._qmp_set:
3406eeb3de7SJohn Snow            if isinstance(self._monitor_address, str):
341beb6b57bSJohn Snow                self._remove_files.append(self._monitor_address)
342beb6b57bSJohn Snow            self._qmp_connection = QEMUMonitorProtocol(
343beb6b57bSJohn Snow                self._monitor_address,
344beb6b57bSJohn Snow                server=True,
345beb6b57bSJohn Snow                nickname=self._name
346beb6b57bSJohn Snow            )
347beb6b57bSJohn Snow
348beb6b57bSJohn Snow        # NOTE: Make sure any opened resources are *definitely* freed in
349beb6b57bSJohn Snow        # _post_shutdown()!
350beb6b57bSJohn Snow        # pylint: disable=consider-using-with
351b306e26cSCleber Rosa        self._qemu_log_path = os.path.join(self.log_dir, self._name + ".log")
352beb6b57bSJohn Snow        self._qemu_log_file = open(self._qemu_log_path, 'wb')
353beb6b57bSJohn Snow
354b1ca9919SJohn Snow        self._iolog = None
355b1ca9919SJohn Snow        self._qemu_full_args = tuple(chain(
356b1ca9919SJohn Snow            self._wrapper,
357b1ca9919SJohn Snow            [self._binary],
358b1ca9919SJohn Snow            self._base_args,
359b1ca9919SJohn Snow            self._args
360b1ca9919SJohn Snow        ))
361b1ca9919SJohn Snow
362beb6b57bSJohn Snow    def _post_launch(self) -> None:
363beb6b57bSJohn Snow        if self._qmp_connection:
364e2f948a8SEmanuele Giuseppe Esposito            self._qmp.accept(self._qmp_timer)
365beb6b57bSJohn Snow
366eb7a91d0SEmanuele Giuseppe Esposito    def _close_qemu_log_file(self) -> None:
367eb7a91d0SEmanuele Giuseppe Esposito        if self._qemu_log_file is not None:
368eb7a91d0SEmanuele Giuseppe Esposito            self._qemu_log_file.close()
369eb7a91d0SEmanuele Giuseppe Esposito            self._qemu_log_file = None
370eb7a91d0SEmanuele Giuseppe Esposito
371beb6b57bSJohn Snow    def _post_shutdown(self) -> None:
372beb6b57bSJohn Snow        """
373beb6b57bSJohn Snow        Called to cleanup the VM instance after the process has exited.
374beb6b57bSJohn Snow        May also be called after a failed launch.
375beb6b57bSJohn Snow        """
37649a608b8SJohn Snow        try:
37749a608b8SJohn Snow            self._close_qmp_connection()
37849a608b8SJohn Snow        except Exception as err:  # pylint: disable=broad-except
37949a608b8SJohn Snow            LOG.warning(
38049a608b8SJohn Snow                "Exception closing QMP connection: %s",
38149a608b8SJohn Snow                str(err) if str(err) else type(err).__name__
38249a608b8SJohn Snow            )
38349a608b8SJohn Snow        finally:
38449a608b8SJohn Snow            assert self._qmp_connection is None
385beb6b57bSJohn Snow
386eb7a91d0SEmanuele Giuseppe Esposito        self._close_qemu_log_file()
387beb6b57bSJohn Snow
388beb6b57bSJohn Snow        self._load_io_log()
389beb6b57bSJohn Snow
390beb6b57bSJohn Snow        self._qemu_log_path = None
391beb6b57bSJohn Snow
392beb6b57bSJohn Snow        if self._temp_dir is not None:
393beb6b57bSJohn Snow            shutil.rmtree(self._temp_dir)
394beb6b57bSJohn Snow            self._temp_dir = None
395beb6b57bSJohn Snow
396beb6b57bSJohn Snow        while len(self._remove_files) > 0:
397beb6b57bSJohn Snow            self._remove_if_exists(self._remove_files.pop())
398beb6b57bSJohn Snow
399beb6b57bSJohn Snow        exitcode = self.exitcode()
400beb6b57bSJohn Snow        if (exitcode is not None and exitcode < 0
401beb6b57bSJohn Snow                and not (self._user_killed and exitcode == -signal.SIGKILL)):
402beb6b57bSJohn Snow            msg = 'qemu received signal %i; command: "%s"'
403beb6b57bSJohn Snow            if self._qemu_full_args:
404beb6b57bSJohn Snow                command = ' '.join(self._qemu_full_args)
405beb6b57bSJohn Snow            else:
406beb6b57bSJohn Snow                command = ''
407beb6b57bSJohn Snow            LOG.warning(msg, -int(exitcode), command)
408beb6b57bSJohn Snow
409b9420e4fSJohn Snow        self._quit_issued = False
410beb6b57bSJohn Snow        self._user_killed = False
411beb6b57bSJohn Snow        self._launched = False
412beb6b57bSJohn Snow
413beb6b57bSJohn Snow    def launch(self) -> None:
414beb6b57bSJohn Snow        """
415beb6b57bSJohn Snow        Launch the VM and make sure we cleanup and expose the
416beb6b57bSJohn Snow        command line/output in case of exception
417beb6b57bSJohn Snow        """
418beb6b57bSJohn Snow
419beb6b57bSJohn Snow        if self._launched:
420beb6b57bSJohn Snow            raise QEMUMachineError('VM already launched')
421beb6b57bSJohn Snow
422beb6b57bSJohn Snow        try:
423beb6b57bSJohn Snow            self._launch()
42450465f94SJohn Snow        except BaseException as exc:
4251611e6cfSJohn Snow            # We may have launched the process but it may
4261611e6cfSJohn Snow            # have exited before we could connect via QMP.
4271611e6cfSJohn Snow            # Assume the VM didn't launch or is exiting.
4281611e6cfSJohn Snow            # If we don't wait for the process, exitcode() may still be
4291611e6cfSJohn Snow            # 'None' by the time control is ceded back to the caller.
4301611e6cfSJohn Snow            if self._launched:
4311611e6cfSJohn Snow                self.wait()
4321611e6cfSJohn Snow            else:
433beb6b57bSJohn Snow                self._post_shutdown()
434beb6b57bSJohn Snow
43550465f94SJohn Snow            if isinstance(exc, Exception):
43650465f94SJohn Snow                raise VMLaunchFailure(
43750465f94SJohn Snow                    exitcode=self.exitcode(),
43850465f94SJohn Snow                    command=' '.join(self._qemu_full_args),
43950465f94SJohn Snow                    output=self._iolog
44050465f94SJohn Snow                ) from exc
44150465f94SJohn Snow
44250465f94SJohn Snow            # Don't wrap 'BaseException'; doing so would downgrade
44350465f94SJohn Snow            # that exception. However, we still want to clean up.
444beb6b57bSJohn Snow            raise
445beb6b57bSJohn Snow
446beb6b57bSJohn Snow    def _launch(self) -> None:
447beb6b57bSJohn Snow        """
448beb6b57bSJohn Snow        Launch the VM and establish a QMP connection
449beb6b57bSJohn Snow        """
450beb6b57bSJohn Snow        self._pre_launch()
451beb6b57bSJohn Snow        LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
452beb6b57bSJohn Snow
453beb6b57bSJohn Snow        # Cleaning up of this subprocess is guaranteed by _do_shutdown.
454beb6b57bSJohn Snow        # pylint: disable=consider-using-with
455beb6b57bSJohn Snow        self._popen = subprocess.Popen(self._qemu_full_args,
456beb6b57bSJohn Snow                                       stdin=subprocess.DEVNULL,
457beb6b57bSJohn Snow                                       stdout=self._qemu_log_file,
458beb6b57bSJohn Snow                                       stderr=subprocess.STDOUT,
459beb6b57bSJohn Snow                                       shell=False,
460beb6b57bSJohn Snow                                       close_fds=False)
4611611e6cfSJohn Snow        self._launched = True
462beb6b57bSJohn Snow        self._post_launch()
463beb6b57bSJohn Snow
46449a608b8SJohn Snow    def _close_qmp_connection(self) -> None:
46549a608b8SJohn Snow        """
46649a608b8SJohn Snow        Close the underlying QMP connection, if any.
46749a608b8SJohn Snow
46849a608b8SJohn Snow        Dutifully report errors that occurred while closing, but assume
46949a608b8SJohn Snow        that any error encountered indicates an abnormal termination
47049a608b8SJohn Snow        process and not a failure to close.
47149a608b8SJohn Snow        """
47249a608b8SJohn Snow        if self._qmp_connection is None:
47349a608b8SJohn Snow            return
47449a608b8SJohn Snow
47549a608b8SJohn Snow        try:
47649a608b8SJohn Snow            self._qmp.close()
47749a608b8SJohn Snow        except EOFError:
47849a608b8SJohn Snow            # EOF can occur as an Exception here when using the Async
47949a608b8SJohn Snow            # QMP backend. It indicates that the server closed the
48049a608b8SJohn Snow            # stream. If we successfully issued 'quit' at any point,
48149a608b8SJohn Snow            # then this was expected. If the remote went away without
48249a608b8SJohn Snow            # our permission, it's worth reporting that as an abnormal
48349a608b8SJohn Snow            # shutdown case.
48449a608b8SJohn Snow            if not (self._user_killed or self._quit_issued):
48549a608b8SJohn Snow                raise
48649a608b8SJohn Snow        finally:
48749a608b8SJohn Snow            self._qmp_connection = None
48849a608b8SJohn Snow
489beb6b57bSJohn Snow    def _early_cleanup(self) -> None:
490beb6b57bSJohn Snow        """
491beb6b57bSJohn Snow        Perform any cleanup that needs to happen before the VM exits.
492beb6b57bSJohn Snow
4931611e6cfSJohn Snow        This method may be called twice upon shutdown, once each by soft
4941611e6cfSJohn Snow        and hard shutdown in failover scenarios.
495beb6b57bSJohn Snow        """
496beb6b57bSJohn Snow        # If we keep the console socket open, we may deadlock waiting
497beb6b57bSJohn Snow        # for QEMU to exit, while QEMU is waiting for the socket to
498*9323e79fSPeter Maydell        # become writable.
499beb6b57bSJohn Snow        if self._console_socket is not None:
500beb6b57bSJohn Snow            self._console_socket.close()
501beb6b57bSJohn Snow            self._console_socket = None
502beb6b57bSJohn Snow
503beb6b57bSJohn Snow    def _hard_shutdown(self) -> None:
504beb6b57bSJohn Snow        """
505beb6b57bSJohn Snow        Perform early cleanup, kill the VM, and wait for it to terminate.
506beb6b57bSJohn Snow
507beb6b57bSJohn Snow        :raise subprocess.Timeout: When timeout is exceeds 60 seconds
508beb6b57bSJohn Snow            waiting for the QEMU process to terminate.
509beb6b57bSJohn Snow        """
510beb6b57bSJohn Snow        self._early_cleanup()
511beb6b57bSJohn Snow        self._subp.kill()
512beb6b57bSJohn Snow        self._subp.wait(timeout=60)
513beb6b57bSJohn Snow
514b9420e4fSJohn Snow    def _soft_shutdown(self, timeout: Optional[int]) -> None:
515beb6b57bSJohn Snow        """
516beb6b57bSJohn Snow        Perform early cleanup, attempt to gracefully shut down the VM, and wait
517beb6b57bSJohn Snow        for it to terminate.
518beb6b57bSJohn Snow
519beb6b57bSJohn Snow        :param timeout: Timeout in seconds for graceful shutdown.
520beb6b57bSJohn Snow                        A value of None is an infinite wait.
521beb6b57bSJohn Snow
522beb6b57bSJohn Snow        :raise ConnectionReset: On QMP communication errors
523beb6b57bSJohn Snow        :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
524beb6b57bSJohn Snow            the QEMU process to terminate.
525beb6b57bSJohn Snow        """
526beb6b57bSJohn Snow        self._early_cleanup()
527beb6b57bSJohn Snow
528beb6b57bSJohn Snow        if self._qmp_connection:
52949a608b8SJohn Snow            try:
530b9420e4fSJohn Snow                if not self._quit_issued:
53149a608b8SJohn Snow                    # May raise ExecInterruptedError or StateError if the
53249a608b8SJohn Snow                    # connection dies or has *already* died.
533b9420e4fSJohn Snow                    self.qmp('quit')
53449a608b8SJohn Snow            finally:
53549a608b8SJohn Snow                # Regardless, we want to quiesce the connection.
53649a608b8SJohn Snow                self._close_qmp_connection()
537beb6b57bSJohn Snow
538beb6b57bSJohn Snow        # May raise subprocess.TimeoutExpired
539beb6b57bSJohn Snow        self._subp.wait(timeout=timeout)
540beb6b57bSJohn Snow
541b9420e4fSJohn Snow    def _do_shutdown(self, timeout: Optional[int]) -> None:
542beb6b57bSJohn Snow        """
543beb6b57bSJohn Snow        Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
544beb6b57bSJohn Snow
545beb6b57bSJohn Snow        :param timeout: Timeout in seconds for graceful shutdown.
546beb6b57bSJohn Snow                        A value of None is an infinite wait.
547beb6b57bSJohn Snow
548beb6b57bSJohn Snow        :raise AbnormalShutdown: When the VM could not be shut down gracefully.
549beb6b57bSJohn Snow            The inner exception will likely be ConnectionReset or
550beb6b57bSJohn Snow            subprocess.TimeoutExpired. In rare cases, non-graceful termination
551beb6b57bSJohn Snow            may result in its own exceptions, likely subprocess.TimeoutExpired.
552beb6b57bSJohn Snow        """
553beb6b57bSJohn Snow        try:
554b9420e4fSJohn Snow            self._soft_shutdown(timeout)
555beb6b57bSJohn Snow        except Exception as exc:
556beb6b57bSJohn Snow            self._hard_shutdown()
557beb6b57bSJohn Snow            raise AbnormalShutdown("Could not perform graceful shutdown") \
558beb6b57bSJohn Snow                from exc
559beb6b57bSJohn Snow
560b9420e4fSJohn Snow    def shutdown(self,
561beb6b57bSJohn Snow                 hard: bool = False,
562beb6b57bSJohn Snow                 timeout: Optional[int] = 30) -> None:
563beb6b57bSJohn Snow        """
564beb6b57bSJohn Snow        Terminate the VM (gracefully if possible) and perform cleanup.
565beb6b57bSJohn Snow        Cleanup will always be performed.
566beb6b57bSJohn Snow
567beb6b57bSJohn Snow        If the VM has not yet been launched, or shutdown(), wait(), or kill()
568beb6b57bSJohn Snow        have already been called, this method does nothing.
569beb6b57bSJohn Snow
570beb6b57bSJohn Snow        :param hard: When true, do not attempt graceful shutdown, and
571beb6b57bSJohn Snow                     suppress the SIGKILL warning log message.
572beb6b57bSJohn Snow        :param timeout: Optional timeout in seconds for graceful shutdown.
573beb6b57bSJohn Snow                        Default 30 seconds, A `None` value is an infinite wait.
574beb6b57bSJohn Snow        """
575beb6b57bSJohn Snow        if not self._launched:
576beb6b57bSJohn Snow            return
577beb6b57bSJohn Snow
578beb6b57bSJohn Snow        try:
579beb6b57bSJohn Snow            if hard:
580beb6b57bSJohn Snow                self._user_killed = True
581beb6b57bSJohn Snow                self._hard_shutdown()
582beb6b57bSJohn Snow            else:
583b9420e4fSJohn Snow                self._do_shutdown(timeout)
584beb6b57bSJohn Snow        finally:
585beb6b57bSJohn Snow            self._post_shutdown()
586beb6b57bSJohn Snow
587beb6b57bSJohn Snow    def kill(self) -> None:
588beb6b57bSJohn Snow        """
589beb6b57bSJohn Snow        Terminate the VM forcefully, wait for it to exit, and perform cleanup.
590beb6b57bSJohn Snow        """
591beb6b57bSJohn Snow        self.shutdown(hard=True)
592beb6b57bSJohn Snow
593beb6b57bSJohn Snow    def wait(self, timeout: Optional[int] = 30) -> None:
594beb6b57bSJohn Snow        """
595beb6b57bSJohn Snow        Wait for the VM to power off and perform post-shutdown cleanup.
596beb6b57bSJohn Snow
597beb6b57bSJohn Snow        :param timeout: Optional timeout in seconds. Default 30 seconds.
598beb6b57bSJohn Snow                        A value of `None` is an infinite wait.
599beb6b57bSJohn Snow        """
600b9420e4fSJohn Snow        self._quit_issued = True
601b9420e4fSJohn Snow        self.shutdown(timeout=timeout)
602beb6b57bSJohn Snow
603beb6b57bSJohn Snow    def set_qmp_monitor(self, enabled: bool = True) -> None:
604beb6b57bSJohn Snow        """
605beb6b57bSJohn Snow        Set the QMP monitor.
606beb6b57bSJohn Snow
607beb6b57bSJohn Snow        @param enabled: if False, qmp monitor options will be removed from
608beb6b57bSJohn Snow                        the base arguments of the resulting QEMU command
609beb6b57bSJohn Snow                        line. Default is True.
6105c02c865SJohn Snow
6115c02c865SJohn Snow        .. note:: Call this function before launch().
612beb6b57bSJohn Snow        """
613beb6b57bSJohn Snow        self._qmp_set = enabled
614beb6b57bSJohn Snow
615beb6b57bSJohn Snow    @property
616beb6b57bSJohn Snow    def _qmp(self) -> QEMUMonitorProtocol:
617beb6b57bSJohn Snow        if self._qmp_connection is None:
618beb6b57bSJohn Snow            raise QEMUMachineError("Attempt to access QMP with no connection")
619beb6b57bSJohn Snow        return self._qmp_connection
620beb6b57bSJohn Snow
621beb6b57bSJohn Snow    @classmethod
622c7daa57eSVladimir Sementsov-Ogievskiy    def _qmp_args(cls, conv_keys: bool,
623c7daa57eSVladimir Sementsov-Ogievskiy                  args: Dict[str, Any]) -> Dict[str, object]:
624c7daa57eSVladimir Sementsov-Ogievskiy        if conv_keys:
625c7daa57eSVladimir Sementsov-Ogievskiy            return {k.replace('_', '-'): v for k, v in args.items()}
626c7daa57eSVladimir Sementsov-Ogievskiy
627c7daa57eSVladimir Sementsov-Ogievskiy        return args
628beb6b57bSJohn Snow
629beb6b57bSJohn Snow    def qmp(self, cmd: str,
6303f3c9b4cSVladimir Sementsov-Ogievskiy            args_dict: Optional[Dict[str, object]] = None,
6313f3c9b4cSVladimir Sementsov-Ogievskiy            conv_keys: Optional[bool] = None,
632beb6b57bSJohn Snow            **args: Any) -> QMPMessage:
633beb6b57bSJohn Snow        """
634beb6b57bSJohn Snow        Invoke a QMP command and return the response dict
635beb6b57bSJohn Snow        """
6363f3c9b4cSVladimir Sementsov-Ogievskiy        if args_dict is not None:
6373f3c9b4cSVladimir Sementsov-Ogievskiy            assert not args
6383f3c9b4cSVladimir Sementsov-Ogievskiy            assert conv_keys is None
6393f3c9b4cSVladimir Sementsov-Ogievskiy            args = args_dict
6403f3c9b4cSVladimir Sementsov-Ogievskiy            conv_keys = False
6413f3c9b4cSVladimir Sementsov-Ogievskiy
6423f3c9b4cSVladimir Sementsov-Ogievskiy        if conv_keys is None:
6433f3c9b4cSVladimir Sementsov-Ogievskiy            conv_keys = True
6443f3c9b4cSVladimir Sementsov-Ogievskiy
645c7daa57eSVladimir Sementsov-Ogievskiy        qmp_args = self._qmp_args(conv_keys, args)
646b9420e4fSJohn Snow        ret = self._qmp.cmd(cmd, args=qmp_args)
647b9420e4fSJohn Snow        if cmd == 'quit' and 'error' not in ret and 'return' in ret:
648b9420e4fSJohn Snow            self._quit_issued = True
649b9420e4fSJohn Snow        return ret
650beb6b57bSJohn Snow
651beb6b57bSJohn Snow    def command(self, cmd: str,
652beb6b57bSJohn Snow                conv_keys: bool = True,
653beb6b57bSJohn Snow                **args: Any) -> QMPReturnValue:
654beb6b57bSJohn Snow        """
655beb6b57bSJohn Snow        Invoke a QMP command.
656beb6b57bSJohn Snow        On success return the response dict.
657beb6b57bSJohn Snow        On failure raise an exception.
658beb6b57bSJohn Snow        """
659c7daa57eSVladimir Sementsov-Ogievskiy        qmp_args = self._qmp_args(conv_keys, args)
660b9420e4fSJohn Snow        ret = self._qmp.command(cmd, **qmp_args)
661b9420e4fSJohn Snow        if cmd == 'quit':
662b9420e4fSJohn Snow            self._quit_issued = True
663b9420e4fSJohn Snow        return ret
664beb6b57bSJohn Snow
665beb6b57bSJohn Snow    def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
666beb6b57bSJohn Snow        """
667beb6b57bSJohn Snow        Poll for one queued QMP events and return it
668beb6b57bSJohn Snow        """
669beb6b57bSJohn Snow        if self._events:
670beb6b57bSJohn Snow            return self._events.pop(0)
671beb6b57bSJohn Snow        return self._qmp.pull_event(wait=wait)
672beb6b57bSJohn Snow
673beb6b57bSJohn Snow    def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
674beb6b57bSJohn Snow        """
675beb6b57bSJohn Snow        Poll for queued QMP events and return a list of dicts
676beb6b57bSJohn Snow        """
677beb6b57bSJohn Snow        events = self._qmp.get_events(wait=wait)
678beb6b57bSJohn Snow        events.extend(self._events)
679beb6b57bSJohn Snow        del self._events[:]
680beb6b57bSJohn Snow        return events
681beb6b57bSJohn Snow
682beb6b57bSJohn Snow    @staticmethod
683beb6b57bSJohn Snow    def event_match(event: Any, match: Optional[Any]) -> bool:
684beb6b57bSJohn Snow        """
685beb6b57bSJohn Snow        Check if an event matches optional match criteria.
686beb6b57bSJohn Snow
687beb6b57bSJohn Snow        The match criteria takes the form of a matching subdict. The event is
688beb6b57bSJohn Snow        checked to be a superset of the subdict, recursively, with matching
689beb6b57bSJohn Snow        values whenever the subdict values are not None.
690beb6b57bSJohn Snow
691beb6b57bSJohn Snow        This has a limitation that you cannot explicitly check for None values.
692beb6b57bSJohn Snow
693beb6b57bSJohn Snow        Examples, with the subdict queries on the left:
694beb6b57bSJohn Snow         - None matches any object.
695beb6b57bSJohn Snow         - {"foo": None} matches {"foo": {"bar": 1}}
696beb6b57bSJohn Snow         - {"foo": None} matches {"foo": 5}
697beb6b57bSJohn Snow         - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
698beb6b57bSJohn Snow         - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
699beb6b57bSJohn Snow        """
700beb6b57bSJohn Snow        if match is None:
701beb6b57bSJohn Snow            return True
702beb6b57bSJohn Snow
703beb6b57bSJohn Snow        try:
704beb6b57bSJohn Snow            for key in match:
705beb6b57bSJohn Snow                if key in event:
706beb6b57bSJohn Snow                    if not QEMUMachine.event_match(event[key], match[key]):
707beb6b57bSJohn Snow                        return False
708beb6b57bSJohn Snow                else:
709beb6b57bSJohn Snow                    return False
710beb6b57bSJohn Snow            return True
711beb6b57bSJohn Snow        except TypeError:
712beb6b57bSJohn Snow            # either match or event wasn't iterable (not a dict)
713beb6b57bSJohn Snow            return bool(match == event)
714beb6b57bSJohn Snow
715beb6b57bSJohn Snow    def event_wait(self, name: str,
716beb6b57bSJohn Snow                   timeout: float = 60.0,
717beb6b57bSJohn Snow                   match: Optional[QMPMessage] = None) -> Optional[QMPMessage]:
718beb6b57bSJohn Snow        """
719beb6b57bSJohn Snow        event_wait waits for and returns a named event from QMP with a timeout.
720beb6b57bSJohn Snow
721beb6b57bSJohn Snow        name: The event to wait for.
722beb6b57bSJohn Snow        timeout: QEMUMonitorProtocol.pull_event timeout parameter.
723beb6b57bSJohn Snow        match: Optional match criteria. See event_match for details.
724beb6b57bSJohn Snow        """
725beb6b57bSJohn Snow        return self.events_wait([(name, match)], timeout)
726beb6b57bSJohn Snow
727beb6b57bSJohn Snow    def events_wait(self,
728beb6b57bSJohn Snow                    events: Sequence[Tuple[str, Any]],
729beb6b57bSJohn Snow                    timeout: float = 60.0) -> Optional[QMPMessage]:
730beb6b57bSJohn Snow        """
731beb6b57bSJohn Snow        events_wait waits for and returns a single named event from QMP.
732beb6b57bSJohn Snow        In the case of multiple qualifying events, this function returns the
733beb6b57bSJohn Snow        first one.
734beb6b57bSJohn Snow
735beb6b57bSJohn Snow        :param events: A sequence of (name, match_criteria) tuples.
736beb6b57bSJohn Snow                       The match criteria are optional and may be None.
737beb6b57bSJohn Snow                       See event_match for details.
738beb6b57bSJohn Snow        :param timeout: Optional timeout, in seconds.
739beb6b57bSJohn Snow                        See QEMUMonitorProtocol.pull_event.
740beb6b57bSJohn Snow
741a4225303SJohn Snow        :raise asyncio.TimeoutError:
742a4225303SJohn Snow            If timeout was non-zero and no matching events were found.
743a4225303SJohn Snow
744beb6b57bSJohn Snow        :return: A QMP event matching the filter criteria.
745beb6b57bSJohn Snow                 If timeout was 0 and no event matched, None.
746beb6b57bSJohn Snow        """
747beb6b57bSJohn Snow        def _match(event: QMPMessage) -> bool:
748beb6b57bSJohn Snow            for name, match in events:
749beb6b57bSJohn Snow                if event['event'] == name and self.event_match(event, match):
750beb6b57bSJohn Snow                    return True
751beb6b57bSJohn Snow            return False
752beb6b57bSJohn Snow
753beb6b57bSJohn Snow        event: Optional[QMPMessage]
754beb6b57bSJohn Snow
755beb6b57bSJohn Snow        # Search cached events
756beb6b57bSJohn Snow        for event in self._events:
757beb6b57bSJohn Snow            if _match(event):
758beb6b57bSJohn Snow                self._events.remove(event)
759beb6b57bSJohn Snow                return event
760beb6b57bSJohn Snow
761beb6b57bSJohn Snow        # Poll for new events
762beb6b57bSJohn Snow        while True:
763beb6b57bSJohn Snow            event = self._qmp.pull_event(wait=timeout)
764beb6b57bSJohn Snow            if event is None:
765beb6b57bSJohn Snow                # NB: None is only returned when timeout is false-ish.
766a4225303SJohn Snow                # Timeouts raise asyncio.TimeoutError instead!
767beb6b57bSJohn Snow                break
768beb6b57bSJohn Snow            if _match(event):
769beb6b57bSJohn Snow                return event
770beb6b57bSJohn Snow            self._events.append(event)
771beb6b57bSJohn Snow
772beb6b57bSJohn Snow        return None
773beb6b57bSJohn Snow
774beb6b57bSJohn Snow    def get_log(self) -> Optional[str]:
775beb6b57bSJohn Snow        """
776beb6b57bSJohn Snow        After self.shutdown or failed qemu execution, this returns the output
777beb6b57bSJohn Snow        of the qemu process.
778beb6b57bSJohn Snow        """
779beb6b57bSJohn Snow        return self._iolog
780beb6b57bSJohn Snow
781beb6b57bSJohn Snow    def add_args(self, *args: str) -> None:
782beb6b57bSJohn Snow        """
783beb6b57bSJohn Snow        Adds to the list of extra arguments to be given to the QEMU binary
784beb6b57bSJohn Snow        """
785beb6b57bSJohn Snow        self._args.extend(args)
786beb6b57bSJohn Snow
787beb6b57bSJohn Snow    def set_machine(self, machine_type: str) -> None:
788beb6b57bSJohn Snow        """
789beb6b57bSJohn Snow        Sets the machine type
790beb6b57bSJohn Snow
791beb6b57bSJohn Snow        If set, the machine type will be added to the base arguments
792beb6b57bSJohn Snow        of the resulting QEMU command line.
793beb6b57bSJohn Snow        """
794beb6b57bSJohn Snow        self._machine = machine_type
795beb6b57bSJohn Snow
796beb6b57bSJohn Snow    def set_console(self,
797beb6b57bSJohn Snow                    device_type: Optional[str] = None,
798beb6b57bSJohn Snow                    console_index: int = 0) -> None:
799beb6b57bSJohn Snow        """
800beb6b57bSJohn Snow        Sets the device type for a console device
801beb6b57bSJohn Snow
802beb6b57bSJohn Snow        If set, the console device and a backing character device will
803beb6b57bSJohn Snow        be added to the base arguments of the resulting QEMU command
804beb6b57bSJohn Snow        line.
805beb6b57bSJohn Snow
806beb6b57bSJohn Snow        This is a convenience method that will either use the provided
807beb6b57bSJohn Snow        device type, or default to a "-serial chardev:console" command
808beb6b57bSJohn Snow        line argument.
809beb6b57bSJohn Snow
810beb6b57bSJohn Snow        The actual setting of command line arguments will be be done at
811beb6b57bSJohn Snow        machine launch time, as it depends on the temporary directory
812beb6b57bSJohn Snow        to be created.
813beb6b57bSJohn Snow
814beb6b57bSJohn Snow        @param device_type: the device type, such as "isa-serial".  If
815beb6b57bSJohn Snow                            None is given (the default value) a "-serial
816beb6b57bSJohn Snow                            chardev:console" command line argument will
817beb6b57bSJohn Snow                            be used instead, resorting to the machine's
818beb6b57bSJohn Snow                            default device type.
819beb6b57bSJohn Snow        @param console_index: the index of the console device to use.
820beb6b57bSJohn Snow                              If not zero, the command line will create
821beb6b57bSJohn Snow                              'index - 1' consoles and connect them to
822beb6b57bSJohn Snow                              the 'null' backing character device.
823beb6b57bSJohn Snow        """
824beb6b57bSJohn Snow        self._console_set = True
825beb6b57bSJohn Snow        self._console_device_type = device_type
826beb6b57bSJohn Snow        self._console_index = console_index
827beb6b57bSJohn Snow
828beb6b57bSJohn Snow    @property
829beb6b57bSJohn Snow    def console_socket(self) -> socket.socket:
830beb6b57bSJohn Snow        """
831beb6b57bSJohn Snow        Returns a socket connected to the console
832beb6b57bSJohn Snow        """
833beb6b57bSJohn Snow        if self._console_socket is None:
834beb6b57bSJohn Snow            self._console_socket = console_socket.ConsoleSocket(
835beb6b57bSJohn Snow                self._console_address,
836beb6b57bSJohn Snow                file=self._console_log_path,
837beb6b57bSJohn Snow                drain=self._drain_console)
838beb6b57bSJohn Snow        return self._console_socket
839beb6b57bSJohn Snow
840beb6b57bSJohn Snow    @property
841beb6b57bSJohn Snow    def temp_dir(self) -> str:
842beb6b57bSJohn Snow        """
843beb6b57bSJohn Snow        Returns a temporary directory to be used for this machine
844beb6b57bSJohn Snow        """
845beb6b57bSJohn Snow        if self._temp_dir is None:
846beb6b57bSJohn Snow            self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-",
847beb6b57bSJohn Snow                                              dir=self._base_temp_dir)
848beb6b57bSJohn Snow        return self._temp_dir
849b306e26cSCleber Rosa
850b306e26cSCleber Rosa    @property
85187bf1fe5SJohn Snow    def sock_dir(self) -> str:
85287bf1fe5SJohn Snow        """
85387bf1fe5SJohn Snow        Returns the directory used for sockfiles by this machine.
85487bf1fe5SJohn Snow        """
85587bf1fe5SJohn Snow        if self._sock_dir:
85687bf1fe5SJohn Snow            return self._sock_dir
85787bf1fe5SJohn Snow        return self.temp_dir
85887bf1fe5SJohn Snow
85987bf1fe5SJohn Snow    @property
860b306e26cSCleber Rosa    def log_dir(self) -> str:
861b306e26cSCleber Rosa        """
862b306e26cSCleber Rosa        Returns a directory to be used for writing logs
863b306e26cSCleber Rosa        """
864b306e26cSCleber Rosa        if self._log_dir is None:
865b306e26cSCleber Rosa            return self.temp_dir
866b306e26cSCleber Rosa        return self._log_dir
867