xref: /openbmc/qemu/python/qemu/machine/machine.py (revision 49a608b8)
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
43d1e04769SJohn Snowfrom qemu.qmp import (  # pylint: disable=import-error
44beb6b57bSJohn Snow    QEMUMonitorProtocol,
45beb6b57bSJohn Snow    QMPMessage,
46beb6b57bSJohn Snow    QMPReturnValue,
47beb6b57bSJohn Snow    SocketAddrT,
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
72beb6b57bSJohn Snowclass AbnormalShutdown(QEMUMachineError):
73beb6b57bSJohn Snow    """
74beb6b57bSJohn Snow    Exception raised when a graceful shutdown was requested, but not performed.
75beb6b57bSJohn Snow    """
76beb6b57bSJohn Snow
77beb6b57bSJohn Snow
7815c3b863SVladimir Sementsov-Ogievskiy_T = TypeVar('_T', bound='QEMUMachine')
7915c3b863SVladimir Sementsov-Ogievskiy
8015c3b863SVladimir Sementsov-Ogievskiy
81beb6b57bSJohn Snowclass QEMUMachine:
82beb6b57bSJohn Snow    """
83beb6b57bSJohn Snow    A QEMU VM.
84beb6b57bSJohn Snow
85beb6b57bSJohn Snow    Use this object as a context manager to ensure
86beb6b57bSJohn Snow    the QEMU process terminates::
87beb6b57bSJohn Snow
88beb6b57bSJohn Snow        with VM(binary) as vm:
89beb6b57bSJohn Snow            ...
90beb6b57bSJohn Snow        # vm is guaranteed to be shut down here
91beb6b57bSJohn Snow    """
9282e6517dSJohn Snow    # pylint: disable=too-many-instance-attributes, too-many-public-methods
93beb6b57bSJohn Snow
94beb6b57bSJohn Snow    def __init__(self,
95beb6b57bSJohn Snow                 binary: str,
96beb6b57bSJohn Snow                 args: Sequence[str] = (),
97beb6b57bSJohn Snow                 wrapper: Sequence[str] = (),
98beb6b57bSJohn Snow                 name: Optional[str] = None,
99beb6b57bSJohn Snow                 base_temp_dir: str = "/var/tmp",
100beb6b57bSJohn Snow                 monitor_address: Optional[SocketAddrT] = None,
101beb6b57bSJohn Snow                 sock_dir: Optional[str] = None,
102beb6b57bSJohn Snow                 drain_console: bool = False,
103b306e26cSCleber Rosa                 console_log: Optional[str] = None,
104e2f948a8SEmanuele Giuseppe Esposito                 log_dir: Optional[str] = None,
105e2f948a8SEmanuele Giuseppe Esposito                 qmp_timer: Optional[float] = None):
106beb6b57bSJohn Snow        '''
107beb6b57bSJohn Snow        Initialize a QEMUMachine
108beb6b57bSJohn Snow
109beb6b57bSJohn Snow        @param binary: path to the qemu binary
110beb6b57bSJohn Snow        @param args: list of extra arguments
111beb6b57bSJohn Snow        @param wrapper: list of arguments used as prefix to qemu binary
112beb6b57bSJohn Snow        @param name: prefix for socket and log file names (default: qemu-PID)
113beb6b57bSJohn Snow        @param base_temp_dir: default location where temp files are created
114beb6b57bSJohn Snow        @param monitor_address: address for QMP monitor
115beb6b57bSJohn Snow        @param sock_dir: where to create socket (defaults to base_temp_dir)
116beb6b57bSJohn Snow        @param drain_console: (optional) True to drain console socket to buffer
117beb6b57bSJohn Snow        @param console_log: (optional) path to console log file
118b306e26cSCleber Rosa        @param log_dir: where to create and keep log files
119e2f948a8SEmanuele Giuseppe Esposito        @param qmp_timer: (optional) default QMP socket timeout
120beb6b57bSJohn Snow        @note: Qemu process is not started until launch() is used.
121beb6b57bSJohn Snow        '''
12282e6517dSJohn Snow        # pylint: disable=too-many-arguments
12382e6517dSJohn Snow
124beb6b57bSJohn Snow        # Direct user configuration
125beb6b57bSJohn Snow
126beb6b57bSJohn Snow        self._binary = binary
127beb6b57bSJohn Snow        self._args = list(args)
128beb6b57bSJohn Snow        self._wrapper = wrapper
129e2f948a8SEmanuele Giuseppe Esposito        self._qmp_timer = qmp_timer
130beb6b57bSJohn Snow
131beb6b57bSJohn Snow        self._name = name or "qemu-%d" % os.getpid()
132beb6b57bSJohn Snow        self._base_temp_dir = base_temp_dir
133beb6b57bSJohn Snow        self._sock_dir = sock_dir or self._base_temp_dir
134b306e26cSCleber Rosa        self._log_dir = log_dir
135beb6b57bSJohn Snow
136beb6b57bSJohn Snow        if monitor_address is not None:
137beb6b57bSJohn Snow            self._monitor_address = monitor_address
138beb6b57bSJohn Snow            self._remove_monitor_sockfile = False
139beb6b57bSJohn Snow        else:
140beb6b57bSJohn Snow            self._monitor_address = os.path.join(
141beb6b57bSJohn Snow                self._sock_dir, f"{self._name}-monitor.sock"
142beb6b57bSJohn Snow            )
143beb6b57bSJohn Snow            self._remove_monitor_sockfile = True
144beb6b57bSJohn Snow
145beb6b57bSJohn Snow        self._console_log_path = console_log
146beb6b57bSJohn Snow        if self._console_log_path:
147beb6b57bSJohn Snow            # In order to log the console, buffering needs to be enabled.
148beb6b57bSJohn Snow            self._drain_console = True
149beb6b57bSJohn Snow        else:
150beb6b57bSJohn Snow            self._drain_console = drain_console
151beb6b57bSJohn Snow
152beb6b57bSJohn Snow        # Runstate
153beb6b57bSJohn Snow        self._qemu_log_path: Optional[str] = None
154beb6b57bSJohn Snow        self._qemu_log_file: Optional[BinaryIO] = None
155beb6b57bSJohn Snow        self._popen: Optional['subprocess.Popen[bytes]'] = None
156beb6b57bSJohn Snow        self._events: List[QMPMessage] = []
157beb6b57bSJohn Snow        self._iolog: Optional[str] = None
158beb6b57bSJohn Snow        self._qmp_set = True   # Enable QMP monitor by default.
159beb6b57bSJohn Snow        self._qmp_connection: Optional[QEMUMonitorProtocol] = None
160beb6b57bSJohn Snow        self._qemu_full_args: Tuple[str, ...] = ()
161beb6b57bSJohn Snow        self._temp_dir: Optional[str] = None
162beb6b57bSJohn Snow        self._launched = False
163beb6b57bSJohn Snow        self._machine: Optional[str] = None
164beb6b57bSJohn Snow        self._console_index = 0
165beb6b57bSJohn Snow        self._console_set = False
166beb6b57bSJohn Snow        self._console_device_type: Optional[str] = None
167beb6b57bSJohn Snow        self._console_address = os.path.join(
168beb6b57bSJohn Snow            self._sock_dir, f"{self._name}-console.sock"
169beb6b57bSJohn Snow        )
170beb6b57bSJohn Snow        self._console_socket: Optional[socket.socket] = None
171beb6b57bSJohn Snow        self._remove_files: List[str] = []
172beb6b57bSJohn Snow        self._user_killed = False
173b9420e4fSJohn Snow        self._quit_issued = False
174beb6b57bSJohn Snow
17515c3b863SVladimir Sementsov-Ogievskiy    def __enter__(self: _T) -> _T:
176beb6b57bSJohn Snow        return self
177beb6b57bSJohn Snow
178beb6b57bSJohn Snow    def __exit__(self,
179beb6b57bSJohn Snow                 exc_type: Optional[Type[BaseException]],
180beb6b57bSJohn Snow                 exc_val: Optional[BaseException],
181beb6b57bSJohn Snow                 exc_tb: Optional[TracebackType]) -> None:
182beb6b57bSJohn Snow        self.shutdown()
183beb6b57bSJohn Snow
184beb6b57bSJohn Snow    def add_monitor_null(self) -> None:
185beb6b57bSJohn Snow        """
186beb6b57bSJohn Snow        This can be used to add an unused monitor instance.
187beb6b57bSJohn Snow        """
188beb6b57bSJohn Snow        self._args.append('-monitor')
189beb6b57bSJohn Snow        self._args.append('null')
190beb6b57bSJohn Snow
19115c3b863SVladimir Sementsov-Ogievskiy    def add_fd(self: _T, fd: int, fdset: int,
19215c3b863SVladimir Sementsov-Ogievskiy               opaque: str, opts: str = '') -> _T:
193beb6b57bSJohn Snow        """
194beb6b57bSJohn Snow        Pass a file descriptor to the VM
195beb6b57bSJohn Snow        """
196beb6b57bSJohn Snow        options = ['fd=%d' % fd,
197beb6b57bSJohn Snow                   'set=%d' % fdset,
198beb6b57bSJohn Snow                   'opaque=%s' % opaque]
199beb6b57bSJohn Snow        if opts:
200beb6b57bSJohn Snow            options.append(opts)
201beb6b57bSJohn Snow
202beb6b57bSJohn Snow        # This did not exist before 3.4, but since then it is
203beb6b57bSJohn Snow        # mandatory for our purpose
204beb6b57bSJohn Snow        if hasattr(os, 'set_inheritable'):
205beb6b57bSJohn Snow            os.set_inheritable(fd, True)
206beb6b57bSJohn Snow
207beb6b57bSJohn Snow        self._args.append('-add-fd')
208beb6b57bSJohn Snow        self._args.append(','.join(options))
209beb6b57bSJohn Snow        return self
210beb6b57bSJohn Snow
211beb6b57bSJohn Snow    def send_fd_scm(self, fd: Optional[int] = None,
212beb6b57bSJohn Snow                    file_path: Optional[str] = None) -> int:
213beb6b57bSJohn Snow        """
214514d00dfSJohn Snow        Send an fd or file_path to the remote via SCM_RIGHTS.
215beb6b57bSJohn Snow
216514d00dfSJohn Snow        Exactly one of fd and file_path must be given.  If it is
217514d00dfSJohn Snow        file_path, the file will be opened read-only and the new file
218514d00dfSJohn Snow        descriptor will be sent to the remote.
219beb6b57bSJohn Snow        """
220beb6b57bSJohn Snow        if file_path is not None:
221beb6b57bSJohn Snow            assert fd is None
222514d00dfSJohn Snow            with open(file_path, "rb") as passfile:
223514d00dfSJohn Snow                fd = passfile.fileno()
224514d00dfSJohn Snow                self._qmp.send_fd_scm(fd)
225beb6b57bSJohn Snow        else:
226beb6b57bSJohn Snow            assert fd is not None
227514d00dfSJohn Snow            self._qmp.send_fd_scm(fd)
228beb6b57bSJohn Snow
229514d00dfSJohn Snow        return 0
230beb6b57bSJohn Snow
231beb6b57bSJohn Snow    @staticmethod
232beb6b57bSJohn Snow    def _remove_if_exists(path: str) -> None:
233beb6b57bSJohn Snow        """
234beb6b57bSJohn Snow        Remove file object at path if it exists
235beb6b57bSJohn Snow        """
236beb6b57bSJohn Snow        try:
237beb6b57bSJohn Snow            os.remove(path)
238beb6b57bSJohn Snow        except OSError as exception:
239beb6b57bSJohn Snow            if exception.errno == errno.ENOENT:
240beb6b57bSJohn Snow                return
241beb6b57bSJohn Snow            raise
242beb6b57bSJohn Snow
243beb6b57bSJohn Snow    def is_running(self) -> bool:
244beb6b57bSJohn Snow        """Returns true if the VM is running."""
245beb6b57bSJohn Snow        return self._popen is not None and self._popen.poll() is None
246beb6b57bSJohn Snow
247beb6b57bSJohn Snow    @property
248beb6b57bSJohn Snow    def _subp(self) -> 'subprocess.Popen[bytes]':
249beb6b57bSJohn Snow        if self._popen is None:
250beb6b57bSJohn Snow            raise QEMUMachineError('Subprocess pipe not present')
251beb6b57bSJohn Snow        return self._popen
252beb6b57bSJohn Snow
253beb6b57bSJohn Snow    def exitcode(self) -> Optional[int]:
254beb6b57bSJohn Snow        """Returns the exit code if possible, or None."""
255beb6b57bSJohn Snow        if self._popen is None:
256beb6b57bSJohn Snow            return None
257beb6b57bSJohn Snow        return self._popen.poll()
258beb6b57bSJohn Snow
259beb6b57bSJohn Snow    def get_pid(self) -> Optional[int]:
260beb6b57bSJohn Snow        """Returns the PID of the running process, or None."""
261beb6b57bSJohn Snow        if not self.is_running():
262beb6b57bSJohn Snow            return None
263beb6b57bSJohn Snow        return self._subp.pid
264beb6b57bSJohn Snow
265beb6b57bSJohn Snow    def _load_io_log(self) -> None:
2665690b437SJohn Snow        # Assume that the output encoding of QEMU's terminal output is
2675690b437SJohn Snow        # defined by our locale. If indeterminate, allow open() to fall
2685690b437SJohn Snow        # back to the platform default.
2695690b437SJohn Snow        _, encoding = locale.getlocale()
270beb6b57bSJohn Snow        if self._qemu_log_path is not None:
2715690b437SJohn Snow            with open(self._qemu_log_path, "r", encoding=encoding) as iolog:
272beb6b57bSJohn Snow                self._iolog = iolog.read()
273beb6b57bSJohn Snow
274beb6b57bSJohn Snow    @property
275beb6b57bSJohn Snow    def _base_args(self) -> List[str]:
276beb6b57bSJohn Snow        args = ['-display', 'none', '-vga', 'none']
277beb6b57bSJohn Snow
278beb6b57bSJohn Snow        if self._qmp_set:
279beb6b57bSJohn Snow            if isinstance(self._monitor_address, tuple):
280beb6b57bSJohn Snow                moncdev = "socket,id=mon,host={},port={}".format(
281beb6b57bSJohn Snow                    *self._monitor_address
282beb6b57bSJohn Snow                )
283beb6b57bSJohn Snow            else:
284beb6b57bSJohn Snow                moncdev = f"socket,id=mon,path={self._monitor_address}"
285beb6b57bSJohn Snow            args.extend(['-chardev', moncdev, '-mon',
286beb6b57bSJohn Snow                         'chardev=mon,mode=control'])
287beb6b57bSJohn Snow
288beb6b57bSJohn Snow        if self._machine is not None:
289beb6b57bSJohn Snow            args.extend(['-machine', self._machine])
290beb6b57bSJohn Snow        for _ in range(self._console_index):
291beb6b57bSJohn Snow            args.extend(['-serial', 'null'])
292beb6b57bSJohn Snow        if self._console_set:
293beb6b57bSJohn Snow            chardev = ('socket,id=console,path=%s,server=on,wait=off' %
294beb6b57bSJohn Snow                       self._console_address)
295beb6b57bSJohn Snow            args.extend(['-chardev', chardev])
296beb6b57bSJohn Snow            if self._console_device_type is None:
297beb6b57bSJohn Snow                args.extend(['-serial', 'chardev:console'])
298beb6b57bSJohn Snow            else:
299beb6b57bSJohn Snow                device = '%s,chardev=console' % self._console_device_type
300beb6b57bSJohn Snow                args.extend(['-device', device])
301beb6b57bSJohn Snow        return args
302beb6b57bSJohn Snow
303555fe0c2SWainer dos Santos Moschetta    @property
304555fe0c2SWainer dos Santos Moschetta    def args(self) -> List[str]:
305555fe0c2SWainer dos Santos Moschetta        """Returns the list of arguments given to the QEMU binary."""
306555fe0c2SWainer dos Santos Moschetta        return self._args
307555fe0c2SWainer dos Santos Moschetta
308beb6b57bSJohn Snow    def _pre_launch(self) -> None:
309beb6b57bSJohn Snow        if self._console_set:
310beb6b57bSJohn Snow            self._remove_files.append(self._console_address)
311beb6b57bSJohn Snow
312beb6b57bSJohn Snow        if self._qmp_set:
313beb6b57bSJohn Snow            if self._remove_monitor_sockfile:
314beb6b57bSJohn Snow                assert isinstance(self._monitor_address, str)
315beb6b57bSJohn Snow                self._remove_files.append(self._monitor_address)
316beb6b57bSJohn Snow            self._qmp_connection = QEMUMonitorProtocol(
317beb6b57bSJohn Snow                self._monitor_address,
318beb6b57bSJohn Snow                server=True,
319beb6b57bSJohn Snow                nickname=self._name
320beb6b57bSJohn Snow            )
321beb6b57bSJohn Snow
322beb6b57bSJohn Snow        # NOTE: Make sure any opened resources are *definitely* freed in
323beb6b57bSJohn Snow        # _post_shutdown()!
324beb6b57bSJohn Snow        # pylint: disable=consider-using-with
325b306e26cSCleber Rosa        self._qemu_log_path = os.path.join(self.log_dir, self._name + ".log")
326beb6b57bSJohn Snow        self._qemu_log_file = open(self._qemu_log_path, 'wb')
327beb6b57bSJohn Snow
328beb6b57bSJohn Snow    def _post_launch(self) -> None:
329beb6b57bSJohn Snow        if self._qmp_connection:
330e2f948a8SEmanuele Giuseppe Esposito            self._qmp.accept(self._qmp_timer)
331beb6b57bSJohn Snow
332eb7a91d0SEmanuele Giuseppe Esposito    def _close_qemu_log_file(self) -> None:
333eb7a91d0SEmanuele Giuseppe Esposito        if self._qemu_log_file is not None:
334eb7a91d0SEmanuele Giuseppe Esposito            self._qemu_log_file.close()
335eb7a91d0SEmanuele Giuseppe Esposito            self._qemu_log_file = None
336eb7a91d0SEmanuele Giuseppe Esposito
337beb6b57bSJohn Snow    def _post_shutdown(self) -> None:
338beb6b57bSJohn Snow        """
339beb6b57bSJohn Snow        Called to cleanup the VM instance after the process has exited.
340beb6b57bSJohn Snow        May also be called after a failed launch.
341beb6b57bSJohn Snow        """
342beb6b57bSJohn Snow        # Comprehensive reset for the failed launch case:
343beb6b57bSJohn Snow        self._early_cleanup()
344beb6b57bSJohn Snow
345*49a608b8SJohn Snow        try:
346*49a608b8SJohn Snow            self._close_qmp_connection()
347*49a608b8SJohn Snow        except Exception as err:  # pylint: disable=broad-except
348*49a608b8SJohn Snow            LOG.warning(
349*49a608b8SJohn Snow                "Exception closing QMP connection: %s",
350*49a608b8SJohn Snow                str(err) if str(err) else type(err).__name__
351*49a608b8SJohn Snow            )
352*49a608b8SJohn Snow        finally:
353*49a608b8SJohn Snow            assert self._qmp_connection is None
354beb6b57bSJohn Snow
355eb7a91d0SEmanuele Giuseppe Esposito        self._close_qemu_log_file()
356beb6b57bSJohn Snow
357beb6b57bSJohn Snow        self._load_io_log()
358beb6b57bSJohn Snow
359beb6b57bSJohn Snow        self._qemu_log_path = None
360beb6b57bSJohn Snow
361beb6b57bSJohn Snow        if self._temp_dir is not None:
362beb6b57bSJohn Snow            shutil.rmtree(self._temp_dir)
363beb6b57bSJohn Snow            self._temp_dir = None
364beb6b57bSJohn Snow
365beb6b57bSJohn Snow        while len(self._remove_files) > 0:
366beb6b57bSJohn Snow            self._remove_if_exists(self._remove_files.pop())
367beb6b57bSJohn Snow
368beb6b57bSJohn Snow        exitcode = self.exitcode()
369beb6b57bSJohn Snow        if (exitcode is not None and exitcode < 0
370beb6b57bSJohn Snow                and not (self._user_killed and exitcode == -signal.SIGKILL)):
371beb6b57bSJohn Snow            msg = 'qemu received signal %i; command: "%s"'
372beb6b57bSJohn Snow            if self._qemu_full_args:
373beb6b57bSJohn Snow                command = ' '.join(self._qemu_full_args)
374beb6b57bSJohn Snow            else:
375beb6b57bSJohn Snow                command = ''
376beb6b57bSJohn Snow            LOG.warning(msg, -int(exitcode), command)
377beb6b57bSJohn Snow
378b9420e4fSJohn Snow        self._quit_issued = False
379beb6b57bSJohn Snow        self._user_killed = False
380beb6b57bSJohn Snow        self._launched = False
381beb6b57bSJohn Snow
382beb6b57bSJohn Snow    def launch(self) -> None:
383beb6b57bSJohn Snow        """
384beb6b57bSJohn Snow        Launch the VM and make sure we cleanup and expose the
385beb6b57bSJohn Snow        command line/output in case of exception
386beb6b57bSJohn Snow        """
387beb6b57bSJohn Snow
388beb6b57bSJohn Snow        if self._launched:
389beb6b57bSJohn Snow            raise QEMUMachineError('VM already launched')
390beb6b57bSJohn Snow
391beb6b57bSJohn Snow        self._iolog = None
392beb6b57bSJohn Snow        self._qemu_full_args = ()
393beb6b57bSJohn Snow        try:
394beb6b57bSJohn Snow            self._launch()
395beb6b57bSJohn Snow            self._launched = True
396beb6b57bSJohn Snow        except:
397beb6b57bSJohn Snow            self._post_shutdown()
398beb6b57bSJohn Snow
399beb6b57bSJohn Snow            LOG.debug('Error launching VM')
400beb6b57bSJohn Snow            if self._qemu_full_args:
401beb6b57bSJohn Snow                LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
402beb6b57bSJohn Snow            if self._iolog:
403beb6b57bSJohn Snow                LOG.debug('Output: %r', self._iolog)
404beb6b57bSJohn Snow            raise
405beb6b57bSJohn Snow
406beb6b57bSJohn Snow    def _launch(self) -> None:
407beb6b57bSJohn Snow        """
408beb6b57bSJohn Snow        Launch the VM and establish a QMP connection
409beb6b57bSJohn Snow        """
410beb6b57bSJohn Snow        self._pre_launch()
411beb6b57bSJohn Snow        self._qemu_full_args = tuple(
412beb6b57bSJohn Snow            chain(self._wrapper,
413beb6b57bSJohn Snow                  [self._binary],
414beb6b57bSJohn Snow                  self._base_args,
415beb6b57bSJohn Snow                  self._args)
416beb6b57bSJohn Snow        )
417beb6b57bSJohn Snow        LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
418beb6b57bSJohn Snow
419beb6b57bSJohn Snow        # Cleaning up of this subprocess is guaranteed by _do_shutdown.
420beb6b57bSJohn Snow        # pylint: disable=consider-using-with
421beb6b57bSJohn Snow        self._popen = subprocess.Popen(self._qemu_full_args,
422beb6b57bSJohn Snow                                       stdin=subprocess.DEVNULL,
423beb6b57bSJohn Snow                                       stdout=self._qemu_log_file,
424beb6b57bSJohn Snow                                       stderr=subprocess.STDOUT,
425beb6b57bSJohn Snow                                       shell=False,
426beb6b57bSJohn Snow                                       close_fds=False)
427beb6b57bSJohn Snow        self._post_launch()
428beb6b57bSJohn Snow
429*49a608b8SJohn Snow    def _close_qmp_connection(self) -> None:
430*49a608b8SJohn Snow        """
431*49a608b8SJohn Snow        Close the underlying QMP connection, if any.
432*49a608b8SJohn Snow
433*49a608b8SJohn Snow        Dutifully report errors that occurred while closing, but assume
434*49a608b8SJohn Snow        that any error encountered indicates an abnormal termination
435*49a608b8SJohn Snow        process and not a failure to close.
436*49a608b8SJohn Snow        """
437*49a608b8SJohn Snow        if self._qmp_connection is None:
438*49a608b8SJohn Snow            return
439*49a608b8SJohn Snow
440*49a608b8SJohn Snow        try:
441*49a608b8SJohn Snow            self._qmp.close()
442*49a608b8SJohn Snow        except EOFError:
443*49a608b8SJohn Snow            # EOF can occur as an Exception here when using the Async
444*49a608b8SJohn Snow            # QMP backend. It indicates that the server closed the
445*49a608b8SJohn Snow            # stream. If we successfully issued 'quit' at any point,
446*49a608b8SJohn Snow            # then this was expected. If the remote went away without
447*49a608b8SJohn Snow            # our permission, it's worth reporting that as an abnormal
448*49a608b8SJohn Snow            # shutdown case.
449*49a608b8SJohn Snow            if not (self._user_killed or self._quit_issued):
450*49a608b8SJohn Snow                raise
451*49a608b8SJohn Snow        finally:
452*49a608b8SJohn Snow            self._qmp_connection = None
453*49a608b8SJohn Snow
454beb6b57bSJohn Snow    def _early_cleanup(self) -> None:
455beb6b57bSJohn Snow        """
456beb6b57bSJohn Snow        Perform any cleanup that needs to happen before the VM exits.
457beb6b57bSJohn Snow
458beb6b57bSJohn Snow        May be invoked by both soft and hard shutdown in failover scenarios.
459beb6b57bSJohn Snow        Called additionally by _post_shutdown for comprehensive cleanup.
460beb6b57bSJohn Snow        """
461beb6b57bSJohn Snow        # If we keep the console socket open, we may deadlock waiting
462beb6b57bSJohn Snow        # for QEMU to exit, while QEMU is waiting for the socket to
463beb6b57bSJohn Snow        # become writeable.
464beb6b57bSJohn Snow        if self._console_socket is not None:
465beb6b57bSJohn Snow            self._console_socket.close()
466beb6b57bSJohn Snow            self._console_socket = None
467beb6b57bSJohn Snow
468beb6b57bSJohn Snow    def _hard_shutdown(self) -> None:
469beb6b57bSJohn Snow        """
470beb6b57bSJohn Snow        Perform early cleanup, kill the VM, and wait for it to terminate.
471beb6b57bSJohn Snow
472beb6b57bSJohn Snow        :raise subprocess.Timeout: When timeout is exceeds 60 seconds
473beb6b57bSJohn Snow            waiting for the QEMU process to terminate.
474beb6b57bSJohn Snow        """
475beb6b57bSJohn Snow        self._early_cleanup()
476beb6b57bSJohn Snow        self._subp.kill()
477beb6b57bSJohn Snow        self._subp.wait(timeout=60)
478beb6b57bSJohn Snow
479b9420e4fSJohn Snow    def _soft_shutdown(self, timeout: Optional[int]) -> None:
480beb6b57bSJohn Snow        """
481beb6b57bSJohn Snow        Perform early cleanup, attempt to gracefully shut down the VM, and wait
482beb6b57bSJohn Snow        for it to terminate.
483beb6b57bSJohn Snow
484beb6b57bSJohn Snow        :param timeout: Timeout in seconds for graceful shutdown.
485beb6b57bSJohn Snow                        A value of None is an infinite wait.
486beb6b57bSJohn Snow
487beb6b57bSJohn Snow        :raise ConnectionReset: On QMP communication errors
488beb6b57bSJohn Snow        :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
489beb6b57bSJohn Snow            the QEMU process to terminate.
490beb6b57bSJohn Snow        """
491beb6b57bSJohn Snow        self._early_cleanup()
492beb6b57bSJohn Snow
493beb6b57bSJohn Snow        if self._qmp_connection:
494*49a608b8SJohn Snow            try:
495b9420e4fSJohn Snow                if not self._quit_issued:
496*49a608b8SJohn Snow                    # May raise ExecInterruptedError or StateError if the
497*49a608b8SJohn Snow                    # connection dies or has *already* died.
498b9420e4fSJohn Snow                    self.qmp('quit')
499*49a608b8SJohn Snow            finally:
500*49a608b8SJohn Snow                # Regardless, we want to quiesce the connection.
501*49a608b8SJohn Snow                self._close_qmp_connection()
502beb6b57bSJohn Snow
503beb6b57bSJohn Snow        # May raise subprocess.TimeoutExpired
504beb6b57bSJohn Snow        self._subp.wait(timeout=timeout)
505beb6b57bSJohn Snow
506b9420e4fSJohn Snow    def _do_shutdown(self, timeout: Optional[int]) -> None:
507beb6b57bSJohn Snow        """
508beb6b57bSJohn Snow        Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
509beb6b57bSJohn Snow
510beb6b57bSJohn Snow        :param timeout: Timeout in seconds for graceful shutdown.
511beb6b57bSJohn Snow                        A value of None is an infinite wait.
512beb6b57bSJohn Snow
513beb6b57bSJohn Snow        :raise AbnormalShutdown: When the VM could not be shut down gracefully.
514beb6b57bSJohn Snow            The inner exception will likely be ConnectionReset or
515beb6b57bSJohn Snow            subprocess.TimeoutExpired. In rare cases, non-graceful termination
516beb6b57bSJohn Snow            may result in its own exceptions, likely subprocess.TimeoutExpired.
517beb6b57bSJohn Snow        """
518beb6b57bSJohn Snow        try:
519b9420e4fSJohn Snow            self._soft_shutdown(timeout)
520beb6b57bSJohn Snow        except Exception as exc:
521beb6b57bSJohn Snow            self._hard_shutdown()
522beb6b57bSJohn Snow            raise AbnormalShutdown("Could not perform graceful shutdown") \
523beb6b57bSJohn Snow                from exc
524beb6b57bSJohn Snow
525b9420e4fSJohn Snow    def shutdown(self,
526beb6b57bSJohn Snow                 hard: bool = False,
527beb6b57bSJohn Snow                 timeout: Optional[int] = 30) -> None:
528beb6b57bSJohn Snow        """
529beb6b57bSJohn Snow        Terminate the VM (gracefully if possible) and perform cleanup.
530beb6b57bSJohn Snow        Cleanup will always be performed.
531beb6b57bSJohn Snow
532beb6b57bSJohn Snow        If the VM has not yet been launched, or shutdown(), wait(), or kill()
533beb6b57bSJohn Snow        have already been called, this method does nothing.
534beb6b57bSJohn Snow
535beb6b57bSJohn Snow        :param hard: When true, do not attempt graceful shutdown, and
536beb6b57bSJohn Snow                     suppress the SIGKILL warning log message.
537beb6b57bSJohn Snow        :param timeout: Optional timeout in seconds for graceful shutdown.
538beb6b57bSJohn Snow                        Default 30 seconds, A `None` value is an infinite wait.
539beb6b57bSJohn Snow        """
540beb6b57bSJohn Snow        if not self._launched:
541beb6b57bSJohn Snow            return
542beb6b57bSJohn Snow
543beb6b57bSJohn Snow        try:
544beb6b57bSJohn Snow            if hard:
545beb6b57bSJohn Snow                self._user_killed = True
546beb6b57bSJohn Snow                self._hard_shutdown()
547beb6b57bSJohn Snow            else:
548b9420e4fSJohn Snow                self._do_shutdown(timeout)
549beb6b57bSJohn Snow        finally:
550beb6b57bSJohn Snow            self._post_shutdown()
551beb6b57bSJohn Snow
552beb6b57bSJohn Snow    def kill(self) -> None:
553beb6b57bSJohn Snow        """
554beb6b57bSJohn Snow        Terminate the VM forcefully, wait for it to exit, and perform cleanup.
555beb6b57bSJohn Snow        """
556beb6b57bSJohn Snow        self.shutdown(hard=True)
557beb6b57bSJohn Snow
558beb6b57bSJohn Snow    def wait(self, timeout: Optional[int] = 30) -> None:
559beb6b57bSJohn Snow        """
560beb6b57bSJohn Snow        Wait for the VM to power off and perform post-shutdown cleanup.
561beb6b57bSJohn Snow
562beb6b57bSJohn Snow        :param timeout: Optional timeout in seconds. Default 30 seconds.
563beb6b57bSJohn Snow                        A value of `None` is an infinite wait.
564beb6b57bSJohn Snow        """
565b9420e4fSJohn Snow        self._quit_issued = True
566b9420e4fSJohn Snow        self.shutdown(timeout=timeout)
567beb6b57bSJohn Snow
568beb6b57bSJohn Snow    def set_qmp_monitor(self, enabled: bool = True) -> None:
569beb6b57bSJohn Snow        """
570beb6b57bSJohn Snow        Set the QMP monitor.
571beb6b57bSJohn Snow
572beb6b57bSJohn Snow        @param enabled: if False, qmp monitor options will be removed from
573beb6b57bSJohn Snow                        the base arguments of the resulting QEMU command
574beb6b57bSJohn Snow                        line. Default is True.
5755c02c865SJohn Snow
5765c02c865SJohn Snow        .. note:: Call this function before launch().
577beb6b57bSJohn Snow        """
578beb6b57bSJohn Snow        self._qmp_set = enabled
579beb6b57bSJohn Snow
580beb6b57bSJohn Snow    @property
581beb6b57bSJohn Snow    def _qmp(self) -> QEMUMonitorProtocol:
582beb6b57bSJohn Snow        if self._qmp_connection is None:
583beb6b57bSJohn Snow            raise QEMUMachineError("Attempt to access QMP with no connection")
584beb6b57bSJohn Snow        return self._qmp_connection
585beb6b57bSJohn Snow
586beb6b57bSJohn Snow    @classmethod
587c7daa57eSVladimir Sementsov-Ogievskiy    def _qmp_args(cls, conv_keys: bool,
588c7daa57eSVladimir Sementsov-Ogievskiy                  args: Dict[str, Any]) -> Dict[str, object]:
589c7daa57eSVladimir Sementsov-Ogievskiy        if conv_keys:
590c7daa57eSVladimir Sementsov-Ogievskiy            return {k.replace('_', '-'): v for k, v in args.items()}
591c7daa57eSVladimir Sementsov-Ogievskiy
592c7daa57eSVladimir Sementsov-Ogievskiy        return args
593beb6b57bSJohn Snow
594beb6b57bSJohn Snow    def qmp(self, cmd: str,
5953f3c9b4cSVladimir Sementsov-Ogievskiy            args_dict: Optional[Dict[str, object]] = None,
5963f3c9b4cSVladimir Sementsov-Ogievskiy            conv_keys: Optional[bool] = None,
597beb6b57bSJohn Snow            **args: Any) -> QMPMessage:
598beb6b57bSJohn Snow        """
599beb6b57bSJohn Snow        Invoke a QMP command and return the response dict
600beb6b57bSJohn Snow        """
6013f3c9b4cSVladimir Sementsov-Ogievskiy        if args_dict is not None:
6023f3c9b4cSVladimir Sementsov-Ogievskiy            assert not args
6033f3c9b4cSVladimir Sementsov-Ogievskiy            assert conv_keys is None
6043f3c9b4cSVladimir Sementsov-Ogievskiy            args = args_dict
6053f3c9b4cSVladimir Sementsov-Ogievskiy            conv_keys = False
6063f3c9b4cSVladimir Sementsov-Ogievskiy
6073f3c9b4cSVladimir Sementsov-Ogievskiy        if conv_keys is None:
6083f3c9b4cSVladimir Sementsov-Ogievskiy            conv_keys = True
6093f3c9b4cSVladimir Sementsov-Ogievskiy
610c7daa57eSVladimir Sementsov-Ogievskiy        qmp_args = self._qmp_args(conv_keys, args)
611b9420e4fSJohn Snow        ret = self._qmp.cmd(cmd, args=qmp_args)
612b9420e4fSJohn Snow        if cmd == 'quit' and 'error' not in ret and 'return' in ret:
613b9420e4fSJohn Snow            self._quit_issued = True
614b9420e4fSJohn Snow        return ret
615beb6b57bSJohn Snow
616beb6b57bSJohn Snow    def command(self, cmd: str,
617beb6b57bSJohn Snow                conv_keys: bool = True,
618beb6b57bSJohn Snow                **args: Any) -> QMPReturnValue:
619beb6b57bSJohn Snow        """
620beb6b57bSJohn Snow        Invoke a QMP command.
621beb6b57bSJohn Snow        On success return the response dict.
622beb6b57bSJohn Snow        On failure raise an exception.
623beb6b57bSJohn Snow        """
624c7daa57eSVladimir Sementsov-Ogievskiy        qmp_args = self._qmp_args(conv_keys, args)
625b9420e4fSJohn Snow        ret = self._qmp.command(cmd, **qmp_args)
626b9420e4fSJohn Snow        if cmd == 'quit':
627b9420e4fSJohn Snow            self._quit_issued = True
628b9420e4fSJohn Snow        return ret
629beb6b57bSJohn Snow
630beb6b57bSJohn Snow    def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
631beb6b57bSJohn Snow        """
632beb6b57bSJohn Snow        Poll for one queued QMP events and return it
633beb6b57bSJohn Snow        """
634beb6b57bSJohn Snow        if self._events:
635beb6b57bSJohn Snow            return self._events.pop(0)
636beb6b57bSJohn Snow        return self._qmp.pull_event(wait=wait)
637beb6b57bSJohn Snow
638beb6b57bSJohn Snow    def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
639beb6b57bSJohn Snow        """
640beb6b57bSJohn Snow        Poll for queued QMP events and return a list of dicts
641beb6b57bSJohn Snow        """
642beb6b57bSJohn Snow        events = self._qmp.get_events(wait=wait)
643beb6b57bSJohn Snow        events.extend(self._events)
644beb6b57bSJohn Snow        del self._events[:]
645beb6b57bSJohn Snow        return events
646beb6b57bSJohn Snow
647beb6b57bSJohn Snow    @staticmethod
648beb6b57bSJohn Snow    def event_match(event: Any, match: Optional[Any]) -> bool:
649beb6b57bSJohn Snow        """
650beb6b57bSJohn Snow        Check if an event matches optional match criteria.
651beb6b57bSJohn Snow
652beb6b57bSJohn Snow        The match criteria takes the form of a matching subdict. The event is
653beb6b57bSJohn Snow        checked to be a superset of the subdict, recursively, with matching
654beb6b57bSJohn Snow        values whenever the subdict values are not None.
655beb6b57bSJohn Snow
656beb6b57bSJohn Snow        This has a limitation that you cannot explicitly check for None values.
657beb6b57bSJohn Snow
658beb6b57bSJohn Snow        Examples, with the subdict queries on the left:
659beb6b57bSJohn Snow         - None matches any object.
660beb6b57bSJohn Snow         - {"foo": None} matches {"foo": {"bar": 1}}
661beb6b57bSJohn Snow         - {"foo": None} matches {"foo": 5}
662beb6b57bSJohn Snow         - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
663beb6b57bSJohn Snow         - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
664beb6b57bSJohn Snow        """
665beb6b57bSJohn Snow        if match is None:
666beb6b57bSJohn Snow            return True
667beb6b57bSJohn Snow
668beb6b57bSJohn Snow        try:
669beb6b57bSJohn Snow            for key in match:
670beb6b57bSJohn Snow                if key in event:
671beb6b57bSJohn Snow                    if not QEMUMachine.event_match(event[key], match[key]):
672beb6b57bSJohn Snow                        return False
673beb6b57bSJohn Snow                else:
674beb6b57bSJohn Snow                    return False
675beb6b57bSJohn Snow            return True
676beb6b57bSJohn Snow        except TypeError:
677beb6b57bSJohn Snow            # either match or event wasn't iterable (not a dict)
678beb6b57bSJohn Snow            return bool(match == event)
679beb6b57bSJohn Snow
680beb6b57bSJohn Snow    def event_wait(self, name: str,
681beb6b57bSJohn Snow                   timeout: float = 60.0,
682beb6b57bSJohn Snow                   match: Optional[QMPMessage] = None) -> Optional[QMPMessage]:
683beb6b57bSJohn Snow        """
684beb6b57bSJohn Snow        event_wait waits for and returns a named event from QMP with a timeout.
685beb6b57bSJohn Snow
686beb6b57bSJohn Snow        name: The event to wait for.
687beb6b57bSJohn Snow        timeout: QEMUMonitorProtocol.pull_event timeout parameter.
688beb6b57bSJohn Snow        match: Optional match criteria. See event_match for details.
689beb6b57bSJohn Snow        """
690beb6b57bSJohn Snow        return self.events_wait([(name, match)], timeout)
691beb6b57bSJohn Snow
692beb6b57bSJohn Snow    def events_wait(self,
693beb6b57bSJohn Snow                    events: Sequence[Tuple[str, Any]],
694beb6b57bSJohn Snow                    timeout: float = 60.0) -> Optional[QMPMessage]:
695beb6b57bSJohn Snow        """
696beb6b57bSJohn Snow        events_wait waits for and returns a single named event from QMP.
697beb6b57bSJohn Snow        In the case of multiple qualifying events, this function returns the
698beb6b57bSJohn Snow        first one.
699beb6b57bSJohn Snow
700beb6b57bSJohn Snow        :param events: A sequence of (name, match_criteria) tuples.
701beb6b57bSJohn Snow                       The match criteria are optional and may be None.
702beb6b57bSJohn Snow                       See event_match for details.
703beb6b57bSJohn Snow        :param timeout: Optional timeout, in seconds.
704beb6b57bSJohn Snow                        See QEMUMonitorProtocol.pull_event.
705beb6b57bSJohn Snow
706beb6b57bSJohn Snow        :raise QMPTimeoutError: If timeout was non-zero and no matching events
707beb6b57bSJohn Snow                                were found.
708beb6b57bSJohn Snow        :return: A QMP event matching the filter criteria.
709beb6b57bSJohn Snow                 If timeout was 0 and no event matched, None.
710beb6b57bSJohn Snow        """
711beb6b57bSJohn Snow        def _match(event: QMPMessage) -> bool:
712beb6b57bSJohn Snow            for name, match in events:
713beb6b57bSJohn Snow                if event['event'] == name and self.event_match(event, match):
714beb6b57bSJohn Snow                    return True
715beb6b57bSJohn Snow            return False
716beb6b57bSJohn Snow
717beb6b57bSJohn Snow        event: Optional[QMPMessage]
718beb6b57bSJohn Snow
719beb6b57bSJohn Snow        # Search cached events
720beb6b57bSJohn Snow        for event in self._events:
721beb6b57bSJohn Snow            if _match(event):
722beb6b57bSJohn Snow                self._events.remove(event)
723beb6b57bSJohn Snow                return event
724beb6b57bSJohn Snow
725beb6b57bSJohn Snow        # Poll for new events
726beb6b57bSJohn Snow        while True:
727beb6b57bSJohn Snow            event = self._qmp.pull_event(wait=timeout)
728beb6b57bSJohn Snow            if event is None:
729beb6b57bSJohn Snow                # NB: None is only returned when timeout is false-ish.
730beb6b57bSJohn Snow                # Timeouts raise QMPTimeoutError instead!
731beb6b57bSJohn Snow                break
732beb6b57bSJohn Snow            if _match(event):
733beb6b57bSJohn Snow                return event
734beb6b57bSJohn Snow            self._events.append(event)
735beb6b57bSJohn Snow
736beb6b57bSJohn Snow        return None
737beb6b57bSJohn Snow
738beb6b57bSJohn Snow    def get_log(self) -> Optional[str]:
739beb6b57bSJohn Snow        """
740beb6b57bSJohn Snow        After self.shutdown or failed qemu execution, this returns the output
741beb6b57bSJohn Snow        of the qemu process.
742beb6b57bSJohn Snow        """
743beb6b57bSJohn Snow        return self._iolog
744beb6b57bSJohn Snow
745beb6b57bSJohn Snow    def add_args(self, *args: str) -> None:
746beb6b57bSJohn Snow        """
747beb6b57bSJohn Snow        Adds to the list of extra arguments to be given to the QEMU binary
748beb6b57bSJohn Snow        """
749beb6b57bSJohn Snow        self._args.extend(args)
750beb6b57bSJohn Snow
751beb6b57bSJohn Snow    def set_machine(self, machine_type: str) -> None:
752beb6b57bSJohn Snow        """
753beb6b57bSJohn Snow        Sets the machine type
754beb6b57bSJohn Snow
755beb6b57bSJohn Snow        If set, the machine type will be added to the base arguments
756beb6b57bSJohn Snow        of the resulting QEMU command line.
757beb6b57bSJohn Snow        """
758beb6b57bSJohn Snow        self._machine = machine_type
759beb6b57bSJohn Snow
760beb6b57bSJohn Snow    def set_console(self,
761beb6b57bSJohn Snow                    device_type: Optional[str] = None,
762beb6b57bSJohn Snow                    console_index: int = 0) -> None:
763beb6b57bSJohn Snow        """
764beb6b57bSJohn Snow        Sets the device type for a console device
765beb6b57bSJohn Snow
766beb6b57bSJohn Snow        If set, the console device and a backing character device will
767beb6b57bSJohn Snow        be added to the base arguments of the resulting QEMU command
768beb6b57bSJohn Snow        line.
769beb6b57bSJohn Snow
770beb6b57bSJohn Snow        This is a convenience method that will either use the provided
771beb6b57bSJohn Snow        device type, or default to a "-serial chardev:console" command
772beb6b57bSJohn Snow        line argument.
773beb6b57bSJohn Snow
774beb6b57bSJohn Snow        The actual setting of command line arguments will be be done at
775beb6b57bSJohn Snow        machine launch time, as it depends on the temporary directory
776beb6b57bSJohn Snow        to be created.
777beb6b57bSJohn Snow
778beb6b57bSJohn Snow        @param device_type: the device type, such as "isa-serial".  If
779beb6b57bSJohn Snow                            None is given (the default value) a "-serial
780beb6b57bSJohn Snow                            chardev:console" command line argument will
781beb6b57bSJohn Snow                            be used instead, resorting to the machine's
782beb6b57bSJohn Snow                            default device type.
783beb6b57bSJohn Snow        @param console_index: the index of the console device to use.
784beb6b57bSJohn Snow                              If not zero, the command line will create
785beb6b57bSJohn Snow                              'index - 1' consoles and connect them to
786beb6b57bSJohn Snow                              the 'null' backing character device.
787beb6b57bSJohn Snow        """
788beb6b57bSJohn Snow        self._console_set = True
789beb6b57bSJohn Snow        self._console_device_type = device_type
790beb6b57bSJohn Snow        self._console_index = console_index
791beb6b57bSJohn Snow
792beb6b57bSJohn Snow    @property
793beb6b57bSJohn Snow    def console_socket(self) -> socket.socket:
794beb6b57bSJohn Snow        """
795beb6b57bSJohn Snow        Returns a socket connected to the console
796beb6b57bSJohn Snow        """
797beb6b57bSJohn Snow        if self._console_socket is None:
798beb6b57bSJohn Snow            self._console_socket = console_socket.ConsoleSocket(
799beb6b57bSJohn Snow                self._console_address,
800beb6b57bSJohn Snow                file=self._console_log_path,
801beb6b57bSJohn Snow                drain=self._drain_console)
802beb6b57bSJohn Snow        return self._console_socket
803beb6b57bSJohn Snow
804beb6b57bSJohn Snow    @property
805beb6b57bSJohn Snow    def temp_dir(self) -> str:
806beb6b57bSJohn Snow        """
807beb6b57bSJohn Snow        Returns a temporary directory to be used for this machine
808beb6b57bSJohn Snow        """
809beb6b57bSJohn Snow        if self._temp_dir is None:
810beb6b57bSJohn Snow            self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-",
811beb6b57bSJohn Snow                                              dir=self._base_temp_dir)
812beb6b57bSJohn Snow        return self._temp_dir
813b306e26cSCleber Rosa
814b306e26cSCleber Rosa    @property
815b306e26cSCleber Rosa    def log_dir(self) -> str:
816b306e26cSCleber Rosa        """
817b306e26cSCleber Rosa        Returns a directory to be used for writing logs
818b306e26cSCleber Rosa        """
819b306e26cSCleber Rosa        if self._log_dir is None:
820b306e26cSCleber Rosa            return self.temp_dir
821b306e26cSCleber Rosa        return self._log_dir
822