qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [PATCH v3 6/6] tests/qapi-schema: Test cases for aliases


From: Markus Armbruster
Subject: Re: [PATCH v3 6/6] tests/qapi-schema: Test cases for aliases
Date: Wed, 13 Oct 2021 13:10:08 +0200
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/27.2 (gnu/linux)

Markus Armbruster <armbru@redhat.com> writes:

> Kevin Wolf <kwolf@redhat.com> writes:
>
>> Am 11.10.2021 um 09:44 hat Markus Armbruster geschrieben:
>>> Kevin Wolf <kwolf@redhat.com> writes:
>>> 
>>> [...]
>>> 
>>> > What I had in mind was using the schema to generate the necessary code,
>>> > using the generic keyval parser everywhere, and just providing a hook
>>> > where the QDict could be modified after the keyval parser and before the
>>> > visitor. Most command line options would not have any explicit code for
>>> > parsing input, only the declarative schema and the final option handler
>>> > getting a QAPI type (which could actually be the corresponding QMP
>>> > command handler in the common case).
>>> 
>>> A walk to the bakery made me see a problem with transforming the qdict
>>> between keyval parsing and the visitor: error reporting.  On closer
>>> investigation, the problem exists even with just aliases.
>>
>> I already commented on part of this on IRC, but let me reply here as
>> well.
>>
>> On general remark is that I would make the same distinction between
>> aliases for compatibility and usability that I mentioned elsewhere in
>> this thread.
>>
>> In the case of compatibility, it's already a concession that we still
>> accept the option - suboptimal error messages for incorrect command
>> lines aren't a major concern for me there. Users are supposed to move to
>> the new syntax anyway.
>
> Well, these aren't "incorrect", merely deprecated.  Bad UX is still bad
> there, but at least it'll "fix" itself when the deprecated part goes
> away.
>
>> On the other hand, aliases we employ for usability are cases where we
>> don't expect humans to move to something else. I think this is mostly
>> for flattening structures. Here error messages should be good.
>>
>>> All experiments performed with your complete QAPIfication at
>>> https://repo.or.cz/qemu/kevin.git qapi-alias-chardev-v4.
>>> 
>>> 
>>> Example: flattening leads to suboptimal error
>>> 
>>>     $ qemu-system-x86_64 -chardev udp,id=chr0,port=12345,ipv4=om
>>>     qemu-system-x86_64: -chardev udp,id=chr0,port=12345,ipv4=om: Parameter 
>>> 'backend.data.remote.data.ipv4' expects 'on' or 'off'
>>> 
>>> We're using "alternate" notation, but the error message barks back in
>>> "standard" notation.  It comes from the visitor.  While less than
>>> pleasant, it's still understandable, because the "standard" key ends
>>> with the "alternate" key.
>>
>> This is not a fundamental problem with aliases. The right name for the
>> option is unambiguous and known to the visitor: It's the name that the
>> user specified.
>
> This is "1. The visitor reports errors as if aliases didn't exist"
> below.
>
>> With manual QDict modification it becomes a more fundamental problem
>> because the visitor can't know the original name any more.
>
> This is "2. The visitor reports errors as if manual transformation
> didn't exist" below.
>
> I agree 1 is easier to fix than 2.
>
>>> Example: renaming leads to confusing error
>>> 
>>> So far, we rename only members of type 'str', where the visitor can't
>>> fail.  Just for illustrating the problem, I'm adding one where it can:
>>> 
>>>     diff --git a/qapi/char.json b/qapi/char.json
>>>     index 0e39840d4f..b436d83cbe 100644
>>>     --- a/qapi/char.json
>>>     +++ b/qapi/char.json
>>>     @@ -398,7 +398,8 @@
>>>      ##
>>>      { 'struct': 'ChardevRingbuf',
>>>        'data': { '*size': 'int' },
>>>     -  'base': 'ChardevCommon' }
>>>     +  'base': 'ChardevCommon',
>>>     +  'aliases': [ { 'name': 'capacity', 'source': [ 'size' ] } ] }
>>> 
>>>      ##
>>>      # @ChardevQemuVDAgent:
>>> 
>>> With this patch:
>>> 
>>>     $ qemu-system-x86_64 -chardev ringbuf,id=chr0,capacity=lots
>>>     qemu-system-x86_64: -chardev ringbuf,id=chr0,capacity=lots: Parameter 
>>> 'backend.data.size' expects integer
>>> 
>>> The error message fails to connect to the offending key=value.
>>
>> Same problem as above. The error message should use the key that the
>> user actually specified.
>
> Yes.
>
>>> Example: manual transformation leads to confusion #1
>>> 
>>> Starting point:
>>> 
>>>     $ qemu-system-x86_64 -chardev udp,id=chr0,port=12345,localaddr=localhost
>>> 
>>> Works.  host defaults to localhost, localport defaults to 0, same as in
>>> git master.
>>> 
>>> Now "forget" to specify @port:
>>> 
>>>     $ qemu-system-x86_64 -chardev udp,id=chr0,localaddr=localhost
>>>     qemu-system-x86_64: -chardev udp,id=chr0,localaddr=localhost: Parameter 
>>> 'backend.data.remote.data.port' is missing
>>> 
>>> Again, the visitor's error message uses "standard" notation.
>>
>> The output isn't wrong, it's just more verbose than necessary.
>>
>> Getting this one shortened is a bit harder because the right name is
>> ambiguous, the user didn't specify anything we can just print back.
>>
>> Possibly in CLI context, making use of any wildcard aliases would be a
>> reasonable strategy. This would reduce this one to just 'port'.
>
> Which of the possible keys we use in the error message boils down to
> picking a preferred key.
>
>>> We obediently do what the error message tells us to do:
>>> 
>>>     $ qemu-system-x86_64 -chardev 
>>> udp,id=chr0,localaddr=localhost,backend.data.remote.data.port=12345
>>>     qemu-system-x86_64: -chardev 
>>> udp,id=chr0,localaddr=localhost,backend.data.remote.data.port=12345: 
>>> Parameters 'backend.*' used inconsistently
>>> 
>>> Mission accomplished: confusion :)
>>
>> This one already fails before aliases do their work. The reason is that
>> the default key for -chardev is 'backend', and QMP and CLI use the same
>> name 'backend' for two completely different things.
>
> Right.  I was confused (and the mission was truly accomplished).
>
>> We could rename the default key into 'x-backend' and make it behave the
>> same as 'backend', then the keyval parser would only fail when you
>> explicitly wrote '-chardev backend=udp,...' and the problem is more
>> obvious.
>
> Technically a compatibility break, but we can hope that the longhand
> form we change isn't used.
>
>> By the way, we have a very similar issue with '-drive file=...', if we
>> ever want to parse that one with the keyval parser. It can be both a
>> string for the filename or an object for the configuration of the 'file'
>> child that many block drivers have.
>
> Should I be running for the hills?
>
>>> Example: manual transformation leads to confusion #2
>>> 
>>> Confusion is possible even without tricking the user into mixing
>>> "standard" and "alternate" explicitly:
>>> 
>>>     $ qemu-system-x86_64 -chardev udp,id=chr0,backend.data.remote.type=udp
>>>     qemu-system-x86_64: -chardev udp,id=chr0,backend.data.remote.type=udp: 
>>> Parameters 'backend.*' used inconsistently
>>> 
>>> Here, the user tried to stick to "standard", but forgot to specify a
>>> required member.  The transformation machinery then "helpfully"
>>> transformed nothing into something, which made the visitor throw up.
>>
>> Not the visitor, but the keyval parser. Same problem as above.
>>
>>> Clear error reporting is a critical part of a human-friendly interface.
>>> We have two separate problems with it:
>>> 
>>> 1. The visitor reports errors as if aliases didn't exist
>>> 
>>>    Fixing this is "merely" a matter of tracing back alias applications.
>>>    More complexity...
>>> 
>>> 2. The visitor reports errors as if manual transformation didn't exist
>>> 
>>>    Manual transformation can distort the users input beyond recognition.
>>>    The visitor reports errors for the transformed input.
>>> 
>>>    To fix this one, we'd have to augment the parse tree so it points
>>>    back at the actual user input, and then make the manual
>>>    transformations preserve that.  Uff!
>>
>> Manual transformations are hard to write in a way that they give perfect
>> results. As long as they are only used for legacy syntax and we expect
>> users to move to a new way to spell things, this might be acceptable for
>> a transition period until we remove the old syntax.
>
> Valid point.
>
>> In other cases, the easiest way is probably to duplicate even more of
>> the schema and manually make sure that the visitor will accept the input
>> before we transform it.
>>
>> The best way to avoid this is providing tools in QAPI that make manual
>> transformations unnecessary.
>
> Reducing the amount of handwritten code is good, as long as the price is
> reasonable.  Complex code fetches a higher price.
>
> I think there are a couple of ways to skin this cat.
>
> 0. Common to all ways: specify "standard" in the QAPI schema.  This
>    specifies both the "standard" wire format, its parse tree
>    (represented as QObject), and the "standard" C interface (C types,
>    basically).
>
>    Generic parsers parse into the parse tree.  The appropriate input
>    visitor validates and converts to C types.
>
> 1. Parse "alternate" by any means (QemuOpts, keyval, ad hoc), validate,
>    then transform for the "standard" C interface.  Parsing and
>    validating can fail, the transformation can't.
>
>    Drawbacks:
>
>    * We duplicate validation, which is a standing invitation for bugs.
>
>    * Longwinded, handwritten transformation code.  Less vulnerable to
>      bugs than validation code, I believe.
>
> 2. Parse "alternate" by any means (QemuOpts, keyval, ad hoc), transform
>    to "standard" parse tree.
>
>    Drawbacks:
>
>    * Bad error messages from input visitor.
>
>    * The handwritten transformation code is more longwinded than with 1.
>
> 3. Specify "alternate" in the QAPI schema.  Map "alternate" C interface
>    to the "standard" one.
>
>    Drawbacks:
>
>    * QAPI schema code duplication
>
>    * Longwinded, handwritten mapping code.
>
>    This is what we did with SocketAddressLegacy.
>
> 4. Extend the QAPI schema language to let us specify non-standard wire
>    format(s).  Use that to get the "alternate" wire format we need.
>
>    Drawbacks:
>
>    * QAPI schema language and generator become more complex.
>
>    * Input visitor(s) become more complex.
>
>    * Unless we accept "alternate" everywhere, we need a way to limit it
>      to where it's actually needed.  More complexity.
>
>    The concrete proposal on the table is aliases.  They are not a
>    complete solution.  To solve the whole problem, we combine them with
>    2.  We accept 4's drawbacks for a (sizable) reduction of 2's.
>    Additionally, we accept "ghost" aliases.
>
> 5. Extend the QAPI schema language to let us specify "alternate" wire
>    formats for "standard" types
>
>    This is like 3, except the schema code is mostly referenced instead
>    duplicated, and the mapping code is generated.  Something like
>    "derive type T' from T with these members moved / renamed".
>
>    Drawbacks
>
>    * QAPI schema language and generator become more complex.
>
>    * Unlike 4, we don't have working code.
>
>    Like 4, this will likely solve only part of the problem.

