master
py 976 lines 33 KB
Raw
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 <famz@redhat.com>
13 #
14 # This work is licensed under the terms of the GNU GPL, version 2. See
15 # the COPYING file in the top-level directory.
16 #
17 # Based on qmp.py.
18 #
19
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 SocketAddrT
44 from qemu.qmp.legacy import (
45 QEMUMonitorProtocol,
46 QMPMessage,
47 QMPReturnValue,
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 VMLaunchFailure(QEMUMachineError):
73 """
74 Exception raised when a VM launch was attempted, but failed.
75 """
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
81 self.output = output
82
83 def __str__(self) -> str:
84 ret = ''
85 if self.__cause__ is not None:
86 name = type(self.__cause__).__name__
87 reason = str(self.__cause__)
88 if reason:
89 ret += f"{name}: {reason}"
90 else:
91 ret += f"{name}"
92 ret += '\n'
93
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"
98 return ret
99
100
101 class AbnormalShutdown(QEMUMachineError):
102 """
103 Exception raised when a graceful shutdown was requested, but not performed.
104 """
105
106
107 _T = TypeVar('_T', bound='QEMUMachine')
108
109
110 class QEMUMachine:
111 """
112 A QEMU VM.
113
114 Use this object as a context manager to ensure
115 the QEMU process terminates::
116
117 with VM(binary) as vm:
118 ...
119 # vm is guaranteed to be shut down here
120 """
121 # pylint: disable=too-many-instance-attributes, too-many-public-methods
122
123 def __init__(self,
124 binary: str,
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 drain_console: bool = False,
131 console_log: Optional[str] = None,
132 log_dir: Optional[str] = None,
133 qmp_timer: Optional[float] = 30):
134 '''
135 Initialize a QEMUMachine
136
137 @param binary: path to the qemu binary
138 @param args: list of extra arguments
139 @param wrapper: list of arguments used as prefix to qemu binary
140 @param name: prefix for socket and log file names (default: qemu-PID)
141 @param base_temp_dir: default location where temp files are created
142 @param monitor_address: address for QMP monitor
143 @param drain_console: (optional) True to drain console socket to buffer
144 @param console_log: (optional) path to console log file
145 @param log_dir: where to create and keep log files
146 @param qmp_timer: (optional) default QMP socket timeout
147 @note: Qemu process is not started until launch() is used.
148 '''
149 # pylint: disable=too-many-arguments
150
151 # Direct user configuration
152
153 self._binary = binary
154 self._args = list(args)
155 self._wrapper = wrapper
156 self._qmp_timer = qmp_timer
157
158 self._name = name or f"{id(self):x}"
159 self._sock_pair: Optional[Tuple[socket.socket, socket.socket]] = None
160 self._cons_sock_pair: Optional[
161 Tuple[socket.socket, socket.socket]] = None
162 self._temp_dir: Optional[str] = None
163 self._base_temp_dir = base_temp_dir
164 self._log_dir = log_dir
165
166 self._monitor_address = monitor_address
167
168 self._console_log_path = console_log
169 if self._console_log_path:
170 # In order to log the console, buffering needs to be enabled.
171 self._drain_console = True
172 else:
173 self._drain_console = drain_console
174
175 # Runstate
176 self._qemu_log_path: Optional[str] = None
177 self._qemu_log_file: Optional[BinaryIO] = None
178 self._popen: Optional['subprocess.Popen[bytes]'] = None
179 self._events: List[QMPMessage] = []
180 self._iolog: Optional[str] = None
181 self._qmp_set = True # Enable QMP monitor by default.
182 self._qmp_connection: Optional[QEMUMonitorProtocol] = None
183 self._qemu_full_args: Tuple[str, ...] = ()
184 self._launched = False
185 self._machine: Optional[str] = None
186 self._console_index = 0
187 self._console_set = False
188 self._console_device_type: Optional[str] = None
189 self._console_socket: Optional[socket.socket] = None
190 self._console_file: Optional[socket.SocketIO] = None
191 self._remove_files: List[str] = []
192 self._user_killed = False
193 self._quit_issued = False
194
195 def __enter__(self: _T) -> _T:
196 return self
197
198 def __exit__(self,
199 exc_type: Optional[Type[BaseException]],
200 exc_val: Optional[BaseException],
201 exc_tb: Optional[TracebackType]) -> None:
202 self.shutdown()
203
204 def add_monitor_null(self) -> None:
205 """
206 This can be used to add an unused monitor instance.
207 """
208 self._args.append('-monitor')
209 self._args.append('null')
210
211 def add_fd(self: _T, fd: int, fdset: int,
212 opaque: str, opts: str = '') -> _T:
213 """
214 Pass a file descriptor to the VM
215 """
216 options = ['fd=%d' % fd,
217 'set=%d' % fdset,
218 'opaque=%s' % opaque]
219 if opts:
220 options.append(opts)
221
222 # This did not exist before 3.4, but since then it is
223 # mandatory for our purpose
224 if hasattr(os, 'set_inheritable'):
225 os.set_inheritable(fd, True)
226
227 self._args.append('-add-fd')
228 self._args.append(','.join(options))
229 return self
230
231 def send_fd_scm(self, fd: Optional[int] = None,
232 file_path: Optional[str] = None) -> int:
233 """
234 Send an fd or file_path to the remote via SCM_RIGHTS.
235
236 Exactly one of fd and file_path must be given. If it is
237 file_path, the file will be opened read-only and the new file
238 descriptor will be sent to the remote.
239 """
240 if file_path is not None:
241 assert fd is None
242 with open(file_path, "rb") as passfile:
243 fd = passfile.fileno()
244 self._qmp.send_fd_scm(fd)
245 else:
246 assert fd is not None
247 self._qmp.send_fd_scm(fd)
248
249 return 0
250
251 @staticmethod
252 def _remove_if_exists(path: str) -> None:
253 """
254 Remove file object at path if it exists
255 """
256 try:
257 os.remove(path)
258 except OSError as exception:
259 if exception.errno == errno.ENOENT:
260 return
261 raise
262
263 def is_running(self) -> bool:
264 """Returns true if the VM is running."""
265 return self._popen is not None and self._popen.poll() is None
266
267 @property
268 def _subp(self) -> 'subprocess.Popen[bytes]':
269 if self._popen is None:
270 raise QEMUMachineError('Subprocess pipe not present')
271 return self._popen
272
273 def exitcode(self) -> Optional[int]:
274 """Returns the exit code if possible, or None."""
275 if self._popen is None:
276 return None
277 return self._popen.poll()
278
279 def get_pid(self) -> Optional[int]:
280 """Returns the PID of the running process, or None."""
281 if not self.is_running():
282 return None
283 return self._subp.pid
284
285 def _load_io_log(self) -> None:
286 # Assume that the output encoding of QEMU's terminal output is
287 # defined by our locale. If indeterminate, allow open() to fall
288 # back to the platform default.
289 _, encoding = locale.getlocale()
290 if self._qemu_log_path is not None:
291 with open(self._qemu_log_path, "r", encoding=encoding) as iolog:
292 self._iolog = iolog.read()
293
294 @property
295 def _harness_args(self) -> List[str]:
296 args: List[str] = []
297
298 if self._qmp_set:
299 if self._sock_pair:
300 moncdev = f"socket,id=mon,fd={self._sock_pair[0].fileno()}"
301 elif isinstance(self._monitor_address, tuple):
302 moncdev = "socket,id=mon,host={},port={}".format(
303 *self._monitor_address
304 )
305 else:
306 moncdev = f"socket,id=mon,path={self._monitor_address}"
307 args.extend(['-chardev', moncdev, '-object',
308 'monitor-qmp,id=qmp,chardev=mon'])
309 return args
310
311 def _console_args(self, interactive: bool = False) -> List[str]:
312 args: List[str] = []
313 # redirect pre_console_index serials to null
314 for _ in range(self._console_index):
315 args.extend(['-serial', 'null'])
316
317 if interactive:
318 args.extend(['-serial', 'mon:stdio'])
319 elif self._console_set:
320 assert self._cons_sock_pair is not None
321 fd = self._cons_sock_pair[0].fileno()
322 chardev = f"socket,id=console,fd={fd}"
323 args.extend(['-chardev', chardev])
324 if self._console_device_type is None:
325 args.extend(['-serial', 'chardev:console'])
326 else:
327 device = '%s,chardev=console' % self._console_device_type
328 args.extend(['-device', device])
329 return args
330
331 @property
332 def _base_args(self) -> List[str]:
333 args: List[str] = ['-display', 'none', '-vga', 'none']
334 if self._machine is not None:
335 args.extend(['-machine', self._machine])
336 return args
337
338 @property
339 def args(self) -> List[str]:
340 """Returns the list of arguments given to the QEMU binary."""
341 return self._args
342
343 @property
344 def binary(self) -> str:
345 """Returns path to the QEMU binary"""
346 return self._binary
347
348 def _pre_launch(self) -> None:
349 if self._qmp_set:
350 sock = None
351 if self._monitor_address is None:
352 self._sock_pair = socket.socketpair()
353 os.set_inheritable(self._sock_pair[0].fileno(), True)
354 sock = self._sock_pair[1]
355 if isinstance(self._monitor_address, str):
356 self._remove_files.append(self._monitor_address)
357
358 sock_or_addr = self._monitor_address or sock
359 assert sock_or_addr is not None
360
361 self._qmp_connection = QEMUMonitorProtocol(
362 sock_or_addr,
363 server=bool(self._monitor_address),
364 nickname=self._name
365 )
366
367 if self._console_set:
368 self._cons_sock_pair = socket.socketpair()
369 os.set_inheritable(self._cons_sock_pair[0].fileno(), True)
370
371 # NOTE: Make sure any opened resources are *definitely* freed in
372 # _post_shutdown()!
373 # pylint: disable=consider-using-with
374 self._qemu_log_path = os.path.join(self.log_dir, self._name + ".log")
375 self._qemu_log_file = open(self._qemu_log_path, 'wb')
376
377 self._iolog = None
378 self._qemu_full_args = tuple(chain(
379 self._wrapper,
380 [self._binary],
381 self._harness_args,
382 self._console_args(),
383 self._base_args,
384 self._args
385 ))
386
387 def _post_launch(self) -> None:
388 if self._sock_pair:
389 self._sock_pair[0].close()
390 if self._cons_sock_pair:
391 self._cons_sock_pair[0].close()
392
393 if self._qmp_connection:
394 if self._sock_pair:
395 self._qmp.connect()
396 else:
397 self._qmp.accept(self._qmp_timer)
398
399 def _close_qemu_log_file(self) -> None:
400 if self._qemu_log_file is not None:
401 self._qemu_log_file.close()
402 self._qemu_log_file = None
403
404 def _post_shutdown(self) -> None:
405 """
406 Called to cleanup the VM instance after the process has exited.
407 May also be called after a failed launch.
408 """
409 LOG.debug("Cleaning up after VM process")
410 try:
411 self._close_qmp_connection()
412 except Exception as err: # pylint: disable=broad-except
413 LOG.warning(
414 "Exception closing QMP connection: %s",
415 str(err) if str(err) else type(err).__name__
416 )
417 finally:
418 assert self._qmp_connection is None
419
420 if self._sock_pair:
421 self._sock_pair[0].close()
422 self._sock_pair[1].close()
423 self._sock_pair = None
424
425 self._close_qemu_log_file()
426
427 self._load_io_log()
428
429 self._qemu_log_path = None
430
431 if self._temp_dir is not None:
432 shutil.rmtree(self._temp_dir)
433 self._temp_dir = None
434
435 while len(self._remove_files) > 0:
436 self._remove_if_exists(self._remove_files.pop())
437
438 exitcode = self.exitcode()
439 if (exitcode is not None and exitcode < 0
440 and not (self._user_killed and exitcode == -signal.SIGKILL)):
441 msg = 'qemu received signal %i; command: "%s"'
442 if self._qemu_full_args:
443 command = ' '.join(self._qemu_full_args)
444 else:
445 command = ''
446 LOG.warning(msg, -int(exitcode), command)
447
448 self._quit_issued = False
449 self._user_killed = False
450 self._launched = False
451
452 def launch(self) -> None:
453 """
454 Launch the VM and make sure we cleanup and expose the
455 command line/output in case of exception
456 """
457
458 if self._launched:
459 raise QEMUMachineError('VM already launched')
460
461 try:
462 self._launch()
463 except BaseException as exc:
464 # We may have launched the process but it may
465 # have exited before we could connect via QMP.
466 # Assume the VM didn't launch or is exiting.
467 # If we don't wait for the process, exitcode() may still be
468 # 'None' by the time control is ceded back to the caller.
469 if self._launched:
470 self.wait()
471 else:
472 self._post_shutdown()
473
474 if isinstance(exc, Exception):
475 raise VMLaunchFailure(
476 exitcode=self.exitcode(),
477 command=' '.join(self._qemu_full_args),
478 output=self._iolog
479 ) from exc
480
481 # Don't wrap 'BaseException'; doing so would downgrade
482 # that exception. However, we still want to clean up.
483 raise
484
485 def _launch(self) -> None:
486 """
487 Launch the VM and establish a QMP connection
488 """
489 self._pre_launch()
490 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
491 # Log a simplified, developer-runnable command:
492 # Exclude harness-managed infrastructure args (harness_args)
493 # and wrapper.
494 debug_cmd = [self._binary]
495 debug_cmd.extend(self._console_args(interactive=True))
496 debug_cmd.extend(self._base_args)
497 debug_cmd.extend(self._args)
498 LOG.debug('Developer-runnable command: %r', ' '.join(debug_cmd))
499
500 # Cleaning up of this subprocess is guaranteed by _do_shutdown.
501 # pylint: disable=consider-using-with
502 self._popen = subprocess.Popen(self._qemu_full_args,
503 stdin=subprocess.DEVNULL,
504 stdout=self._qemu_log_file,
505 stderr=subprocess.STDOUT,
506 shell=False,
507 close_fds=False)
508 self._launched = True
509 self._post_launch()
510
511 def _close_qmp_connection(self) -> None:
512 """
513 Close the underlying QMP connection, if any.
514
515 Dutifully report errors that occurred while closing, but assume
516 that any error encountered indicates an abnormal termination
517 process and not a failure to close.
518 """
519 if self._qmp_connection is None:
520 return
521
522 try:
523 self._qmp.close()
524 except EOFError:
525 # EOF can occur as an Exception here when using the Async
526 # QMP backend. It indicates that the server closed the
527 # stream. If we successfully issued 'quit' at any point,
528 # then this was expected. If the remote went away without
529 # our permission, it's worth reporting that as an abnormal
530 # shutdown case.
531 if not (self._user_killed or self._quit_issued):
532 raise
533 finally:
534 self._qmp_connection = None
535
536 def _early_cleanup(self) -> None:
537 """
538 Perform any cleanup that needs to happen before the VM exits.
539
540 This method may be called twice upon shutdown, once each by soft
541 and hard shutdown in failover scenarios.
542 """
543 # If we keep the console socket open, we may deadlock waiting
544 # for QEMU to exit, while QEMU is waiting for the socket to
545 # become writable.
546 if self._console_file is not None:
547 LOG.debug("Closing console file")
548 self._console_file.close()
549 self._console_file = None
550
551 if self._console_socket is not None:
552 LOG.debug("Closing console socket")
553 self._console_socket.close()
554 self._console_socket = None
555
556 if self._cons_sock_pair:
557 self._cons_sock_pair[0].close()
558 self._cons_sock_pair[1].close()
559 self._cons_sock_pair = None
560
561 def _hard_shutdown(self) -> None:
562 """
563 Perform early cleanup, kill the VM, and wait for it to terminate.
564
565 :raise subprocess.Timeout: When timeout is exceeds 60 seconds
566 waiting for the QEMU process to terminate.
567 """
568 LOG.debug("Performing hard shutdown")
569 self._early_cleanup()
570 self._subp.kill()
571 self._subp.wait(timeout=60)
572
573 def _soft_shutdown(self, timeout: Optional[int]) -> None:
574 """
575 Perform early cleanup, attempt to gracefully shut down the VM, and wait
576 for it to terminate.
577
578 :param timeout: Timeout in seconds for graceful shutdown.
579 A value of None is an infinite wait.
580
581 :raise ConnectionReset: On QMP communication errors
582 :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
583 the QEMU process to terminate.
584 """
585 LOG.debug("Attempting graceful termination")
586
587 self._early_cleanup()
588
589 if self._quit_issued:
590 LOG.debug(
591 "Anticipating QEMU termination due to prior 'quit' command, "
592 "or explicit call to wait()"
593 )
594 else:
595 LOG.debug("Politely asking QEMU to terminate")
596
597 if self._qmp_connection:
598 try:
599 if not self._quit_issued:
600 # May raise ExecInterruptedError or StateError if the
601 # connection dies or has *already* died.
602 self.qmp('quit')
603 finally:
604 # Regardless, we want to quiesce the connection.
605 self._close_qmp_connection()
606 elif not self._quit_issued:
607 LOG.debug(
608 "Not anticipating QEMU quit and no QMP connection present, "
609 "issuing SIGTERM"
610 )
611 self._subp.terminate()
612
613 # May raise subprocess.TimeoutExpired
614 LOG.debug(
615 "Waiting (timeout=%s) for QEMU process (pid=%s) to terminate",
616 timeout, self._subp.pid
617 )
618 self._subp.wait(timeout=timeout)
619
620 def _do_shutdown(self, timeout: Optional[int]) -> None:
621 """
622 Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
623
624 :param timeout: Timeout in seconds for graceful shutdown.
625 A value of None is an infinite wait.
626
627 :raise AbnormalShutdown: When the VM could not be shut down gracefully.
628 The inner exception will likely be ConnectionReset or
629 subprocess.TimeoutExpired. In rare cases, non-graceful termination
630 may result in its own exceptions, likely subprocess.TimeoutExpired.
631 """
632 try:
633 self._soft_shutdown(timeout)
634 except Exception as exc:
635 if isinstance(exc, subprocess.TimeoutExpired):
636 LOG.debug("Timed out waiting for QEMU process to exit")
637 LOG.debug("Graceful shutdown failed", exc_info=True)
638 LOG.debug("Falling back to hard shutdown")
639 self._hard_shutdown()
640 raise AbnormalShutdown("Could not perform graceful shutdown") \
641 from exc
642
643 def shutdown(self,
644 hard: bool = False,
645 timeout: Optional[int] = 30) -> None:
646 """
647 Terminate the VM (gracefully if possible) and perform cleanup.
648 Cleanup will always be performed.
649
650 If the VM has not yet been launched, or shutdown(), wait(), or kill()
651 have already been called, this method does nothing.
652
653 :param hard: When true, do not attempt graceful shutdown, and
654 suppress the SIGKILL warning log message.
655 :param timeout: Optional timeout in seconds for graceful shutdown.
656 Default 30 seconds, A `None` value is an infinite wait.
657 """
658 if not self._launched:
659 return
660
661 LOG.debug("Shutting down VM appliance; timeout=%s", timeout)
662 if hard:
663 LOG.debug("Caller requests immediate termination of QEMU process.")
664
665 try:
666 if hard:
667 self._user_killed = True
668 self._hard_shutdown()
669 else:
670 self._do_shutdown(timeout)
671 finally:
672 self._post_shutdown()
673
674 def kill(self) -> None:
675 """
676 Terminate the VM forcefully, wait for it to exit, and perform cleanup.
677 """
678 self.shutdown(hard=True)
679
680 def wait(self, timeout: Optional[int] = 30) -> None:
681 """
682 Wait for the VM to power off and perform post-shutdown cleanup.
683
684 :param timeout: Optional timeout in seconds. Default 30 seconds.
685 A value of `None` is an infinite wait.
686 """
687 self._quit_issued = True
688 self.shutdown(timeout=timeout)
689
690 def set_qmp_monitor(self, enabled: bool = True) -> None:
691 """
692 Set the QMP monitor.
693
694 @param enabled: if False, qmp monitor options will be removed from
695 the base arguments of the resulting QEMU command
696 line. Default is True.
697
698 .. note:: Call this function before launch().
699 """
700 self._qmp_set = enabled
701
702 @property
703 def _qmp(self) -> QEMUMonitorProtocol:
704 if self._qmp_connection is None:
705 raise QEMUMachineError("Attempt to access QMP with no connection")
706 return self._qmp_connection
707
708 @classmethod
709 def _qmp_args(cls, conv_keys: bool,
710 args: Dict[str, Any]) -> Dict[str, object]:
711 if conv_keys:
712 return {k.replace('_', '-'): v for k, v in args.items()}
713
714 return args
715
716 def qmp(self, cmd: str,
717 args_dict: Optional[Dict[str, object]] = None,
718 conv_keys: Optional[bool] = None,
719 **args: Any) -> QMPMessage:
720 """
721 Invoke a QMP command and return the response dict
722 """
723 if args_dict is not None:
724 assert not args
725 assert conv_keys is None
726 args = args_dict
727 conv_keys = False
728
729 if conv_keys is None:
730 conv_keys = True
731
732 qmp_args = self._qmp_args(conv_keys, args)
733 ret = self._qmp.cmd_raw(cmd, args=qmp_args)
734 if cmd == 'quit' and 'error' not in ret and 'return' in ret:
735 self._quit_issued = True
736 return ret
737
738 def cmd(self, cmd: str,
739 args_dict: Optional[Dict[str, object]] = None,
740 conv_keys: Optional[bool] = None,
741 **args: Any) -> QMPReturnValue:
742 """
743 Invoke a QMP command.
744 On success return the response dict.
745 On failure raise an exception.
746 """
747 if args_dict is not None:
748 assert not args
749 assert conv_keys is None
750 args = args_dict
751 conv_keys = False
752
753 if conv_keys is None:
754 conv_keys = True
755
756 qmp_args = self._qmp_args(conv_keys, args)
757 ret = self._qmp.cmd(cmd, **qmp_args)
758 if cmd == 'quit':
759 self._quit_issued = True
760 return ret
761
762 def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
763 """
764 Poll for one queued QMP events and return it
765 """
766 if self._events:
767 return self._events.pop(0)
768 return self._qmp.pull_event(wait=wait)
769
770 def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
771 """
772 Poll for queued QMP events and return a list of dicts
773 """
774 events = self._qmp.get_events(wait=wait)
775 events.extend(self._events)
776 del self._events[:]
777 return events
778
779 @staticmethod
780 def event_match(event: Any, match: Optional[Any]) -> bool:
781 """
782 Check if an event matches optional match criteria.
783
784 The match criteria takes the form of a matching subdict. The event is
785 checked to be a superset of the subdict, recursively, with matching
786 values whenever the subdict values are not None.
787
788 This has a limitation that you cannot explicitly check for None values.
789
790 Examples, with the subdict queries on the left:
791 - None matches any object.
792 - {"foo": None} matches {"foo": {"bar": 1}}
793 - {"foo": None} matches {"foo": 5}
794 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
795 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
796 """
797 if match is None:
798 return True
799
800 try:
801 for key in match:
802 if key in event:
803 if not QEMUMachine.event_match(event[key], match[key]):
804 return False
805 else:
806 return False
807 return True
808 except TypeError:
809 # either match or event wasn't iterable (not a dict)
810 return bool(match == event)
811
812 def event_wait(self, name: str,
813 timeout: float = 60.0,
814 match: Optional[QMPMessage] = None) -> Optional[QMPMessage]:
815 """
816 event_wait waits for and returns a named event from QMP with a timeout.
817
818 name: The event to wait for.
819 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
820 match: Optional match criteria. See event_match for details.
821 """
822 return self.events_wait([(name, match)], timeout)
823
824 def events_wait(self,
825 events: Sequence[Tuple[str, Any]],
826 timeout: float = 60.0) -> Optional[QMPMessage]:
827 """
828 events_wait waits for and returns a single named event from QMP.
829 In the case of multiple qualifying events, this function returns the
830 first one.
831
832 :param events: A sequence of (name, match_criteria) tuples.
833 The match criteria are optional and may be None.
834 See event_match for details.
835 :param timeout: Optional timeout, in seconds.
836 See QEMUMonitorProtocol.pull_event.
837
838 :raise asyncio.TimeoutError:
839 If timeout was non-zero and no matching events were found.
840
841 :return: A QMP event matching the filter criteria.
842 If timeout was 0 and no event matched, None.
843 """
844 def _match(event: QMPMessage) -> bool:
845 for name, match in events:
846 if event['event'] == name and self.event_match(event, match):
847 return True
848 return False
849
850 event: Optional[QMPMessage]
851
852 # Search cached events
853 for event in self._events:
854 if _match(event):
855 self._events.remove(event)
856 return event
857
858 # Poll for new events
859 while True:
860 event = self._qmp.pull_event(wait=timeout)
861 if event is None:
862 # NB: None is only returned when timeout is false-ish.
863 # Timeouts raise asyncio.TimeoutError instead!
864 break
865 if _match(event):
866 return event
867 self._events.append(event)
868
869 return None
870
871 def get_log(self) -> Optional[str]:
872 """
873 After self.shutdown or failed qemu execution, this returns the output
874 of the qemu process.
875 """
876 return self._iolog
877
878 def add_args(self, *args: str) -> None:
879 """
880 Adds to the list of extra arguments to be given to the QEMU binary
881 """
882 self._args.extend(args)
883
884 def set_machine(self, machine_type: str) -> None:
885 """
886 Sets the machine type
887
888 If set, the machine type will be added to the base arguments
889 of the resulting QEMU command line.
890 """
891 self._machine = machine_type
892
893 def set_console(self,
894 device_type: Optional[str] = None,
895 console_index: int = 0) -> None:
896 """
897 Sets the device type for a console device
898
899 If set, the console device and a backing character device will
900 be added to the base arguments of the resulting QEMU command
901 line.
902
903 This is a convenience method that will either use the provided
904 device type, or default to a "-serial chardev:console" command
905 line argument.
906
907 The actual setting of command line arguments will be be done at
908 machine launch time, as it depends on the temporary directory
909 to be created.
910
911 @param device_type: the device type, such as "isa-serial". If
912 None is given (the default value) a "-serial
913 chardev:console" command line argument will
914 be used instead, resorting to the machine's
915 default device type.
916 @param console_index: the index of the console device to use.
917 If not zero, the command line will create
918 'index - 1' consoles and connect them to
919 the 'null' backing character device.
920 """
921 self._console_set = True
922 self._console_device_type = device_type
923 self._console_index = console_index
924
925 @property
926 def console_socket(self) -> socket.socket:
927 """
928 Returns a socket connected to the console
929 """
930 if self._console_socket is None:
931 LOG.debug("Opening console socket")
932 if not self._console_set:
933 raise QEMUMachineError(
934 "Attempt to access console socket with no connection")
935 assert self._cons_sock_pair is not None
936 # os.dup() is used here for sock_fd because otherwise we'd
937 # have two rich python socket objects that would each try to
938 # close the same underlying fd when either one gets garbage
939 # collected.
940 self._console_socket = console_socket.ConsoleSocket(
941 sock_fd=os.dup(self._cons_sock_pair[1].fileno()),
942 file=self._console_log_path,
943 drain=self._drain_console)
944 self._cons_sock_pair[1].close()
945 return self._console_socket
946
947 @property
948 def console_file(self) -> socket.SocketIO:
949 """
950 Returns a file associated with the console socket
951 """
952 if self._console_file is None:
953 LOG.debug("Opening console file")
954 self._console_file = self.console_socket.makefile(mode='rb',
955 buffering=0,
956 encoding='utf-8')
957 return self._console_file
958
959 @property
960 def temp_dir(self) -> str:
961 """
962 Returns a temporary directory to be used for this machine
963 """
964 if self._temp_dir is None:
965 self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-",
966 dir=self._base_temp_dir)
967 return self._temp_dir
968
969 @property
970 def log_dir(self) -> str:
971 """
972 Returns a directory to be used for writing logs
973 """
974 if self._log_dir is None:
975 return self.temp_dir
976 return self._log_dir