machine.py (26786B)
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 ( # pylint: disable=import-error 44 QEMUMonitorProtocol, 45 QMPMessage, 46 QMPReturnValue, 47 SocketAddrT, 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 AbnormalShutdown(QEMUMachineError): 73 """ 74 Exception raised when a graceful shutdown was requested, but not performed. 75 """ 76 77 78_T = TypeVar('_T', bound='QEMUMachine') 79 80 81class QEMUMachine: 82 """ 83 A QEMU VM. 84 85 Use this object as a context manager to ensure 86 the QEMU process terminates:: 87 88 with VM(binary) as vm: 89 ... 90 # vm is guaranteed to be shut down here 91 """ 92 # pylint: disable=too-many-instance-attributes, too-many-public-methods 93 94 def __init__(self, 95 binary: str, 96 args: Sequence[str] = (), 97 wrapper: Sequence[str] = (), 98 name: Optional[str] = None, 99 base_temp_dir: str = "/var/tmp", 100 monitor_address: Optional[SocketAddrT] = None, 101 sock_dir: Optional[str] = None, 102 drain_console: bool = False, 103 console_log: Optional[str] = None, 104 log_dir: Optional[str] = None, 105 qmp_timer: Optional[float] = None): 106 ''' 107 Initialize a QEMUMachine 108 109 @param binary: path to the qemu binary 110 @param args: list of extra arguments 111 @param wrapper: list of arguments used as prefix to qemu binary 112 @param name: prefix for socket and log file names (default: qemu-PID) 113 @param base_temp_dir: default location where temp files are created 114 @param monitor_address: address for QMP monitor 115 @param sock_dir: where to create socket (defaults to base_temp_dir) 116 @param drain_console: (optional) True to drain console socket to buffer 117 @param console_log: (optional) path to console log file 118 @param log_dir: where to create and keep log files 119 @param qmp_timer: (optional) default QMP socket timeout 120 @note: Qemu process is not started until launch() is used. 121 ''' 122 # pylint: disable=too-many-arguments 123 124 # Direct user configuration 125 126 self._binary = binary 127 self._args = list(args) 128 self._wrapper = wrapper 129 self._qmp_timer = qmp_timer 130 131 self._name = name or "qemu-%d" % os.getpid() 132 self._base_temp_dir = base_temp_dir 133 self._sock_dir = sock_dir or self._base_temp_dir 134 self._log_dir = log_dir 135 136 if monitor_address is not None: 137 self._monitor_address = monitor_address 138 self._remove_monitor_sockfile = False 139 else: 140 self._monitor_address = os.path.join( 141 self._sock_dir, f"{self._name}-monitor.sock" 142 ) 143 self._remove_monitor_sockfile = True 144 145 self._console_log_path = console_log 146 if self._console_log_path: 147 # In order to log the console, buffering needs to be enabled. 148 self._drain_console = True 149 else: 150 self._drain_console = drain_console 151 152 # Runstate 153 self._qemu_log_path: Optional[str] = None 154 self._qemu_log_file: Optional[BinaryIO] = None 155 self._popen: Optional['subprocess.Popen[bytes]'] = None 156 self._events: List[QMPMessage] = [] 157 self._iolog: Optional[str] = None 158 self._qmp_set = True # Enable QMP monitor by default. 159 self._qmp_connection: Optional[QEMUMonitorProtocol] = None 160 self._qemu_full_args: Tuple[str, ...] = () 161 self._temp_dir: Optional[str] = None 162 self._launched = False 163 self._machine: Optional[str] = None 164 self._console_index = 0 165 self._console_set = False 166 self._console_device_type: Optional[str] = None 167 self._console_address = os.path.join( 168 self._sock_dir, f"{self._name}-console.sock" 169 ) 170 self._console_socket: Optional[socket.socket] = None 171 self._remove_files: List[str] = [] 172 self._user_killed = False 173 174 def __enter__(self: _T) -> _T: 175 return self 176 177 def __exit__(self, 178 exc_type: Optional[Type[BaseException]], 179 exc_val: Optional[BaseException], 180 exc_tb: Optional[TracebackType]) -> None: 181 self.shutdown() 182 183 def add_monitor_null(self) -> None: 184 """ 185 This can be used to add an unused monitor instance. 186 """ 187 self._args.append('-monitor') 188 self._args.append('null') 189 190 def add_fd(self: _T, fd: int, fdset: int, 191 opaque: str, opts: str = '') -> _T: 192 """ 193 Pass a file descriptor to the VM 194 """ 195 options = ['fd=%d' % fd, 196 'set=%d' % fdset, 197 'opaque=%s' % opaque] 198 if opts: 199 options.append(opts) 200 201 # This did not exist before 3.4, but since then it is 202 # mandatory for our purpose 203 if hasattr(os, 'set_inheritable'): 204 os.set_inheritable(fd, True) 205 206 self._args.append('-add-fd') 207 self._args.append(','.join(options)) 208 return self 209 210 def send_fd_scm(self, fd: Optional[int] = None, 211 file_path: Optional[str] = None) -> int: 212 """ 213 Send an fd or file_path to the remote via SCM_RIGHTS. 214 215 Exactly one of fd and file_path must be given. If it is 216 file_path, the file will be opened read-only and the new file 217 descriptor will be sent to the remote. 218 """ 219 if file_path is not None: 220 assert fd is None 221 with open(file_path, "rb") as passfile: 222 fd = passfile.fileno() 223 self._qmp.send_fd_scm(fd) 224 else: 225 assert fd is not None 226 self._qmp.send_fd_scm(fd) 227 228 return 0 229 230 @staticmethod 231 def _remove_if_exists(path: str) -> None: 232 """ 233 Remove file object at path if it exists 234 """ 235 try: 236 os.remove(path) 237 except OSError as exception: 238 if exception.errno == errno.ENOENT: 239 return 240 raise 241 242 def is_running(self) -> bool: 243 """Returns true if the VM is running.""" 244 return self._popen is not None and self._popen.poll() is None 245 246 @property 247 def _subp(self) -> 'subprocess.Popen[bytes]': 248 if self._popen is None: 249 raise QEMUMachineError('Subprocess pipe not present') 250 return self._popen 251 252 def exitcode(self) -> Optional[int]: 253 """Returns the exit code if possible, or None.""" 254 if self._popen is None: 255 return None 256 return self._popen.poll() 257 258 def get_pid(self) -> Optional[int]: 259 """Returns the PID of the running process, or None.""" 260 if not self.is_running(): 261 return None 262 return self._subp.pid 263 264 def _load_io_log(self) -> None: 265 # Assume that the output encoding of QEMU's terminal output is 266 # defined by our locale. If indeterminate, allow open() to fall 267 # back to the platform default. 268 _, encoding = locale.getlocale() 269 if self._qemu_log_path is not None: 270 with open(self._qemu_log_path, "r", encoding=encoding) as iolog: 271 self._iolog = iolog.read() 272 273 @property 274 def _base_args(self) -> List[str]: 275 args = ['-display', 'none', '-vga', 'none'] 276 277 if self._qmp_set: 278 if isinstance(self._monitor_address, tuple): 279 moncdev = "socket,id=mon,host={},port={}".format( 280 *self._monitor_address 281 ) 282 else: 283 moncdev = f"socket,id=mon,path={self._monitor_address}" 284 args.extend(['-chardev', moncdev, '-mon', 285 'chardev=mon,mode=control']) 286 287 if self._machine is not None: 288 args.extend(['-machine', self._machine]) 289 for _ in range(self._console_index): 290 args.extend(['-serial', 'null']) 291 if self._console_set: 292 chardev = ('socket,id=console,path=%s,server=on,wait=off' % 293 self._console_address) 294 args.extend(['-chardev', chardev]) 295 if self._console_device_type is None: 296 args.extend(['-serial', 'chardev:console']) 297 else: 298 device = '%s,chardev=console' % self._console_device_type 299 args.extend(['-device', device]) 300 return args 301 302 @property 303 def args(self) -> List[str]: 304 """Returns the list of arguments given to the QEMU binary.""" 305 return self._args 306 307 def _pre_launch(self) -> None: 308 if self._console_set: 309 self._remove_files.append(self._console_address) 310 311 if self._qmp_set: 312 if self._remove_monitor_sockfile: 313 assert isinstance(self._monitor_address, str) 314 self._remove_files.append(self._monitor_address) 315 self._qmp_connection = QEMUMonitorProtocol( 316 self._monitor_address, 317 server=True, 318 nickname=self._name 319 ) 320 321 # NOTE: Make sure any opened resources are *definitely* freed in 322 # _post_shutdown()! 323 # pylint: disable=consider-using-with 324 self._qemu_log_path = os.path.join(self.log_dir, self._name + ".log") 325 self._qemu_log_file = open(self._qemu_log_path, 'wb') 326 327 def _post_launch(self) -> None: 328 if self._qmp_connection: 329 self._qmp.accept(self._qmp_timer) 330 331 def _close_qemu_log_file(self) -> None: 332 if self._qemu_log_file is not None: 333 self._qemu_log_file.close() 334 self._qemu_log_file = None 335 336 def _post_shutdown(self) -> None: 337 """ 338 Called to cleanup the VM instance after the process has exited. 339 May also be called after a failed launch. 340 """ 341 # Comprehensive reset for the failed launch case: 342 self._early_cleanup() 343 344 if self._qmp_connection: 345 self._qmp.close() 346 self._qmp_connection = None 347 348 self._close_qemu_log_file() 349 350 self._load_io_log() 351 352 self._qemu_log_path = None 353 354 if self._temp_dir is not None: 355 shutil.rmtree(self._temp_dir) 356 self._temp_dir = None 357 358 while len(self._remove_files) > 0: 359 self._remove_if_exists(self._remove_files.pop()) 360 361 exitcode = self.exitcode() 362 if (exitcode is not None and exitcode < 0 363 and not (self._user_killed and exitcode == -signal.SIGKILL)): 364 msg = 'qemu received signal %i; command: "%s"' 365 if self._qemu_full_args: 366 command = ' '.join(self._qemu_full_args) 367 else: 368 command = '' 369 LOG.warning(msg, -int(exitcode), command) 370 371 self._user_killed = False 372 self._launched = False 373 374 def launch(self) -> None: 375 """ 376 Launch the VM and make sure we cleanup and expose the 377 command line/output in case of exception 378 """ 379 380 if self._launched: 381 raise QEMUMachineError('VM already launched') 382 383 self._iolog = None 384 self._qemu_full_args = () 385 try: 386 self._launch() 387 self._launched = True 388 except: 389 self._post_shutdown() 390 391 LOG.debug('Error launching VM') 392 if self._qemu_full_args: 393 LOG.debug('Command: %r', ' '.join(self._qemu_full_args)) 394 if self._iolog: 395 LOG.debug('Output: %r', self._iolog) 396 raise 397 398 def _launch(self) -> None: 399 """ 400 Launch the VM and establish a QMP connection 401 """ 402 self._pre_launch() 403 self._qemu_full_args = tuple( 404 chain(self._wrapper, 405 [self._binary], 406 self._base_args, 407 self._args) 408 ) 409 LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args)) 410 411 # Cleaning up of this subprocess is guaranteed by _do_shutdown. 412 # pylint: disable=consider-using-with 413 self._popen = subprocess.Popen(self._qemu_full_args, 414 stdin=subprocess.DEVNULL, 415 stdout=self._qemu_log_file, 416 stderr=subprocess.STDOUT, 417 shell=False, 418 close_fds=False) 419 self._post_launch() 420 421 def _early_cleanup(self) -> None: 422 """ 423 Perform any cleanup that needs to happen before the VM exits. 424 425 May be invoked by both soft and hard shutdown in failover scenarios. 426 Called additionally by _post_shutdown for comprehensive cleanup. 427 """ 428 # If we keep the console socket open, we may deadlock waiting 429 # for QEMU to exit, while QEMU is waiting for the socket to 430 # become writeable. 431 if self._console_socket is not None: 432 self._console_socket.close() 433 self._console_socket = None 434 435 def _hard_shutdown(self) -> None: 436 """ 437 Perform early cleanup, kill the VM, and wait for it to terminate. 438 439 :raise subprocess.Timeout: When timeout is exceeds 60 seconds 440 waiting for the QEMU process to terminate. 441 """ 442 self._early_cleanup() 443 self._subp.kill() 444 self._subp.wait(timeout=60) 445 446 def _soft_shutdown(self, timeout: Optional[int], 447 has_quit: bool = False) -> None: 448 """ 449 Perform early cleanup, attempt to gracefully shut down the VM, and wait 450 for it to terminate. 451 452 :param timeout: Timeout in seconds for graceful shutdown. 453 A value of None is an infinite wait. 454 :param has_quit: When True, don't attempt to issue 'quit' QMP command 455 456 :raise ConnectionReset: On QMP communication errors 457 :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for 458 the QEMU process to terminate. 459 """ 460 self._early_cleanup() 461 462 if self._qmp_connection: 463 if not has_quit: 464 # Might raise ConnectionReset 465 self._qmp.cmd('quit') 466 467 # May raise subprocess.TimeoutExpired 468 self._subp.wait(timeout=timeout) 469 470 def _do_shutdown(self, timeout: Optional[int], 471 has_quit: bool = False) -> None: 472 """ 473 Attempt to shutdown the VM gracefully; fallback to a hard shutdown. 474 475 :param timeout: Timeout in seconds for graceful shutdown. 476 A value of None is an infinite wait. 477 :param has_quit: When True, don't attempt to issue 'quit' QMP command 478 479 :raise AbnormalShutdown: When the VM could not be shut down gracefully. 480 The inner exception will likely be ConnectionReset or 481 subprocess.TimeoutExpired. In rare cases, non-graceful termination 482 may result in its own exceptions, likely subprocess.TimeoutExpired. 483 """ 484 try: 485 self._soft_shutdown(timeout, has_quit) 486 except Exception as exc: 487 self._hard_shutdown() 488 raise AbnormalShutdown("Could not perform graceful shutdown") \ 489 from exc 490 491 def shutdown(self, has_quit: bool = False, 492 hard: bool = False, 493 timeout: Optional[int] = 30) -> None: 494 """ 495 Terminate the VM (gracefully if possible) and perform cleanup. 496 Cleanup will always be performed. 497 498 If the VM has not yet been launched, or shutdown(), wait(), or kill() 499 have already been called, this method does nothing. 500 501 :param has_quit: When true, do not attempt to issue 'quit' QMP command. 502 :param hard: When true, do not attempt graceful shutdown, and 503 suppress the SIGKILL warning log message. 504 :param timeout: Optional timeout in seconds for graceful shutdown. 505 Default 30 seconds, A `None` value is an infinite wait. 506 """ 507 if not self._launched: 508 return 509 510 try: 511 if hard: 512 self._user_killed = True 513 self._hard_shutdown() 514 else: 515 self._do_shutdown(timeout, has_quit) 516 finally: 517 self._post_shutdown() 518 519 def kill(self) -> None: 520 """ 521 Terminate the VM forcefully, wait for it to exit, and perform cleanup. 522 """ 523 self.shutdown(hard=True) 524 525 def wait(self, timeout: Optional[int] = 30) -> None: 526 """ 527 Wait for the VM to power off and perform post-shutdown cleanup. 528 529 :param timeout: Optional timeout in seconds. Default 30 seconds. 530 A value of `None` is an infinite wait. 531 """ 532 self.shutdown(has_quit=True, timeout=timeout) 533 534 def set_qmp_monitor(self, enabled: bool = True) -> None: 535 """ 536 Set the QMP monitor. 537 538 @param enabled: if False, qmp monitor options will be removed from 539 the base arguments of the resulting QEMU command 540 line. Default is True. 541 542 .. note:: Call this function before launch(). 543 """ 544 self._qmp_set = enabled 545 546 @property 547 def _qmp(self) -> QEMUMonitorProtocol: 548 if self._qmp_connection is None: 549 raise QEMUMachineError("Attempt to access QMP with no connection") 550 return self._qmp_connection 551 552 @classmethod 553 def _qmp_args(cls, conv_keys: bool, 554 args: Dict[str, Any]) -> Dict[str, object]: 555 if conv_keys: 556 return {k.replace('_', '-'): v for k, v in args.items()} 557 558 return args 559 560 def qmp(self, cmd: str, 561 args_dict: Optional[Dict[str, object]] = None, 562 conv_keys: Optional[bool] = None, 563 **args: Any) -> QMPMessage: 564 """ 565 Invoke a QMP command and return the response dict 566 """ 567 if args_dict is not None: 568 assert not args 569 assert conv_keys is None 570 args = args_dict 571 conv_keys = False 572 573 if conv_keys is None: 574 conv_keys = True 575 576 qmp_args = self._qmp_args(conv_keys, args) 577 return self._qmp.cmd(cmd, args=qmp_args) 578 579 def command(self, cmd: str, 580 conv_keys: bool = True, 581 **args: Any) -> QMPReturnValue: 582 """ 583 Invoke a QMP command. 584 On success return the response dict. 585 On failure raise an exception. 586 """ 587 qmp_args = self._qmp_args(conv_keys, args) 588 return self._qmp.command(cmd, **qmp_args) 589 590 def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]: 591 """ 592 Poll for one queued QMP events and return it 593 """ 594 if self._events: 595 return self._events.pop(0) 596 return self._qmp.pull_event(wait=wait) 597 598 def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]: 599 """ 600 Poll for queued QMP events and return a list of dicts 601 """ 602 events = self._qmp.get_events(wait=wait) 603 events.extend(self._events) 604 del self._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 774 @property 775 def log_dir(self) -> str: 776 """ 777 Returns a directory to be used for writing logs 778 """ 779 if self._log_dir is None: 780 return self.temp_dir 781 return self._log_dir