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