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