QAPI Interface Design:
Best Practices
Eric Blake
Red Hat
23 Oct 2013
Overview
● What is QAPI?
● Why is it Important?
● Recent Additions
● Rules of Thumb
● Future Work Needed
2 KVM Forum 2013 | Eric Blake
What is QAPI?
3 KVM Forum 2013 | Eric Blake
History
● Up through qemu 0.11, just the HMP text monitor
● Output was ad hoc, and could be misparsed by
management apps
● No support for async events
● qemu 0.12 introduced QMP monitor and QAPI
● First patches in Nov 2009, by Luiz
● Based on JSON data structure (RFC 4627)
● Libvirt has used QMP as sole management interface
since qemu 1.3, once there were enough commands to
no longer need to parse 'qemu -help' output
4 KVM Forum 2013 | Eric Blake
Sample QMP session
$ qemukvm M none nodefaults nographic qmp stdio
{"QMP": {"version": {"qemu": {"micro": 1, "minor": 6,
"major": 1}, "package": ""}, "capabilities": []}}
{"execute": "qmp_capabilities"}
{"return": {}}
{"execute": "queryversion"}
{"return": {"qemu": {"micro": 1, "minor": 6, "major":
1}, "package": ""}}
{"execute": "quit"}
{"return": {}}
{"timestamp": {"seconds": 1382123176, "microseconds":
50452}, "event": "SHUTDOWN"}
5 KVM Forum 2013 | Eric Blake
Other options for testing
● Use a second monitor
$ qemu [...] chardev stdio,id=mon0 mon
chardev=mon0,mode=readline chardev
socket,id=mon1,host=localhost,port=4444,server,nowait
mon chardev=mon1,mode=control,pretty=on
$ telnet localhost:4444
{"execute":"..."}
● Use libvirt's virsh
$ virsh qemumonitorcommand pretty guest
'{"execute":"..."}'
6 KVM Forum 2013 | Eric Blake
Sample QMP session, revisited
$ qemukvm M none nodefaults nographic qmp stdio
{"QMP": {"version": {"qemu": {"micro": 1, "minor": 6,
"major": 1}, "package": ""}, "capabilities": []}}
{"execute": "qmp_capabilities"}
{"return": {}}
{"execute": "queryversion"}
{"return": {"qemu": {"micro": 1, "minor": 6, "major":
1}, "package": ""}}
{"execute": "quit"}
{"return": {}}
{"timestamp": {"seconds": 1382123176, "microseconds":
50452}, "event": "SHUTDOWN"}
7 KVM Forum 2013 | Eric Blake
Sample QAPI
● qapi-schema.json:
##
# @VersionInfo:
#
# A description of QEMU's version.
...
##
{ 'type': 'VersionInfo',
'data': {'qemu': {'major': 'int', 'minor': 'int', 'micro': 'int'},
'package': 'str'} }
##
# @queryversion:
#
# Returns the current version of QEMU.
#
# Returns: A @VersionInfo object describing the current version of QEMU.
#
# Since: 0.14.0
##
{ 'command': 'queryversion', 'returns': 'VersionInfo' }
8 KVM Forum 2013 | Eric Blake
Sample Generated C Code
● qmp-commands.h (generated):
VersionInfo * qmp_query_version(Error **errp);
● qapi-types.h (generated):
struct VersionInfo
{
struct
{
int64_t major;
int64_t minor;
int64_t micro;
} qemu;
char * package;
};
void qapi_free_VersionInfo(VersionInfo * obj);
● Allocate with glib (g_malloc0 or g_new0)
● Deep cleanup with automatic qapi_free_*
● Conversions between structs and QDict to provide flexible usage
9 KVM Forum 2013 | Eric Blake
Use in qemu C Code
● qmp.c:
VersionInfo *qmp_query_version(Error **err)
{
VersionInfo *info = g_malloc0(sizeof(*info));
const char *version = QEMU_VERSION;
char *tmp;
info>qemu.major = strtol(version, &tmp, 10);
tmp++;
info>qemu.minor = strtol(tmp, &tmp, 10);
tmp++;
info>qemu.micro = strtol(tmp, &tmp, 10);
info>package = g_strdup(QEMU_PKGVERSION);
return info;
}
10 KVM Forum 2013 | Eric Blake
More complex
● qapi-schema.json supports enums, optional parameters:
{ 'enum': 'DataFormat',
'data': [ 'utf8', 'base64' ] }
{ 'command': 'ringbufwrite',
'data': {'device': 'str', 'data': 'str',
'*format': 'DataFormat'} }
● Generated code then has distinction between present and absent:
● qapi-types.h
extern const char *DataFormat_lookup[];
typedef enum DataFormat
{
DATA_FORMAT_UTF8 = 0,
DATA_FORMAT_BASE64 = 1,
DATA_FORMAT_MAX = 2,
} DataFormat;
● qmp-commands.h
void qmp_ringbuf_write(const char * device, const char * data,
bool has_format, DataFormat format, Error **errp);
11 KVM Forum 2013 | Eric Blake
Why is QAPI Important?
12 KVM Forum 2013 | Eric Blake
Ease of Maintenance
● Tedious work is automated
● Header files automatically align with wire format
● Visitor patterns allow safe allocation and cleanup
● Lets the developer focus on just the unique aspects of
the implementation, rather than repeating boilerplate
● Lends itself to testing
● Compiler enforces that implementation matches header
● Wire format is described in a machine-parseable
manner, so it is easier to exercise an interface in tests
● QAPI was basis for qemu-ga interface
13 KVM Forum 2013 | Eric Blake
Interface Contract
● Older management's use of QMP must continue to
work with newer qemu
● Once released, only backward-compatible changes are
allowed
● Exceptions:
● Any command or field beginning with “x-” is experimental
● Any command beginning with __RFQDN_ (such as
__com.redhat_) is a non-portable downstream addition
● Documentation includes first upstream version
● But downstream can backport features to earlier
versions, so check feature rather than version
14 KVM Forum 2013 | Eric Blake
Designed for Extension
● On input:
● Any new fields must be optional, with default behavior
when field is omitted remaining unchanged
● JSON's name/value pairing in dictionaries ensures order
of arguments will not cause problems
● Enums by name, not number
● On output:
● Management app should ignore fields it does not
recognize
● qemu must continue to output all non-optional fields
ever used in past
15 KVM Forum 2013 | Eric Blake
Recent Additions in QAPI
16 KVM Forum 2013 | Eric Blake
Base Classes
● Several improvements driven by Kevin's recent
'blockdev-add' work, but useful elsewhere
● QAPI base classes:
{ 'type': 'BlockdevOptionsGenericFormat',
'data': { 'file': 'BlockdevRef' } }
{ 'type': 'BlockdevOptionsGenericCOWFormat',
'base': 'BlockdevOptionsGenericFormat',
'data': { '*backing': 'BlockdevRef' } }
● Allows inline use of base class fields in QMP:
{ "file": "/some/place/myimage",
"backing": "/some/place/mybackingfile" }
17 KVM Forum 2013 | Eric Blake
Discriminated Unions
● QAPI:
{ 'type': 'Qcow2Options',
'data': { 'backingfile': 'str', 'lazyrefcounts': 'bool' } }
{ 'type': 'BlockdevCommonOptions',
'data': { 'driver': 'str', 'readonly': 'bool' } }
{ 'union': 'BlockdevOptions',
'base': 'BlockdevCommonOptions',
'discriminator': 'driver',
'data': { 'raw': 'RawOptions',
'qcow2': 'Qcow2Options' } }
● Allows inline use of discriminator to determine
remaining valid fields in QMP:
{ "driver": "qcow2",
"readonly": false,
"backingfile": "/some/place/myimage",
"lazyrefcounts": true }
18 KVM Forum 2013 | Eric Blake
Anonymous Unions
● QAPI:
{ 'union': 'BlockRef',
'discriminator': {},
'data': { 'definition': 'BlockdevOptions',
'reference': 'str' } }
● Allows type-based discrimination in QMP (between
JSON primitive vs. object):
{ "file": "my_existing_block_device_id" }
{ "file": { "driver": "file",
"readonly": false,
"filename": "/tmp/mydisk.qcow2" } }
19 KVM Forum 2013 | Eric Blake
'blockdev-add' example
● Command line (added in qemu 1.5 and 1.6):
qemu ... drive file.driver=qcow2,file=test.qcow2,\
backing.file.filename=test.raw
● QMP (added in qemu 1.7):
{ "execute": "blockdevadd",
"arguments": { "options" :
{ "driver": "qcow2",
"file": { "driver": "file",
"filename": "test.qcow2" },
"backing": { "driver": "file",
"filename": "test.raw" } } } }
● Both map to same C code, and gives us the option of
using JSON on command line in future
20 KVM Forum 2013 | Eric Blake
'blockdev-add' example, continued
● QAPI:
{ 'enum': 'BlockdevDiscardOptions',
'data': [ 'ignore', 'unmap' ] }
{ 'enum': 'BlockdevAioOptions',
'data': [ 'threads', 'native' ] }
{ 'type': 'BlockdevCacheOptions',
'data': { '*writeback': 'bool',
'*direct': 'bool',
'*noflush': 'bool' } }
{ 'type': 'BlockdevOptionsBase',
'data': { 'driver': 'str',
'*id': 'str',
'*discard': 'BlockdevDiscardOptions',
'*cache': 'BlockdevCacheOptions',
'*aio': 'BlockdevAioOptions',
'*rerror': 'BlockdevOnError',
'*werror': 'BlockdevOnError',
'*readonly': 'bool' } }
21 KVM Forum 2013 | Eric Blake
'blockdev-add' example, continued
{ 'type': 'BlockdevOptionsFile',
'data': { 'filename': 'str' } }
{ 'type': 'BlockdevOptionsGenericFormat',
'data': { 'file': 'BlockdevRef' } }
{ 'type': 'BlockdevOptionsGenericCOWFormat',
'base': 'BlockdevOptionsGenericFormat',
'data': { '*backing': 'BlockdevRef' } }
{ 'type': 'BlockdevOptionsQcow2',
'base': 'BlockdevOptionsGenericCOWFormat',
'data': { '*lazyrefcounts': 'bool',
'*passdiscardrequest': 'bool',
'*passdiscardsnapshot': 'bool',
'*passdiscardother': 'bool' } }
22 KVM Forum 2013 | Eric Blake
'blockdev-add' example, continued
{ 'union': 'BlockdevOptions',
'base': 'BlockdevOptionsBase',
'discriminator': 'driver',
'data': {
'file': 'BlockdevOptionsFile',
'qcow2': 'BlockdevOptionsQcow2',
...
} }
{ 'union': 'BlockdevRef',
'discriminator': {},
'data': { 'definition': 'BlockdevOptions',
'reference': 'str' } }
{ 'command': 'blockdevadd', 'data': { 'options':
'BlockdevOptions' } }
23 KVM Forum 2013 | Eric Blake
Design Rules of Thumb
24 KVM Forum 2013 | Eric Blake
Don't Force a Reparse
● If the receiver must parse a string into individual
components, the JSON was too high-level
● Compare Max's first attempt...
https://lists.gnu.org/archive/html/qemu-devel/2013-09/msg00976.html
# @infostring: #optional string supplying additional formatspecific
# information (since 1.7)
{ 'type': 'ImageInfo',
'data': {'filename': 'str', 'format': 'str', '*dirtyflag': 'bool',
'*actualsize': 'int', 'virtualsize': 'int',
'*clustersize': 'int', '*encrypted': 'bool',
'*backingfilename': 'str', '*fullbackingfilename': 'str',
'*backingfilenameformat': 'str', '*snapshots': ['SnapshotInfo'],
'*backingimage': 'ImageInfo', '*infostring': 'str' } }
25 KVM Forum 2013 | Eric Blake
Don't Force a Reparse, continued
● ...with his final implementation (commit f2bb8a8a):
https://lists.gnu.org/archive/html/qemu-devel/2013-10/msg01067.html
{ 'type': 'ImageInfoSpecificQCow2',
'data': {
'compat': 'str',
'*lazyrefcounts': 'bool'
} }
{ 'union': 'ImageInfoSpecific',
'data': {
'qcow2': 'ImageInfoSpecificQCow2'
} }
{ 'type': 'ImageInfo',
'data': {'filename': 'str', 'format': 'str', '*dirtyflag': 'bool',
'*actualsize': 'int', 'virtualsize': 'int',
'*clustersize': 'int', '*encrypted': 'bool',
'*backingfilename': 'str', '*fullbackingfilename': 'str',
'*backingfilenameformat': 'str', '*snapshots': ['SnapshotInfo'],
'*formatspecific': 'ImageInfoSpecific' } }
26 KVM Forum 2013 | Eric Blake
Avoid open-coded finite sets of strings
● Use an enum
● QMP wire representation is the same
● But C code gets to use enum constants that map to
string in an easier manner
● Example: Fam's conversion of block job string to an
enum
https://lists.gnu.org/archive/html/qemu-devel/2013-10/msg00833.html
{ 'enum': 'BlockJobType',
'data': ['commit', 'stream', 'mirror', 'backup'] }
● Allows for 'type':'BlockJobType' instead of
'type':'str' when representing a block job event
27 KVM Forum 2013 | Eric Blake
Favor Consistent Naming
● MixedCase types, dashed-name commands and fields
● New commands should use '-' in QAPI (the
corresponding C code will still use '_')
● But when extending old commands, stick with the
prevailing style
● Pending idea of making the parser more lenient:
https://lists.gnu.org/archive/html/qemu-devel/2013-04/msg00681.html
● Careful what gets flattened – command names are
okay, but values passed on to guest are not
28 KVM Forum 2013 | Eric Blake
Try for Human Legibility
● Although QAPI's main focus is machine parsing, it
helps to also be human readable
● Use consistent 'verb' or 'noun-verb' commands
● Avoid abbreviations
● Use full power of QAPI to avoid overly nesting structs
● Compare Amos' first attempt at macvtap programming
https://lists.gnu.org/archive/html/qemu-devel/2013-05/msg02174.html
● with his final implementation (commit b1be4280)
https://lists.gnu.org/archive/html/qemu-devel/2013-06/msg02223.html
29 KVM Forum 2013 | Eric Blake
Try for Human Legibility, continued
● First try:
{ 'type': 'MacTableInfo', 'data': {
'name': 'str', '*promisc': 'bool',
'*allmulti': 'bool', '*alluni': 'bool',
'*nomulti': 'bool', '*nouni': 'bool',
'*nobcast': 'bool', '*multioverflow': 'bool',
'*unioverflow': 'bool', '*unicast': ['str'],
'*multicast': ['str'] }}
● Final version:
{ 'enum': 'RxState', 'data': [ 'normal', 'none', 'all' ] }
{ 'type': 'RxFilterInfo', 'data': {
'name': 'str', 'promiscuous': 'bool',
'multicast': 'RxState','unicast': 'RxState',
'broadcastallowed':'bool', 'multicastoverflow':'bool',
'unicastoverflow': 'bool', 'mainmac': 'str',
'vlantable': ['int'], 'unicasttable': ['str'],
'multicasttable': ['str'] }}
30 KVM Forum 2013 | Eric Blake
Documentation is Essential
● Mention which release a command was introduced,
and which release an optional field was added
● Document each field of a struct, each value of enum
● Don't forget qmp-commands.hx (although that may
hopefully go away if we automate more from qapi-
schema.json)
● Post ideas to the list, and respond to feedback
31 KVM Forum 2013 | Eric Blake
Documentation Example
##
# @ImageInfo:
...
# @snapshots: #optional list of VM snapshots
# @backingimage: #optional info of the backing image (since 1.6)
# @formatspecific: #optional structure supplying additional formatspecific
# information (since 1.7)
#
# Since: 1.3
##
{ 'type': 'ImageInfo',
'data': {'filename': 'str', 'format': 'str', '*dirtyflag': 'bool',
'*actualsize': 'int', 'virtualsize': 'int',
'*clustersize': 'int', '*encrypted': 'bool',
'*backingfilename': 'str', '*fullbackingfilename': 'str',
'*backingfilenameformat': 'str', '*snapshots': ['SnapshotInfo'],
'*backingimage': 'ImageInfo',
'*formatspecific': 'ImageInfoSpecific' } }
32 KVM Forum 2013 | Eric Blake
Try to be Discoverable
● When adding a new feature by extending an existing
enum or struct, can newer management safely
determine whether the feature is usable?
● Detection by failing on unrecognized argument is a last
resort, but generally has poor error message quality
● No write-only interfaces: if something can be set, it
should also be something that can be queried
● Introspection is a heavy hammer – is there something
lighter-weight that does the same job?
33 KVM Forum 2013 | Eric Blake
Try to be Discoverable, example
● Migration command has added several features
● Introspecting the enum would work...
{ 'enum': 'MigrationCapability',
'data': ['xbzrle', 'xrdmapinall',
'autoconverge', 'zeroblocks'] }
● But even nicer is a query command
{ 'type': 'MigrationCapabilityStatus',
'data': { 'capability' : 'MigrationCapability',
'state' : 'bool' } }
{ 'command': 'querymigratecapabilities',
'returns': ['MigrationCapabilityStatus']}
34 KVM Forum 2013 | Eric Blake
Good Interface goes beyond QMP
● Many of these design principles also apply to C code
● Command line design should also be structured
● 'query-command-line-options' QMP command is useful
● Publish designs early, listen to feedback
● Contribute review time to share the load
35 KVM Forum 2013 | Eric Blake
Future Additions
36 KVM Forum 2013 | Eric Blake
Move Events into formal QAPI
● Events currently tracked ad hoc in docs/qmp/qmp-
events.txt
● Wenchao's patches this week creates enum:
https://lists.gnu.org/archive/html/qemu-devel/2013-10/msg02733.html
● Kevin's suggestion for using union type:
https://lists.gnu.org/archive/html/qemu-devel/2013-09/msg02164.html
● Will guarantee which elements are part of each event
37 KVM Forum 2013 | Eric Blake
Introspection
● Amos' initial work in 1.6 time frame, stalled at the time
https://lists.gnu.org/archive/html/qemu-devel/2013-07/msg02494.html
● Now that Kevin's QAPI improvements allow better type
expressions, this should be revived
● Basically a way to query all commands, types, enums,
and events from the QAPI
38 KVM Forum 2013 | Eric Blake
Other Cleanups
● Maintaining qapi-schema.json and qmp-commands.hx
in parallel is prone to get out of sync
● Would be nice to automate documentation of examples
from one file
● HMP should serve as an ideal client of QMP
● Several commands still exist as HMP only, such as
'savevm'
● Various patches under review, but not done yet
● Rewrite HMP to call into QMP layer
● Possibly expose qemu-ga directly through QMP
39 KVM Forum 2013 | Eric Blake
Patches Welcome!
● Check the to-do list:
● https://lists.gnu.org/archive/html/qemu-devel/2013-09/msg01816.html
● Patch submission hints:
● http://wiki.qemu.org/Contribute/SubmitAPatch
● Thanks to the QMP/QAPI maintainers:
● Luiz C.
● Michael R.
● Markus A.
40 KVM Forum 2013 | Eric Blake
Thank You
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.