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