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