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