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