From: John Snow <jsnow@redhat.com>
To: qemu-devel@nongnu.org
Cc: Cleber Rosa <crosa@redhat.com>, John Snow <jsnow@redhat.com>,
Markus Armbruster <armbru@redhat.com>,
Eduardo Habkost <ehabkost@redhat.com>
Subject: [PATCH v2 01/72] python/qmp: Add qom script rewrites
Date: Tue, 3 Nov 2020 19:34:51 -0500 [thread overview]
Message-ID: <20201104003602.1293560-2-jsnow@redhat.com> (raw)
In-Reply-To: <20201104003602.1293560-1-jsnow@redhat.com>
Inspired by qom-set, qom-get, qom-tree and qom-list; combine all four of
those scripts into one script.
A later addition of qom-fuse necessitates that some common features are
split out and shared between them.
Signed-off-by: John Snow <jsnow@redhat.com>
---
python/qemu/qmp/qom.py | 207 ++++++++++++++++++++++++++++++++++
python/qemu/qmp/qom_common.py | 153 +++++++++++++++++++++++++
2 files changed, 360 insertions(+)
create mode 100644 python/qemu/qmp/qom.py
create mode 100644 python/qemu/qmp/qom_common.py
diff --git a/python/qemu/qmp/qom.py b/python/qemu/qmp/qom.py
new file mode 100644
index 000000000000..912d1809e60d
--- /dev/null
+++ b/python/qemu/qmp/qom.py
@@ -0,0 +1,207 @@
+"""
+QEMU Object Model testing tools.
+
+usage: qom.py [-h] {set,get,list,tree} ...
+
+Query and manipulate QOM data
+
+optional arguments:
+ -h, --help show this help message and exit
+
+QOM commands:
+ {set,get,list,tree,fuse}
+ set Set a QOM property value
+ get Get a QOM property value
+ list List QOM properties at a given path
+ tree Show QOM tree from a given path
+"""
+##
+# 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 sys
+
+from . import QMPResponseError
+from .qom_common import QOMCommand
+
+
+class QOMSet(QOMCommand):
+ """QOM Command - Set a property to a given value."""
+ name = 'set'
+ help = 'Set a QOM property value'
+
+ @classmethod
+ def configure_parser(cls, parser: argparse.ArgumentParser) -> None:
+ super().configure_parser(parser)
+ cls.add_path_prop_arg(parser)
+ parser.add_argument(
+ 'value',
+ metavar='<value>',
+ action='store',
+ help='new QOM property value'
+ )
+
+ def __init__(self, args: argparse.Namespace):
+ super().__init__(args)
+ self.path, self.prop = args.path_prop.rsplit('.', 1)
+ self.value = args.value
+
+ def run(self) -> int:
+ rsp = self.qmp.command(
+ 'qom-set',
+ path=self.path,
+ property=self.prop,
+ value=self.value
+ )
+ print(rsp)
+ return 0
+
+
+class QOMGet(QOMCommand):
+ """QOM Command - Get a property's current value."""
+ name = 'get'
+ help = 'Get a QOM property value'
+
+ @classmethod
+ def configure_parser(cls, parser: argparse.ArgumentParser) -> None:
+ super().configure_parser(parser)
+ cls.add_path_prop_arg(parser)
+
+ def __init__(self, args: argparse.Namespace):
+ super().__init__(args)
+ try:
+ tmp = args.path_prop.rsplit('.', 1)
+ except ValueError as err:
+ raise ValueError('Invalid format for <path>.<property>') from err
+ self.path = tmp[0]
+ self.prop = tmp[1]
+
+ def run(self) -> int:
+ rsp = self.qmp.command(
+ 'qom-get',
+ path=self.path,
+ property=self.prop
+ )
+ if isinstance(rsp, dict):
+ for key, value in rsp.items():
+ print(f"{key}: {value}")
+ else:
+ print(rsp)
+ return 0
+
+
+class QOMList(QOMCommand):
+ """QOM Command - List the properties at a given path."""
+ name = 'list'
+ help = 'List QOM properties at a given path'
+
+ @classmethod
+ def configure_parser(cls, parser: argparse.ArgumentParser) -> None:
+ super().configure_parser(parser)
+ parser.add_argument(
+ 'path',
+ metavar='<path>',
+ action='store',
+ help='QOM path',
+ )
+
+ def __init__(self, args: argparse.Namespace):
+ super().__init__(args)
+ self.path = args.path
+
+ def run(self) -> int:
+ rsp = self.qom_list(self.path)
+ for item in rsp:
+ if item.child:
+ print(f"{item.name}/")
+ elif item.link:
+ print(f"@{item.name}/")
+ else:
+ print(item.name)
+ return 0
+
+
+class QOMTree(QOMCommand):
+ """QOM Command - Show the full tree below a given path."""
+ name = 'tree'
+ help = 'Show QOM tree from a given path'
+
+ @classmethod
+ def configure_parser(cls, parser: argparse.ArgumentParser) -> None:
+ super().configure_parser(parser)
+ parser.add_argument(
+ 'path',
+ metavar='<path>',
+ action='store',
+ help='QOM path',
+ nargs='?',
+ default='/'
+ )
+
+ def __init__(self, args: argparse.Namespace):
+ super().__init__(args)
+ self.path = args.path
+
+ def _list_node(self, path: str) -> None:
+ print(path)
+ items = self.qom_list(path)
+ for item in items:
+ if item.child:
+ continue
+ try:
+ rsp = self.qmp.command('qom-get', path=path,
+ property=item.name)
+ print(f" {item.name}: {rsp} ({item.type})")
+ except QMPResponseError as err:
+ print(f" {item.name}: <EXCEPTION: {err!s}> ({item.type})")
+ print('')
+ for item in items:
+ if not item.child:
+ continue
+ if path == '/':
+ path = ''
+ self._list_node(f"{path}/{item.name}")
+
+ def run(self) -> int:
+ self._list_node(self.path)
+ return 0
+
+
+def main() -> int:
+ """QOM script main entry point."""
+ parser = argparse.ArgumentParser(
+ description='Query and manipulate QOM data'
+ )
+ subparsers = parser.add_subparsers(
+ title='QOM commands',
+ dest='command'
+ )
+
+ for command in QOMCommand.__subclasses__():
+ command.register(subparsers)
+
+ args = parser.parse_args()
+
+ if args.command is None:
+ parser.error('Command not specified.')
+ return 1
+
+ assert issubclass(args.cmd_class, QOMCommand)
+ cmd = args.cmd_class(args)
+ assert isinstance(cmd, QOMCommand)
+ return cmd.run()
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/python/qemu/qmp/qom_common.py b/python/qemu/qmp/qom_common.py
new file mode 100644
index 000000000000..3890b247ae68
--- /dev/null
+++ b/python/qemu/qmp/qom_common.py
@@ -0,0 +1,153 @@
+"""
+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
+from typing import (
+ Any,
+ Dict,
+ List,
+ Optional,
+)
+
+from . import QEMUMonitorProtocol
+
+
+Subparsers = argparse._SubParsersAction # pylint: disable=protected-access
+
+
+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<')
+
+
+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 Exception("No QMP socket path or address given")
+ self.qmp = QEMUMonitorProtocol(args.socket)
+ self.qmp.connect()
+
+ @classmethod
+ def register(cls, subparsers: Subparsers) -> 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 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()
+ cmd = cls(args)
+ return cmd.run()
--
2.26.2
next prev parent reply other threads:[~2020-11-04 0:44 UTC|newest]
Thread overview: 73+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-11-04 0:34 [PATCH v2 00/72] python: move scripts/qmp to python/qemu/qmp John Snow
2020-11-04 0:34 ` John Snow [this message]
2020-11-04 0:34 ` [PATCH v2 02/72] python/qmp: add qom script entry points John Snow
2020-11-04 0:34 ` [PATCH v2 03/72] scripts/qmp: redirect qom-xxx scripts to python/qemu/qmp/ John Snow
2020-11-04 0:34 ` [PATCH v2 04/72] scripts/qom-fuse: apply isort rules John Snow
2020-11-04 0:34 ` [PATCH v2 05/72] scripts/qom-fuse: apply flake8 rules John Snow
2020-11-04 0:34 ` [PATCH v2 06/72] python: Add 'fh' to known-good variable names John Snow
2020-11-04 0:34 ` [PATCH v2 07/72] scripts/qom-fuse: Apply pylint rules John Snow
2020-11-04 0:34 ` [PATCH v2 08/72] scripts/qom-fuse: Add docstrings John Snow
2020-11-04 0:34 ` [PATCH v2 09/72] scripts/qom-fuse: Convert to QOMCommand John Snow
2020-11-04 0:35 ` [PATCH v2 10/72] scripts/qom-fuse: use QOMCommand.qom_list() John Snow
2020-11-04 0:35 ` [PATCH v2 11/72] scripts/qom-fuse: ensure QOMFuse.read always returns bytes John Snow
2020-11-04 0:35 ` [PATCH v2 12/72] scripts/qom-fuse: add static type hints John Snow
2020-11-04 0:35 ` [PATCH v2 13/72] scripts/qom-fuse: move to python/qemu/qmp/qom_fuse.py John Snow
2020-11-04 0:35 ` [PATCH v2 14/72] scripts/qom-fuse: add redirection shim to python/qemu/qmp/qom-fuse.py John Snow
2020-11-04 0:35 ` [PATCH v2 15/72] python: add fuse command to 'qom' tools John Snow
2020-11-04 0:35 ` [PATCH v2 16/72] python: add optional fuse dependency John Snow
2020-11-04 0:35 ` [PATCH v2 17/72] scripts/qemu-ga-client: apply isort rules John Snow
2020-11-04 0:35 ` [PATCH v2 18/72] scripts/qemu-ga-client: apply (most) flake8 rules John Snow
2020-11-04 0:35 ` [PATCH v2 19/72] scripts/qemu-ga-client: Fix exception handling John Snow
2020-11-04 0:35 ` [PATCH v2 20/72] scripts/qemu-ga-client: replace deprecated optparse with argparse John Snow
2020-11-04 0:35 ` [PATCH v2 21/72] scripts/qemu-ga-client: add module docstring John Snow
2020-11-04 0:35 ` [PATCH v2 22/72] scripts/qemu-ga-client: apply (most) pylint rules John Snow
2020-11-04 0:35 ` [PATCH v2 23/72] python/qmp: Correct type of QMPReturnValue John Snow
2020-11-04 0:35 ` [PATCH v2 24/72] scripts/qmp/qemu-ga-client: add mypy type hints John Snow
2020-11-04 0:35 ` [PATCH v2 25/72] scripts/qemu-ga-client: move to python/qemu/qmp/qemu_ga_client.py John Snow
2020-11-04 0:35 ` [PATCH v2 26/72] python/qemu-ga-client: add entry point John Snow
2020-11-04 0:35 ` [PATCH v2 27/72] scripts/qemu-ga-client: Add forwarder stub John Snow
2020-11-04 0:35 ` [PATCH v2 28/72] scripts/qmp-shell: apply isort rules John Snow
2020-11-04 0:35 ` [PATCH v2 29/72] scripts/qmp-shell: Apply flake8 rules John Snow
2020-11-04 0:35 ` [PATCH v2 30/72] scripts/qmp-shell: fix show_banner signature John Snow
2020-11-04 0:35 ` [PATCH v2 31/72] scripts/qmp-shell: fix exception handling John Snow
2020-11-04 0:35 ` [PATCH v2 32/72] scripts/qmp-shell: fix connect method signature John Snow
2020-11-04 0:35 ` [PATCH v2 33/72] scripts/qmp-shell: remove shadowed variable from _print() John Snow
2020-11-04 0:35 ` [PATCH v2 34/72] scripts/qmp-shell: use @classmethod where appropriate John Snow
2020-11-04 0:35 ` [PATCH v2 35/72] scripts/qmp-shell: Use python3-style super() John Snow
2020-11-04 0:35 ` [PATCH v2 36/72] scripts/qmp-shell: declare verbose in __init__ John Snow
2020-11-04 0:35 ` [PATCH v2 37/72] scripts/qmp-shell: use triple-double-quote docstring style John Snow
2020-11-04 0:35 ` [PATCH v2 38/72] scripts/qmp-shell: ignore visit_Name name John Snow
2020-11-04 0:35 ` [PATCH v2 39/72] scripts/qmp-shell: make QMPCompleter returns explicit John Snow
2020-11-04 0:35 ` [PATCH v2 40/72] scripts/qmp-shell: rename one and two-letter variables John Snow
2020-11-04 0:35 ` [PATCH v2 41/72] scripts/qmp-shell: fix shell history exception handling John Snow
2020-11-04 0:35 ` [PATCH v2 42/72] scripts/qmp-shell: explicitly chain exception context John Snow
2020-11-04 0:35 ` [PATCH v2 43/72] scripts/qmp-shell: remove if-raise-else patterns John Snow
2020-11-04 0:35 ` [PATCH v2 44/72] scripts/qmp-shell: use isinstance() instead of type() John Snow
2020-11-04 0:35 ` [PATCH v2 45/72] scripts/qmp-shell: use argparse John Snow
2020-11-04 0:35 ` [PATCH v2 46/72] python/qmp: Fix type of SocketAddrT John Snow
2020-11-04 0:35 ` [PATCH v2 47/72] python/qmp: add parse_address classmethod John Snow
2020-11-04 0:35 ` [PATCH v2 48/72] scripts/qmp-shell: Add pretty attribute to HMP shell John Snow
2020-11-04 0:35 ` [PATCH v2 49/72] scripts/qmp-shell: Make verbose a public attribute John Snow
2020-11-04 0:35 ` [PATCH v2 50/72] scripts/qmp-shell: move get_prompt() to prompt property John Snow
2020-11-04 0:35 ` [PATCH v2 51/72] scripts/qmp-shell: remove prompt argument from read_exec_command John Snow
2020-11-04 0:35 ` [PATCH v2 52/72] scripts/qmp-shell: move the REPL functionality into QMPShell John Snow
2020-11-04 0:35 ` [PATCH v2 53/72] scripts/qmp-shell: Fix "FuzzyJSON" parser John Snow
2020-11-04 0:35 ` [PATCH v2 54/72] scripts/qmp-shell: refactor QMPCompleter John Snow
2020-11-04 0:35 ` [PATCH v2 55/72] scripts/qmp-shell: initialize completer early John Snow
2020-11-04 0:35 ` [PATCH v2 56/72] python/qmp: add QMPObject type alias John Snow
2020-11-04 0:35 ` [PATCH v2 57/72] scripts/qmp-shell: add mypy types John Snow
2020-11-04 0:35 ` [PATCH v2 58/72] scripts/qmp-shell: Accept SocketAddrT instead of string John Snow
2020-11-04 0:35 ` [PATCH v2 59/72] scripts/qmp-shell: unprivatize 'pretty' property John Snow
2020-11-04 0:35 ` [PATCH v2 60/72] python/qmp: return generic type from context manager John Snow
2020-11-04 0:35 ` [PATCH v2 61/72] scripts/qmp-shell: Use context manager instead of atexit John Snow
2020-11-04 0:35 ` [PATCH v2 62/72] scripts/qmp-shell: use logging to show warnings John Snow
2020-11-04 0:35 ` [PATCH v2 63/72] scripts/qmp-shell: remove TODO John Snow
2020-11-04 0:35 ` [PATCH v2 64/72] scripts/qmp-shell: Fix empty-transaction invocation John Snow
2020-11-04 0:35 ` [PATCH v2 65/72] scripts/qmp-shell: Remove too-broad-exception John Snow
2020-11-04 0:35 ` [PATCH v2 66/72] scripts/qmp-shell: convert usage comment to docstring John Snow
2020-11-04 0:35 ` [PATCH v2 67/72] scripts/qmp-shell: remove double-underscores John Snow
2020-11-04 0:35 ` [PATCH v2 68/72] scripts/qmp-shell: make QMPShellError inherit QMPError John Snow
2020-11-04 0:35 ` [PATCH v2 69/72] scripts/qmp-shell: add docstrings John Snow
2020-11-04 0:36 ` [PATCH v2 70/72] scripts/qmp-shell: move to python/qemu/qmp/qmp_shell.py John Snow
2020-11-04 0:36 ` [PATCH v2 71/72] python: add qmp-shell entry point John Snow
2020-11-04 0:36 ` [PATCH v2 72/72] scripts/qmp-shell: add redirection shim John Snow
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20201104003602.1293560-2-jsnow@redhat.com \
--to=jsnow@redhat.com \
--cc=armbru@redhat.com \
--cc=crosa@redhat.com \
--cc=ehabkost@redhat.com \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).