4 The machine module primarily provides the QEMUMachine class,
5 which provides facilities for managing the lifetime of a QEMU VM.
8 # Copyright (C) 2015-2016 Red Hat Inc.
9 # Copyright (C) 2012 IBM Corp.
14 # This work is licensed under the terms of the GNU GPL, version 2. See
15 # the COPYING file in the top-level directory.
21 from itertools import chain
30 from types import TracebackType
43 from qemu.qmp import SocketAddrT
44 from qemu.qmp.legacy import (
50 from . import console_socket
53 LOG = logging.getLogger(__name__)
56 class QEMUMachineError(Exception):
58 Exception called when an error in QEMUMachine happens.
62 class QEMUMachineAddDeviceError(QEMUMachineError):
64 Exception raised when a request to add a device can not be fulfilled
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.
72 class VMLaunchFailure(QEMUMachineError):
74 Exception raised when a VM launch was attempted, but failed.
76 def __init__(self, exitcode: Optional[int],
77 command: str, output: Optional[str]):
78 super().__init__(exitcode, command, output)
79 self.exitcode = exitcode
80 self.command = command
83 def __str__(self) -> str:
85 if self.__cause__ is not None:
86 name = type(self.__cause__).__name__
87 reason = str(self.__cause__)
89 ret += f"{name}: {reason}"
94 if self.exitcode is not None:
95 ret += f"\tExit code: {self.exitcode}\n"
96 ret += f"\tCommand: {self.command}\n"
97 ret += f"\tOutput: {self.output}\n"
101 class AbnormalShutdown(QEMUMachineError):
103 Exception raised when a graceful shutdown was requested, but not performed.
107 _T = TypeVar('_T', bound='QEMUMachine')
114 Use this object as a context manager to ensure
115 the QEMU process terminates::
117 with VM(binary) as vm:
119 # vm is guaranteed to be shut down here
121 # pylint: disable=too-many-instance-attributes, too-many-public-methods
125 args: Sequence[str] = (),
126 wrapper: Sequence[str] = (),
127 name: Optional[str] = None,
128 base_temp_dir: str = "/var/tmp",
129 monitor_address: Optional[SocketAddrT] = None,
130 sock_dir: Optional[str] = None,
131 drain_console: bool = False,
132 console_log: Optional[str] = None,
133 log_dir: Optional[str] = None,
134 qmp_timer: Optional[float] = None):
136 Initialize a QEMUMachine
138 @param binary: path to the qemu binary
139 @param args: list of extra arguments
140 @param wrapper: list of arguments used as prefix to qemu binary
141 @param name: prefix for socket and log file names (default: qemu-PID)
142 @param base_temp_dir: default location where temp files are created
143 @param monitor_address: address for QMP monitor
144 @param sock_dir: where to create socket (defaults to base_temp_dir)
145 @param drain_console: (optional) True to drain console socket to buffer
146 @param console_log: (optional) path to console log file
147 @param log_dir: where to create and keep log files
148 @param qmp_timer: (optional) default QMP socket timeout
149 @note: Qemu process is not started until launch() is used.
151 # pylint: disable=too-many-arguments
153 # Direct user configuration
155 self._binary = binary
156 self._args = list(args)
157 self._wrapper = wrapper
158 self._qmp_timer = qmp_timer
160 self._name = name or f"qemu-{os.getpid()}-{id(self):02x}"
161 self._temp_dir: Optional[str] = None
162 self._base_temp_dir = base_temp_dir
163 self._sock_dir = sock_dir
164 self._log_dir = log_dir
166 if monitor_address is not None:
167 self._monitor_address = monitor_address
169 self._monitor_address = os.path.join(
170 self.sock_dir, f"{self._name}-monitor.sock"
173 self._console_log_path = console_log
174 if self._console_log_path:
175 # In order to log the console, buffering needs to be enabled.
176 self._drain_console = True
178 self._drain_console = drain_console
181 self._qemu_log_path: Optional[str] = None
182 self._qemu_log_file: Optional[BinaryIO] = None
183 self._popen: Optional['subprocess.Popen[bytes]'] = None
184 self._events: List[QMPMessage] = []
185 self._iolog: Optional[str] = None
186 self._qmp_set = True # Enable QMP monitor by default.
187 self._qmp_connection: Optional[QEMUMonitorProtocol] = None
188 self._qemu_full_args: Tuple[str, ...] = ()
189 self._launched = False
190 self._machine: Optional[str] = None
191 self._console_index = 0
192 self._console_set = False
193 self._console_device_type: Optional[str] = None
194 self._console_address = os.path.join(
195 self.sock_dir, f"{self._name}-console.sock"
197 self._console_socket: Optional[socket.socket] = None
198 self._remove_files: List[str] = []
199 self._user_killed = False
200 self._quit_issued = False
202 def __enter__(self: _T) -> _T:
206 exc_type: Optional[Type[BaseException]],
207 exc_val: Optional[BaseException],
208 exc_tb: Optional[TracebackType]) -> None:
211 def add_monitor_null(self) -> None:
213 This can be used to add an unused monitor instance.
215 self._args.append('-monitor')
216 self._args.append('null')
218 def add_fd(self: _T, fd: int, fdset: int,
219 opaque: str, opts: str = '') -> _T:
221 Pass a file descriptor to the VM
223 options = ['fd=%d' % fd,
225 'opaque=%s' % opaque]
229 # This did not exist before 3.4, but since then it is
230 # mandatory for our purpose
231 if hasattr(os, 'set_inheritable'):
232 os.set_inheritable(fd, True)
234 self._args.append('-add-fd')
235 self._args.append(','.join(options))
238 def send_fd_scm(self, fd: Optional[int] = None,
239 file_path: Optional[str] = None) -> int:
241 Send an fd or file_path to the remote via SCM_RIGHTS.
243 Exactly one of fd and file_path must be given. If it is
244 file_path, the file will be opened read-only and the new file
245 descriptor will be sent to the remote.
247 if file_path is not None:
249 with open(file_path, "rb") as passfile:
250 fd = passfile.fileno()
251 self._qmp.send_fd_scm(fd)
253 assert fd is not None
254 self._qmp.send_fd_scm(fd)
259 def _remove_if_exists(path: str) -> None:
261 Remove file object at path if it exists
265 except OSError as exception:
266 if exception.errno == errno.ENOENT:
270 def is_running(self) -> bool:
271 """Returns true if the VM is running."""
272 return self._popen is not None and self._popen.poll() is None
275 def _subp(self) -> 'subprocess.Popen[bytes]':
276 if self._popen is None:
277 raise QEMUMachineError('Subprocess pipe not present')
280 def exitcode(self) -> Optional[int]:
281 """Returns the exit code if possible, or None."""
282 if self._popen is None:
284 return self._popen.poll()
286 def get_pid(self) -> Optional[int]:
287 """Returns the PID of the running process, or None."""
288 if not self.is_running():
290 return self._subp.pid
292 def _load_io_log(self) -> None:
293 # Assume that the output encoding of QEMU's terminal output is
294 # defined by our locale. If indeterminate, allow open() to fall
295 # back to the platform default.
296 _, encoding = locale.getlocale()
297 if self._qemu_log_path is not None:
298 with open(self._qemu_log_path, "r", encoding=encoding) as iolog:
299 self._iolog = iolog.read()
302 def _base_args(self) -> List[str]:
303 args = ['-display', 'none', '-vga', 'none']
306 if isinstance(self._monitor_address, tuple):
307 moncdev = "socket,id=mon,host={},port={}".format(
308 *self._monitor_address
311 moncdev = f"socket,id=mon,path={self._monitor_address}"
312 args.extend(['-chardev', moncdev, '-mon',
313 'chardev=mon,mode=control'])
315 if self._machine is not None:
316 args.extend(['-machine', self._machine])
317 for _ in range(self._console_index):
318 args.extend(['-serial', 'null'])
319 if self._console_set:
320 chardev = ('socket,id=console,path=%s,server=on,wait=off' %
321 self._console_address)
322 args.extend(['-chardev', chardev])
323 if self._console_device_type is None:
324 args.extend(['-serial', 'chardev:console'])
326 device = '%s,chardev=console' % self._console_device_type
327 args.extend(['-device', device])
331 def args(self) -> List[str]:
332 """Returns the list of arguments given to the QEMU binary."""
335 def _pre_launch(self) -> None:
336 if self._console_set:
337 self._remove_files.append(self._console_address)
340 if isinstance(self._monitor_address, str):
341 self._remove_files.append(self._monitor_address)
342 self._qmp_connection = QEMUMonitorProtocol(
343 self._monitor_address,
348 # NOTE: Make sure any opened resources are *definitely* freed in
350 # pylint: disable=consider-using-with
351 self._qemu_log_path = os.path.join(self.log_dir, self._name + ".log")
352 self._qemu_log_file = open(self._qemu_log_path, 'wb')
355 self._qemu_full_args = tuple(chain(
362 def _post_launch(self) -> None:
363 if self._qmp_connection:
364 self._qmp.accept(self._qmp_timer)
366 def _close_qemu_log_file(self) -> None:
367 if self._qemu_log_file is not None:
368 self._qemu_log_file.close()
369 self._qemu_log_file = None
371 def _post_shutdown(self) -> None:
373 Called to cleanup the VM instance after the process has exited.
374 May also be called after a failed launch.
376 LOG.debug("Cleaning up after VM process")
378 self._close_qmp_connection()
379 except Exception as err: # pylint: disable=broad-except
381 "Exception closing QMP connection: %s",
382 str(err) if str(err) else type(err).__name__
385 assert self._qmp_connection is None
387 self._close_qemu_log_file()
391 self._qemu_log_path = None
393 if self._temp_dir is not None:
394 shutil.rmtree(self._temp_dir)
395 self._temp_dir = None
397 while len(self._remove_files) > 0:
398 self._remove_if_exists(self._remove_files.pop())
400 exitcode = self.exitcode()
401 if (exitcode is not None and exitcode < 0
402 and not (self._user_killed and exitcode == -signal.SIGKILL)):
403 msg = 'qemu received signal %i; command: "%s"'
404 if self._qemu_full_args:
405 command = ' '.join(self._qemu_full_args)
408 LOG.warning(msg, -int(exitcode), command)
410 self._quit_issued = False
411 self._user_killed = False
412 self._launched = False
414 def launch(self) -> None:
416 Launch the VM and make sure we cleanup and expose the
417 command line/output in case of exception
421 raise QEMUMachineError('VM already launched')
425 except BaseException as exc:
426 # We may have launched the process but it may
427 # have exited before we could connect via QMP.
428 # Assume the VM didn't launch or is exiting.
429 # If we don't wait for the process, exitcode() may still be
430 # 'None' by the time control is ceded back to the caller.
434 self._post_shutdown()
436 if isinstance(exc, Exception):
437 raise VMLaunchFailure(
438 exitcode=self.exitcode(),
439 command=' '.join(self._qemu_full_args),
443 # Don't wrap 'BaseException'; doing so would downgrade
444 # that exception. However, we still want to clean up.
447 def _launch(self) -> None:
449 Launch the VM and establish a QMP connection
452 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
454 # Cleaning up of this subprocess is guaranteed by _do_shutdown.
455 # pylint: disable=consider-using-with
456 self._popen = subprocess.Popen(self._qemu_full_args,
457 stdin=subprocess.DEVNULL,
458 stdout=self._qemu_log_file,
459 stderr=subprocess.STDOUT,
462 self._launched = True
465 def _close_qmp_connection(self) -> None:
467 Close the underlying QMP connection, if any.
469 Dutifully report errors that occurred while closing, but assume
470 that any error encountered indicates an abnormal termination
471 process and not a failure to close.
473 if self._qmp_connection is None:
479 # EOF can occur as an Exception here when using the Async
480 # QMP backend. It indicates that the server closed the
481 # stream. If we successfully issued 'quit' at any point,
482 # then this was expected. If the remote went away without
483 # our permission, it's worth reporting that as an abnormal
485 if not (self._user_killed or self._quit_issued):
488 self._qmp_connection = None
490 def _early_cleanup(self) -> None:
492 Perform any cleanup that needs to happen before the VM exits.
494 This method may be called twice upon shutdown, once each by soft
495 and hard shutdown in failover scenarios.
497 # If we keep the console socket open, we may deadlock waiting
498 # for QEMU to exit, while QEMU is waiting for the socket to
500 if self._console_socket is not None:
501 LOG.debug("Closing console socket")
502 self._console_socket.close()
503 self._console_socket = None
505 def _hard_shutdown(self) -> None:
507 Perform early cleanup, kill the VM, and wait for it to terminate.
509 :raise subprocess.Timeout: When timeout is exceeds 60 seconds
510 waiting for the QEMU process to terminate.
512 LOG.debug("Performing hard shutdown")
513 self._early_cleanup()
515 self._subp.wait(timeout=60)
517 def _soft_shutdown(self, timeout: Optional[int]) -> None:
519 Perform early cleanup, attempt to gracefully shut down the VM, and wait
522 :param timeout: Timeout in seconds for graceful shutdown.
523 A value of None is an infinite wait.
525 :raise ConnectionReset: On QMP communication errors
526 :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
527 the QEMU process to terminate.
529 LOG.debug("Attempting graceful termination")
531 self._early_cleanup()
533 if self._quit_issued:
535 "Anticipating QEMU termination due to prior 'quit' command, "
536 "or explicit call to wait()"
539 LOG.debug("Politely asking QEMU to terminate")
541 if self._qmp_connection:
543 if not self._quit_issued:
544 # May raise ExecInterruptedError or StateError if the
545 # connection dies or has *already* died.
548 # Regardless, we want to quiesce the connection.
549 self._close_qmp_connection()
550 elif not self._quit_issued:
552 "Not anticipating QEMU quit and no QMP connection present, "
555 self._subp.terminate()
557 # May raise subprocess.TimeoutExpired
559 "Waiting (timeout=%s) for QEMU process (pid=%s) to terminate",
560 timeout, self._subp.pid
562 self._subp.wait(timeout=timeout)
564 def _do_shutdown(self, timeout: Optional[int]) -> None:
566 Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
568 :param timeout: Timeout in seconds for graceful shutdown.
569 A value of None is an infinite wait.
571 :raise AbnormalShutdown: When the VM could not be shut down gracefully.
572 The inner exception will likely be ConnectionReset or
573 subprocess.TimeoutExpired. In rare cases, non-graceful termination
574 may result in its own exceptions, likely subprocess.TimeoutExpired.
577 self._soft_shutdown(timeout)
578 except Exception as exc:
579 if isinstance(exc, subprocess.TimeoutExpired):
580 LOG.debug("Timed out waiting for QEMU process to exit")
581 LOG.debug("Graceful shutdown failed", exc_info=True)
582 LOG.debug("Falling back to hard shutdown")
583 self._hard_shutdown()
584 raise AbnormalShutdown("Could not perform graceful shutdown") \
589 timeout: Optional[int] = 30) -> None:
591 Terminate the VM (gracefully if possible) and perform cleanup.
592 Cleanup will always be performed.
594 If the VM has not yet been launched, or shutdown(), wait(), or kill()
595 have already been called, this method does nothing.
597 :param hard: When true, do not attempt graceful shutdown, and
598 suppress the SIGKILL warning log message.
599 :param timeout: Optional timeout in seconds for graceful shutdown.
600 Default 30 seconds, A `None` value is an infinite wait.
602 if not self._launched:
605 LOG.debug("Shutting down VM appliance; timeout=%s", timeout)
607 LOG.debug("Caller requests immediate termination of QEMU process.")
611 self._user_killed = True
612 self._hard_shutdown()
614 self._do_shutdown(timeout)
616 self._post_shutdown()
618 def kill(self) -> None:
620 Terminate the VM forcefully, wait for it to exit, and perform cleanup.
622 self.shutdown(hard=True)
624 def wait(self, timeout: Optional[int] = 30) -> None:
626 Wait for the VM to power off and perform post-shutdown cleanup.
628 :param timeout: Optional timeout in seconds. Default 30 seconds.
629 A value of `None` is an infinite wait.
631 self._quit_issued = True
632 self.shutdown(timeout=timeout)
634 def set_qmp_monitor(self, enabled: bool = True) -> None:
638 @param enabled: if False, qmp monitor options will be removed from
639 the base arguments of the resulting QEMU command
640 line. Default is True.
642 .. note:: Call this function before launch().
644 self._qmp_set = enabled
647 def _qmp(self) -> QEMUMonitorProtocol:
648 if self._qmp_connection is None:
649 raise QEMUMachineError("Attempt to access QMP with no connection")
650 return self._qmp_connection
653 def _qmp_args(cls, conv_keys: bool,
654 args: Dict[str, Any]) -> Dict[str, object]:
656 return {k.replace('_', '-'): v for k, v in args.items()}
660 def qmp(self, cmd: str,
661 args_dict: Optional[Dict[str, object]] = None,
662 conv_keys: Optional[bool] = None,
663 **args: Any) -> QMPMessage:
665 Invoke a QMP command and return the response dict
667 if args_dict is not None:
669 assert conv_keys is None
673 if conv_keys is None:
676 qmp_args = self._qmp_args(conv_keys, args)
677 ret = self._qmp.cmd(cmd, args=qmp_args)
678 if cmd == 'quit' and 'error' not in ret and 'return' in ret:
679 self._quit_issued = True
682 def command(self, cmd: str,
683 conv_keys: bool = True,
684 **args: Any) -> QMPReturnValue:
686 Invoke a QMP command.
687 On success return the response dict.
688 On failure raise an exception.
690 qmp_args = self._qmp_args(conv_keys, args)
691 ret = self._qmp.command(cmd, **qmp_args)
693 self._quit_issued = True
696 def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
698 Poll for one queued QMP events and return it
701 return self._events.pop(0)
702 return self._qmp.pull_event(wait=wait)
704 def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
706 Poll for queued QMP events and return a list of dicts
708 events = self._qmp.get_events(wait=wait)
709 events.extend(self._events)
714 def event_match(event: Any, match: Optional[Any]) -> bool:
716 Check if an event matches optional match criteria.
718 The match criteria takes the form of a matching subdict. The event is
719 checked to be a superset of the subdict, recursively, with matching
720 values whenever the subdict values are not None.
722 This has a limitation that you cannot explicitly check for None values.
724 Examples, with the subdict queries on the left:
725 - None matches any object.
726 - {"foo": None} matches {"foo": {"bar": 1}}
727 - {"foo": None} matches {"foo": 5}
728 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
729 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
737 if not QEMUMachine.event_match(event[key], match[key]):
743 # either match or event wasn't iterable (not a dict)
744 return bool(match == event)
746 def event_wait(self, name: str,
747 timeout: float = 60.0,
748 match: Optional[QMPMessage] = None) -> Optional[QMPMessage]:
750 event_wait waits for and returns a named event from QMP with a timeout.
752 name: The event to wait for.
753 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
754 match: Optional match criteria. See event_match for details.
756 return self.events_wait([(name, match)], timeout)
758 def events_wait(self,
759 events: Sequence[Tuple[str, Any]],
760 timeout: float = 60.0) -> Optional[QMPMessage]:
762 events_wait waits for and returns a single named event from QMP.
763 In the case of multiple qualifying events, this function returns the
766 :param events: A sequence of (name, match_criteria) tuples.
767 The match criteria are optional and may be None.
768 See event_match for details.
769 :param timeout: Optional timeout, in seconds.
770 See QEMUMonitorProtocol.pull_event.
772 :raise asyncio.TimeoutError:
773 If timeout was non-zero and no matching events were found.
775 :return: A QMP event matching the filter criteria.
776 If timeout was 0 and no event matched, None.
778 def _match(event: QMPMessage) -> bool:
779 for name, match in events:
780 if event['event'] == name and self.event_match(event, match):
784 event: Optional[QMPMessage]
786 # Search cached events
787 for event in self._events:
789 self._events.remove(event)
792 # Poll for new events
794 event = self._qmp.pull_event(wait=timeout)
796 # NB: None is only returned when timeout is false-ish.
797 # Timeouts raise asyncio.TimeoutError instead!
801 self._events.append(event)
805 def get_log(self) -> Optional[str]:
807 After self.shutdown or failed qemu execution, this returns the output
812 def add_args(self, *args: str) -> None:
814 Adds to the list of extra arguments to be given to the QEMU binary
816 self._args.extend(args)
818 def set_machine(self, machine_type: str) -> None:
820 Sets the machine type
822 If set, the machine type will be added to the base arguments
823 of the resulting QEMU command line.
825 self._machine = machine_type
827 def set_console(self,
828 device_type: Optional[str] = None,
829 console_index: int = 0) -> None:
831 Sets the device type for a console device
833 If set, the console device and a backing character device will
834 be added to the base arguments of the resulting QEMU command
837 This is a convenience method that will either use the provided
838 device type, or default to a "-serial chardev:console" command
841 The actual setting of command line arguments will be be done at
842 machine launch time, as it depends on the temporary directory
845 @param device_type: the device type, such as "isa-serial". If
846 None is given (the default value) a "-serial
847 chardev:console" command line argument will
848 be used instead, resorting to the machine's
850 @param console_index: the index of the console device to use.
851 If not zero, the command line will create
852 'index - 1' consoles and connect them to
853 the 'null' backing character device.
855 self._console_set = True
856 self._console_device_type = device_type
857 self._console_index = console_index
860 def console_socket(self) -> socket.socket:
862 Returns a socket connected to the console
864 if self._console_socket is None:
865 self._console_socket = console_socket.ConsoleSocket(
866 self._console_address,
867 file=self._console_log_path,
868 drain=self._drain_console)
869 return self._console_socket
872 def temp_dir(self) -> str:
874 Returns a temporary directory to be used for this machine
876 if self._temp_dir is None:
877 self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-",
878 dir=self._base_temp_dir)
879 return self._temp_dir
882 def sock_dir(self) -> str:
884 Returns the directory used for sockfiles by this machine.
887 return self._sock_dir
891 def log_dir(self) -> str:
893 Returns a directory to be used for writing logs
895 if self._log_dir is None: