qapi: Add feature flags to commands
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 6a8c0b5102
.
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>
This commit is contained in:
parent
758f272b6d
commit
23394b4c39
@ -457,7 +457,8 @@ Syntax:
|
||||
'*gen': false,
|
||||
'*allow-oob': true,
|
||||
'*allow-preconfig': true,
|
||||
'*if': COND }
|
||||
'*if': COND,
|
||||
'*features': FEATURES }
|
||||
|
||||
Member 'command' names the command.
|
||||
|
||||
@ -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
|
||||
|
@ -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:
|
||||
|
@ -277,7 +277,8 @@ void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds);
|
||||
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
|
||||
|
@ -249,12 +249,14 @@ class QAPISchemaGenDocVisitor(QAPISchemaVisitor):
|
||||
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)
|
||||
|
@ -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}
|
||||
@ -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):
|
||||
@ -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):
|
||||
@ -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,
|
||||
|
@ -211,13 +211,18 @@ const QLitObject %(c_name)s = %(c_string)s;
|
||||
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):
|
||||
|
@ -110,7 +110,8 @@ class QAPISchemaVisitor(object):
|
||||
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):
|
||||
@ -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
|
||||
@ -672,6 +677,7 @@ class QAPISchemaCommand(QAPISchemaEntity):
|
||||
self.boxed = boxed
|
||||
self.allow_oob = allow_oob
|
||||
self.allow_preconfig = allow_preconfig
|
||||
self.features = features
|
||||
|
||||
def check(self, schema):
|
||||
QAPISchemaEntity.check(self, schema)
|
||||
@ -701,13 +707,19 @@ class QAPISchemaCommand(QAPISchemaEntity):
|
||||
"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):
|
||||
@ -984,6 +996,7 @@ class QAPISchema(object):
|
||||
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))
|
||||
@ -992,7 +1005,8 @@ class QAPISchema(object):
|
||||
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']
|
||||
|
@ -72,7 +72,8 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor):
|
||||
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))
|
||||
|
Loading…
Reference in New Issue
Block a user