[PATCH 08/10] qemu-replies-tool: Add mode to dump all QMP schema query strings

Peter Krempa posted 10 patches 12 months ago
[PATCH 08/10] qemu-replies-tool: Add mode to dump all QMP schema query strings
Posted by Peter Krempa 12 months ago
Make the tool useful also for non-testing purposes by adding 'dump'
mode, which will process the data and output information about the qemu
version.

The first 'dump' mode produces all possible valid query strings per
virQEMUQAPISchemaPathGet/virQEMUCapsQMPSchemaQueries. This is useful for
users to look up a query string via 'grep' rather than trying to come up
with it manually.

Additionally the data as represented by qemu changes naming very often
and that makes it un-reviewable to find changes between two qemu builds.
By using the dump mode, which produces results in stable order we can
use it to 'diff' two .replies file without churn.

Sample output '[...]' denotes an arbitrary trim:

$ ./scripts/qemu-replies-tool.py tests/qemucapabilitiesdata/caps_9.0.0_x86_64.replies --dump-qmp-query-strings
[...]
(qmp) blockdev-add
(qmp) blockdev-add/arg-type/auto-read-only
(qmp) blockdev-add/arg-type/auto-read-only/!bool
(qmp) blockdev-add/arg-type/cache
(qmp) blockdev-add/arg-type/cache/direct
(qmp) blockdev-add/arg-type/cache/direct/!bool
(qmp) blockdev-add/arg-type/cache/no-flush
(qmp) blockdev-add/arg-type/cache/no-flush/!bool
(qmp) blockdev-add/arg-type/detect-zeroes
(qmp) blockdev-add/arg-type/detect-zeroes/^off
(qmp) blockdev-add/arg-type/detect-zeroes/^on
(qmp) blockdev-add/arg-type/detect-zeroes/^unmap
[...]
(qmp) blockdev-add/arg-type/driver
(qmp) blockdev-add/arg-type/driver/^blkdebug
(qmp) blockdev-add/arg-type/driver/^blklogwrites
(qmp) blockdev-add/arg-type/driver/^blkreplay
(qmp) blockdev-add/arg-type/driver/^blkverify
(qmp) blockdev-add/arg-type/driver/^bochs
(qmp) blockdev-add/arg-type/driver/^cloop
[...]
(qmp) blockdev-add/arg-type/+blkdebug
(qmp) blockdev-add/arg-type/+blkdebug/align
(qmp) blockdev-add/arg-type/+blkdebug/align/!int
(qmp) blockdev-add/arg-type/+blkdebug/config
(qmp) blockdev-add/arg-type/+blkdebug/config/!str
(qmp) blockdev-add/arg-type/+blkdebug/image
(qmp) blockdev-add/arg-type/+blkdebug/image (recursion)
(qmp) blockdev-add/arg-type/+blkdebug/image/!str
(qmp) blockdev-add/arg-type/+blkdebug/inject-error

Signed-off-by: Peter Krempa <pkrempa@redhat.com>
---
 scripts/qemu-replies-tool.py | 112 +++++++++++++++++++++++++++++++++++
 1 file changed, 112 insertions(+)

diff --git a/scripts/qemu-replies-tool.py b/scripts/qemu-replies-tool.py
index c5dee9a66a..5dcc975756 100755
--- a/scripts/qemu-replies-tool.py
+++ b/scripts/qemu-replies-tool.py
@@ -290,9 +290,95 @@ def validate_qmp_schema(schemalist):
                 raise qmpSchemaException("unknown or missing 'meta-type' in schema entry '%s'" % entry)


