]> Git Repo - qemu.git/blob - python/qemu/machine/machine.py
d33b02d2ce6c98eb6dbe85c07cd08051eafcf4e1
[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 logging
23 import os
24 import shutil
25 import signal
26 import socket
27 import subprocess
28 import tempfile
29 from types import TracebackType
30 from typing import (
31     Any,
32     BinaryIO,
33     Dict,
34     List,
35     Optional,
36     Sequence,
37     Tuple,
38     Type,
39 )
40
41 from qemu.qmp import (
42     QEMUMonitorProtocol,
43     QMPMessage,
44     QMPReturnValue,
45     SocketAddrT,
46 )
47
48 from . import console_socket
49
50
51 LOG = logging.getLogger(__name__)
52
53
54 class QEMUMachineError(Exception):
55     """
56     Exception called when an error in QEMUMachine happens.
57     """
58
59
60 class 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
70 class AbnormalShutdown(QEMUMachineError):
71     """
72     Exception raised when a graceful shutdown was requested, but not performed.
73     """
74
75
76 class 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
This page took 0.0580039999999999 seconds and 2 git commands to generate.