 8d6cdc5118
			
		
	
	
		8d6cdc5118
		
	
	
	
	
		
			
			Signed-off-by: John Snow <jsnow@redhat.com> Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> Reviewed-by: Beraldo Leal <bleal@redhat.com>
		
			
				
	
	
		
			176 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			176 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| """
 | |
| QOM Command abstractions.
 | |
| """
 | |
| ##
 | |
| # Copyright John Snow 2020, for Red Hat, Inc.
 | |
| # Copyright IBM, Corp. 2011
 | |
| #
 | |
| # Authors:
 | |
| #  John Snow <jsnow@redhat.com>
 | |
| #  Anthony Liguori <aliguori@amazon.com>
 | |
| #
 | |
| # This work is licensed under the terms of the GNU GPL, version 2 or later.
 | |
| # See the COPYING file in the top-level directory.
 | |
| #
 | |
| # Based on ./scripts/qmp/qom-[set|get|tree|list]
 | |
| ##
 | |
| 
 | |
| import argparse
 | |
| import os
 | |
| import sys
 | |
| from typing import (
 | |
|     Any,
 | |
|     Dict,
 | |
|     List,
 | |
|     Optional,
 | |
|     Type,
 | |
|     TypeVar,
 | |
| )
 | |
| 
 | |
| from qemu.aqmp import QMPError
 | |
| from qemu.aqmp.legacy import QEMUMonitorProtocol
 | |
| 
 | |
| 
 | |
| class ObjectPropertyInfo:
 | |
|     """
 | |
|     Represents the return type from e.g. qom-list.
 | |
|     """
 | |
|     def __init__(self, name: str, type_: str,
 | |
|                  description: Optional[str] = None,
 | |
|                  default_value: Optional[object] = None):
 | |
|         self.name = name
 | |
|         self.type = type_
 | |
|         self.description = description
 | |
|         self.default_value = default_value
 | |
| 
 | |
|     @classmethod
 | |
|     def make(cls, value: Dict[str, Any]) -> 'ObjectPropertyInfo':
 | |
|         """
 | |
|         Build an ObjectPropertyInfo from a Dict with an unknown shape.
 | |
|         """
 | |
|         assert value.keys() >= {'name', 'type'}
 | |
|         assert value.keys() <= {'name', 'type', 'description', 'default-value'}
 | |
|         return cls(value['name'], value['type'],
 | |
|                    value.get('description'),
 | |
|                    value.get('default-value'))
 | |
| 
 | |
|     @property
 | |
|     def child(self) -> bool:
 | |
|         """Is this property a child property?"""
 | |
|         return self.type.startswith('child<')
 | |
| 
 | |
|     @property
 | |
|     def link(self) -> bool:
 | |
|         """Is this property a link property?"""
 | |
|         return self.type.startswith('link<')
 | |
| 
 | |
| 
 | |
| CommandT = TypeVar('CommandT', bound='QOMCommand')
 | |
| 
 | |
| 
 | |
| class QOMCommand:
 | |
|     """
 | |
|     Represents a QOM sub-command.
 | |
| 
 | |
|     :param args: Parsed arguments, as returned from parser.parse_args.
 | |
|     """
 | |
|     name: str
 | |
|     help: str
 | |
| 
 | |
|     def __init__(self, args: argparse.Namespace):
 | |
|         if args.socket is None:
 | |
|             raise QMPError("No QMP socket path or address given")
 | |
|         self.qmp = QEMUMonitorProtocol(
 | |
|             QEMUMonitorProtocol.parse_address(args.socket)
 | |
|         )
 | |
|         self.qmp.connect()
 | |
| 
 | |
|     @classmethod
 | |
|     def register(cls, subparsers: Any) -> None:
 | |
|         """
 | |
|         Register this command with the argument parser.
 | |
| 
 | |
|         :param subparsers: argparse subparsers object, from "add_subparsers".
 | |
|         """
 | |
|         subparser = subparsers.add_parser(cls.name, help=cls.help,
 | |
|                                           description=cls.help)
 | |
|         cls.configure_parser(subparser)
 | |
| 
 | |
|     @classmethod
 | |
|     def configure_parser(cls, parser: argparse.ArgumentParser) -> None:
 | |
|         """
 | |
|         Configure a parser with this command's arguments.
 | |
| 
 | |
|         :param parser: argparse parser or subparser object.
 | |
|         """
 | |
|         default_path = os.environ.get('QMP_SOCKET')
 | |
|         parser.add_argument(
 | |
|             '--socket', '-s',
 | |
|             dest='socket',
 | |
|             action='store',
 | |
|             help='QMP socket path or address (addr:port).'
 | |
|             ' May also be set via QMP_SOCKET environment variable.',
 | |
|             default=default_path
 | |
|         )
 | |
|         parser.set_defaults(cmd_class=cls)
 | |
| 
 | |
|     @classmethod
 | |
|     def add_path_prop_arg(cls, parser: argparse.ArgumentParser) -> None:
 | |
|         """
 | |
|         Add the <path>.<proptery> positional argument to this command.
 | |
| 
 | |
|         :param parser: The parser to add the argument to.
 | |
|         """
 | |
|         parser.add_argument(
 | |
|             'path_prop',
 | |
|             metavar='<path>.<property>',
 | |
|             action='store',
 | |
|             help="QOM path and property, separated by a period '.'"
 | |
|         )
 | |
| 
 | |
|     def run(self) -> int:
 | |
|         """
 | |
|         Run this command.
 | |
| 
 | |
|         :return: 0 on success, 1 otherwise.
 | |
|         """
 | |
|         raise NotImplementedError
 | |
| 
 | |
|     def qom_list(self, path: str) -> List[ObjectPropertyInfo]:
 | |
|         """
 | |
|         :return: a strongly typed list from the 'qom-list' command.
 | |
|         """
 | |
|         rsp = self.qmp.command('qom-list', path=path)
 | |
|         # qom-list returns List[ObjectPropertyInfo]
 | |
|         assert isinstance(rsp, list)
 | |
|         return [ObjectPropertyInfo.make(x) for x in rsp]
 | |
| 
 | |
|     @classmethod
 | |
|     def command_runner(
 | |
|             cls: Type[CommandT],
 | |
|             args: argparse.Namespace
 | |
|     ) -> int:
 | |
|         """
 | |
|         Run a fully-parsed subcommand, with error-handling for the CLI.
 | |
| 
 | |
|         :return: The return code from `run()`.
 | |
|         """
 | |
|         try:
 | |
|             cmd = cls(args)
 | |
|             return cmd.run()
 | |
|         except QMPError as err:
 | |
|             print(f"{type(err).__name__}: {err!s}", file=sys.stderr)
 | |
|             return -1
 | |
| 
 | |
|     @classmethod
 | |
|     def entry_point(cls) -> int:
 | |
|         """
 | |
|         Build this command's parser, parse arguments, and run the command.
 | |
| 
 | |
|         :return: `run`'s return code.
 | |
|         """
 | |
|         parser = argparse.ArgumentParser(description=cls.help)
 | |
|         cls.configure_parser(parser)
 | |
|         args = parser.parse_args()
 | |
|         return cls.command_runner(args)
 |