qom_common.py (5095B)
1""" 2QOM Command abstractions. 3""" 4## 5# Copyright John Snow 2020, for Red Hat, Inc. 6# Copyright IBM, Corp. 2011 7# 8# Authors: 9# John Snow <jsnow@redhat.com> 10# Anthony Liguori <aliguori@amazon.com> 11# 12# This work is licensed under the terms of the GNU GPL, version 2 or later. 13# See the COPYING file in the top-level directory. 14# 15# Based on ./scripts/qmp/qom-[set|get|tree|list] 16## 17 18import argparse 19import os 20import sys 21from typing import ( 22 Any, 23 Dict, 24 List, 25 Optional, 26 Type, 27 TypeVar, 28) 29 30from . import QEMUMonitorProtocol, QMPError 31 32 33# The following is needed only for a type alias. 34Subparsers = argparse._SubParsersAction # pylint: disable=protected-access 35 36 37class ObjectPropertyInfo: 38 """ 39 Represents the return type from e.g. qom-list. 40 """ 41 def __init__(self, name: str, type_: str, 42 description: Optional[str] = None, 43 default_value: Optional[object] = None): 44 self.name = name 45 self.type = type_ 46 self.description = description 47 self.default_value = default_value 48 49 @classmethod 50 def make(cls, value: Dict[str, Any]) -> 'ObjectPropertyInfo': 51 """ 52 Build an ObjectPropertyInfo from a Dict with an unknown shape. 53 """ 54 assert value.keys() >= {'name', 'type'} 55 assert value.keys() <= {'name', 'type', 'description', 'default-value'} 56 return cls(value['name'], value['type'], 57 value.get('description'), 58 value.get('default-value')) 59 60 @property 61 def child(self) -> bool: 62 """Is this property a child property?""" 63 return self.type.startswith('child<') 64 65 @property 66 def link(self) -> bool: 67 """Is this property a link property?""" 68 return self.type.startswith('link<') 69 70 71CommandT = TypeVar('CommandT', bound='QOMCommand') 72 73 74class QOMCommand: 75 """ 76 Represents a QOM sub-command. 77 78 :param args: Parsed arguments, as returned from parser.parse_args. 79 """ 80 name: str 81 help: str 82 83 def __init__(self, args: argparse.Namespace): 84 if args.socket is None: 85 raise QMPError("No QMP socket path or address given") 86 self.qmp = QEMUMonitorProtocol( 87 QEMUMonitorProtocol.parse_address(args.socket) 88 ) 89 self.qmp.connect() 90 91 @classmethod 92 def register(cls, subparsers: Subparsers) -> None: 93 """ 94 Register this command with the argument parser. 95 96 :param subparsers: argparse subparsers object, from "add_subparsers". 97 """ 98 subparser = subparsers.add_parser(cls.name, help=cls.help, 99 description=cls.help) 100 cls.configure_parser(subparser) 101 102 @classmethod 103 def configure_parser(cls, parser: argparse.ArgumentParser) -> None: 104 """ 105 Configure a parser with this command's arguments. 106 107 :param parser: argparse parser or subparser object. 108 """ 109 default_path = os.environ.get('QMP_SOCKET') 110 parser.add_argument( 111 '--socket', '-s', 112 dest='socket', 113 action='store', 114 help='QMP socket path or address (addr:port).' 115 ' May also be set via QMP_SOCKET environment variable.', 116 default=default_path 117 ) 118 parser.set_defaults(cmd_class=cls) 119 120 @classmethod 121 def add_path_prop_arg(cls, parser: argparse.ArgumentParser) -> None: 122 """ 123 Add the <path>.<proptery> positional argument to this command. 124 125 :param parser: The parser to add the argument to. 126 """ 127 parser.add_argument( 128 'path_prop', 129 metavar='<path>.<property>', 130 action='store', 131 help="QOM path and property, separated by a period '.'" 132 ) 133 134 def run(self) -> int: 135 """ 136 Run this command. 137 138 :return: 0 on success, 1 otherwise. 139 """ 140 raise NotImplementedError 141 142 def qom_list(self, path: str) -> List[ObjectPropertyInfo]: 143 """ 144 :return: a strongly typed list from the 'qom-list' command. 145 """ 146 rsp = self.qmp.command('qom-list', path=path) 147 # qom-list returns List[ObjectPropertyInfo] 148 assert isinstance(rsp, list) 149 return [ObjectPropertyInfo.make(x) for x in rsp] 150 151 @classmethod 152 def command_runner( 153 cls: Type[CommandT], 154 args: argparse.Namespace 155 ) -> int: 156 """ 157 Run a fully-parsed subcommand, with error-handling for the CLI. 158 159 :return: The return code from `run()`. 160 """ 161 try: 162 cmd = cls(args) 163 return cmd.run() 164 except QMPError as err: 165 print(f"{type(err).__name__}: {err!s}", file=sys.stderr) 166 return -1 167 168 @classmethod 169 def entry_point(cls) -> int: 170 """ 171 Build this command's parser, parse arguments, and run the command. 172 173 :return: `run`'s return code. 174 """ 175 parser = argparse.ArgumentParser(description=cls.help) 176 cls.configure_parser(parser) 177 args = parser.parse_args() 178 return cls.command_runner(args)