1""" 2QEMU machine module: 3 4The machine module primarily provides the QEMUMachine class, 5which 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 20import errno 21from itertools import chain 22import locale 23import logging 24import os 25import shutil 26import signal 27import socket 28import subprocess 29import tempfile 30from types import TracebackType 31from typing import ( 32 Any, 33 BinaryIO, 34 Dict, 35 List, 36 Optional, 37 Sequence, 38 Tuple, 39 Type, 40 TypeVar, 41) 42 43from qemu.qmp import SocketAddrT 44from qemu.qmp.legacy import ( 45 QEMUMonitorProtocol, 46 QMPMessage, 47 QMPReturnValue, 48) 49 50from . import console_socket 51 52 53LOG = logging.getLogger(__name__) 54 55 56class QEMUMachineError(Exception): 57 """ 58 Exception called when an error in QEMUMachine happens. 59 """ 60 61 62class 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 72class 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 101class 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 110class 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 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): 135 ''' 136 Initialize a QEMUMachine 137 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. 150 ''' 151 # pylint: disable=too-many-arguments 152 153 # Direct user configuration 154 155 self._binary = binary 156 self._args = list(args) 157 self._wrapper = wrapper 158 self._qmp_timer = qmp_timer 159 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 165 166 if monitor_address is not None: 167 self._monitor_address = monitor_address 168 else: 169 self._monitor_address = os.path.join( 170 self.sock_dir, f"{self._name}-monitor.sock" 171 ) 172 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 177 else: 178 self._drain_console = drain_console 179 180 # Runstate 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" 196 ) 197 self._console_socket: Optional[socket.socket] = None 198 self._remove_files: List[str] = [] 199 self._user_killed = False 200 self._quit_issued = False 201 202 def __enter__(self: _T) -> _T: 203 return self 204 205 def __exit__(self, 206 exc_type: Optional[Type[BaseException]], 207 exc_val: Optional[BaseException], 208 exc_tb: Optional[TracebackType]) -> None: 209 self.shutdown() 210 211 def add_monitor_null(self) -> None: 212 """ 213 This can be used to add an unused monitor instance. 214 """ 215 self._args.append('-monitor') 216 self._args.append('null') 217 218 def add_fd(self: _T, fd: int, fdset: int, 219 opaque: str, opts: str = '') -> _T: 220 """ 221 Pass a file descriptor to the VM 222 """ 223 options = ['fd=%d' % fd, 224 'set=%d' % fdset, 225 'opaque=%s' % opaque] 226 if opts: 227 options.append(opts) 228 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) 233 234 self._args.append('-add-fd') 235 self._args.append(','.join(options)) 236 return self 237 238 def send_fd_scm(self, fd: Optional[int] = None, 239 file_path: Optional[str] = None) -> int: 240 """ 241 Send an fd or file_path to the remote via SCM_RIGHTS. 242 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. 246 """ 247 if file_path is not None: 248 assert fd is None 249 with open(file_path, "rb") as passfile: 250 fd = passfile.fileno() 251 self._qmp.send_fd_scm(fd) 252 else: 253 assert fd is not None 254 self._qmp.send_fd_scm(fd) 255 256 return 0 257 258 @staticmethod 259 def _remove_if_exists(path: str) -> None: 260 """ 261 Remove file object at path if it exists 262 """ 263 try: 264 os.remove(path) 265 except OSError as exception: 266 if exception.errno == errno.ENOENT: 267 return 268 raise 269 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 273 274 @property 275 def _subp(self) -> 'subprocess.Popen[bytes]': 276 if self._popen is None: 277 raise QEMUMachineError('Subprocess pipe not present') 278 return self._popen 279 280 def exitcode(self) -> Optional[int]: 281 """Returns the exit code if possible, or None.""" 282 if self._popen is None: 283 return None 284 return self._popen.poll() 285 286 def get_pid(self) -> Optional[int]: 287 """Returns the PID of the running process, or None.""" 288 if not self.is_running(): 289 return None 290 return self._subp.pid 291 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() 300 301 @property 302 def _base_args(self) -> List[str]: 303 args = ['-display', 'none', '-vga', 'none'] 304 305 if self._qmp_set: 306 if isinstance(self._monitor_address, tuple): 307 moncdev = "socket,id=mon,host={},port={}".format( 308 *self._monitor_address 309 ) 310 else: 311 moncdev = f"socket,id=mon,path={self._monitor_address}" 312 args.extend(['-chardev', moncdev, '-mon', 313 'chardev=mon,mode=control']) 314 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']) 325 else: 326 device = '%s,chardev=console' % self._console_device_type 327 args.extend(['-device', device]) 328 return args 329 330 @property 331 def args(self) -> List[str]: 332 """Returns the list of arguments given to the QEMU binary.""" 333 return self._args 334 335 def _pre_launch(self) -> None: 336 if self._console_set: 337 self._remove_files.append(self._console_address) 338 339 if self._qmp_set: 340 if isinstance(self._monitor_address, str): 341 self._remove_files.append(self._monitor_address) 342 self._qmp_connection = QEMUMonitorProtocol( 343 self._monitor_address, 344 server=True, 345 nickname=self._name 346 ) 347 348 # NOTE: Make sure any opened resources are *definitely* freed in 349 # _post_shutdown()! 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') 353 354 self._iolog = None 355 self._qemu_full_args = tuple(chain( 356 self._wrapper, 357 [self._binary], 358 self._base_args, 359 self._args 360 )) 361 362 def _post_launch(self) -> None: 363 if self._qmp_connection: 364 self._qmp.accept(self._qmp_timer) 365 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 370 371 def _post_shutdown(self) -> None: 372 """ 373 Called to cleanup the VM instance after the process has exited. 374 May also be called after a failed launch. 375 """ 376 LOG.debug("Cleaning up after VM process") 377 try: 378 self._close_qmp_connection() 379 except Exception as err: # pylint: disable=broad-except 380 LOG.warning( 381 "Exception closing QMP connection: %s", 382 str(err) if str(err) else type(err).__name__ 383 ) 384 finally: 385 assert self._qmp_connection is None 386 387 self._close_qemu_log_file() 388 389 self._load_io_log() 390 391 self._qemu_log_path = None 392 393 if self._temp_dir is not None: 394 shutil.rmtree(self._temp_dir) 395 self._temp_dir = None 396 397 while len(self._remove_files) > 0: 398 self._remove_if_exists(self._remove_files.pop()) 399 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) 406 else: 407 command = '' 408 LOG.warning(msg, -int(exitcode), command) 409 410 self._quit_issued = False 411 self._user_killed = False 412 self._launched = False 413 414 def launch(self) -> None: 415 """ 416 Launch the VM and make sure we cleanup and expose the 417 command line/output in case of exception 418 """ 419 420 if self._launched: 421 raise QEMUMachineError('VM already launched') 422 423 try: 424 self._launch() 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. 431 if self._launched: 432 self.wait() 433 else: 434 self._post_shutdown() 435 436 if isinstance(exc, Exception): 437 raise VMLaunchFailure( 438 exitcode=self.exitcode(), 439 command=' '.join(self._qemu_full_args), 440 output=self._iolog 441 ) from exc 442 443 # Don't wrap 'BaseException'; doing so would downgrade 444 # that exception. However, we still want to clean up. 445 raise 446 447 def _launch(self) -> None: 448 """ 449 Launch the VM and establish a QMP connection 450 """ 451 self._pre_launch() 452 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args)) 453 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, 460 shell=False, 461 close_fds=False) 462 self._launched = True 463 self._post_launch() 464 465 def _close_qmp_connection(self) -> None: 466 """ 467 Close the underlying QMP connection, if any. 468 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. 472 """ 473 if self._qmp_connection is None: 474 return 475 476 try: 477 self._qmp.close() 478 except EOFError: 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 484 # shutdown case. 485 if not (self._user_killed or self._quit_issued): 486 raise 487 finally: 488 self._qmp_connection = None 489 490 def _early_cleanup(self) -> None: 491 """ 492 Perform any cleanup that needs to happen before the VM exits. 493 494 This method may be called twice upon shutdown, once each by soft 495 and hard shutdown in failover scenarios. 496 """ 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 499 # become writable. 500 if self._console_socket is not None: 501 LOG.debug("Closing console socket") 502 self._console_socket.close() 503 self._console_socket = None 504 505 def _hard_shutdown(self) -> None: 506 """ 507 Perform early cleanup, kill the VM, and wait for it to terminate. 508 509 :raise subprocess.Timeout: When timeout is exceeds 60 seconds 510 waiting for the QEMU process to terminate. 511 """ 512 LOG.debug("Performing hard shutdown") 513 self._early_cleanup() 514 self._subp.kill() 515 self._subp.wait(timeout=60) 516 517 def _soft_shutdown(self, timeout: Optional[int]) -> None: 518 """ 519 Perform early cleanup, attempt to gracefully shut down the VM, and wait 520 for it to terminate. 521 522 :param timeout: Timeout in seconds for graceful shutdown. 523 A value of None is an infinite wait. 524 525 :raise ConnectionReset: On QMP communication errors 526 :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for 527 the QEMU process to terminate. 528 """ 529 LOG.debug("Attempting graceful termination") 530 531 self._early_cleanup() 532 533 if self._quit_issued: 534 LOG.debug( 535 "Anticipating QEMU termination due to prior 'quit' command, " 536 "or explicit call to wait()" 537 ) 538 else: 539 LOG.debug("Politely asking QEMU to terminate") 540 541 if self._qmp_connection: 542 try: 543 if not self._quit_issued: 544 # May raise ExecInterruptedError or StateError if the 545 # connection dies or has *already* died. 546 self.qmp('quit') 547 finally: 548 # Regardless, we want to quiesce the connection. 549 self._close_qmp_connection() 550 551 # May raise subprocess.TimeoutExpired 552 LOG.debug( 553 "Waiting (timeout=%s) for QEMU process (pid=%s) to terminate", 554 timeout, self._subp.pid 555 ) 556 self._subp.wait(timeout=timeout) 557 558 def _do_shutdown(self, timeout: Optional[int]) -> None: 559 """ 560 Attempt to shutdown the VM gracefully; fallback to a hard shutdown. 561 562 :param timeout: Timeout in seconds for graceful shutdown. 563 A value of None is an infinite wait. 564 565 :raise AbnormalShutdown: When the VM could not be shut down gracefully. 566 The inner exception will likely be ConnectionReset or 567 subprocess.TimeoutExpired. In rare cases, non-graceful termination 568 may result in its own exceptions, likely subprocess.TimeoutExpired. 569 """ 570 try: 571 self._soft_shutdown(timeout) 572 except Exception as exc: 573 if isinstance(exc, subprocess.TimeoutExpired): 574 LOG.debug("Timed out waiting for QEMU process to exit") 575 LOG.debug("Graceful shutdown failed", exc_info=True) 576 LOG.debug("Falling back to hard shutdown") 577 self._hard_shutdown() 578 raise AbnormalShutdown("Could not perform graceful shutdown") \ 579 from exc 580 581 def shutdown(self, 582 hard: bool = False, 583 timeout: Optional[int] = 30) -> None: 584 """ 585 Terminate the VM (gracefully if possible) and perform cleanup. 586 Cleanup will always be performed. 587 588 If the VM has not yet been launched, or shutdown(), wait(), or kill() 589 have already been called, this method does nothing. 590 591 :param hard: When true, do not attempt graceful shutdown, and 592 suppress the SIGKILL warning log message. 593 :param timeout: Optional timeout in seconds for graceful shutdown. 594 Default 30 seconds, A `None` value is an infinite wait. 595 """ 596 if not self._launched: 597 return 598 599 LOG.debug("Shutting down VM appliance; timeout=%s", timeout) 600 if hard: 601 LOG.debug("Caller requests immediate termination of QEMU process.") 602 603 try: 604 if hard: 605 self._user_killed = True 606 self._hard_shutdown() 607 else: 608 self._do_shutdown(timeout) 609 finally: 610 self._post_shutdown() 611 612 def kill(self) -> None: 613 """ 614 Terminate the VM forcefully, wait for it to exit, and perform cleanup. 615 """ 616 self.shutdown(hard=True) 617 618 def wait(self, timeout: Optional[int] = 30) -> None: 619 """ 620 Wait for the VM to power off and perform post-shutdown cleanup. 621 622 :param timeout: Optional timeout in seconds. Default 30 seconds. 623 A value of `None` is an infinite wait. 624 """ 625 self._quit_issued = True 626 self.shutdown(timeout=timeout) 627 628 def set_qmp_monitor(self, enabled: bool = True) -> None: 629 """ 630 Set the QMP monitor. 631 632 @param enabled: if False, qmp monitor options will be removed from 633 the base arguments of the resulting QEMU command 634 line. Default is True. 635 636 .. note:: Call this function before launch(). 637 """ 638 self._qmp_set = enabled 639 640 @property 641 def _qmp(self) -> QEMUMonitorProtocol: 642 if self._qmp_connection is None: 643 raise QEMUMachineError("Attempt to access QMP with no connection") 644 return self._qmp_connection 645 646 @classmethod 647 def _qmp_args(cls, conv_keys: bool, 648 args: Dict[str, Any]) -> Dict[str, object]: 649 if conv_keys: 650 return {k.replace('_', '-'): v for k, v in args.items()} 651 652 return args 653 654 def qmp(self, cmd: str, 655 args_dict: Optional[Dict[str, object]] = None, 656 conv_keys: Optional[bool] = None, 657 **args: Any) -> QMPMessage: 658 """ 659 Invoke a QMP command and return the response dict 660 """ 661 if args_dict is not None: 662 assert not args 663 assert conv_keys is None 664 args = args_dict 665 conv_keys = False 666 667 if conv_keys is None: 668 conv_keys = True 669 670 qmp_args = self._qmp_args(conv_keys, args) 671 ret = self._qmp.cmd(cmd, args=qmp_args) 672 if cmd == 'quit' and 'error' not in ret and 'return' in ret: 673 self._quit_issued = True 674 return ret 675 676 def command(self, cmd: str, 677 conv_keys: bool = True, 678 **args: Any) -> QMPReturnValue: 679 """ 680 Invoke a QMP command. 681 On success return the response dict. 682 On failure raise an exception. 683 """ 684 qmp_args = self._qmp_args(conv_keys, args) 685 ret = self._qmp.command(cmd, **qmp_args) 686 if cmd == 'quit': 687 self._quit_issued = True 688 return ret 689 690 def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]: 691 """ 692 Poll for one queued QMP events and return it 693 """ 694 if self._events: 695 return self._events.pop(0) 696 return self._qmp.pull_event(wait=wait) 697 698 def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]: 699 """ 700 Poll for queued QMP events and return a list of dicts 701 """ 702 events = self._qmp.get_events(wait=wait) 703 events.extend(self._events) 704 del self._events[:] 705 return events 706 707 @staticmethod 708 def event_match(event: Any, match: Optional[Any]) -> bool: 709 """ 710 Check if an event matches optional match criteria. 711 712 The match criteria takes the form of a matching subdict. The event is 713 checked to be a superset of the subdict, recursively, with matching 714 values whenever the subdict values are not None. 715 716 This has a limitation that you cannot explicitly check for None values. 717 718 Examples, with the subdict queries on the left: 719 - None matches any object. 720 - {"foo": None} matches {"foo": {"bar": 1}} 721 - {"foo": None} matches {"foo": 5} 722 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}} 723 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}} 724 """ 725 if match is None: 726 return True 727 728 try: 729 for key in match: 730 if key in event: 731 if not QEMUMachine.event_match(event[key], match[key]): 732 return False 733 else: 734 return False 735 return True 736 except TypeError: 737 # either match or event wasn't iterable (not a dict) 738 return bool(match == event) 739 740 def event_wait(self, name: str, 741 timeout: float = 60.0, 742 match: Optional[QMPMessage] = None) -> Optional[QMPMessage]: 743 """ 744 event_wait waits for and returns a named event from QMP with a timeout. 745 746 name: The event to wait for. 747 timeout: QEMUMonitorProtocol.pull_event timeout parameter. 748 match: Optional match criteria. See event_match for details. 749 """ 750 return self.events_wait([(name, match)], timeout) 751 752 def events_wait(self, 753 events: Sequence[Tuple[str, Any]], 754 timeout: float = 60.0) -> Optional[QMPMessage]: 755 """ 756 events_wait waits for and returns a single named event from QMP. 757 In the case of multiple qualifying events, this function returns the 758 first one. 759 760 :param events: A sequence of (name, match_criteria) tuples. 761 The match criteria are optional and may be None. 762 See event_match for details. 763 :param timeout: Optional timeout, in seconds. 764 See QEMUMonitorProtocol.pull_event. 765 766 :raise asyncio.TimeoutError: 767 If timeout was non-zero and no matching events were found. 768 769 :return: A QMP event matching the filter criteria. 770 If timeout was 0 and no event matched, None. 771 """ 772 def _match(event: QMPMessage) -> bool: 773 for name, match in events: 774 if event['event'] == name and self.event_match(event, match): 775 return True 776 return False 777 778 event: Optional[QMPMessage] 779 780 # Search cached events 781 for event in self._events: 782 if _match(event): 783 self._events.remove(event) 784 return event 785 786 # Poll for new events 787 while True: 788 event = self._qmp.pull_event(wait=timeout) 789 if event is None: 790 # NB: None is only returned when timeout is false-ish. 791 # Timeouts raise asyncio.TimeoutError instead! 792 break 793 if _match(event): 794 return event 795 self._events.append(event) 796 797 return None 798 799 def get_log(self) -> Optional[str]: 800 """ 801 After self.shutdown or failed qemu execution, this returns the output 802 of the qemu process. 803 """ 804 return self._iolog 805 806 def add_args(self, *args: str) -> None: 807 """ 808 Adds to the list of extra arguments to be given to the QEMU binary 809 """ 810 self._args.extend(args) 811 812 def set_machine(self, machine_type: str) -> None: 813 """ 814 Sets the machine type 815 816 If set, the machine type will be added to the base arguments 817 of the resulting QEMU command line. 818 """ 819 self._machine = machine_type 820 821 def set_console(self, 822 device_type: Optional[str] = None, 823 console_index: int = 0) -> None: 824 """ 825 Sets the device type for a console device 826 827 If set, the console device and a backing character device will 828 be added to the base arguments of the resulting QEMU command 829 line. 830 831 This is a convenience method that will either use the provided 832 device type, or default to a "-serial chardev:console" command 833 line argument. 834 835 The actual setting of command line arguments will be be done at 836 machine launch time, as it depends on the temporary directory 837 to be created. 838 839 @param device_type: the device type, such as "isa-serial". If 840 None is given (the default value) a "-serial 841 chardev:console" command line argument will 842 be used instead, resorting to the machine's 843 default device type. 844 @param console_index: the index of the console device to use. 845 If not zero, the command line will create 846 'index - 1' consoles and connect them to 847 the 'null' backing character device. 848 """ 849 self._console_set = True 850 self._console_device_type = device_type 851 self._console_index = console_index 852 853 @property 854 def console_socket(self) -> socket.socket: 855 """ 856 Returns a socket connected to the console 857 """ 858 if self._console_socket is None: 859 self._console_socket = console_socket.ConsoleSocket( 860 self._console_address, 861 file=self._console_log_path, 862 drain=self._drain_console) 863 return self._console_socket 864 865 @property 866 def temp_dir(self) -> str: 867 """ 868 Returns a temporary directory to be used for this machine 869 """ 870 if self._temp_dir is None: 871 self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-", 872 dir=self._base_temp_dir) 873 return self._temp_dir 874 875 @property 876 def sock_dir(self) -> str: 877 """ 878 Returns the directory used for sockfiles by this machine. 879 """ 880 if self._sock_dir: 881 return self._sock_dir 882 return self.temp_dir 883 884 @property 885 def log_dir(self) -> str: 886 """ 887 Returns a directory to be used for writing logs 888 """ 889 if self._log_dir is None: 890 return self.temp_dir 891 return self._log_dir 892