[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [Qemu-devel] [PATCH RFC v3 30/32] qapi: New QMP command query-schema
From: |
Markus Armbruster |
Subject: |
Re: [Qemu-devel] [PATCH RFC v3 30/32] qapi: New QMP command query-schema for QMP schema introspection |
Date: |
Thu, 06 Aug 2015 08:23:05 +0200 |
User-agent: |
Gnus/5.13 (Gnus v5.13) Emacs/24.5 (gnu/linux) |
Eric Blake <address@hidden> writes:
> On 08/04/2015 09:58 AM, Markus Armbruster wrote:
>> Caution, rough edges.
>>
>> qapi/introspect.json defines the introspection schema. It should do
>> for uses other than QMP.
>> FIXME it's almost entirely devoid of comments.
>>
>
> That part explains why this is still RFC.
>
>> The introspection schema does not reflect all the rules and
>> restrictions that apply to QAPI schemata. A valid QAPI schema has an
>> introspection value conforming to the introspection schema, but the
>> converse is not true.
>>
>> Introspection lowers away a number of schema details:
>>
>> * The built-in types are declared with their JSON type.
>>
>> TODO Should we map all the integer types to just int?
>
> Is it worth squashing that fix (patch 31) into this one?
If we're sufficiently sure the mapping is here to stay.
>>
>> * Implicit type definitions are made explicit, and given
>> auto-generated names. These names start with ':' so they don't
>> clash with the user's names.
>>
>> Example: a simple union implicitly defines an enumeration type for
>> its discriminator.
>
> The names all change if we go with patch 32; but that one should stay
> separate.
Agree.
>>
>> * All type references are by name.
>>
>> * Base types are flattened.
>>
>> * The top type (named 'any') can hold any value.
>>
>> * The struct and union types are generalized into an object type.
>>
>> * Commands take a single argument and return a single result.
>>
>> Dictionary argument/result or list result is an implicit type
>> definition.
>
> Dictionary result is not possible, due to earlier changes.
Will update.
>>
>> The empty object type is used when a command takes no arguments or
>> produces no results.
>>
>> The argument is always of object type, but the introspection schema
>> doesn't reflect that.
>>
>> The 'gen': false directive is omitted as implementation detail.
>>
>> The 'success-response' directive is ommitted as well for now, even
>
> s/ommitted/omitted/
Will fix.
>> though it's not an implementation detail.
>>
>> * Events carry a single data value.
>>
>> Implicit type definition and empty object type use, just like for
>> commands.
>>
>> The value is of object type, but the introspection schema doesn't
>> reflect that.
>>
>> * Types not used by commands or events are omitted.
>>
>> Indirect use counts as use.
>>
>> * Optional members have a default, which can only be null right now
>>
>> Instead of a mandatory "optional" flag, we have an optional default.
>> No default means mandatory, default null means optional without
>> default value. Non-null is available for optional with default.
>>
>> Alternate members can't have defaults, but the introspection schema
>> doesn't reflect that.
>
> This sentence is no longer necessary, now that alternates have their own
> QAPI representation.
Will update.
>>
>> * Clients should *not* look up types by name, because type names are
>> not ABI. Look up the command or event you're interested in, then
>> follow the references.
>>
>> TODO Should we hide the type names to eliminate the temptation?
>>
>> TODO much of the above should go into docs.
>>
>> New generator scripts/qapi-introspect.py computes an introspection
>> value for its input, and generates a C variable holding it.
>>
>> FIXME it can generate awfully long lines
>
> Worth repeating this FIXME in the code?
Yes.
>>
>> A new test-qmp-input-visitor test case feeds its result for both
>> tests/qapi-schema/qapi-schema-test.json and qapi-schema.json to a
>> QmpInputVisitor to verify it actually conforms to the schema.
>>
>> New QMP command query-schema takes its return value from that
>> variable. Command documentation is incomplete, and marked FIXME. Its
>> reply is some 85KiBytes for me right now.
>>
>> If this turns out to be too much, we have a couple of options:
>>
>> * We can use shorter names in the JSON. Not the QMP style.
>>
>> * Optionally return the sub-schema for commands and events given as
>> arguments.
>>
>> Right now qmp_query_schema() sends the string literal computed by
>> qmp-introspect.py. To compute sub-schema at run time, we'd have to
>> duplicate parts of qapi-introspect.py in C. Unattractive.
>>
>> * Let clients cache the output of query-schema.
>>
>> It changes only on QEMU upgrades, i.e. rarely. Provide a command
>> query-schema-hash. Clients can have a cache indexed by hash, and
>> re-query the schema only when they don't have it cached.
>>
>> Signed-off-by: Markus Armbruster <address@hidden>
>> ---
>
> Lack of in-place documentation is still the biggest reason I'm not
> comfortable with R-b yet (although this round is a bit further along
> than v2 was); but there's also a bug that you need to fix on the
> introspection for simple unions [1].
>
>> +++ b/docs/qapi-code-gen.txt
>
>> +Example:
>> +
>> + $ python scripts/qapi-event.py --output-dir="qapi-generated"
>
> s/event/introspect/
Will fix.
>> + --prefix="example-" example-schema.json
>> + $ cat qapi-generated/example-qmp-introspect.c
>> +[Uninteresting stuff omitted...]
>> +
>> + const char example_qmp_schema_json[] = "["
>> + "{ \"arg-type\": \":empty\", \"name\": \"MY_EVENT\",
>> \"meta-type\": \"event\" }, "
>> + "{ \"meta-type\": \"builtin\", \"name\": \"int\", \"json-type\":
>> \"int\" }, "
>
> Any reason our poor-man's json formatter doesn't output dictionaries in
> any particular order? It's particularly odd that 'meta-type' and 'name'
> switch places across rows. I'm not sure if an OrderedDict and/or
> strategic call to sort() keys would make the introspection nicer for
> humans. Also, forcing a particular order means that we will be
> guaranteed identical output regardless of python version or other random
> hash factors that might otherwise render the generated file differently
> across machines. On the other hand, the end result doesn't violate JSON
> and shouldn't affect machine parsing, so it's not a reason to hold up
> the patch. See also [2]
Yes, see [2]. I realized unstable dictionary order could be annoying,
so I added sorting, but forgot to regenerate this example. Will fix.
>> + "{ \"meta-type\": \"builtin\", \"name\": \"str\", \"json-type\":
>> \"string\" }, "
>> + "{ \"meta-type\": \"object\", \"name\": \":empty\", \"members\": [
>> ] }, "
>> + "{ \"meta-type\": \"object\", \"name\": \":obj-my-command-arg\",
>> \"members\": [{ \"type\": \"UserDefOne\", \"name\": \"arg1\" } ] },
>> "
>
> Why a space after { but not after [, particularly since there is also a
> space before ] and }? Is it so you don't generate "[ ]" for :empty?
> But JSON doesn't care about amount of whitespace, so it is not fatal.
Accident. I'll normalize the spacing.
>> +/*
>> + * Minor hack: generated marshalling suppressed for this command
>> + * ('gen': false in the schema) so we can parse the JSON string
>> + * directly into QObject instead of first parsing it with
>> + * visit_type_SchemaInfoList() into a SchemaInfoList, then marshal it
>> + * to QObject with generated output marshallers, every time. Instead,
>> + * we do it in test-qmp-input-visitor.c, just to make sure
>> + * qapi-introspect.py's output actually conforms to the schema.
>> + */
>> +static void qmp_query_schema(QDict *qdict, QObject **ret_data, Error **errp)
>> +{
>> + *ret_data = qobject_from_json(qmp_schema_json);
>
> Huh, I just realized: in v2, I complained that your generated string
> used 'quotes' instead of "quotes", because I was afraid the 'quote'
> would leak onto the QMP wire and cause grief to strict-JSON clients.
> But since we are parsing the string with qobject_from_json() (our own
> parser, that understands the extension of 'string'), and then generating
> the actual JSON output from QObject, what we pass over the QMP wire
> _will_ have "quotes", even if qmp_schema_json did not. Furthermore,
> what is sent on the wire has different spacing (all one single line,
> unless you turn on the pretty qmp option), so it really doesn't matter
> that you did all the work to use \" inside the generated string, nor
> whether you use spacing for legibility.
Correct. I forgot that detail, then bought your "must use double quote
in JSON" argument. It came back later when I wondered why I see a
different order on the wire than in qmp-introspect.c, but by then the
double quote change had been paged out of my working memory.
> But at least I don't think it is
> worth to try and output the generated string directly by skipping the
> round trip into qobject and back out to JSON (it might be more
> efficient, but this command will not be frequently called, and doing the
> round trip avoids us having to worry about consistency issues).
Yes, too invasive to be worthwhile: in addition to the generated
marshaller ('gen': false), we'd have to bypass the QMP wire protocol
emitter.
> So if it makes v4 easier, you could output "'string'" rather than
> "\"string\"" for a slightly smaller generated file. But since you've
> already done the work for escaping ", I'm also fine with the style used
> in v3.
For what it's worth, json.dumps() uses double quotes. I figure it can
be made to use single quotes, but that would be work. I'm leaning
towards keeping the double quotes.
>> +++ b/qapi/introspect.json
>
>> +{ 'struct': 'SchemaInfoObject',
>> + 'data': { 'members': [ 'SchemaInfoObjectMember' ],
>> + '*tag': 'str',
>> + '*variants': [ 'SchemaInfoObjectVariant' ] } }
>
> I take it that either tag and variants will both be omitted, or both be
> present. No clean way to represent that in the schema, though.
Needs a comment.
>> +++ b/scripts/qapi-commands.py
>> @@ -260,7 +260,7 @@ class QAPISchemaGenCommandVisitor(QAPISchemaVisitor):
>> self.defn = None
>> self.regy = None
>> self.visited_ret_types = None
>> - def visit_begin(self):
>> + def visit_begin(self, schema):
>
> You already mentioned that you ran out of time in v3 to float this to an
> earlier patch; maybe v4 will be better?
I intend to give it a try to see whether it turns out better.
>> +++ b/scripts/qapi-introspect.py
>> @@ -0,0 +1,172 @@
>> +#
>> +# QAPI introspection generator
>> +#
>> +# Copyright (C) 2015 Red Hat, Inc.
>> +#
>> +# Authors:
>> +# Markus Armbruster <address@hidden>
>> +#
>> +# This work is licensed under the terms of the GNU GPL, version 2.
>> +# See the COPYING file in the top-level directory.
>> +
>> +from qapi import *
>> +
>> +# Caveman's json.dumps() replacement (we're stuck at 2.4)
>> +# TODO try to use json.dumps() once we get unstuck
>> +def to_json(obj, level=0):
>> + if obj == None:
>> + ret = 'null'
>> + elif isinstance(obj, str):
>> + ret = '"' + obj.replace('"', r'\"') + '"'
>> + elif isinstance(obj, list):
>> + elts = [to_json(elt, level + 1)
>> + for elt in obj]
>
> No sorting here makes sense (the json-to-string converter can't know
> whether we are using arrays as sets, vs. in the usual JSON semantics of
> order-preserving). Thus, if we intend to sort things like enum values
> or object members by name, that sorting has to be done prior to feeding
> it to this pretty-printer.
Correct. I didn't, because I ran out of time.
>> + ret = '[' + ', '.join(elts) + ' ]'
>
> Here's where I was wondering if you want '[ '.
Yes.
>> + elif isinstance(obj, dict):
>> + elts = ['"%s": %s' % (key, to_json(obj[key], level + 1))
>> + for key in sorted(obj.keys())]
>
> [2] Sorting here makes sense, except that I'm not sure what happened to
> the sorting! Oh, I see - you captured your docs/qapi-code-gen.txt
> _before_ adding the sort; because when I reran the steps in the docs,
> the dictionary keys for "MY_EVENT" correctly list "meta-type" before
> "name", and "json-type" before "meta-type", contrary to what you pasted
> in the docs.
You're right.
>> + ret = '{ ' + ', '.join(elts) + ' }'
>> + else:
>> + assert False # not implemented
>
> This would be number and boolean types. We would need to implement it
> when we add support for defaults, but that's down the road.
Yes. I prefer honest assert to untestable code.
>> + def _gen_json(self, name, mtype, obj={}):
>> + obj['name'] = name
>> + obj['meta-type'] = mtype
>> + self.jsons.append(obj)
>
> Why is 'obj' given a default here, when all callers pass a non-empty dict?
I'll drop it. If a caller wants {}, it can pass {}.
>> +
>> + def _gen_variants(self, tag_name, variants):
>> + return { 'tag': tag_name or 'kind',
>
> [1] Bug mentioned above. The QMP wire format for a simple union tag is
> 'type', not 'kind'.
Will fix.
>> + 'variants': [self._gen_variant(v) for v in variants] }
>
> Do you want to add a sort() on variant 'case's here?
>
>> +
>> + def visit_enum_type(self, name, info, values):
>> + self._gen_json(name, 'enum', { 'values': values })
>
> Do you want to add a sort() on values here?
I want to review the introspection schema systematically for sorting
opportunities.
>> +
>> + def visit_array_type(self, name, info, element_type):
>> + self._gen_json(name, 'array',
>> + { 'element-type': self._use_type(element_type) })
>> +
>> + def visit_object_type_flat(self, name, info, members, variants):
>> + obj = { 'members': [self._gen_member(m) for m in members] }
>
> Do you want to add a sort() on member 'name's here?
>
>> + if variants:
>> + obj.update(self._gen_variants(variants.tag_name,
>> + variants.variants))
>> + self._gen_json(name, 'object', obj)
>> +
>> + def visit_alternate_type(self, name, info, variants):
>> + self._gen_json(name, 'alternate',
>> + { 'members': [ { 'type': self._use_type(m.type) }
>> + for m in variants.variants] })
>
> Do you want to add a sort() on member 'type's here?
>
>> +++ b/tests/test-qmp-input-strict.c
>
>> +
>> +static void test_validate_qmp_introspect(TestInputVisitorData *data,
>> + const void *unused)
>> +{
>
> Indentation is off.
Will fix.
> Overall, still looking fairly close to the end product.
Thanks!
- [Qemu-devel] [PATCH RFC v3 16/32] qapi: Generate comments to simplify splitting for review, (continued)
- [Qemu-devel] [PATCH RFC v3 16/32] qapi: Generate comments to simplify splitting for review, Markus Armbruster, 2015/08/04
- [Qemu-devel] [PATCH RFC v3 26/32] qapi: Introduce a first class 'any' type, Markus Armbruster, 2015/08/04
- [Qemu-devel] [PATCH RFC v3 19/32] qapi: Clean up after recent conversions to QAPISchemaVisitor, Markus Armbruster, 2015/08/04
- [Qemu-devel] [PATCH RFC v3 22/32] qapi: Rename qmp_marshal_input_FOO() to qmp_marshal_FOO(), Markus Armbruster, 2015/08/04
- [Qemu-devel] [PATCH RFC v3 30/32] qapi: New QMP command query-schema for QMP schema introspection, Markus Armbruster, 2015/08/04
- Re: [Qemu-devel] [PATCH RFC v3 30/32] qapi: New QMP command query-schema for QMP schema introspection, Eric Blake, 2015/08/05
- Re: [Qemu-devel] [PATCH RFC v3 30/32] qapi: New QMP command query-schema for QMP schema introspection,
Markus Armbruster <=
- Re: [Qemu-devel] [PATCH RFC v3 30/32] qapi: New QMP command query-schema for QMP schema introspection, Eric Blake, 2015/08/05
- Re: [Qemu-devel] [PATCH RFC v3 30/32] qapi: New QMP command query-schema for QMP schema introspection, Eric Blake, 2015/08/23
- Re: [Qemu-devel] [PATCH RFC v3 30/32] qapi: New QMP command query-schema for QMP schema introspection, Markus Armbruster, 2015/08/24
- Re: [Qemu-devel] [PATCH RFC v3 30/32] qapi: New QMP command query-schema for QMP schema introspection, Eric Blake, 2015/08/24
- Re: [Qemu-devel] [PATCH RFC v3 30/32] qapi: New QMP command query-schema for QMP schema introspection, Eric Blake, 2015/08/24
- Re: [Qemu-devel] [PATCH RFC v3 30/32] qapi: New QMP command query-schema for QMP schema introspection, Markus Armbruster, 2015/08/24
- Re: [Qemu-devel] [PATCH RFC v3 30/32] qapi: New QMP command query-schema for QMP schema introspection, Eric Blake, 2015/08/24
- Re: [Qemu-devel] [PATCH RFC v3 30/32] qapi: New QMP command query-schema for QMP schema introspection, Markus Armbruster, 2015/08/25