xref: /qemu/python/qemu/machine/machine.py (revision 0955d66e)
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 (  # pylint: disable=import-error
44    QEMUMonitorProtocol,
45    QMPMessage,
46    QMPReturnValue,
47    SocketAddrT,
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 AbnormalShutdown(QEMUMachineError):
73    """
74    Exception raised when a graceful shutdown was requested, but not performed.
75    """
76
77
78_T = TypeVar('_T', bound='QEMUMachine')
79
80
81class QEMUMachine:
82    """
83    A QEMU VM.
84
85    Use this object as a context manager to ensure
86    the QEMU process terminates::
87
88        with VM(binary) as vm:
89            ...
90        # vm is guaranteed to be shut down here
91    """
92    # pylint: disable=too-many-instance-attributes, too-many-public-methods
93
94    def __init__(self,
95                 binary: str,
96                 args: Sequence[str] = (),
97                 wrapper: Sequence[str] = (),
98                 name: Optional[str] = None,
99                 base_temp_dir: str = "/var/tmp",
100                 monitor_address: Optional[SocketAddrT] = None,
101                 sock_dir: Optional[str] = None,
102                 drain_console: bool = False,
103                 console_log: Optional[str] = None,
104                 log_dir: Optional[str] = None,
105                 qmp_timer: Optional[float] = None):
106        '''
107        Initialize a QEMUMachine
108
109        @param binary: path to the qemu binary
110        @param args: list of extra arguments
111        @param wrapper: list of arguments used as prefix to qemu binary
112        @param name: prefix for socket and log file names (default: qemu-PID)
113        @param base_temp_dir: default location where temp files are created
114        @param monitor_address: address for QMP monitor
115        @param sock_dir: where to create socket (defaults to base_temp_dir)
116        @param drain_console: (optional) True to drain console socket to buffer
117        @param console_log: (optional) path to console log file
118        @param log_dir: where to create and keep log files
119        @param qmp_timer: (optional) default QMP socket timeout
120        @note: Qemu process is not started until launch() is used.
121        '''
122        # pylint: disable=too-many-arguments
123
124        # Direct user configuration
125
126        self._binary = binary
127        self._args = list(args)
128        self._wrapper = wrapper
129        self._qmp_timer = qmp_timer
130
131        self._name = name or "qemu-%d" % os.getpid()
132        self._base_temp_dir = base_temp_dir
133        self._sock_dir = sock_dir or self._base_temp_dir
134        self._log_dir = log_dir
135
136        if monitor_address is not None:
137            self._monitor_address = monitor_address
138            self._remove_monitor_sockfile = False
139        else:
140            self._monitor_address = os.path.join(
141                self._sock_dir, f"{self._name}-monitor.sock"
142            )
143            self._remove_monitor_sockfile = True
144
145        self._console_log_path = console_log
146        if self._console_log_path:
147            # In order to log the console, buffering needs to be enabled.
148            self._drain_console = True
149        else:
150            self._drain_console = drain_console
151
152        # Runstate
153        self._qemu_log_path: Optional[str] = None
154        self._qemu_log_file: Optional[BinaryIO] = None
155        self._popen: Optional['subprocess.Popen[bytes]'] = None
156        self._events: List[QMPMessage] = []
157        self._iolog: Optional[str] = None
158        self._qmp_set = True   # Enable QMP monitor by default.
159        self._qmp_connection: Optional[QEMUMonitorProtocol] = None
160        self._qemu_full_args: Tuple[str, ...] = ()
161        self._temp_dir: Optional[str] = None
162        self._launched = False
163        self._machine: Optional[str] = None
164        self._console_index = 0
165        self._console_set = False
166        self._console_device_type: Optional[str] = None
167        self._console_address = os.path.join(
168            self._sock_dir, f"{self._name}-console.sock"
169        )
170        self._console_socket: Optional[socket.socket] = None
171        self._remove_files: List[str] = []
172        self._user_killed = False
173
174    def __enter__(self: _T) -> _T:
175        return self
176
177    def __exit__(self,
178                 exc_type: Optional[Type[BaseException]],
179                 exc_val: Optional[BaseException],
180                 exc_tb: Optional[TracebackType]) -> None:
181        self.shutdown()
182
183    def add_monitor_null(self) -> None:
184        """
185        This can be used to add an unused monitor instance.
186        """
187        self._args.append('-monitor')
188        self._args.append('null')
189
190    def add_fd(self: _T, fd: int, fdset: int,
191               opaque: str, opts: str = '') -> _T:
192        """
193        Pass a file descriptor to the VM
194        """
195        options = ['fd=%d' % fd,
196                   'set=%d' % fdset,
197                   'opaque=%s' % opaque]
198        if opts:
199            options.append(opts)
200
201        # This did not exist before 3.4, but since then it is
202        # mandatory for our purpose
203        if hasattr(os, 'set_inheritable'):
204            os.set_inheritable(fd, True)
205
206        self._args.append('-add-fd')
207        self._args.append(','.join(options))
208        return self
209
210    def send_fd_scm(self, fd: Optional[int] = None,
211                    file_path: Optional[str] = None) -> int:
212        """
213        Send an fd or file_path to the remote via SCM_RIGHTS.
214
215        Exactly one of fd and file_path must be given.  If it is
216        file_path, the file will be opened read-only and the new file
217        descriptor will be sent to the remote.
218        """
219        if file_path is not None:
220            assert fd is None
221            with open(file_path, "rb") as passfile:
222                fd = passfile.fileno()
223                self._qmp.send_fd_scm(fd)
224        else:
225            assert fd is not None
226            self._qmp.send_fd_scm(fd)
227
228        return 0
229
230    @staticmethod
231    def _remove_if_exists(path: str) -> None:
232        """
233        Remove file object at path if it exists
234        """
235        try:
236            os.remove(path)
237        except OSError as exception:
238            if exception.errno == errno.ENOENT:
239                return
240            raise
241
242    def is_running(self) -> bool:
243        """Returns true if the VM is running."""
244        return self._popen is not None and self._popen.poll() is None
245
246    @property
247    def _subp(self) -> 'subprocess.Popen[bytes]':
248        if self._popen is None:
249            raise QEMUMachineError('Subprocess pipe not present')
250        return self._popen
251
252    def exitcode(self) -> Optional[int]:
253        """Returns the exit code if possible, or None."""
254        if self._popen is None:
255            return None
256        return self._popen.poll()
257
258    def get_pid(self) -> Optional[int]:
259        """Returns the PID of the running process, or None."""
260        if not self.is_running():
261            return None
262        return self._subp.pid
263
264    def _load_io_log(self) -> None:
265        # Assume that the output encoding of QEMU's terminal output is
266        # defined by our locale. If indeterminate, allow open() to fall
267        # back to the platform default.
268        _, encoding = locale.getlocale()
269        if self._qemu_log_path is not None:
270            with open(self._qemu_log_path, "r", encoding=encoding) as iolog:
271                self._iolog = iolog.read()
272
273    @property
274    def _base_args(self) -> List[str]:
275        args = ['-display', 'none', '-vga', 'none']
276
277        if self._qmp_set:
278            if isinstance(self._monitor_address, tuple):
279                moncdev = "socket,id=mon,host={},port={}".format(
280                    *self._monitor_address
281                )
282            else:
283                moncdev = f"socket,id=mon,path={self._monitor_address}"
284            args.extend(['-chardev', moncdev, '-mon',
285                         'chardev=mon,mode=control'])
286
287        if self._machine is not None:
288            args.extend(['-machine', self._machine])
289        for _ in range(self._console_index):
290            args.extend(['-serial', 'null'])
291        if self._console_set:
292            chardev = ('socket,id=console,path=%s,server=on,wait=off' %
293                       self._console_address)
294            args.extend(['-chardev', chardev])
295            if self._console_device_type is None:
296                args.extend(['-serial', 'chardev:console'])
297            else:
298                device = '%s,chardev=console' % self._console_device_type
299                args.extend(['-device', device])
300        return args
301
302    @property
303    def args(self) -> List[str]:
304        """Returns the list of arguments given to the QEMU binary."""
305        return self._args
306
307    def _pre_launch(self) -> None:
308        if self._console_set:
309            self._remove_files.append(self._console_address)
310
311        if self._qmp_set:
312            if self._remove_monitor_sockfile:
313                assert isinstance(self._monitor_address, str)
314                self._remove_files.append(self._monitor_address)
315            self._qmp_connection = QEMUMonitorProtocol(
316                self._monitor_address,
317                server=True,
318                nickname=self._name
319            )
320
321        # NOTE: Make sure any opened resources are *definitely* freed in
322        # _post_shutdown()!
323        # pylint: disable=consider-using-with
324        self._qemu_log_path = os.path.join(self.log_dir, self._name + ".log")
325        self._qemu_log_file = open(self._qemu_log_path, 'wb')
326
327    def _post_launch(self) -> None:
328        if self._qmp_connection:
329            self._qmp.accept(self._qmp_timer)
330
331    def _close_qemu_log_file(self) -> None:
332        if self._qemu_log_file is not None:
333            self._qemu_log_file.close()
334            self._qemu_log_file = None
335
336    def _post_shutdown(self) -> None:
337        """
338        Called to cleanup the VM instance after the process has exited.
339        May also be called after a failed launch.
340        """
341        # Comprehensive reset for the failed launch case:
342        self._early_cleanup()
343
344        if self._qmp_connection:
345            self._qmp.close()
346            self._qmp_connection = None
347
348        self._close_qemu_log_file()
349
350        self._load_io_log()
351
352        self._qemu_log_path = None
353
354        if self._temp_dir is not None:
355            shutil.rmtree(self._temp_dir)
356            self._temp_dir = None
357
358        while len(self._remove_files) > 0:
359            self._remove_if_exists(self._remove_files.pop())
360
361        exitcode = self.exitcode()
362        if (exitcode is not None and exitcode < 0
363                and not (self._user_killed and exitcode == -signal.SIGKILL)):
364            msg = 'qemu received signal %i; command: "%s"'
365            if self._qemu_full_args:
366                command = ' '.join(self._qemu_full_args)
367            else:
368                command = ''
369            LOG.warning(msg, -int(exitcode), command)
370
371        self._user_killed = False
372        self._launched = False
373
374    def launch(self) -> None:
375        """
376        Launch the VM and make sure we cleanup and expose the
377        command line/output in case of exception
378        """
379
380        if self._launched:
381            raise QEMUMachineError('VM already launched')
382
383        self._iolog = None
384        self._qemu_full_args = ()
385        try:
386            self._launch()
387            self._launched = True
388        except:
389            self._post_shutdown()
390
391            LOG.debug('Error launching VM')
392            if self._qemu_full_args:
393                LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
394            if self._iolog:
395                LOG.debug('Output: %r', self._iolog)
396            raise
397
398    def _launch(self) -> None:
399        """
400        Launch the VM and establish a QMP connection
401        """
402        self._pre_launch()
403        self._qemu_full_args = tuple(
404            chain(self._wrapper,
405                  [self._binary],
406                  self._base_args,
407                  self._args)
408        )
409        LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
410
411        # Cleaning up of this subprocess is guaranteed by _do_shutdown.
412        # pylint: disable=consider-using-with
413        self._popen = subprocess.Popen(self._qemu_full_args,
414                                       stdin=subprocess.DEVNULL,
415                                       stdout=self._qemu_log_file,
416                                       stderr=subprocess.STDOUT,
417                                       shell=False,
418                                       close_fds=False)
419        self._post_launch()
420
421    def _early_cleanup(self) -> None:
422        """
423        Perform any cleanup that needs to happen before the VM exits.
424
425        May be invoked by both soft and hard shutdown in failover scenarios.
426        Called additionally by _post_shutdown for comprehensive cleanup.
427        """
428        # If we keep the console socket open, we may deadlock waiting
429        # for QEMU to exit, while QEMU is waiting for the socket to
430        # become writeable.
431        if self._console_socket is not None:
432            self._console_socket.close()
433            self._console_socket = None
434
435    def _hard_shutdown(self) -> None:
436        """
437        Perform early cleanup, kill the VM, and wait for it to terminate.
438
439        :raise subprocess.Timeout: When timeout is exceeds 60 seconds
440            waiting for the QEMU process to terminate.
441        """
442        self._early_cleanup()
443        self._subp.kill()
444        self._subp.wait(timeout=60)
445
446    def _soft_shutdown(self, timeout: Optional[int],
447                       has_quit: bool = False) -> None:
448        """
449        Perform early cleanup, attempt to gracefully shut down the VM, and wait
450        for it to terminate.
451
452        :param timeout: Timeout in seconds for graceful shutdown.
453                        A value of None is an infinite wait.
454        :param has_quit: When True, don't attempt to issue 'quit' QMP command
455
456        :raise ConnectionReset: On QMP communication errors
457        :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
458            the QEMU process to terminate.
459        """
460        self._early_cleanup()
461
462        if self._qmp_connection:
463            if not has_quit:
464                # Might raise ConnectionReset
465                self._qmp.cmd('quit')
466
467        # May raise subprocess.TimeoutExpired
468        self._subp.wait(timeout=timeout)
469
470    def _do_shutdown(self, timeout: Optional[int],
471                     has_quit: bool = False) -> None:
472        """
473        Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
474
475        :param timeout: Timeout in seconds for graceful shutdown.
476                        A value of None is an infinite wait.
477        :param has_quit: When True, don't attempt to issue 'quit' QMP command
478
479        :raise AbnormalShutdown: When the VM could not be shut down gracefully.
480            The inner exception will likely be ConnectionReset or
481            subprocess.TimeoutExpired. In rare cases, non-graceful termination
482            may result in its own exceptions, likely subprocess.TimeoutExpired.
483        """
484        try:
485            self._soft_shutdown(timeout, has_quit)
486        except Exception as exc:
487            self._hard_shutdown()
488            raise AbnormalShutdown("Could not perform graceful shutdown") \
489                from exc
490
491    def shutdown(self, has_quit: bool = False,
492                 hard: bool = False,
493                 timeout: Optional[int] = 30) -> None:
494        """
495        Terminate the VM (gracefully if possible) and perform cleanup.
496        Cleanup will always be performed.
497
498        If the VM has not yet been launched, or shutdown(), wait(), or kill()
499        have already been called, this method does nothing.
500
501        :param has_quit: When true, do not attempt to issue 'quit' QMP command.
502        :param hard: When true, do not attempt graceful shutdown, and
503                     suppress the SIGKILL warning log message.
504        :param timeout: Optional timeout in seconds for graceful shutdown.
505                        Default 30 seconds, A `None` value is an infinite wait.
506        """
507        if not self._launched:
508            return
509
510        try:
511            if hard:
512                self._user_killed = True
513                self._hard_shutdown()
514            else:
515                self._do_shutdown(timeout, has_quit)
516        finally:
517            self._post_shutdown()
518
519    def kill(self) -> None:
520        """
521        Terminate the VM forcefully, wait for it to exit, and perform cleanup.
522        """
523        self.shutdown(hard=True)
524
525    def wait(self, timeout: Optional[int] = 30) -> None:
526        """
527        Wait for the VM to power off and perform post-shutdown cleanup.
528
529        :param timeout: Optional timeout in seconds. Default 30 seconds.
530                        A value of `None` is an infinite wait.
531        """
532        self.shutdown(has_quit=True, timeout=timeout)
533
534    def set_qmp_monitor(self, enabled: bool = True) -> None:
535        """
536        Set the QMP monitor.
537
538        @param enabled: if False, qmp monitor options will be removed from
539                        the base arguments of the resulting QEMU command
540                        line. Default is True.
541
542        .. note:: Call this function before launch().
543        """
544        self._qmp_set = enabled
545
546    @property
547    def _qmp(self) -> QEMUMonitorProtocol:
548        if self._qmp_connection is None:
549            raise QEMUMachineError("Attempt to access QMP with no connection")
550        return self._qmp_connection
551
552    @classmethod
553    def _qmp_args(cls, conv_keys: bool,
554                  args: Dict[str, Any]) -> Dict[str, object]:
555        if conv_keys:
556            return {k.replace('_', '-'): v for k, v in args.items()}
557
558        return args
559
560    def qmp(self, cmd: str,
561            args_dict: Optional[Dict[str, object]] = None,
562            conv_keys: Optional[bool] = None,
563            **args: Any) -> QMPMessage:
564        """
565        Invoke a QMP command and return the response dict
566        """
567        if args_dict is not None:
568            assert not args
569            assert conv_keys is None
570            args = args_dict
571            conv_keys = False
572
573        if conv_keys is None:
574            conv_keys = True
575
576        qmp_args = self._qmp_args(conv_keys, args)
577        return self._qmp.cmd(cmd, args=qmp_args)
578
579    def command(self, cmd: str,
580                conv_keys: bool = True,
581                **args: Any) -> QMPReturnValue:
582        """
583        Invoke a QMP command.
584        On success return the response dict.
585        On failure raise an exception.
586        """
587        qmp_args = self._qmp_args(conv_keys, args)
588        return self._qmp.command(cmd, **qmp_args)
589
590    def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
591        """
592        Poll for one queued QMP events and return it
593        """
594        if self._events:
595            return self._events.pop(0)
596        return self._qmp.pull_event(wait=wait)
597
598    def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
599        """
600        Poll for queued QMP events and return a list of dicts
601        """
602        events = self._qmp.get_events(wait=wait)
603        events.extend(self._events)
604        del self._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
774    @property
775    def log_dir(self) -> str:
776        """
777        Returns a directory to be used for writing logs
778        """
779        if self._log_dir is None:
780            return self.temp_dir
781        return self._log_dir
782