xref: /openbmc/qemu/python/qemu/machine/machine.py (revision 50465f94)
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
77*50465f94SJohn Snowclass VMLaunchFailure(QEMUMachineError):
78*50465f94SJohn Snow    """
79*50465f94SJohn Snow    Exception raised when a VM launch was attempted, but failed.
80*50465f94SJohn Snow    """
81*50465f94SJohn Snow    def __init__(self, exitcode: Optional[int],
82*50465f94SJohn Snow                 command: str, output: Optional[str]):
83*50465f94SJohn Snow        super().__init__(exitcode, command, output)
84*50465f94SJohn Snow        self.exitcode = exitcode
85*50465f94SJohn Snow        self.command = command
86*50465f94SJohn Snow        self.output = output
87*50465f94SJohn Snow
88*50465f94SJohn Snow    def __str__(self) -> str:
89*50465f94SJohn Snow        ret = ''
90*50465f94SJohn Snow        if self.__cause__ is not None:
91*50465f94SJohn Snow            name = type(self.__cause__).__name__
92*50465f94SJohn Snow            reason = str(self.__cause__)
93*50465f94SJohn Snow            if reason:
94*50465f94SJohn Snow                ret += f"{name}: {reason}"
95*50465f94SJohn Snow            else:
96*50465f94SJohn Snow                ret += f"{name}"
97*50465f94SJohn Snow        ret += '\n'
98*50465f94SJohn Snow
99*50465f94SJohn Snow        if self.exitcode is not None:
100*50465f94SJohn Snow            ret += f"\tExit code: {self.exitcode}\n"
101*50465f94SJohn Snow        ret += f"\tCommand: {self.command}\n"
102*50465f94SJohn Snow        ret += f"\tOutput: {self.output}\n"
103*50465f94SJohn Snow        return ret
104*50465f94SJohn Snow
105*50465f94SJohn Snow
106beb6b57bSJohn Snowclass AbnormalShutdown(QEMUMachineError):
107beb6b57bSJohn Snow    """
108beb6b57bSJohn Snow    Exception raised when a graceful shutdown was requested, but not performed.
109beb6b57bSJohn Snow    """
110beb6b57bSJohn Snow
111beb6b57bSJohn Snow
11215c3b863SVladimir Sementsov-Ogievskiy_T = TypeVar('_T', bound='QEMUMachine')
11315c3b863SVladimir Sementsov-Ogievskiy
11415c3b863SVladimir Sementsov-Ogievskiy
115beb6b57bSJohn Snowclass QEMUMachine:
116beb6b57bSJohn Snow    """
117beb6b57bSJohn Snow    A QEMU VM.
118beb6b57bSJohn Snow
119beb6b57bSJohn Snow    Use this object as a context manager to ensure
120beb6b57bSJohn Snow    the QEMU process terminates::
121beb6b57bSJohn Snow
122beb6b57bSJohn Snow        with VM(binary) as vm:
123beb6b57bSJohn Snow            ...
124beb6b57bSJohn Snow        # vm is guaranteed to be shut down here
125beb6b57bSJohn Snow    """
12682e6517dSJohn Snow    # pylint: disable=too-many-instance-attributes, too-many-public-methods
127beb6b57bSJohn Snow
128beb6b57bSJohn Snow    def __init__(self,
129beb6b57bSJohn Snow                 binary: str,
130beb6b57bSJohn Snow                 args: Sequence[str] = (),
131beb6b57bSJohn Snow                 wrapper: Sequence[str] = (),
132beb6b57bSJohn Snow                 name: Optional[str] = None,
133beb6b57bSJohn Snow                 base_temp_dir: str = "/var/tmp",
134beb6b57bSJohn Snow                 monitor_address: Optional[SocketAddrT] = None,
135beb6b57bSJohn Snow                 sock_dir: Optional[str] = None,
136beb6b57bSJohn Snow                 drain_console: bool = False,
137b306e26cSCleber Rosa                 console_log: Optional[str] = None,
138e2f948a8SEmanuele Giuseppe Esposito                 log_dir: Optional[str] = None,
139e2f948a8SEmanuele Giuseppe Esposito                 qmp_timer: Optional[float] = None):
140beb6b57bSJohn Snow        '''
141beb6b57bSJohn Snow        Initialize a QEMUMachine
142beb6b57bSJohn Snow
143beb6b57bSJohn Snow        @param binary: path to the qemu binary
144beb6b57bSJohn Snow        @param args: list of extra arguments
145beb6b57bSJohn Snow        @param wrapper: list of arguments used as prefix to qemu binary
146beb6b57bSJohn Snow        @param name: prefix for socket and log file names (default: qemu-PID)
147beb6b57bSJohn Snow        @param base_temp_dir: default location where temp files are created
148beb6b57bSJohn Snow        @param monitor_address: address for QMP monitor
149beb6b57bSJohn Snow        @param sock_dir: where to create socket (defaults to base_temp_dir)
150beb6b57bSJohn Snow        @param drain_console: (optional) True to drain console socket to buffer
151beb6b57bSJohn Snow        @param console_log: (optional) path to console log file
152b306e26cSCleber Rosa        @param log_dir: where to create and keep log files
153e2f948a8SEmanuele Giuseppe Esposito        @param qmp_timer: (optional) default QMP socket timeout
154beb6b57bSJohn Snow        @note: Qemu process is not started until launch() is used.
155beb6b57bSJohn Snow        '''
15682e6517dSJohn Snow        # pylint: disable=too-many-arguments
15782e6517dSJohn Snow
158beb6b57bSJohn Snow        # Direct user configuration
159beb6b57bSJohn Snow
160beb6b57bSJohn Snow        self._binary = binary
161beb6b57bSJohn Snow        self._args = list(args)
162beb6b57bSJohn Snow        self._wrapper = wrapper
163e2f948a8SEmanuele Giuseppe Esposito        self._qmp_timer = qmp_timer
164beb6b57bSJohn Snow
16572b17fe7SJohn Snow        self._name = name or f"qemu-{os.getpid()}-{id(self):02x}"
16687bf1fe5SJohn Snow        self._temp_dir: Optional[str] = None
167beb6b57bSJohn Snow        self._base_temp_dir = base_temp_dir
16887bf1fe5SJohn Snow        self._sock_dir = sock_dir
169b306e26cSCleber Rosa        self._log_dir = log_dir
170beb6b57bSJohn Snow
171beb6b57bSJohn Snow        if monitor_address is not None:
172beb6b57bSJohn Snow            self._monitor_address = monitor_address
173beb6b57bSJohn Snow        else:
174beb6b57bSJohn Snow            self._monitor_address = os.path.join(
17587bf1fe5SJohn Snow                self.sock_dir, f"{self._name}-monitor.sock"
176beb6b57bSJohn Snow            )
177beb6b57bSJohn Snow
178beb6b57bSJohn Snow        self._console_log_path = console_log
179beb6b57bSJohn Snow        if self._console_log_path:
180beb6b57bSJohn Snow            # In order to log the console, buffering needs to be enabled.
181beb6b57bSJohn Snow            self._drain_console = True
182beb6b57bSJohn Snow        else:
183beb6b57bSJohn Snow            self._drain_console = drain_console
184beb6b57bSJohn Snow
185beb6b57bSJohn Snow        # Runstate
186beb6b57bSJohn Snow        self._qemu_log_path: Optional[str] = None
187beb6b57bSJohn Snow        self._qemu_log_file: Optional[BinaryIO] = None
188beb6b57bSJohn Snow        self._popen: Optional['subprocess.Popen[bytes]'] = None
189beb6b57bSJohn Snow        self._events: List[QMPMessage] = []
190beb6b57bSJohn Snow        self._iolog: Optional[str] = None
191beb6b57bSJohn Snow        self._qmp_set = True   # Enable QMP monitor by default.
192beb6b57bSJohn Snow        self._qmp_connection: Optional[QEMUMonitorProtocol] = None
193beb6b57bSJohn Snow        self._qemu_full_args: Tuple[str, ...] = ()
194beb6b57bSJohn Snow        self._launched = False
195beb6b57bSJohn Snow        self._machine: Optional[str] = None
196beb6b57bSJohn Snow        self._console_index = 0
197beb6b57bSJohn Snow        self._console_set = False
198beb6b57bSJohn Snow        self._console_device_type: Optional[str] = None
199beb6b57bSJohn Snow        self._console_address = os.path.join(
20087bf1fe5SJohn Snow            self.sock_dir, f"{self._name}-console.sock"
201beb6b57bSJohn Snow        )
202beb6b57bSJohn Snow        self._console_socket: Optional[socket.socket] = None
203beb6b57bSJohn Snow        self._remove_files: List[str] = []
204beb6b57bSJohn Snow        self._user_killed = False
205b9420e4fSJohn Snow        self._quit_issued = False
206beb6b57bSJohn Snow
20715c3b863SVladimir Sementsov-Ogievskiy    def __enter__(self: _T) -> _T:
208beb6b57bSJohn Snow        return self
209beb6b57bSJohn Snow
210beb6b57bSJohn Snow    def __exit__(self,
211beb6b57bSJohn Snow                 exc_type: Optional[Type[BaseException]],
212beb6b57bSJohn Snow                 exc_val: Optional[BaseException],
213beb6b57bSJohn Snow                 exc_tb: Optional[TracebackType]) -> None:
214beb6b57bSJohn Snow        self.shutdown()
215beb6b57bSJohn Snow
216beb6b57bSJohn Snow    def add_monitor_null(self) -> None:
217beb6b57bSJohn Snow        """
218beb6b57bSJohn Snow        This can be used to add an unused monitor instance.
219beb6b57bSJohn Snow        """
220beb6b57bSJohn Snow        self._args.append('-monitor')
221beb6b57bSJohn Snow        self._args.append('null')
222beb6b57bSJohn Snow
22315c3b863SVladimir Sementsov-Ogievskiy    def add_fd(self: _T, fd: int, fdset: int,
22415c3b863SVladimir Sementsov-Ogievskiy               opaque: str, opts: str = '') -> _T:
225beb6b57bSJohn Snow        """
226beb6b57bSJohn Snow        Pass a file descriptor to the VM
227beb6b57bSJohn Snow        """
228beb6b57bSJohn Snow        options = ['fd=%d' % fd,
229beb6b57bSJohn Snow                   'set=%d' % fdset,
230beb6b57bSJohn Snow                   'opaque=%s' % opaque]
231beb6b57bSJohn Snow        if opts:
232beb6b57bSJohn Snow            options.append(opts)
233beb6b57bSJohn Snow
234beb6b57bSJohn Snow        # This did not exist before 3.4, but since then it is
235beb6b57bSJohn Snow        # mandatory for our purpose
236beb6b57bSJohn Snow        if hasattr(os, 'set_inheritable'):
237beb6b57bSJohn Snow            os.set_inheritable(fd, True)
238beb6b57bSJohn Snow
239beb6b57bSJohn Snow        self._args.append('-add-fd')
240beb6b57bSJohn Snow        self._args.append(','.join(options))
241beb6b57bSJohn Snow        return self
242beb6b57bSJohn Snow
243beb6b57bSJohn Snow    def send_fd_scm(self, fd: Optional[int] = None,
244beb6b57bSJohn Snow                    file_path: Optional[str] = None) -> int:
245beb6b57bSJohn Snow        """
246514d00dfSJohn Snow        Send an fd or file_path to the remote via SCM_RIGHTS.
247beb6b57bSJohn Snow
248514d00dfSJohn Snow        Exactly one of fd and file_path must be given.  If it is
249514d00dfSJohn Snow        file_path, the file will be opened read-only and the new file
250514d00dfSJohn Snow        descriptor will be sent to the remote.
251beb6b57bSJohn Snow        """
252beb6b57bSJohn Snow        if file_path is not None:
253beb6b57bSJohn Snow            assert fd is None
254514d00dfSJohn Snow            with open(file_path, "rb") as passfile:
255514d00dfSJohn Snow                fd = passfile.fileno()
256514d00dfSJohn Snow                self._qmp.send_fd_scm(fd)
257beb6b57bSJohn Snow        else:
258beb6b57bSJohn Snow            assert fd is not None
259514d00dfSJohn Snow            self._qmp.send_fd_scm(fd)
260beb6b57bSJohn Snow
261514d00dfSJohn Snow        return 0
262beb6b57bSJohn Snow
263beb6b57bSJohn Snow    @staticmethod
264beb6b57bSJohn Snow    def _remove_if_exists(path: str) -> None:
265beb6b57bSJohn Snow        """
266beb6b57bSJohn Snow        Remove file object at path if it exists
267beb6b57bSJohn Snow        """
268beb6b57bSJohn Snow        try:
269beb6b57bSJohn Snow            os.remove(path)
270beb6b57bSJohn Snow        except OSError as exception:
271beb6b57bSJohn Snow            if exception.errno == errno.ENOENT:
272beb6b57bSJohn Snow                return
273beb6b57bSJohn Snow            raise
274beb6b57bSJohn Snow
275beb6b57bSJohn Snow    def is_running(self) -> bool:
276beb6b57bSJohn Snow        """Returns true if the VM is running."""
277beb6b57bSJohn Snow        return self._popen is not None and self._popen.poll() is None
278beb6b57bSJohn Snow
279beb6b57bSJohn Snow    @property
280beb6b57bSJohn Snow    def _subp(self) -> 'subprocess.Popen[bytes]':
281beb6b57bSJohn Snow        if self._popen is None:
282beb6b57bSJohn Snow            raise QEMUMachineError('Subprocess pipe not present')
283beb6b57bSJohn Snow        return self._popen
284beb6b57bSJohn Snow
285beb6b57bSJohn Snow    def exitcode(self) -> Optional[int]:
286beb6b57bSJohn Snow        """Returns the exit code if possible, or None."""
287beb6b57bSJohn Snow        if self._popen is None:
288beb6b57bSJohn Snow            return None
289beb6b57bSJohn Snow        return self._popen.poll()
290beb6b57bSJohn Snow
291beb6b57bSJohn Snow    def get_pid(self) -> Optional[int]:
292beb6b57bSJohn Snow        """Returns the PID of the running process, or None."""
293beb6b57bSJohn Snow        if not self.is_running():
294beb6b57bSJohn Snow            return None
295beb6b57bSJohn Snow        return self._subp.pid
296beb6b57bSJohn Snow
297beb6b57bSJohn Snow    def _load_io_log(self) -> None:
2985690b437SJohn Snow        # Assume that the output encoding of QEMU's terminal output is
2995690b437SJohn Snow        # defined by our locale. If indeterminate, allow open() to fall
3005690b437SJohn Snow        # back to the platform default.
3015690b437SJohn Snow        _, encoding = locale.getlocale()
302beb6b57bSJohn Snow        if self._qemu_log_path is not None:
3035690b437SJohn Snow            with open(self._qemu_log_path, "r", encoding=encoding) as iolog:
304beb6b57bSJohn Snow                self._iolog = iolog.read()
305beb6b57bSJohn Snow
306beb6b57bSJohn Snow    @property
307beb6b57bSJohn Snow    def _base_args(self) -> List[str]:
308beb6b57bSJohn Snow        args = ['-display', 'none', '-vga', 'none']
309beb6b57bSJohn Snow
310beb6b57bSJohn Snow        if self._qmp_set:
311beb6b57bSJohn Snow            if isinstance(self._monitor_address, tuple):
312beb6b57bSJohn Snow                moncdev = "socket,id=mon,host={},port={}".format(
313beb6b57bSJohn Snow                    *self._monitor_address
314beb6b57bSJohn Snow                )
315beb6b57bSJohn Snow            else:
316beb6b57bSJohn Snow                moncdev = f"socket,id=mon,path={self._monitor_address}"
317beb6b57bSJohn Snow            args.extend(['-chardev', moncdev, '-mon',
318beb6b57bSJohn Snow                         'chardev=mon,mode=control'])
319beb6b57bSJohn Snow
320beb6b57bSJohn Snow        if self._machine is not None:
321beb6b57bSJohn Snow            args.extend(['-machine', self._machine])
322beb6b57bSJohn Snow        for _ in range(self._console_index):
323beb6b57bSJohn Snow            args.extend(['-serial', 'null'])
324beb6b57bSJohn Snow        if self._console_set:
325beb6b57bSJohn Snow            chardev = ('socket,id=console,path=%s,server=on,wait=off' %
326beb6b57bSJohn Snow                       self._console_address)
327beb6b57bSJohn Snow            args.extend(['-chardev', chardev])
328beb6b57bSJohn Snow            if self._console_device_type is None:
329beb6b57bSJohn Snow                args.extend(['-serial', 'chardev:console'])
330beb6b57bSJohn Snow            else:
331beb6b57bSJohn Snow                device = '%s,chardev=console' % self._console_device_type
332beb6b57bSJohn Snow                args.extend(['-device', device])
333beb6b57bSJohn Snow        return args
334beb6b57bSJohn Snow
335555fe0c2SWainer dos Santos Moschetta    @property
336555fe0c2SWainer dos Santos Moschetta    def args(self) -> List[str]:
337555fe0c2SWainer dos Santos Moschetta        """Returns the list of arguments given to the QEMU binary."""
338555fe0c2SWainer dos Santos Moschetta        return self._args
339555fe0c2SWainer dos Santos Moschetta
340beb6b57bSJohn Snow    def _pre_launch(self) -> None:
341beb6b57bSJohn Snow        if self._console_set:
342beb6b57bSJohn Snow            self._remove_files.append(self._console_address)
343beb6b57bSJohn Snow
344beb6b57bSJohn Snow        if self._qmp_set:
3456eeb3de7SJohn Snow            if isinstance(self._monitor_address, str):
346beb6b57bSJohn Snow                self._remove_files.append(self._monitor_address)
347beb6b57bSJohn Snow            self._qmp_connection = QEMUMonitorProtocol(
348beb6b57bSJohn Snow                self._monitor_address,
349beb6b57bSJohn Snow                server=True,
350beb6b57bSJohn Snow                nickname=self._name
351beb6b57bSJohn Snow            )
352beb6b57bSJohn Snow
353beb6b57bSJohn Snow        # NOTE: Make sure any opened resources are *definitely* freed in
354beb6b57bSJohn Snow        # _post_shutdown()!
355beb6b57bSJohn Snow        # pylint: disable=consider-using-with
356b306e26cSCleber Rosa        self._qemu_log_path = os.path.join(self.log_dir, self._name + ".log")
357beb6b57bSJohn Snow        self._qemu_log_file = open(self._qemu_log_path, 'wb')
358beb6b57bSJohn Snow
359b1ca9919SJohn Snow        self._iolog = None
360b1ca9919SJohn Snow        self._qemu_full_args = tuple(chain(
361b1ca9919SJohn Snow            self._wrapper,
362b1ca9919SJohn Snow            [self._binary],
363b1ca9919SJohn Snow            self._base_args,
364b1ca9919SJohn Snow            self._args
365b1ca9919SJohn Snow        ))
366b1ca9919SJohn Snow
367beb6b57bSJohn Snow    def _post_launch(self) -> None:
368beb6b57bSJohn Snow        if self._qmp_connection:
369e2f948a8SEmanuele Giuseppe Esposito            self._qmp.accept(self._qmp_timer)
370beb6b57bSJohn Snow
371eb7a91d0SEmanuele Giuseppe Esposito    def _close_qemu_log_file(self) -> None:
372eb7a91d0SEmanuele Giuseppe Esposito        if self._qemu_log_file is not None:
373eb7a91d0SEmanuele Giuseppe Esposito            self._qemu_log_file.close()
374eb7a91d0SEmanuele Giuseppe Esposito            self._qemu_log_file = None
375eb7a91d0SEmanuele Giuseppe Esposito
376beb6b57bSJohn Snow    def _post_shutdown(self) -> None:
377beb6b57bSJohn Snow        """
378beb6b57bSJohn Snow        Called to cleanup the VM instance after the process has exited.
379beb6b57bSJohn Snow        May also be called after a failed launch.
380beb6b57bSJohn Snow        """
38149a608b8SJohn Snow        try:
38249a608b8SJohn Snow            self._close_qmp_connection()
38349a608b8SJohn Snow        except Exception as err:  # pylint: disable=broad-except
38449a608b8SJohn Snow            LOG.warning(
38549a608b8SJohn Snow                "Exception closing QMP connection: %s",
38649a608b8SJohn Snow                str(err) if str(err) else type(err).__name__
38749a608b8SJohn Snow            )
38849a608b8SJohn Snow        finally:
38949a608b8SJohn Snow            assert self._qmp_connection is None
390beb6b57bSJohn Snow
391eb7a91d0SEmanuele Giuseppe Esposito        self._close_qemu_log_file()
392beb6b57bSJohn Snow
393beb6b57bSJohn Snow        self._load_io_log()
394beb6b57bSJohn Snow
395beb6b57bSJohn Snow        self._qemu_log_path = None
396beb6b57bSJohn Snow
397beb6b57bSJohn Snow        if self._temp_dir is not None:
398beb6b57bSJohn Snow            shutil.rmtree(self._temp_dir)
399beb6b57bSJohn Snow            self._temp_dir = None
400beb6b57bSJohn Snow
401beb6b57bSJohn Snow        while len(self._remove_files) > 0:
402beb6b57bSJohn Snow            self._remove_if_exists(self._remove_files.pop())
403beb6b57bSJohn Snow
404beb6b57bSJohn Snow        exitcode = self.exitcode()
405beb6b57bSJohn Snow        if (exitcode is not None and exitcode < 0
406beb6b57bSJohn Snow                and not (self._user_killed and exitcode == -signal.SIGKILL)):
407beb6b57bSJohn Snow            msg = 'qemu received signal %i; command: "%s"'
408beb6b57bSJohn Snow            if self._qemu_full_args:
409beb6b57bSJohn Snow                command = ' '.join(self._qemu_full_args)
410beb6b57bSJohn Snow            else:
411beb6b57bSJohn Snow                command = ''
412beb6b57bSJohn Snow            LOG.warning(msg, -int(exitcode), command)
413beb6b57bSJohn Snow
414b9420e4fSJohn Snow        self._quit_issued = False
415beb6b57bSJohn Snow        self._user_killed = False
416beb6b57bSJohn Snow        self._launched = False
417beb6b57bSJohn Snow
418beb6b57bSJohn Snow    def launch(self) -> None:
419beb6b57bSJohn Snow        """
420beb6b57bSJohn Snow        Launch the VM and make sure we cleanup and expose the
421beb6b57bSJohn Snow        command line/output in case of exception
422beb6b57bSJohn Snow        """
423beb6b57bSJohn Snow
424beb6b57bSJohn Snow        if self._launched:
425beb6b57bSJohn Snow            raise QEMUMachineError('VM already launched')
426beb6b57bSJohn Snow
427beb6b57bSJohn Snow        try:
428beb6b57bSJohn Snow            self._launch()
429*50465f94SJohn Snow        except BaseException as exc:
4301611e6cfSJohn Snow            # We may have launched the process but it may
4311611e6cfSJohn Snow            # have exited before we could connect via QMP.
4321611e6cfSJohn Snow            # Assume the VM didn't launch or is exiting.
4331611e6cfSJohn Snow            # If we don't wait for the process, exitcode() may still be
4341611e6cfSJohn Snow            # 'None' by the time control is ceded back to the caller.
4351611e6cfSJohn Snow            if self._launched:
4361611e6cfSJohn Snow                self.wait()
4371611e6cfSJohn Snow            else:
438beb6b57bSJohn Snow                self._post_shutdown()
439beb6b57bSJohn Snow
440*50465f94SJohn Snow            if isinstance(exc, Exception):
441*50465f94SJohn Snow                raise VMLaunchFailure(
442*50465f94SJohn Snow                    exitcode=self.exitcode(),
443*50465f94SJohn Snow                    command=' '.join(self._qemu_full_args),
444*50465f94SJohn Snow                    output=self._iolog
445*50465f94SJohn Snow                ) from exc
446*50465f94SJohn Snow
447*50465f94SJohn Snow            # Don't wrap 'BaseException'; doing so would downgrade
448*50465f94SJohn Snow            # that exception. However, we still want to clean up.
449beb6b57bSJohn Snow            raise
450beb6b57bSJohn Snow
451beb6b57bSJohn Snow    def _launch(self) -> None:
452beb6b57bSJohn Snow        """
453beb6b57bSJohn Snow        Launch the VM and establish a QMP connection
454beb6b57bSJohn Snow        """
455beb6b57bSJohn Snow        self._pre_launch()
456beb6b57bSJohn Snow        LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
457beb6b57bSJohn Snow
458beb6b57bSJohn Snow        # Cleaning up of this subprocess is guaranteed by _do_shutdown.
459beb6b57bSJohn Snow        # pylint: disable=consider-using-with
460beb6b57bSJohn Snow        self._popen = subprocess.Popen(self._qemu_full_args,
461beb6b57bSJohn Snow                                       stdin=subprocess.DEVNULL,
462beb6b57bSJohn Snow                                       stdout=self._qemu_log_file,
463beb6b57bSJohn Snow                                       stderr=subprocess.STDOUT,
464beb6b57bSJohn Snow                                       shell=False,
465beb6b57bSJohn Snow                                       close_fds=False)
4661611e6cfSJohn Snow        self._launched = True
467beb6b57bSJohn Snow        self._post_launch()
468beb6b57bSJohn Snow
46949a608b8SJohn Snow    def _close_qmp_connection(self) -> None:
47049a608b8SJohn Snow        """
47149a608b8SJohn Snow        Close the underlying QMP connection, if any.
47249a608b8SJohn Snow
47349a608b8SJohn Snow        Dutifully report errors that occurred while closing, but assume
47449a608b8SJohn Snow        that any error encountered indicates an abnormal termination
47549a608b8SJohn Snow        process and not a failure to close.
47649a608b8SJohn Snow        """
47749a608b8SJohn Snow        if self._qmp_connection is None:
47849a608b8SJohn Snow            return
47949a608b8SJohn Snow
48049a608b8SJohn Snow        try:
48149a608b8SJohn Snow            self._qmp.close()
48249a608b8SJohn Snow        except EOFError:
48349a608b8SJohn Snow            # EOF can occur as an Exception here when using the Async
48449a608b8SJohn Snow            # QMP backend. It indicates that the server closed the
48549a608b8SJohn Snow            # stream. If we successfully issued 'quit' at any point,
48649a608b8SJohn Snow            # then this was expected. If the remote went away without
48749a608b8SJohn Snow            # our permission, it's worth reporting that as an abnormal
48849a608b8SJohn Snow            # shutdown case.
48949a608b8SJohn Snow            if not (self._user_killed or self._quit_issued):
49049a608b8SJohn Snow                raise
49149a608b8SJohn Snow        finally:
49249a608b8SJohn Snow            self._qmp_connection = None
49349a608b8SJohn Snow
494beb6b57bSJohn Snow    def _early_cleanup(self) -> None:
495beb6b57bSJohn Snow        """
496beb6b57bSJohn Snow        Perform any cleanup that needs to happen before the VM exits.
497beb6b57bSJohn Snow
4981611e6cfSJohn Snow        This method may be called twice upon shutdown, once each by soft
4991611e6cfSJohn Snow        and hard shutdown in failover scenarios.
500beb6b57bSJohn Snow        """
501beb6b57bSJohn Snow        # If we keep the console socket open, we may deadlock waiting
502beb6b57bSJohn Snow        # for QEMU to exit, while QEMU is waiting for the socket to
503beb6b57bSJohn Snow        # become writeable.
504beb6b57bSJohn Snow        if self._console_socket is not None:
505beb6b57bSJohn Snow            self._console_socket.close()
506beb6b57bSJohn Snow            self._console_socket = None
507beb6b57bSJohn Snow
508beb6b57bSJohn Snow    def _hard_shutdown(self) -> None:
509beb6b57bSJohn Snow        """
510beb6b57bSJohn Snow        Perform early cleanup, kill the VM, and wait for it to terminate.
511beb6b57bSJohn Snow
512beb6b57bSJohn Snow        :raise subprocess.Timeout: When timeout is exceeds 60 seconds
513beb6b57bSJohn Snow            waiting for the QEMU process to terminate.
514beb6b57bSJohn Snow        """
515beb6b57bSJohn Snow        self._early_cleanup()
516beb6b57bSJohn Snow        self._subp.kill()
517beb6b57bSJohn Snow        self._subp.wait(timeout=60)
518beb6b57bSJohn Snow
519b9420e4fSJohn Snow    def _soft_shutdown(self, timeout: Optional[int]) -> None:
520beb6b57bSJohn Snow        """
521beb6b57bSJohn Snow        Perform early cleanup, attempt to gracefully shut down the VM, and wait
522beb6b57bSJohn Snow        for it to terminate.
523beb6b57bSJohn Snow
524beb6b57bSJohn Snow        :param timeout: Timeout in seconds for graceful shutdown.
525beb6b57bSJohn Snow                        A value of None is an infinite wait.
526beb6b57bSJohn Snow
527beb6b57bSJohn Snow        :raise ConnectionReset: On QMP communication errors
528beb6b57bSJohn Snow        :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
529beb6b57bSJohn Snow            the QEMU process to terminate.
530beb6b57bSJohn Snow        """
531beb6b57bSJohn Snow        self._early_cleanup()
532beb6b57bSJohn Snow
533beb6b57bSJohn Snow        if self._qmp_connection:
53449a608b8SJohn Snow            try:
535b9420e4fSJohn Snow                if not self._quit_issued:
53649a608b8SJohn Snow                    # May raise ExecInterruptedError or StateError if the
53749a608b8SJohn Snow                    # connection dies or has *already* died.
538b9420e4fSJohn Snow                    self.qmp('quit')
53949a608b8SJohn Snow            finally:
54049a608b8SJohn Snow                # Regardless, we want to quiesce the connection.
54149a608b8SJohn Snow                self._close_qmp_connection()
542beb6b57bSJohn Snow
543beb6b57bSJohn Snow        # May raise subprocess.TimeoutExpired
544beb6b57bSJohn Snow        self._subp.wait(timeout=timeout)
545beb6b57bSJohn Snow
546b9420e4fSJohn Snow    def _do_shutdown(self, timeout: Optional[int]) -> None:
547beb6b57bSJohn Snow        """
548beb6b57bSJohn Snow        Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
549beb6b57bSJohn Snow
550beb6b57bSJohn Snow        :param timeout: Timeout in seconds for graceful shutdown.
551beb6b57bSJohn Snow                        A value of None is an infinite wait.
552beb6b57bSJohn Snow
553beb6b57bSJohn Snow        :raise AbnormalShutdown: When the VM could not be shut down gracefully.
554beb6b57bSJohn Snow            The inner exception will likely be ConnectionReset or
555beb6b57bSJohn Snow            subprocess.TimeoutExpired. In rare cases, non-graceful termination
556beb6b57bSJohn Snow            may result in its own exceptions, likely subprocess.TimeoutExpired.
557beb6b57bSJohn Snow        """
558beb6b57bSJohn Snow        try:
559b9420e4fSJohn Snow            self._soft_shutdown(timeout)
560beb6b57bSJohn Snow        except Exception as exc:
561beb6b57bSJohn Snow            self._hard_shutdown()
562beb6b57bSJohn Snow            raise AbnormalShutdown("Could not perform graceful shutdown") \
563beb6b57bSJohn Snow                from exc
564beb6b57bSJohn Snow
565b9420e4fSJohn Snow    def shutdown(self,
566beb6b57bSJohn Snow                 hard: bool = False,
567beb6b57bSJohn Snow                 timeout: Optional[int] = 30) -> None:
568beb6b57bSJohn Snow        """
569beb6b57bSJohn Snow        Terminate the VM (gracefully if possible) and perform cleanup.
570beb6b57bSJohn Snow        Cleanup will always be performed.
571beb6b57bSJohn Snow
572beb6b57bSJohn Snow        If the VM has not yet been launched, or shutdown(), wait(), or kill()
573beb6b57bSJohn Snow        have already been called, this method does nothing.
574beb6b57bSJohn Snow
575beb6b57bSJohn Snow        :param hard: When true, do not attempt graceful shutdown, and
576beb6b57bSJohn Snow                     suppress the SIGKILL warning log message.
577beb6b57bSJohn Snow        :param timeout: Optional timeout in seconds for graceful shutdown.
578beb6b57bSJohn Snow                        Default 30 seconds, A `None` value is an infinite wait.
579beb6b57bSJohn Snow        """
580beb6b57bSJohn Snow        if not self._launched:
581beb6b57bSJohn Snow            return
582beb6b57bSJohn Snow
583beb6b57bSJohn Snow        try:
584beb6b57bSJohn Snow            if hard:
585beb6b57bSJohn Snow                self._user_killed = True
586beb6b57bSJohn Snow                self._hard_shutdown()
587beb6b57bSJohn Snow            else:
588b9420e4fSJohn Snow                self._do_shutdown(timeout)
589beb6b57bSJohn Snow        finally:
590beb6b57bSJohn Snow            self._post_shutdown()
591beb6b57bSJohn Snow
592beb6b57bSJohn Snow    def kill(self) -> None:
593beb6b57bSJohn Snow        """
594beb6b57bSJohn Snow        Terminate the VM forcefully, wait for it to exit, and perform cleanup.
595beb6b57bSJohn Snow        """
596beb6b57bSJohn Snow        self.shutdown(hard=True)
597beb6b57bSJohn Snow
598beb6b57bSJohn Snow    def wait(self, timeout: Optional[int] = 30) -> None:
599beb6b57bSJohn Snow        """
600beb6b57bSJohn Snow        Wait for the VM to power off and perform post-shutdown cleanup.
601beb6b57bSJohn Snow
602beb6b57bSJohn Snow        :param timeout: Optional timeout in seconds. Default 30 seconds.
603beb6b57bSJohn Snow                        A value of `None` is an infinite wait.
604beb6b57bSJohn Snow        """
605b9420e4fSJohn Snow        self._quit_issued = True
606b9420e4fSJohn Snow        self.shutdown(timeout=timeout)
607beb6b57bSJohn Snow
608beb6b57bSJohn Snow    def set_qmp_monitor(self, enabled: bool = True) -> None:
609beb6b57bSJohn Snow        """
610beb6b57bSJohn Snow        Set the QMP monitor.
611beb6b57bSJohn Snow
612beb6b57bSJohn Snow        @param enabled: if False, qmp monitor options will be removed from
613beb6b57bSJohn Snow                        the base arguments of the resulting QEMU command
614beb6b57bSJohn Snow                        line. Default is True.
6155c02c865SJohn Snow
6165c02c865SJohn Snow        .. note:: Call this function before launch().
617beb6b57bSJohn Snow        """
618beb6b57bSJohn Snow        self._qmp_set = enabled
619beb6b57bSJohn Snow
620beb6b57bSJohn Snow    @property
621beb6b57bSJohn Snow    def _qmp(self) -> QEMUMonitorProtocol:
622beb6b57bSJohn Snow        if self._qmp_connection is None:
623beb6b57bSJohn Snow            raise QEMUMachineError("Attempt to access QMP with no connection")
624beb6b57bSJohn Snow        return self._qmp_connection
625beb6b57bSJohn Snow
626beb6b57bSJohn Snow    @classmethod
627c7daa57eSVladimir Sementsov-Ogievskiy    def _qmp_args(cls, conv_keys: bool,
628c7daa57eSVladimir Sementsov-Ogievskiy                  args: Dict[str, Any]) -> Dict[str, object]:
629c7daa57eSVladimir Sementsov-Ogievskiy        if conv_keys:
630c7daa57eSVladimir Sementsov-Ogievskiy            return {k.replace('_', '-'): v for k, v in args.items()}
631c7daa57eSVladimir Sementsov-Ogievskiy
632c7daa57eSVladimir Sementsov-Ogievskiy        return args
633beb6b57bSJohn Snow
634beb6b57bSJohn Snow    def qmp(self, cmd: str,
6353f3c9b4cSVladimir Sementsov-Ogievskiy            args_dict: Optional[Dict[str, object]] = None,
6363f3c9b4cSVladimir Sementsov-Ogievskiy            conv_keys: Optional[bool] = None,
637beb6b57bSJohn Snow            **args: Any) -> QMPMessage:
638beb6b57bSJohn Snow        """
639beb6b57bSJohn Snow        Invoke a QMP command and return the response dict
640beb6b57bSJohn Snow        """
6413f3c9b4cSVladimir Sementsov-Ogievskiy        if args_dict is not None:
6423f3c9b4cSVladimir Sementsov-Ogievskiy            assert not args
6433f3c9b4cSVladimir Sementsov-Ogievskiy            assert conv_keys is None
6443f3c9b4cSVladimir Sementsov-Ogievskiy            args = args_dict
6453f3c9b4cSVladimir Sementsov-Ogievskiy            conv_keys = False
6463f3c9b4cSVladimir Sementsov-Ogievskiy
6473f3c9b4cSVladimir Sementsov-Ogievskiy        if conv_keys is None:
6483f3c9b4cSVladimir Sementsov-Ogievskiy            conv_keys = True
6493f3c9b4cSVladimir Sementsov-Ogievskiy
650c7daa57eSVladimir Sementsov-Ogievskiy        qmp_args = self._qmp_args(conv_keys, args)
651b9420e4fSJohn Snow        ret = self._qmp.cmd(cmd, args=qmp_args)
652b9420e4fSJohn Snow        if cmd == 'quit' and 'error' not in ret and 'return' in ret:
653b9420e4fSJohn Snow            self._quit_issued = True
654b9420e4fSJohn Snow        return ret
655beb6b57bSJohn Snow
656beb6b57bSJohn Snow    def command(self, cmd: str,
657beb6b57bSJohn Snow                conv_keys: bool = True,
658beb6b57bSJohn Snow                **args: Any) -> QMPReturnValue:
659beb6b57bSJohn Snow        """
660beb6b57bSJohn Snow        Invoke a QMP command.
661beb6b57bSJohn Snow        On success return the response dict.
662beb6b57bSJohn Snow        On failure raise an exception.
663beb6b57bSJohn Snow        """
664c7daa57eSVladimir Sementsov-Ogievskiy        qmp_args = self._qmp_args(conv_keys, args)
665b9420e4fSJohn Snow        ret = self._qmp.command(cmd, **qmp_args)
666b9420e4fSJohn Snow        if cmd == 'quit':
667b9420e4fSJohn Snow            self._quit_issued = True
668b9420e4fSJohn Snow        return ret
669beb6b57bSJohn Snow
670beb6b57bSJohn Snow    def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
671beb6b57bSJohn Snow        """
672beb6b57bSJohn Snow        Poll for one queued QMP events and return it
673beb6b57bSJohn Snow        """
674beb6b57bSJohn Snow        if self._events:
675beb6b57bSJohn Snow            return self._events.pop(0)
676beb6b57bSJohn Snow        return self._qmp.pull_event(wait=wait)
677beb6b57bSJohn Snow
678beb6b57bSJohn Snow    def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
679beb6b57bSJohn Snow        """
680beb6b57bSJohn Snow        Poll for queued QMP events and return a list of dicts
681beb6b57bSJohn Snow        """
682beb6b57bSJohn Snow        events = self._qmp.get_events(wait=wait)
683beb6b57bSJohn Snow        events.extend(self._events)
684beb6b57bSJohn Snow        del self._events[:]
685beb6b57bSJohn Snow        return events
686beb6b57bSJohn Snow
687beb6b57bSJohn Snow    @staticmethod
688beb6b57bSJohn Snow    def event_match(event: Any, match: Optional[Any]) -> bool:
689beb6b57bSJohn Snow        """
690beb6b57bSJohn Snow        Check if an event matches optional match criteria.
691beb6b57bSJohn Snow
692beb6b57bSJohn Snow        The match criteria takes the form of a matching subdict. The event is
693beb6b57bSJohn Snow        checked to be a superset of the subdict, recursively, with matching
694beb6b57bSJohn Snow        values whenever the subdict values are not None.
695beb6b57bSJohn Snow
696beb6b57bSJohn Snow        This has a limitation that you cannot explicitly check for None values.
697beb6b57bSJohn Snow
698beb6b57bSJohn Snow        Examples, with the subdict queries on the left:
699beb6b57bSJohn Snow         - None matches any object.
700beb6b57bSJohn Snow         - {"foo": None} matches {"foo": {"bar": 1}}
701beb6b57bSJohn Snow         - {"foo": None} matches {"foo": 5}
702beb6b57bSJohn Snow         - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
703beb6b57bSJohn Snow         - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
704beb6b57bSJohn Snow        """
705beb6b57bSJohn Snow        if match is None:
706beb6b57bSJohn Snow            return True
707beb6b57bSJohn Snow
708beb6b57bSJohn Snow        try:
709beb6b57bSJohn Snow            for key in match:
710beb6b57bSJohn Snow                if key in event:
711beb6b57bSJohn Snow                    if not QEMUMachine.event_match(event[key], match[key]):
712beb6b57bSJohn Snow                        return False
713beb6b57bSJohn Snow                else:
714beb6b57bSJohn Snow                    return False
715beb6b57bSJohn Snow            return True
716beb6b57bSJohn Snow        except TypeError:
717beb6b57bSJohn Snow            # either match or event wasn't iterable (not a dict)
718beb6b57bSJohn Snow            return bool(match == event)
719beb6b57bSJohn Snow
720beb6b57bSJohn Snow    def event_wait(self, name: str,
721beb6b57bSJohn Snow                   timeout: float = 60.0,
722beb6b57bSJohn Snow                   match: Optional[QMPMessage] = None) -> Optional[QMPMessage]:
723beb6b57bSJohn Snow        """
724beb6b57bSJohn Snow        event_wait waits for and returns a named event from QMP with a timeout.
725beb6b57bSJohn Snow
726beb6b57bSJohn Snow        name: The event to wait for.
727beb6b57bSJohn Snow        timeout: QEMUMonitorProtocol.pull_event timeout parameter.
728beb6b57bSJohn Snow        match: Optional match criteria. See event_match for details.
729beb6b57bSJohn Snow        """
730beb6b57bSJohn Snow        return self.events_wait([(name, match)], timeout)
731beb6b57bSJohn Snow
732beb6b57bSJohn Snow    def events_wait(self,
733beb6b57bSJohn Snow                    events: Sequence[Tuple[str, Any]],
734beb6b57bSJohn Snow                    timeout: float = 60.0) -> Optional[QMPMessage]:
735beb6b57bSJohn Snow        """
736beb6b57bSJohn Snow        events_wait waits for and returns a single named event from QMP.
737beb6b57bSJohn Snow        In the case of multiple qualifying events, this function returns the
738beb6b57bSJohn Snow        first one.
739beb6b57bSJohn Snow
740beb6b57bSJohn Snow        :param events: A sequence of (name, match_criteria) tuples.
741beb6b57bSJohn Snow                       The match criteria are optional and may be None.
742beb6b57bSJohn Snow                       See event_match for details.
743beb6b57bSJohn Snow        :param timeout: Optional timeout, in seconds.
744beb6b57bSJohn Snow                        See QEMUMonitorProtocol.pull_event.
745beb6b57bSJohn Snow
746beb6b57bSJohn Snow        :raise QMPTimeoutError: If timeout was non-zero and no matching events
747beb6b57bSJohn Snow                                were found.
748beb6b57bSJohn Snow        :return: A QMP event matching the filter criteria.
749beb6b57bSJohn Snow                 If timeout was 0 and no event matched, None.
750beb6b57bSJohn Snow        """
751beb6b57bSJohn Snow        def _match(event: QMPMessage) -> bool:
752beb6b57bSJohn Snow            for name, match in events:
753beb6b57bSJohn Snow                if event['event'] == name and self.event_match(event, match):
754beb6b57bSJohn Snow                    return True
755beb6b57bSJohn Snow            return False
756beb6b57bSJohn Snow
757beb6b57bSJohn Snow        event: Optional[QMPMessage]
758beb6b57bSJohn Snow
759beb6b57bSJohn Snow        # Search cached events
760beb6b57bSJohn Snow        for event in self._events:
761beb6b57bSJohn Snow            if _match(event):
762beb6b57bSJohn Snow                self._events.remove(event)
763beb6b57bSJohn Snow                return event
764beb6b57bSJohn Snow
765beb6b57bSJohn Snow        # Poll for new events
766beb6b57bSJohn Snow        while True:
767beb6b57bSJohn Snow            event = self._qmp.pull_event(wait=timeout)
768beb6b57bSJohn Snow            if event is None:
769beb6b57bSJohn Snow                # NB: None is only returned when timeout is false-ish.
770beb6b57bSJohn Snow                # Timeouts raise QMPTimeoutError instead!
771beb6b57bSJohn Snow                break
772beb6b57bSJohn Snow            if _match(event):
773beb6b57bSJohn Snow                return event
774beb6b57bSJohn Snow            self._events.append(event)
775beb6b57bSJohn Snow
776beb6b57bSJohn Snow        return None
777beb6b57bSJohn Snow
778beb6b57bSJohn Snow    def get_log(self) -> Optional[str]:
779beb6b57bSJohn Snow        """
780beb6b57bSJohn Snow        After self.shutdown or failed qemu execution, this returns the output
781beb6b57bSJohn Snow        of the qemu process.
782beb6b57bSJohn Snow        """
783beb6b57bSJohn Snow        return self._iolog
784beb6b57bSJohn Snow
785beb6b57bSJohn Snow    def add_args(self, *args: str) -> None:
786beb6b57bSJohn Snow        """
787beb6b57bSJohn Snow        Adds to the list of extra arguments to be given to the QEMU binary
788beb6b57bSJohn Snow        """
789beb6b57bSJohn Snow        self._args.extend(args)
790beb6b57bSJohn Snow
791beb6b57bSJohn Snow    def set_machine(self, machine_type: str) -> None:
792beb6b57bSJohn Snow        """
793beb6b57bSJohn Snow        Sets the machine type
794beb6b57bSJohn Snow
795beb6b57bSJohn Snow        If set, the machine type will be added to the base arguments
796beb6b57bSJohn Snow        of the resulting QEMU command line.
797beb6b57bSJohn Snow        """
798beb6b57bSJohn Snow        self._machine = machine_type
799beb6b57bSJohn Snow
800beb6b57bSJohn Snow    def set_console(self,
801beb6b57bSJohn Snow                    device_type: Optional[str] = None,
802beb6b57bSJohn Snow                    console_index: int = 0) -> None:
803beb6b57bSJohn Snow        """
804beb6b57bSJohn Snow        Sets the device type for a console device
805beb6b57bSJohn Snow
806beb6b57bSJohn Snow        If set, the console device and a backing character device will
807beb6b57bSJohn Snow        be added to the base arguments of the resulting QEMU command
808beb6b57bSJohn Snow        line.
809beb6b57bSJohn Snow
810beb6b57bSJohn Snow        This is a convenience method that will either use the provided
811beb6b57bSJohn Snow        device type, or default to a "-serial chardev:console" command
812beb6b57bSJohn Snow        line argument.
813beb6b57bSJohn Snow
814beb6b57bSJohn Snow        The actual setting of command line arguments will be be done at
815beb6b57bSJohn Snow        machine launch time, as it depends on the temporary directory
816beb6b57bSJohn Snow        to be created.
817beb6b57bSJohn Snow
818beb6b57bSJohn Snow        @param device_type: the device type, such as "isa-serial".  If
819beb6b57bSJohn Snow                            None is given (the default value) a "-serial
820beb6b57bSJohn Snow                            chardev:console" command line argument will
821beb6b57bSJohn Snow                            be used instead, resorting to the machine's
822beb6b57bSJohn Snow                            default device type.
823beb6b57bSJohn Snow        @param console_index: the index of the console device to use.
824beb6b57bSJohn Snow                              If not zero, the command line will create
825beb6b57bSJohn Snow                              'index - 1' consoles and connect them to
826beb6b57bSJohn Snow                              the 'null' backing character device.
827beb6b57bSJohn Snow        """
828beb6b57bSJohn Snow        self._console_set = True
829beb6b57bSJohn Snow        self._console_device_type = device_type
830beb6b57bSJohn Snow        self._console_index = console_index
831beb6b57bSJohn Snow
832beb6b57bSJohn Snow    @property
833beb6b57bSJohn Snow    def console_socket(self) -> socket.socket:
834beb6b57bSJohn Snow        """
835beb6b57bSJohn Snow        Returns a socket connected to the console
836beb6b57bSJohn Snow        """
837beb6b57bSJohn Snow        if self._console_socket is None:
838beb6b57bSJohn Snow            self._console_socket = console_socket.ConsoleSocket(
839beb6b57bSJohn Snow                self._console_address,
840beb6b57bSJohn Snow                file=self._console_log_path,
841beb6b57bSJohn Snow                drain=self._drain_console)
842beb6b57bSJohn Snow        return self._console_socket
843beb6b57bSJohn Snow
844beb6b57bSJohn Snow    @property
845beb6b57bSJohn Snow    def temp_dir(self) -> str:
846beb6b57bSJohn Snow        """
847beb6b57bSJohn Snow        Returns a temporary directory to be used for this machine
848beb6b57bSJohn Snow        """
849beb6b57bSJohn Snow        if self._temp_dir is None:
850beb6b57bSJohn Snow            self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-",
851beb6b57bSJohn Snow                                              dir=self._base_temp_dir)
852beb6b57bSJohn Snow        return self._temp_dir
853b306e26cSCleber Rosa
854b306e26cSCleber Rosa    @property
85587bf1fe5SJohn Snow    def sock_dir(self) -> str:
85687bf1fe5SJohn Snow        """
85787bf1fe5SJohn Snow        Returns the directory used for sockfiles by this machine.
85887bf1fe5SJohn Snow        """
85987bf1fe5SJohn Snow        if self._sock_dir:
86087bf1fe5SJohn Snow            return self._sock_dir
86187bf1fe5SJohn Snow        return self.temp_dir
86287bf1fe5SJohn Snow
86387bf1fe5SJohn Snow    @property
864b306e26cSCleber Rosa    def log_dir(self) -> str:
865b306e26cSCleber Rosa        """
866b306e26cSCleber Rosa        Returns a directory to be used for writing logs
867b306e26cSCleber Rosa        """
868b306e26cSCleber Rosa        if self._log_dir is None:
869b306e26cSCleber Rosa            return self.temp_dir
870b306e26cSCleber Rosa        return self._log_dir
871