xref: /openbmc/qemu/python/qemu/machine/machine.py (revision ed75658a)
1"""
2QEMU machine module:
3
4The machine module primarily provides the QEMUMachine class,
5which provides facilities for managing the lifetime of a QEMU VM.
6"""
7
8# Copyright (C) 2015-2016 Red Hat Inc.
9# Copyright (C) 2012 IBM Corp.
10#
11# Authors:
12#  Fam Zheng <famz@redhat.com>
13#
14# This work is licensed under the terms of the GNU GPL, version 2.  See
15# the COPYING file in the top-level directory.
16#
17# Based on qmp.py.
18#
19
20import errno
21from itertools import chain
22import locale
23import logging
24import os
25import shutil
26import signal
27import socket
28import subprocess
29import tempfile
30from types import TracebackType
31from typing import (
32    Any,
33    BinaryIO,
34    Dict,
35    List,
36    Optional,
37    Sequence,
38    Tuple,
39    Type,
40    TypeVar,
41)
42
43from qemu.qmp import SocketAddrT
44from qemu.qmp.legacy import (
45    QEMUMonitorProtocol,
46    QMPMessage,
47    QMPReturnValue,
48)
49
50from . import console_socket
51
52
53LOG = logging.getLogger(__name__)
54
55
56class QEMUMachineError(Exception):
57    """
58    Exception called when an error in QEMUMachine happens.
59    """
60
61
62class QEMUMachineAddDeviceError(QEMUMachineError):
63    """
64    Exception raised when a request to add a device can not be fulfilled
65
66    The failures are caused by limitations, lack of information or conflicting
67    requests on the QEMUMachine methods.  This exception does not represent
68    failures reported by the QEMU binary itself.
69    """
70
71
72class VMLaunchFailure(QEMUMachineError):
73    """
74    Exception raised when a VM launch was attempted, but failed.
75    """
76    def __init__(self, exitcode: Optional[int],
77                 command: str, output: Optional[str]):
78        super().__init__(exitcode, command, output)
79        self.exitcode = exitcode
80        self.command = command
81        self.output = output
82
83    def __str__(self) -> str:
84        ret = ''
85        if self.__cause__ is not None:
86            name = type(self.__cause__).__name__
87            reason = str(self.__cause__)
88            if reason:
89                ret += f"{name}: {reason}"
90            else:
91                ret += f"{name}"
92        ret += '\n'
93
94        if self.exitcode is not None:
95            ret += f"\tExit code: {self.exitcode}\n"
96        ret += f"\tCommand: {self.command}\n"
97        ret += f"\tOutput: {self.output}\n"
98        return ret
99
100
101class AbnormalShutdown(QEMUMachineError):
102    """
103    Exception raised when a graceful shutdown was requested, but not performed.
104    """
105
106
107_T = TypeVar('_T', bound='QEMUMachine')
108
109
110class QEMUMachine:
111    """
112    A QEMU VM.
113
114    Use this object as a context manager to ensure
115    the QEMU process terminates::
116
117        with VM(binary) as vm:
118            ...
119        # vm is guaranteed to be shut down here
120    """
121    # pylint: disable=too-many-instance-attributes, too-many-public-methods
122
123    def __init__(self,
124                 binary: str,
125                 args: Sequence[str] = (),
126                 wrapper: Sequence[str] = (),
127                 name: Optional[str] = None,
128                 base_temp_dir: str = "/var/tmp",
129                 monitor_address: Optional[SocketAddrT] = None,
130                 drain_console: bool = False,
131                 console_log: Optional[str] = None,
132                 log_dir: Optional[str] = None,
133                 qmp_timer: Optional[float] = 30):
134        '''
135        Initialize a QEMUMachine
136
137        @param binary: path to the qemu binary
138        @param args: list of extra arguments
139        @param wrapper: list of arguments used as prefix to qemu binary
140        @param name: prefix for socket and log file names (default: qemu-PID)
141        @param base_temp_dir: default location where temp files are created
142        @param monitor_address: address for QMP monitor
143        @param drain_console: (optional) True to drain console socket to buffer
144        @param console_log: (optional) path to console log file
145        @param log_dir: where to create and keep log files
146        @param qmp_timer: (optional) default QMP socket timeout
147        @note: Qemu process is not started until launch() is used.
148        '''
149        # pylint: disable=too-many-arguments
150
151        # Direct user configuration
152
153        self._binary = binary
154        self._args = list(args)
155        self._wrapper = wrapper
156        self._qmp_timer = qmp_timer
157
158        self._name = name or f"{id(self):x}"
159        self._sock_pair: Optional[Tuple[socket.socket, socket.socket]] = None
160        self._cons_sock_pair: Optional[
161            Tuple[socket.socket, socket.socket]] = None
162        self._temp_dir: Optional[str] = None
163        self._base_temp_dir = base_temp_dir
164        self._log_dir = log_dir
165
166        self._monitor_address = monitor_address
167
168        self._console_log_path = console_log
169        if self._console_log_path:
170            # In order to log the console, buffering needs to be enabled.
171            self._drain_console = True
172        else:
173            self._drain_console = drain_console
174
175        # Runstate
176        self._qemu_log_path: Optional[str] = None
177        self._qemu_log_file: Optional[BinaryIO] = None
178        self._popen: Optional['subprocess.Popen[bytes]'] = None
179        self._events: List[QMPMessage] = []
180        self._iolog: Optional[str] = None
181        self._qmp_set = True   # Enable QMP monitor by default.
182        self._qmp_connection: Optional[QEMUMonitorProtocol] = None
183        self._qemu_full_args: Tuple[str, ...] = ()
184        self._launched = False
185        self._machine: Optional[str] = None
186        self._console_index = 0
187        self._console_set = False
188        self._console_device_type: Optional[str] = None
189        self._console_socket: Optional[socket.socket] = None
190        self._console_file: Optional[socket.SocketIO] = None
191        self._remove_files: List[str] = []
192        self._user_killed = False
193        self._quit_issued = False
194
195    def __enter__(self: _T) -> _T:
196        return self
197
198    def __exit__(self,
199                 exc_type: Optional[Type[BaseException]],
200                 exc_val: Optional[BaseException],
201                 exc_tb: Optional[TracebackType]) -> None:
202        self.shutdown()
203
204    def add_monitor_null(self) -> None:
205        """
206        This can be used to add an unused monitor instance.
207        """
208        self._args.append('-monitor')
209        self._args.append('null')
210
211    def add_fd(self: _T, fd: int, fdset: int,
212               opaque: str, opts: str = '') -> _T:
213        """
214        Pass a file descriptor to the VM
215        """
216        options = ['fd=%d' % fd,
217                   'set=%d' % fdset,
218                   'opaque=%s' % opaque]
219        if opts:
220            options.append(opts)
221
222        # This did not exist before 3.4, but since then it is
223        # mandatory for our purpose
224        if hasattr(os, 'set_inheritable'):
225            os.set_inheritable(fd, True)
226
227        self._args.append('-add-fd')
228        self._args.append(','.join(options))
229        return self
230
231    def send_fd_scm(self, fd: Optional[int] = None,
232                    file_path: Optional[str] = None) -> int:
233        """
234        Send an fd or file_path to the remote via SCM_RIGHTS.
235
236        Exactly one of fd and file_path must be given.  If it is
237        file_path, the file will be opened read-only and the new file
238        descriptor will be sent to the remote.
239        """
240        if file_path is not None:
241            assert fd is None
242            with open(file_path, "rb") as passfile:
243                fd = passfile.fileno()
244                self._qmp.send_fd_scm(fd)
245        else:
246            assert fd is not None
247            self._qmp.send_fd_scm(fd)
248
249        return 0
250
251    @staticmethod
252    def _remove_if_exists(path: str) -> None:
253        """
254        Remove file object at path if it exists
255        """
256        try:
257            os.remove(path)
258        except OSError as exception:
259            if exception.errno == errno.ENOENT:
260                return
261            raise
262
263    def is_running(self) -> bool:
264        """Returns true if the VM is running."""
265        return self._popen is not None and self._popen.poll() is None
266
267    @property
268    def _subp(self) -> 'subprocess.Popen[bytes]':
269        if self._popen is None:
270            raise QEMUMachineError('Subprocess pipe not present')
271        return self._popen
272
273    def exitcode(self) -> Optional[int]:
274        """Returns the exit code if possible, or None."""
275        if self._popen is None:
276            return None
277        return self._popen.poll()
278
279    def get_pid(self) -> Optional[int]:
280        """Returns the PID of the running process, or None."""
281        if not self.is_running():
282            return None
283        return self._subp.pid
284
285    def _load_io_log(self) -> None:
286        # Assume that the output encoding of QEMU's terminal output is
287        # defined by our locale. If indeterminate, allow open() to fall
288        # back to the platform default.
289        _, encoding = locale.getlocale()
290        if self._qemu_log_path is not None:
291            with open(self._qemu_log_path, "r", encoding=encoding) as iolog:
292                self._iolog = iolog.read()
293
294    @property
295    def _base_args(self) -> List[str]:
296        args = ['-display', 'none', '-vga', 'none']
297
298        if self._qmp_set:
299            if self._sock_pair:
300                moncdev = f"socket,id=mon,fd={self._sock_pair[0].fileno()}"
301            elif isinstance(self._monitor_address, tuple):
302                moncdev = "socket,id=mon,host={},port={}".format(
303                    *self._monitor_address
304                )
305            else:
306                moncdev = f"socket,id=mon,path={self._monitor_address}"
307            args.extend(['-chardev', moncdev, '-mon',
308                         'chardev=mon,mode=control'])
309
310        if self._machine is not None:
311            args.extend(['-machine', self._machine])
312        for _ in range(self._console_index):
313            args.extend(['-serial', 'null'])
314        if self._console_set:
315            assert self._cons_sock_pair is not None
316            fd = self._cons_sock_pair[0].fileno()
317            chardev = f"socket,id=console,fd={fd}"
318            args.extend(['-chardev', chardev])
319            if self._console_device_type is None:
320                args.extend(['-serial', 'chardev:console'])
321            else:
322                device = '%s,chardev=console' % self._console_device_type
323                args.extend(['-device', device])
324        return args
325
326    @property
327    def args(self) -> List[str]:
328        """Returns the list of arguments given to the QEMU binary."""
329        return self._args
330
331    def _pre_launch(self) -> None:
332        if self._qmp_set:
333            if self._monitor_address is None:
334                self._sock_pair = socket.socketpair()
335                os.set_inheritable(self._sock_pair[0].fileno(), True)
336                sock = self._sock_pair[1]
337            if isinstance(self._monitor_address, str):
338                self._remove_files.append(self._monitor_address)
339
340            sock_or_addr = self._monitor_address or sock
341            assert sock_or_addr is not None
342
343            self._qmp_connection = QEMUMonitorProtocol(
344                sock_or_addr,
345                server=bool(self._monitor_address),
346                nickname=self._name
347            )
348
349        if self._console_set:
350            self._cons_sock_pair = socket.socketpair()
351            os.set_inheritable(self._cons_sock_pair[0].fileno(), True)
352
353        # NOTE: Make sure any opened resources are *definitely* freed in
354        # _post_shutdown()!
355        # pylint: disable=consider-using-with
356        self._qemu_log_path = os.path.join(self.log_dir, self._name + ".log")
357        self._qemu_log_file = open(self._qemu_log_path, 'wb')
358
359        self._iolog = None
360        self._qemu_full_args = tuple(chain(
361            self._wrapper,
362            [self._binary],
363            self._base_args,
364            self._args
365        ))
366
367    def _post_launch(self) -> None:
368        if self._sock_pair:
369            self._sock_pair[0].close()
370        if self._cons_sock_pair:
371            self._cons_sock_pair[0].close()
372
373        if self._qmp_connection:
374            if self._sock_pair:
375                self._qmp.connect()
376            else:
377                self._qmp.accept(self._qmp_timer)
378
379    def _close_qemu_log_file(self) -> None:
380        if self._qemu_log_file is not None:
381            self._qemu_log_file.close()
382            self._qemu_log_file = None
383
384    def _post_shutdown(self) -> None:
385        """
386        Called to cleanup the VM instance after the process has exited.
387        May also be called after a failed launch.
388        """
389        LOG.debug("Cleaning up after VM process")
390        try:
391            self._close_qmp_connection()
392        except Exception as err:  # pylint: disable=broad-except
393            LOG.warning(
394                "Exception closing QMP connection: %s",
395                str(err) if str(err) else type(err).__name__
396            )
397        finally:
398            assert self._qmp_connection is None
399
400        if self._sock_pair:
401            self._sock_pair[0].close()
402            self._sock_pair[1].close()
403            self._sock_pair = None
404
405        self._close_qemu_log_file()
406
407        self._load_io_log()
408
409        self._qemu_log_path = None
410
411        if self._temp_dir is not None:
412            shutil.rmtree(self._temp_dir)
413            self._temp_dir = None
414
415        while len(self._remove_files) > 0:
416            self._remove_if_exists(self._remove_files.pop())
417
418        exitcode = self.exitcode()
419        if (exitcode is not None and exitcode < 0
420                and not (self._user_killed and exitcode == -signal.SIGKILL)):
421            msg = 'qemu received signal %i; command: "%s"'
422            if self._qemu_full_args:
423                command = ' '.join(self._qemu_full_args)
424            else:
425                command = ''
426            LOG.warning(msg, -int(exitcode), command)
427
428        self._quit_issued = False
429        self._user_killed = False
430        self._launched = False
431
432    def launch(self) -> None:
433        """
434        Launch the VM and make sure we cleanup and expose the
435        command line/output in case of exception
436        """
437
438        if self._launched:
439            raise QEMUMachineError('VM already launched')
440
441        try:
442            self._launch()
443        except BaseException as exc:
444            # We may have launched the process but it may
445            # have exited before we could connect via QMP.
446            # Assume the VM didn't launch or is exiting.
447            # If we don't wait for the process, exitcode() may still be
448            # 'None' by the time control is ceded back to the caller.
449            if self._launched:
450                self.wait()
451            else:
452                self._post_shutdown()
453
454            if isinstance(exc, Exception):
455                raise VMLaunchFailure(
456                    exitcode=self.exitcode(),
457                    command=' '.join(self._qemu_full_args),
458                    output=self._iolog
459                ) from exc
460
461            # Don't wrap 'BaseException'; doing so would downgrade
462            # that exception. However, we still want to clean up.
463            raise
464
465    def _launch(self) -> None:
466        """
467        Launch the VM and establish a QMP connection
468        """
469        self._pre_launch()
470        LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
471
472        # Cleaning up of this subprocess is guaranteed by _do_shutdown.
473        # pylint: disable=consider-using-with
474        self._popen = subprocess.Popen(self._qemu_full_args,
475                                       stdin=subprocess.DEVNULL,
476                                       stdout=self._qemu_log_file,
477                                       stderr=subprocess.STDOUT,
478                                       shell=False,
479                                       close_fds=False)
480        self._launched = True
481        self._post_launch()
482
483    def _close_qmp_connection(self) -> None:
484        """
485        Close the underlying QMP connection, if any.
486
487        Dutifully report errors that occurred while closing, but assume
488        that any error encountered indicates an abnormal termination
489        process and not a failure to close.
490        """
491        if self._qmp_connection is None:
492            return
493
494        try:
495            self._qmp.close()
496        except EOFError:
497            # EOF can occur as an Exception here when using the Async
498            # QMP backend. It indicates that the server closed the
499            # stream. If we successfully issued 'quit' at any point,
500            # then this was expected. If the remote went away without
501            # our permission, it's worth reporting that as an abnormal
502            # shutdown case.
503            if not (self._user_killed or self._quit_issued):
504                raise
505        finally:
506            self._qmp_connection = None
507
508    def _early_cleanup(self) -> None:
509        """
510        Perform any cleanup that needs to happen before the VM exits.
511
512        This method may be called twice upon shutdown, once each by soft
513        and hard shutdown in failover scenarios.
514        """
515        # If we keep the console socket open, we may deadlock waiting
516        # for QEMU to exit, while QEMU is waiting for the socket to
517        # become writable.
518        if self._console_file is not None:
519            LOG.debug("Closing console file")
520            self._console_file.close()
521            self._console_file = None
522
523        if self._console_socket is not None:
524            LOG.debug("Closing console socket")
525            self._console_socket.close()
526            self._console_socket = None
527
528        if self._cons_sock_pair:
529            self._cons_sock_pair[0].close()
530            self._cons_sock_pair[1].close()
531            self._cons_sock_pair = None
532
533    def _hard_shutdown(self) -> None:
534        """
535        Perform early cleanup, kill the VM, and wait for it to terminate.
536
537        :raise subprocess.Timeout: When timeout is exceeds 60 seconds
538            waiting for the QEMU process to terminate.
539        """
540        LOG.debug("Performing hard shutdown")
541        self._early_cleanup()
542        self._subp.kill()
543        self._subp.wait(timeout=60)
544
545    def _soft_shutdown(self, timeout: Optional[int]) -> None:
546        """
547        Perform early cleanup, attempt to gracefully shut down the VM, and wait
548        for it to terminate.
549
550        :param timeout: Timeout in seconds for graceful shutdown.
551                        A value of None is an infinite wait.
552
553        :raise ConnectionReset: On QMP communication errors
554        :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
555            the QEMU process to terminate.
556        """
557        LOG.debug("Attempting graceful termination")
558
559        self._early_cleanup()
560
561        if self._quit_issued:
562            LOG.debug(
563                "Anticipating QEMU termination due to prior 'quit' command, "
564                "or explicit call to wait()"
565            )
566        else:
567            LOG.debug("Politely asking QEMU to terminate")
568
569        if self._qmp_connection:
570            try:
571                if not self._quit_issued:
572                    # May raise ExecInterruptedError or StateError if the
573                    # connection dies or has *already* died.
574                    self.qmp('quit')
575            finally:
576                # Regardless, we want to quiesce the connection.
577                self._close_qmp_connection()
578        elif not self._quit_issued:
579            LOG.debug(
580                "Not anticipating QEMU quit and no QMP connection present, "
581                "issuing SIGTERM"
582            )
583            self._subp.terminate()
584
585        # May raise subprocess.TimeoutExpired
586        LOG.debug(
587            "Waiting (timeout=%s) for QEMU process (pid=%s) to terminate",
588            timeout, self._subp.pid
589        )
590        self._subp.wait(timeout=timeout)
591
592    def _do_shutdown(self, timeout: Optional[int]) -> None:
593        """
594        Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
595
596        :param timeout: Timeout in seconds for graceful shutdown.
597                        A value of None is an infinite wait.
598
599        :raise AbnormalShutdown: When the VM could not be shut down gracefully.
600            The inner exception will likely be ConnectionReset or
601            subprocess.TimeoutExpired. In rare cases, non-graceful termination
602            may result in its own exceptions, likely subprocess.TimeoutExpired.
603        """
604        try:
605            self._soft_shutdown(timeout)
606        except Exception as exc:
607            if isinstance(exc, subprocess.TimeoutExpired):
608                LOG.debug("Timed out waiting for QEMU process to exit")
609            LOG.debug("Graceful shutdown failed", exc_info=True)
610            LOG.debug("Falling back to hard shutdown")
611            self._hard_shutdown()
612            raise AbnormalShutdown("Could not perform graceful shutdown") \
613                from exc
614
615    def shutdown(self,
616                 hard: bool = False,
617                 timeout: Optional[int] = 30) -> None:
618        """
619        Terminate the VM (gracefully if possible) and perform cleanup.
620        Cleanup will always be performed.
621
622        If the VM has not yet been launched, or shutdown(), wait(), or kill()
623        have already been called, this method does nothing.
624
625        :param hard: When true, do not attempt graceful shutdown, and
626                     suppress the SIGKILL warning log message.
627        :param timeout: Optional timeout in seconds for graceful shutdown.
628                        Default 30 seconds, A `None` value is an infinite wait.
629        """
630        if not self._launched:
631            return
632
633        LOG.debug("Shutting down VM appliance; timeout=%s", timeout)
634        if hard:
635            LOG.debug("Caller requests immediate termination of QEMU process.")
636
637        try:
638            if hard:
639                self._user_killed = True
640                self._hard_shutdown()
641            else:
642                self._do_shutdown(timeout)
643        finally:
644            self._post_shutdown()
645
646    def kill(self) -> None:
647        """
648        Terminate the VM forcefully, wait for it to exit, and perform cleanup.
649        """
650        self.shutdown(hard=True)
651
652    def wait(self, timeout: Optional[int] = 30) -> None:
653        """
654        Wait for the VM to power off and perform post-shutdown cleanup.
655
656        :param timeout: Optional timeout in seconds. Default 30 seconds.
657                        A value of `None` is an infinite wait.
658        """
659        self._quit_issued = True
660        self.shutdown(timeout=timeout)
661
662    def set_qmp_monitor(self, enabled: bool = True) -> None:
663        """
664        Set the QMP monitor.
665
666        @param enabled: if False, qmp monitor options will be removed from
667                        the base arguments of the resulting QEMU command
668                        line. Default is True.
669
670        .. note:: Call this function before launch().
671        """
672        self._qmp_set = enabled
673
674    @property
675    def _qmp(self) -> QEMUMonitorProtocol:
676        if self._qmp_connection is None:
677            raise QEMUMachineError("Attempt to access QMP with no connection")
678        return self._qmp_connection
679
680    @classmethod
681    def _qmp_args(cls, conv_keys: bool,
682                  args: Dict[str, Any]) -> Dict[str, object]:
683        if conv_keys:
684            return {k.replace('_', '-'): v for k, v in args.items()}
685
686        return args
687
688    def qmp(self, cmd: str,
689            args_dict: Optional[Dict[str, object]] = None,
690            conv_keys: Optional[bool] = None,
691            **args: Any) -> QMPMessage:
692        """
693        Invoke a QMP command and return the response dict
694        """
695        if args_dict is not None:
696            assert not args
697            assert conv_keys is None
698            args = args_dict
699            conv_keys = False
700
701        if conv_keys is None:
702            conv_keys = True
703
704        qmp_args = self._qmp_args(conv_keys, args)
705        ret = self._qmp.cmd_raw(cmd, args=qmp_args)
706        if cmd == 'quit' and 'error' not in ret and 'return' in ret:
707            self._quit_issued = True
708        return ret
709
710    def cmd(self, cmd: str,
711            args_dict: Optional[Dict[str, object]] = None,
712            conv_keys: Optional[bool] = None,
713            **args: Any) -> QMPReturnValue:
714        """
715        Invoke a QMP command.
716        On success return the response dict.
717        On failure raise an exception.
718        """
719        if args_dict is not None:
720            assert not args
721            assert conv_keys is None
722            args = args_dict
723            conv_keys = False
724
725        if conv_keys is None:
726            conv_keys = True
727
728        qmp_args = self._qmp_args(conv_keys, args)
729        ret = self._qmp.cmd(cmd, **qmp_args)
730        if cmd == 'quit':
731            self._quit_issued = True
732        return ret
733
734    def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
735        """
736        Poll for one queued QMP events and return it
737        """
738        if self._events:
739            return self._events.pop(0)
740        return self._qmp.pull_event(wait=wait)
741
742    def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
743        """
744        Poll for queued QMP events and return a list of dicts
745        """
746        events = self._qmp.get_events(wait=wait)
747        events.extend(self._events)
748        del self._events[:]
749        return events
750
751    @staticmethod
752    def event_match(event: Any, match: Optional[Any]) -> bool:
753        """
754        Check if an event matches optional match criteria.
755
756        The match criteria takes the form of a matching subdict. The event is
757        checked to be a superset of the subdict, recursively, with matching
758        values whenever the subdict values are not None.
759
760        This has a limitation that you cannot explicitly check for None values.
761
762        Examples, with the subdict queries on the left:
763         - None matches any object.
764         - {"foo": None} matches {"foo": {"bar": 1}}
765         - {"foo": None} matches {"foo": 5}
766         - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
767         - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
768        """
769        if match is None:
770            return True
771
772        try:
773            for key in match:
774                if key in event:
775                    if not QEMUMachine.event_match(event[key], match[key]):
776                        return False
777                else:
778                    return False
779            return True
780        except TypeError:
781            # either match or event wasn't iterable (not a dict)
782            return bool(match == event)
783
784    def event_wait(self, name: str,
785                   timeout: float = 60.0,
786                   match: Optional[QMPMessage] = None) -> Optional[QMPMessage]:
787        """
788        event_wait waits for and returns a named event from QMP with a timeout.
789
790        name: The event to wait for.
791        timeout: QEMUMonitorProtocol.pull_event timeout parameter.
792        match: Optional match criteria. See event_match for details.
793        """
794        return self.events_wait([(name, match)], timeout)
795
796    def events_wait(self,
797                    events: Sequence[Tuple[str, Any]],
798                    timeout: float = 60.0) -> Optional[QMPMessage]:
799        """
800        events_wait waits for and returns a single named event from QMP.
801        In the case of multiple qualifying events, this function returns the
802        first one.
803
804        :param events: A sequence of (name, match_criteria) tuples.
805                       The match criteria are optional and may be None.
806                       See event_match for details.
807        :param timeout: Optional timeout, in seconds.
808                        See QEMUMonitorProtocol.pull_event.
809
810        :raise asyncio.TimeoutError:
811            If timeout was non-zero and no matching events were found.
812
813        :return: A QMP event matching the filter criteria.
814                 If timeout was 0 and no event matched, None.
815        """
816        def _match(event: QMPMessage) -> bool:
817            for name, match in events:
818                if event['event'] == name and self.event_match(event, match):
819                    return True
820            return False
821
822        event: Optional[QMPMessage]
823
824        # Search cached events
825        for event in self._events:
826            if _match(event):
827                self._events.remove(event)
828                return event
829
830        # Poll for new events
831        while True:
832            event = self._qmp.pull_event(wait=timeout)
833            if event is None:
834                # NB: None is only returned when timeout is false-ish.
835                # Timeouts raise asyncio.TimeoutError instead!
836                break
837            if _match(event):
838                return event
839            self._events.append(event)
840
841        return None
842
843    def get_log(self) -> Optional[str]:
844        """
845        After self.shutdown or failed qemu execution, this returns the output
846        of the qemu process.
847        """
848        return self._iolog
849
850    def add_args(self, *args: str) -> None:
851        """
852        Adds to the list of extra arguments to be given to the QEMU binary
853        """
854        self._args.extend(args)
855
856    def set_machine(self, machine_type: str) -> None:
857        """
858        Sets the machine type
859
860        If set, the machine type will be added to the base arguments
861        of the resulting QEMU command line.
862        """
863        self._machine = machine_type
864
865    def set_console(self,
866                    device_type: Optional[str] = None,
867                    console_index: int = 0) -> None:
868        """
869        Sets the device type for a console device
870
871        If set, the console device and a backing character device will
872        be added to the base arguments of the resulting QEMU command
873        line.
874
875        This is a convenience method that will either use the provided
876        device type, or default to a "-serial chardev:console" command
877        line argument.
878
879        The actual setting of command line arguments will be be done at
880        machine launch time, as it depends on the temporary directory
881        to be created.
882
883        @param device_type: the device type, such as "isa-serial".  If
884                            None is given (the default value) a "-serial
885                            chardev:console" command line argument will
886                            be used instead, resorting to the machine's
887                            default device type.
888        @param console_index: the index of the console device to use.
889                              If not zero, the command line will create
890                              'index - 1' consoles and connect them to
891                              the 'null' backing character device.
892        """
893        self._console_set = True
894        self._console_device_type = device_type
895        self._console_index = console_index
896
897    @property
898    def console_socket(self) -> socket.socket:
899        """
900        Returns a socket connected to the console
901        """
902        if self._console_socket is None:
903            LOG.debug("Opening console socket")
904            if not self._console_set:
905                raise QEMUMachineError(
906                    "Attempt to access console socket with no connection")
907            assert self._cons_sock_pair is not None
908            # os.dup() is used here for sock_fd because otherwise we'd
909            # have two rich python socket objects that would each try to
910            # close the same underlying fd when either one gets garbage
911            # collected.
912            self._console_socket = console_socket.ConsoleSocket(
913                sock_fd=os.dup(self._cons_sock_pair[1].fileno()),
914                file=self._console_log_path,
915                drain=self._drain_console)
916            self._cons_sock_pair[1].close()
917        return self._console_socket
918
919    @property
920    def console_file(self) -> socket.SocketIO:
921        """
922        Returns a file associated with the console socket
923        """
924        if self._console_file is None:
925            LOG.debug("Opening console file")
926            self._console_file = self.console_socket.makefile(mode='rb',
927                                                              buffering=0,
928                                                              encoding='utf-8')
929        return self._console_file
930
931    @property
932    def temp_dir(self) -> str:
933        """
934        Returns a temporary directory to be used for this machine
935        """
936        if self._temp_dir is None:
937            self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-",
938                                              dir=self._base_temp_dir)
939        return self._temp_dir
940
941    @property
942    def log_dir(self) -> str:
943        """
944        Returns a directory to be used for writing logs
945        """
946        if self._log_dir is None:
947            return self.temp_dir
948        return self._log_dir
949