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