Skip to content

Commit

Permalink
qapi: Add feature flags to commands
Browse files Browse the repository at this point in the history
Similarly to features for struct types introduce the feature flags also
for commands. This will allow notifying management layers of fixes and
compatible changes in the behaviour of a command which may not be
detectable any other way.

The changes were heavily inspired by commit 6a8c0b5.

Signed-off-by: Peter Krempa <pkrempa@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20191018081454.21369-3-armbru@redhat.com>
  • Loading branch information
pipo authored and Markus Armbruster committed Oct 22, 2019
1 parent 758f272 commit 23394b4
Show file tree
Hide file tree
Showing 8 changed files with 62 additions and 28 deletions.
10 changes: 6 additions & 4 deletions docs/devel/qapi-code-gen.txt
Expand Up @@ -457,7 +457,8 @@ Syntax:
'*gen': false,
'*allow-oob': true,
'*allow-preconfig': true,
'*if': COND }
'*if': COND,
'*features': FEATURES }

Member 'command' names the command.

Expand Down Expand Up @@ -640,9 +641,10 @@ change in the QMP syntax (usually by allowing values or operations
that previously resulted in an error). QMP clients may still need to
know whether the extension is available.

For this purpose, a list of features can be specified for a struct type.
This is exposed to the client as a list of string, where each string
signals that this build of QEMU shows a certain behaviour.
For this purpose, a list of features can be specified for a command or
struct type. This is exposed to the client as a list of strings,
where each string signals that this build of QEMU shows a certain
behaviour.

Each member of the 'features' array defines a feature. It can either
be { 'name': STRING, '*if': COND }, or STRING, which is shorthand for
Expand Down
6 changes: 5 additions & 1 deletion qapi/introspect.json
Expand Up @@ -266,13 +266,17 @@
# @allow-oob: whether the command allows out-of-band execution,
# defaults to false (Since: 2.12)
#
# @features: names of features associated with the command, in no particular
# order. (since 4.2)
#
# TODO: @success-response (currently irrelevant, because it's QGA, not QMP)
#
# Since: 2.5
##
{ 'struct': 'SchemaInfoCommand',
'data': { 'arg-type': 'str', 'ret-type': 'str',
'*allow-oob': 'bool' } }
'*allow-oob': 'bool',
'*features': [ 'str' ] } }

##
# @SchemaInfoEvent:
Expand Down
3 changes: 2 additions & 1 deletion scripts/qapi/commands.py
Expand Up @@ -277,7 +277,8 @@ def visit_end(self):
genc.add(gen_registry(self._regy.get_content(), self._prefix))

def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
success_response, boxed, allow_oob, allow_preconfig):
success_response, boxed, allow_oob, allow_preconfig,
features):
if not gen:
return
# FIXME: If T is a user-defined type, the user is responsible
Expand Down
4 changes: 3 additions & 1 deletion scripts/qapi/doc.py
Expand Up @@ -249,12 +249,14 @@ def visit_alternate_type(self, name, info, ifcond, variants):
body=texi_entity(doc, 'Members', ifcond)))

def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
success_response, boxed, allow_oob, allow_preconfig):
success_response, boxed, allow_oob, allow_preconfig,
features):
doc = self.cur_doc
if boxed:
body = texi_body(doc)
body += ('\n@b{Arguments:} the members of @code{%s}\n'
% arg_type.name)
body += texi_features(doc)
body += texi_sections(doc, ifcond)
else:
body = texi_entity(doc, 'Arguments', ifcond)
Expand Down
35 changes: 20 additions & 15 deletions scripts/qapi/expr.py
Expand Up @@ -185,6 +185,22 @@ def normalize_features(features):
for f in features]


def check_features(features, info):
if features is None:
return
if not isinstance(features, list):
raise QAPISemError(info, "'features' must be an array")
for f in features:
source = "'features' member"
assert isinstance(f, dict)
check_keys(f, info, source, ['name'], ['if'])
check_name_is_str(f['name'], info, source)
source = "%s '%s'" % (source, f['name'])
check_name_str(f['name'], info, source)
check_if(f, info, source)
normalize_if(f)


def normalize_enum(expr):
if isinstance(expr['data'], list):
expr['data'] = [m if isinstance(m, dict) else {'name': m}
Expand Down Expand Up @@ -217,23 +233,10 @@ def check_enum(expr, info):
def check_struct(expr, info):
name = expr['struct']
members = expr['data']
features = expr.get('features')

check_type(members, info, "'data'", allow_dict=name)
check_type(expr.get('base'), info, "'base'")

if features:
if not isinstance(features, list):
raise QAPISemError(info, "'features' must be an array")
for f in features:
source = "'features' member"
assert isinstance(f, dict)
check_keys(f, info, source, ['name'], ['if'])
check_name_is_str(f['name'], info, source)
source = "%s '%s'" % (source, f['name'])
check_name_str(f['name'], info, source)
check_if(f, info, source)
normalize_if(f)
check_features(expr.get('features'), info)


def check_union(expr, info):
Expand Down Expand Up @@ -283,6 +286,7 @@ def check_command(expr, info):
raise QAPISemError(info, "'boxed': true requires 'data'")
check_type(args, info, "'data'", allow_dict=not boxed)
check_type(rets, info, "'returns'", allow_array=True)
check_features(expr.get('features'), info)


def check_event(expr, info):
Expand Down Expand Up @@ -358,10 +362,11 @@ def check_exprs(exprs):
elif meta == 'command':
check_keys(expr, info, meta,
['command'],
['data', 'returns', 'boxed', 'if',
['data', 'returns', 'boxed', 'if', 'features',
'gen', 'success-response', 'allow-oob',
'allow-preconfig'])
normalize_members(expr.get('data'))
normalize_features(expr.get('features'))
check_command(expr, info)
elif meta == 'event':
check_keys(expr, info, meta,
Expand Down
7 changes: 6 additions & 1 deletion scripts/qapi/introspect.py
Expand Up @@ -211,13 +211,18 @@ def visit_alternate_type(self, name, info, ifcond, variants):
for m in variants.variants]}, ifcond)