I got one more:

6. Stir more sugar into the input visitor we use with keyval_parse():

   - Recognze unique suffix of full key.  Example: "ipv6" as
     abbreviation of "backend.data.remote.data.ipv4".

   - Default union type tag when variant members of exactly one branch
     are present.  Example: when we got "backend.data.addr.data.host",
     "backend.data.addr.type" defaults to "inet".

   Beware, this is even hairier than it may look.  For instance, we want
   to expand "host=playground.redhat.com,port=12345" into

       backend.type=socket
       backend.data.addr.type=inet
       backend.data.addr.data.host=playground.redhat.com
       backend.data.addr.data.port=12345

   and not into

       backend.type=udp
       backend.data.remote.type=inet
       backend.data.remote.data.host=playground.redhat.com
       backend.data.remote.data.port=12345

   I'm afraid this idea also solves only part of the problem.

> Which one is the least bad?
>
> If this was just about dragging deprecated interfaces to the end of
> their grace period, I'd say "whatever is the least work", and that's
> almost certainly whatever we have now, possibly hacked up to use the
> appropriate internal interface.
>
> Unfortunately, it is also about providing a friendlier interface to
> humans "forever".
>
> With 4 or 5, we invest into infrastructure once, then maintain it
> forever, to make solving the problem easier and the result easier to
> maintain.
>
> Whether the investment will pay off depends on how big and bad the
> problem is.  Hard to say.  One of the reasons we're looking at -chardev
> now is that we believe it's the worst of the bunch.  But how big is the
> bunch, and how bad are the others in there?
>
>>> I'm afraid I need another round of thinking on how to best drag legacy
>>> syntax along when we QAPIfy.
>>
>> Let me know when you've come to any conclusions (or more things to
>> discuss).
>
> Is 3 too awful to contemplate?




reply via email to

[Prev in Thread] Current Thread [Next in Thread]