+# Recursively traverse the schema and print out the schema query strings for
+# the corresponding entries. In certain cases the schema references itself,
+# which is handled by passing a 'trace' list which contains the current path
+def dump_qmp_probe_strings_iter(name, cur, trace, schema):
+    obj = schema[name]
+
+    if name in trace:
+        print('%s (recursion)' % cur)
+        return
+
+    trace = trace + [name]
+
+    match obj['meta-type']:
+        case 'command' | 'event':
+            arguments = obj.get('arg-type', None)
+            returns = obj.get('ret-type', None)
+
+            print(cur)
+
+            for f in obj.get('features', []):
+                print('%s/$%s' % (cur, f))
+
+            if arguments:
+                dump_qmp_probe_strings_iter(arguments, cur + '/arg-type', trace, schema)
+
+            if returns:
+                dump_qmp_probe_strings_iter(returns, cur + '/ret-type', trace, schema)
+
+        case 'object':
+            members = sorted(obj.get('members', []), key=lambda d: d['name'])
+            variants = sorted(obj.get('variants', []), key=lambda d: d['case'])
+
+            for f in obj.get('features', []):
+                print('%s/$%s' % (cur, f))
+
+            for memb in members:
+                membpath = "%s/%s" % (cur, memb['name'])
+                print(membpath)
+
+                for f in memb.get('features', []):
+                    print('%s/$%s' % (membpath, f))
+
+                dump_qmp_probe_strings_iter(memb['type'], membpath, trace, schema)
+
+            for var in variants:
+                varpath = "%s/+%s" % (cur, var['case'])
+                print(varpath)
+                dump_qmp_probe_strings_iter(var['type'], varpath, trace, schema)
+
+        case 'enum':
+            members = sorted(obj.get('members', []), key=lambda d: d['name'])
+
+            for m in members:
+                print('%s/^%s' % (cur, m['name']))
+
+                for f in m.get('features', []):
+                    print('%s/^%s/$%s' % (cur, m['name'], f))
+
+        case 'array':
+            dump_qmp_probe_strings_iter(obj['element-type'], cur, trace, schema)
+
+        case 'builtin':
+            print('%s/!%s' % (cur, name))
+
+        case 'alternate':
+            for var in obj['members']:
+                dump_qmp_probe_strings_iter(var['type'], cur, trace, schema)
+
+
+def dump_qmp_probe_strings(schemalist):
+    schemadict = {}
+    toplevel = []
+
+    for memb in schemalist:
+        schemadict[memb['name']] = memb
+
+        if memb['meta-type'] == 'command' or memb['meta-type'] == 'event':
+            toplevel.append(memb['name'])
+
+    toplevel.sort()
+
+    for c in toplevel:
+        dump_qmp_probe_strings_iter(c, '(qmp) ' + c, [], schemadict)
+
+
 def process_one(filename, args):
     try:
         conv = qemu_replies_load(filename)
+        dumped = False

         modify_replies(conv)

@@ -300,6 +386,13 @@ def process_one(filename, args):
             if cmd['execute'] == 'query-qmp-schema':
                 validate_qmp_schema(rep['return'])

+                if args.dump_all or args.dump_qmp_query_strings:
+                    dump_qmp_probe_strings(rep['return'])
+                    dumped = True
+
+        if dumped:
+            return True
+
         qemu_replies_compare_or_replace(filename, conv, args.regenerate)

     except qrtException as e:
@@ -327,6 +420,19 @@ The default mode is validation which checks the following:
     - the input file has the expected JSON formatting
     - the QMP schema from qemu is fully covered by libvirt's code

+In 'dump' mode if '-dump-all' or one of the specific '-dump-*' flags (below)
+is selected the script outputs information gathered from the given '.replies'
+file. The data is also usable for comparing two '.replies' files in a "diffable"
+fashion as many of the query commands may change ordering or naming without
+functional impact on libvirt.
+
+  --dump-qmp-query-strings
+
+    Dumps all possible valid QMP capability query strings based on the current
+    qemu version in format used by virQEMUQAPISchemaPathGet or
+    virQEMUCapsQMPSchemaQueries. It's useful to find specific query string
+    without having to piece the information together from 'query-qmp-schema'
+
 The tool can be also used to programmaticaly modify the '.replies' file by
 editting the 'modify_replies' method directly in the source, or for
 re-formatting and re-numbering the '.replies' file to conform with the required