def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
success_response, boxed, allow_oob, allow_preconfig):
success_response, boxed, allow_oob, allow_preconfig,
features):
arg_type = arg_type or self._schema.the_empty_object_type
ret_type = ret_type or self._schema.the_empty_object_type
obj = {'arg-type': self._use_type(arg_type),
'ret-type': self._use_type(ret_type)}
if allow_oob:
obj['allow-oob'] = allow_oob

if features:
obj['features'] = [(f.name, {'if': f.ifcond}) for f in features]

self._gen_qlit(name, 'command', obj, ifcond)

def visit_event(self, name, info, ifcond, arg_type, boxed):
Expand Down
22 changes: 18 additions & 4 deletions scripts/qapi/schema.py
Expand Up @@ -110,7 +110,8 @@ def visit_alternate_type(self, name, info, ifcond, variants):
pass

def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
success_response, boxed, allow_oob, allow_preconfig):
success_response, boxed, allow_oob, allow_preconfig,
features):
pass

def visit_event(self, name, info, ifcond, arg_type, boxed):
Expand Down Expand Up @@ -659,10 +660,14 @@ class QAPISchemaCommand(QAPISchemaEntity):
meta = 'command'

def __init__(self, name, info, doc, ifcond, arg_type, ret_type,
gen, success_response, boxed, allow_oob, allow_preconfig):
gen, success_response, boxed, allow_oob, allow_preconfig,
features):
QAPISchemaEntity.__init__(self, name, info, doc, ifcond)
assert not arg_type or isinstance(arg_type, str)
assert not ret_type or isinstance(ret_type, str)
for f in features:
assert isinstance(f, QAPISchemaFeature)
f.set_defined_in(name)
self._arg_type_name = arg_type
self.arg_type = None
self._ret_type_name = ret_type
Expand All @@ -672,6 +677,7 @@ def __init__(self, name, info, doc, ifcond, arg_type, ret_type,
self.boxed = boxed
self.allow_oob = allow_oob
self.allow_preconfig = allow_preconfig
self.features = features

def check(self, schema):
QAPISchemaEntity.check(self, schema)
Expand Down Expand Up @@ -701,13 +707,19 @@ def check(self, schema):
"command's 'returns' cannot take %s"
% self.ret_type.describe())

# Features are in a name space separate from members
seen = {}
for f in self.features:
f.check_clash(self.info, seen)

def visit(self, visitor):
QAPISchemaEntity.visit(self, visitor)
visitor.visit_command(self.name, self.info, self.ifcond,
self.arg_type, self.ret_type,
self.gen, self.success_response,
self.boxed, self.allow_oob,
self.allow_preconfig)
self.allow_preconfig,
self.features)


class QAPISchemaEvent(QAPISchemaEntity):
Expand Down Expand Up @@ -984,6 +996,7 @@ def _def_command(self, expr, info, doc):
allow_oob = expr.get('allow-oob', False)
allow_preconfig = expr.get('allow-preconfig', False)
ifcond = expr.get('if')
features = expr.get('features', [])
if isinstance(data, OrderedDict):
data = self._make_implicit_object_type(
name, info, doc, ifcond, 'arg', self._make_members(data, info))
Expand All @@ -992,7 +1005,8 @@ def _def_command(self, expr, info, doc):
rets = self._make_array_type(rets[0], info)
self._def_entity(QAPISchemaCommand(name, info, doc, ifcond, data, rets,
gen, success_response,
boxed, allow_oob, allow_preconfig))
boxed, allow_oob, allow_preconfig,
self._make_features(features, info)))

def _def_event(self, expr, info, doc):
name = expr['event']
Expand Down
3 changes: 2 additions & 1 deletion tests/qapi-schema/test-qapi.py
Expand Up @@ -72,7 +72,8 @@ def visit_alternate_type(self, name, info, ifcond, variants):
self._print_if(ifcond)

def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
success_response, boxed, allow_oob, allow_preconfig):
success_response, boxed, allow_oob, allow_preconfig,
features):
print('command %s %s -> %s'
% (name, arg_type and arg_type.name,
ret_type and ret_type.name))
Expand Down

0 comments on commit 23394b4

Please sign in to comment.