37094b6dd5
Now that we are fully switched over to the new QMP library, move it back over the old namespace. This is being done primarily so that we may upload this package simply as "qemu.qmp" without introducing confusion over whether or not "aqmp" is a new protocol or not. The trade-off is increased confusion inside the QEMU developer tree. Sorry! Note: the 'private' member "_aqmp" in legacy.py also changes to "_qmp"; not out of necessity, but just to remove any traces of the "aqmp" name. Signed-off-by: John Snow <jsnow@redhat.com> Reviewed-by: Beraldo Leal <bleal@redhat.com> Acked-by: Hanna Reitz <hreitz@redhat.com> Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@openvz.org> Message-id: 20220330172812.3427355-8-jsnow@redhat.com Signed-off-by: John Snow <jsnow@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.qmp import QMPError
|
|
from qemu.qmp.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)
|