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