@@ -351,6 +457,12 @@ parser.add_argument('--repliesdir', default='',
 parser.add_argument('replyfile', nargs='?',
                     help='path to .replies file to use')

+parser.add_argument('--dump-all', action='store_true',
+                    help='invoke all --dump-* sub-commands')
+
+parser.add_argument('--dump-qmp-query-strings', action='store_true',
+                    help='dump QMP schema in form of query strings used to probe capabilities')
+
 args = parser.parse_args()

 if not args.replyfile and not args.repliesdir:
-- 
2.43.0
_______________________________________________
Devel mailing list -- devel@lists.libvirt.org
To unsubscribe send an email to devel-leave@lists.libvirt.org
Re: [PATCH 08/10] qemu-replies-tool: Add mode to dump all QMP schema query strings
Posted by Andrea Bolognani 11 months, 3 weeks ago
On Tue, Jan 16, 2024 at 05:12:42PM +0100, Peter Krempa wrote:
> +# Recursively traverse the schema and print out the schema query strings for
> +# the corresponding entries. In certain cases the schema references itself,
> +# which is handled by passing a 'trace' list which contains the current path
> +def dump_qmp_probe_strings_iter(name, cur, trace, schema):
> +    obj = schema[name]
> +
> +    if name in trace:
> +        print('%s (recursion)' % cur)
> +        return

I'll openly admit that I'm pretty much completely unfamiliar with
this specific query DLS, so it might be a silly question, but what's
the use of printing this line? AFAICT it's just the line above it,
with ' (recursion)' appended. Wouldn't it make sense to skip it?

Other than that, the implementation looks sensible, though I might
have overlooked some subtlety because of the aforementioned
unfamiliarity.

-- 
Andrea Bolognani / Red Hat / Virtualization
_______________________________________________
Devel mailing list -- devel@lists.libvirt.org
To unsubscribe send an email to devel-leave@lists.libvirt.org
Re: [PATCH 08/10] qemu-replies-tool: Add mode to dump all QMP schema query strings
Posted by Peter Krempa 11 months, 3 weeks ago
On Thu, Jan 25, 2024 at 10:03:52 -0800, Andrea Bolognani wrote:
> On Tue, Jan 16, 2024 at 05:12:42PM +0100, Peter Krempa wrote:
> > +# Recursively traverse the schema and print out the schema query strings for
> > +# the corresponding entries. In certain cases the schema references itself,
> > +# which is handled by passing a 'trace' list which contains the current path
> > +def dump_qmp_probe_strings_iter(name, cur, trace, schema):
> > +    obj = schema[name]
> > +
> > +    if name in trace:
> > +        print('%s (recursion)' % cur)
> > +        return
> 
> I'll openly admit that I'm pretty much completely unfamiliar with
> this specific query DLS, so it might be a silly question, but what's
> the use of printing this line? AFAICT it's just the line above it,
> with ' (recursion)' appended. Wouldn't it make sense to skip it?

This line isn't in fact a valid query even. In the query language we
don't care about the infinitely nesting types and you theoretically can
query arbitrarily deep into the recursion.

For the dumper it obviously is a problem as it has to find all
possibilities.

So it might be confusing for anyone looking for a query string, but on
the other hand it's useful when comparing two qemu versions against each
other.

Additionally IIRC (I've wrote this code some time ago already) it's
needed to display the name of the entry, so that e.g. you know that the
object has a member named that way even if it refers to a part of the
schema that was already traversed.

> 
> Other than that, the implementation looks sensible, though I might
> have overlooked some subtlety because of the aforementioned
> unfamiliarity.
> 
> -- 
> Andrea Bolognani / Red Hat / Virtualization
> 
_______________________________________________
Devel mailing list -- devel@lists.libvirt.org
To unsubscribe send an email to devel-leave@lists.libvirt.org
Re: Re: [PATCH 08/10] qemu-replies-tool: Add mode to dump all QMP schema query strings
Posted by Andrea Bolognani 11 months, 3 weeks ago
On Fri, Jan 26, 2024 at 09:37:54AM +0100, Peter Krempa wrote:
> On Thu, Jan 25, 2024 at 10:03:52 -0800, Andrea Bolognani wrote:
> > On Tue, Jan 16, 2024 at 05:12:42PM +0100, Peter Krempa wrote:
> > > +# Recursively traverse the schema and print out the schema query strings for
> > > +# the corresponding entries. In certain cases the schema references itself,
> > > +# which is handled by passing a 'trace' list which contains the current path
> > > +def dump_qmp_probe_strings_iter(name, cur, trace, schema):
> > > +    obj = schema[name]
> > > +
> > > +    if name in trace:
> > > +        print('%s (recursion)' % cur)
> > > +        return
> >
> > I'll openly admit that I'm pretty much completely unfamiliar with
> > this specific query DLS, so it might be a silly question, but what's
> > the use of printing this line? AFAICT it's just the line above it,
> > with ' (recursion)' appended. Wouldn't it make sense to skip it?
>
> This line isn't in fact a valid query even. In the query language we
> don't care about the infinitely nesting types and you theoretically can
> query arbitrarily deep into the recursion.
>
> For the dumper it obviously is a problem as it has to find all
> possibilities.
>
> So it might be confusing for anyone looking for a query string, but on
> the other hand it's useful when comparing two qemu versions against each
> other.
>
> Additionally IIRC (I've wrote this code some time ago already) it's
> needed to display the name of the entry, so that e.g. you know that the
> object has a member named that way even if it refers to a part of the
> schema that was already traversed.

I've tried dropping the print and that doesn't seem to affect
anything other than the print itself, as expected. For example:

   (qmp) blockdev-create/arg-type/options/+qcow2/data-file/+vmdk
   (qmp) blockdev-create/arg-type/options/+qcow2/data-file/+vmdk/backing
  -(qmp) blockdev-create/arg-type/options/+qcow2/data-file/+vmdk/backing
(recursion)
   (qmp) blockdev-create/arg-type/options/+qcow2/data-file/+vmdk/backing/!str
   (qmp) blockdev-create/arg-type/options/+qcow2/data-file/+vmdk/backing/!null
   (qmp) blockdev-create/arg-type/options/+qcow2/data-file/+vmdk/file
  -(qmp) blockdev-create/arg-type/options/+qcow2/data-file/+vmdk/file
(recursion)

Anyway, as I said I don't really understand the query language and
don't care enough to dig further into it. If you think that the
additional lines are useful, keep them. Either way,

Reviewed-by: Andrea Bolognani <abologna@redhat.com>

-- 
Andrea Bolognani / Red Hat / Virtualization
_______________________________________________
Devel mailing list -- devel@lists.libvirt.org
To unsubscribe send an email to devel-leave@lists.libvirt.org