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