[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-devel] [PATCH v2 2/2] full introspection support for QMP
From: |
Amos Kong |
Subject: |
[Qemu-devel] [PATCH v2 2/2] full introspection support for QMP |
Date: |
Tue, 16 Jul 2013 18:37:42 +0800 |
Introduces new monitor command to query QMP schema information,
the return data is a dynamical and nested dict/list, it contains
the useful metadata to help management to check feature support,
QMP commands detail, etc.
I added a document for QMP introspection support.
(docs/qmp-full-introspection.txt)
We need to parse all commands json definition, and generated a
dynamical tree, QMP infrastructure will convert the tree to
json string and return to QMP client.
So here I defined a 'DataObject' type in qapi-schema.json,
it's used to describe the dynamical dictionary/list/string.
{ 'type': 'DataObject',
'data': { '*key': 'str', '*type': 'str', '*data': ['DataObject'] } }
Not all the keys in data will be used.
# List: type
# Dict: key, type
# nested List: type, data
# nested Dict: key, type, data
The DataObject is described in docs/qmp-full-introspection.txt in
detail.
The following content gives an example of query-tpm-types:
## Define example in qapi-schema.json:
{ 'enum': 'TpmType', 'data': [ 'passthrough' ] }
{ 'command': 'query-tpm-types', 'returns': ['TpmType'] }
## Returned description:
{
"name": "query-tpm-types",
"type": "Command",
"returns": [
{
"type": "TpmType",
"data": [
{
"type": "passthrough"
}
]
}
]
},
'TpmType' is a defined type, it will be extended in returned
description. [ 'passthrough' ] is a list, so 'type' and 'data'
will be used.
TODO:
We can add events definations to qapi-schema.json by another
patch, then event can also be queried.
Introduce another command 'query-qga-schema' to query QGA schema
information, it's easy to add this support with current current
patch.
Signed-off-by: Amos Kong <address@hidden>
---
docs/qmp-full-introspection.txt | 143 +++++++++++++++++++
qapi-schema.json | 69 +++++++++
qmp-commands.hx | 39 +++++
qmp.c | 307 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 558 insertions(+)
create mode 100644 docs/qmp-full-introspection.txt
diff --git a/docs/qmp-full-introspection.txt b/docs/qmp-full-introspection.txt
new file mode 100644
index 0000000..cc0fb80
--- /dev/null
+++ b/docs/qmp-full-introspection.txt
@@ -0,0 +1,143 @@
+= full introspection support for QMP =
+
+== Purpose ==
+
+Add a new interface to provide QMP schema information to management,
+the return data is a dynamical and nested dict/list, it contains
+the useful metadata to help management to check feature support,
+QMP commands detail, etc.
+
+== Usage ==
+
+Execute QMP command:
+
+ { "execute": "query-qmp-schema" }
+
+Returns:
+
+ { "return": [
+ {
+ "name": "query-name",
+ "type": "Command",
+ "returns": [
+ {
+ "key": "*name",
+ "type": "str"
+ }
+ ]
+ },
+ ...
+ }
+
+The whole schema information will be returned in one go, it contains
+commands and event. It doesn't support to be filtered by type or name.
+
+We have four types (ImageInfo, BlockStats, PciDeviceInfo, SchemaData)
+that uses themself in their own define data directly or indirectly,
+we will not repeatedly extend them to avoid dead loop.
+
+== more description of 'DataObject' type ==
+
+We use 'DataObject' to describe dynamical data struct, it might be
+nested dictionary, list or string.
+
+'DataObject' itself is a arbitrary and nested dictionary, the
+dictionary has three keys ('key', 'type', 'data'), 'key' and
+'data' are optional.
+
+* For describing Dictionary, we set the key to 'key', and set the
+ value to 'type'
+* For describing List, we don't set 'key', just set the value to
+ 'type'
+* If the value of dictionary or list is non-native type, we extend
+ the non-native type to dictionary, set it to 'data', and set the
+ non-native type's name to 'type'.
+* If the value of dictionary or list is dictionary or list, 'type'
+ won't be set.
+
+== examples ==
+
+1) Dict, value is native type
+{ 'id': 'str', ... }
+--------------------
+[
+ {
+ "key": "id",
+ "type": "str"
+ },
+ .....
+]
+
+2) Dict, value is defined types
+{ 'options': 'TpmTypeOptions' }
+-------------------------------
+[
+ {
+ "key": "options",
+ "type": "TpmTypeOptions",
+ "data": [
+ {
+ "key": "passthrough",
+ "type": "str",
+ }
+ ]
+ },
+ .....
+]
+
+3) List, value is native type
+['str', ... ]
+-------------
+[
+ {
+ "type": "str"
+ },
+ ....
+]
+
+4) List, value is defined types
+['TpmTypeOptions', ... ]
+------------------------
+[
+ {
+ "type": "TpmTypeOptions",
+ "data": [
+ {
+ "key": "passthrough",
+ "type": "str",
+ }
+ ]
+ },
+ .....
+]
+
+5) Dict, value is dictionary
+{ 'info': { 'age': 'init', ... } }
+-----------------------------
+[
+ {
+ "key": "info",
+ "data": [
+ {
+ "key": "age",
+ "type": "init",
+ },
+ ...
+ ]
+ },
+]
+
+6) Dict, value is list
+{ 'info': [ 'str', ... } }
+-----------------------------
+[
+ {
+ "key": "info",
+ "data": [
+ {
+ "type": "str",
+ },
+ ...
+ ]
+ },
+]
diff --git a/qapi-schema.json b/qapi-schema.json
index 7b9fef1..cf03391 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -3679,3 +3679,72 @@
'*cpuid-input-ecx': 'int',
'cpuid-register': 'X86CPURegister32',
'features': 'int' } }
+
+##
+# @DataObject
+#
+# Details of a data object, it can be nested dictionary/list
+#
+# @key: #optional Data object key
+#
+# @type: Data object type name
+#
+# @optional: #optional whether the data object is optional
+#
+# @data: #optional DataObject list, can be a dictionary or list type
+#
+# Since: 1.6
+##
+{ 'type': 'DataObject',
+ 'data': { '*key': 'str', '*type': 'str', '*optional': 'bool', '*data':
['DataObject'] } }
+
+##
+# @SchemaMetaType
+#
+# Possible meta types of a schema entry
+#
+# @Command: QMP command
+#
+# @Type: defined new data type
+#
+# @Enumeration: enumeration data type
+#
+# @Union: union data type
+#
+# Since: 1.6
+##
+{ 'enum': 'SchemaMetaType',
+ 'data': ['Command', 'Type', 'Enumeration', 'Union'] }
+
+##
+# @SchemaEntry
+#
+# Details of schema items
+#
+# @type: Entry's type in string format
+#
+# @name: Entry name
+#
+# @data: #optional list of DataObject. This can have different meaning
+# depending on the 'type' value. For example, for a QMP command,
+# this member contains an argument listing. For an enumeration,
+# it contains the enum's values and so on
+#
+# @returns: #optional list of DataObject, return data after executing
+# QMP command
+#
+# Since: 1.6
+##
+{ 'type': 'SchemaEntry', 'data': { 'type': 'SchemaMetaType',
+ 'name': 'str', '*data': ['DataObject'], '*returns': ['DataObject'] } }
+
+##
+# @query-qmp-schema
+#
+# Query QMP schema information
+#
+# Returns: list of @SchemaEntry. Returns an error if json string is invalid.
+#
+# Since: 1.6
+##
+{ 'command': 'query-qmp-schema', 'returns': ['SchemaEntry'] }
diff --git a/qmp-commands.hx b/qmp-commands.hx
index e075df4..e3cbe93 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -3047,3 +3047,42 @@ Example:
<- { "return": {} }
EQMP
+
+ {
+ .name = "query-qmp-schema",
+ .args_type = "",
+ .mhandler.cmd_new = qmp_marshal_input_query_qmp_schema,
+ },
+
+
+SQMP
+query-qmp-schema
+----------------
+
+query qmp schema information
+
+Return a json-object with the following information:
+
+- "name": qmp schema name (json-string)
+- "type": qmp schema type, it can be 'comand', 'type', 'enum', 'union', 'event'
+- "data": schema data (json-object, optional)
+- "returns": return data of qmp command (json-object, optional)
+
+Example:
+
+-> { "execute": "query-qmp-schema" }
+<- { "return": [
+ {
+ "name": "query-name",
+ "type": "Command",
+ "returns": [
+ {
+ "key": "*name",
+ "type": "str"
+ }
+ ]
+ }
+ ]
+ }
+
+EQMP
diff --git a/qmp.c b/qmp.c
index 4c149b3..3ace3a6 100644
--- a/qmp.c
+++ b/qmp.c
@@ -25,6 +25,8 @@
#include "sysemu/blockdev.h"
#include "qom/qom-qobject.h"
#include "hw/boards.h"
+#include "qmp-schema.h"
+#include "qapi/qmp/qjson.h"
NameInfo *qmp_query_name(Error **errp)
{
@@ -486,6 +488,311 @@ CpuDefinitionInfoList *qmp_query_cpu_definitions(Error
**errp)
return arch_query_cpu_definitions(errp);
}
+/*
+ * Use a string to record the visit path, type index of each node
+ * will be saved to the string, indexes are split by ':'.
+ */
+static char visit_path_str[1024];
+
+/* push the type index to visit_path_str */
+static void push_id(int id)
+{
+ char *end = strrchr(visit_path_str, ':');
+ char type_idx[256];
+ int num;
+
+ num = sprintf(type_idx, "%d:", id);
+
+ if (end) {
+ /* avoid overflow */
+ assert(end - visit_path_str + 1 + num < sizeof(visit_path_str));
+ sprintf(end + 1, "%d:", id);
+ } else {
+ sprintf(visit_path_str, "%d:", id);
+ }
+}
+
+/* pop the type index from visit_path_str */
+static void pop_id(void)
+{
+ char *p = strrchr(visit_path_str, ':');
+
+ assert(p != NULL);
+ *p = '\0';
+ p = strrchr(visit_path_str, ':');
+ if (p) {
+ *(p + 1) = '\0';
+ } else {
+ visit_path_str[0] = '\0';
+ }
+}
+
+static const char *qstring_copy_str(QObject *data)
+{
+ QString *qstr;
+
+ if (!data) {
+ return NULL;
+ }
+ qstr = qobject_to_qstring(data);
+ if (qstr) {
+ return qstring_get_str(qstr);
+ } else {
+ return NULL;
+ }
+}
+
+static DataObjectList *visit_qobj_dict(QObject *data);
+static DataObjectList *visit_qobj_list(QObject *data);
+
+/* extend defined type to json object */
+static DataObjectList *extend_type(const char* str)
+{
+ DataObjectList *data_list;
+ QObject *data;
+ QDict *qdict;
+ const QDictEntry *ent;
+ int i;
+
+ /* don't extend builtin types */
+ if (!strcmp(str, "str") || !strcmp(str, "int") ||
+ !strcmp(str, "number") || !strcmp(str, "bool") ||
+ !strcmp(str, "int8") || !strcmp(str, "int16") ||
+ !strcmp(str, "int32") || !strcmp(str, "int64") ||
+ !strcmp(str, "uint8") || !strcmp(str, "uint16") ||
+ !strcmp(str, "uint32") || !strcmp(str, "uint64")) {
+ return NULL;
+ }
+
+ for (i = 0; qmp_schema_table[i]; i++) {
+ data = qobject_from_json(qmp_schema_table[i]);
+ assert(data != NULL);
+
+ qdict = qobject_to_qdict(data);
+ assert(qdict != NULL);
+
+ ent = qdict_first(qdict);
+ if (!qdict_get(qdict, "enum") && !qdict_get(qdict, "type")
+ && !qdict_get(qdict, "union")) {
+ continue;
+ }
+
+ if (!strcmp(str, qstring_copy_str(ent->value))) {
+ char *start, *end;
+ char cur_idx[256];
+ char type_idx[256];
+
+ start = visit_path_str;
+ sprintf(type_idx, "%d", i);
+ while(start) {
+ end = strchr(start, ':');
+ if (!end) {
+ break;
+ }
+ snprintf(cur_idx, end - start + 1, "%s", start);
+ start = end + 1;
+ /* if the type was already extended in parent node,
+ * we don't extend it again to avoid dead loop. */
+ if (!strcmp(cur_idx, type_idx)) {
+ return NULL;
+ }
+ }
+ /* push index to visit_path_str before extending */
+ push_id(i);
+
+ data = qdict_get(qdict, "data");
+ if(data) {
+ if (data->type->code == QTYPE_QDICT) {
+ data_list = visit_qobj_dict(data);
+ } else if (data->type->code == QTYPE_QLIST) {
+ data_list = visit_qobj_list(data);
+ }
+ /* pop index from visit_path_str after extending */
+ pop_id();
+
+ return data_list;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static DataObjectList *visit_qobj_list(QObject *data)
+{
+ DataObjectList *obj_list, *obj_last_entry, *obj_entry;
+ DataObject *obj_info;
+ const QListEntry *ent;
+ QList *qlist;
+
+ qlist = qobject_to_qlist(data);
+ assert(qlist != NULL);
+
+ obj_list = NULL;
+ for (ent = qlist_first(qlist); ent; ent = qlist_next(ent)) {
+ obj_info = g_malloc0(sizeof(*obj_info));
+ obj_entry = g_malloc0(sizeof(*obj_entry));
+ obj_entry->value = obj_info;
+ obj_entry->next = NULL;
+
+ if (!obj_list) {
+ obj_list = obj_entry;
+ } else {
+ obj_last_entry->next = obj_entry;
+ }
+ obj_last_entry = obj_entry;
+
+ if (ent->value->type->code == QTYPE_QDICT) {
+ obj_info->data = visit_qobj_dict(ent->value);
+ } else if (ent->value->type->code == QTYPE_QLIST) {
+ obj_info->data = visit_qobj_list(ent->value);
+ } else if (ent->value->type->code == QTYPE_QSTRING) {
+ obj_info->has_type = true;
+ obj_info->type = g_strdup(qstring_copy_str(ent->value));
+ obj_info->data = extend_type(qstring_copy_str(ent->value));
+ }
+ if (obj_info->data) {
+ obj_info->has_data = true;
+ }
+ }
+
+ return obj_list;
+}
+
+static DataObjectList *visit_qobj_dict(QObject *data)
+{
+ DataObjectList *obj_list, *obj_last_entry, *obj_entry;
+ DataObject *obj_info;
+ const QDictEntry *ent;
+ QDict *qdict;
+
+ qdict = qobject_to_qdict(data);
+ assert(qdict != NULL);
+
+ obj_list = NULL;
+ for (ent = qdict_first(qdict); ent; ent = qdict_next(qdict, ent)) {
+ obj_info = g_malloc0(sizeof(*obj_info));
+ obj_entry = g_malloc0(sizeof(*obj_entry));
+ obj_entry->value = obj_info;
+ obj_entry->next = NULL;
+
+ if (!obj_list) {
+ obj_list = obj_entry;
+ } else {
+ obj_last_entry->next = obj_entry;
+ }
+ obj_last_entry = obj_entry;
+
+ if (ent->key[0] == '*') {
+ obj_info->key = g_strdup(ent->key + 1);
+ obj_info->has_optional = true;
+ obj_info->optional = true;
+ } else {
+ obj_info->key = g_strdup(ent->key);
+ }
+ obj_info->has_key = true;
+
+ if (ent->value->type->code == QTYPE_QDICT) {
+ obj_info->data = visit_qobj_dict(ent->value);
+ } else if (ent->value->type->code == QTYPE_QLIST) {
+ obj_info->data = visit_qobj_list(ent->value);
+ } else if (ent->value->type->code == QTYPE_QSTRING) {
+ obj_info->has_type = true;
+ obj_info->type = g_strdup(qstring_copy_str(ent->value));
+ obj_info->data = extend_type(qstring_copy_str(ent->value));
+ }
+ if (obj_info->data) {
+ obj_info->has_data = true;
+ }
+ }
+
+ return obj_list;
+}
+
+SchemaEntryList *qmp_query_qmp_schema(Error **errp)
+{
+ SchemaEntryList *list, *last_entry, *entry;
+ SchemaEntry *info;
+ DataObjectList *obj_entry;
+ DataObject *obj_info;
+ QObject *data;
+ QDict *qdict;
+ int i;
+
+ list = NULL;
+ for (i = 0; qmp_schema_table[i]; i++) {
+ data = qobject_from_json(qmp_schema_table[i]);
+ assert(data != NULL);
+
+ qdict = qobject_to_qdict(data);
+ assert(qdict != NULL);
+
+ if (qdict_get(qdict, "command")) {
+ info = g_malloc0(sizeof(*info));
+ info->type = SCHEMA_META_TYPE_COMMAND;
+ info->name = strdup(qdict_get_str(qdict, "command"));
+ } else {
+ continue;
+ }
+
+ memset(visit_path_str, 0, sizeof(visit_path_str));
+ data = qdict_get(qdict, "data");
+ if (data) {
+ info->has_data = true;
+ if (data->type->code == QTYPE_QLIST) {
+ info->data = visit_qobj_list(data);
+ } else if (data->type->code == QTYPE_QDICT) {
+ info->data = visit_qobj_dict(data);
+ } else if (data->type->code == QTYPE_QSTRING) {
+ info->data =
extend_type(qstring_get_str(qobject_to_qstring(data)));
+ if (!info->data) {
+ obj_info = g_malloc0(sizeof(*obj_info));
+ obj_entry = g_malloc0(sizeof(*obj_entry));
+ obj_entry->value = obj_info;
+ obj_info->has_type = true;
+ obj_info->type = g_strdup(qdict_get_str(qdict, "data"));
+ info->data = obj_entry;
+ }
+ } else {
+ abort();
+ }
+ }
+
+ memset(visit_path_str, 0, sizeof(visit_path_str));
+ data = qdict_get(qdict, "returns");
+ if (data) {
+ info->has_returns = true;
+ if (data->type->code == QTYPE_QLIST) {
+ info->returns = visit_qobj_list(data);
+ } else if (data->type->code == QTYPE_QDICT) {
+ info->returns = visit_qobj_dict(data);
+ } else if (data->type->code == QTYPE_QSTRING) {
+ info->returns = extend_type(qstring_copy_str(data));
+ if (!info->returns) {
+ obj_info = g_malloc0(sizeof(*obj_info));
+ obj_entry = g_malloc0(sizeof(*obj_entry));
+ obj_entry->value = obj_info;
+ obj_info->has_type = true;
+ obj_info->type = g_strdup(qdict_get_str(qdict, "returns"));
+ info->returns = obj_entry;
+ }
+ }
+ }
+
+ entry = g_malloc0(sizeof(DataObjectList *));
+ entry->value = info;
+ entry->next = NULL;
+ if (!list) {
+ list = entry;
+ } else {
+ last_entry->next = entry;
+ }
+ last_entry = entry;
+ }
+
+ return list;
+}
+
void qmp_add_client(const char *protocol, const char *fdname,
bool has_skipauth, bool skipauth, bool has_tls, bool tls,
Error **errp)
--
1.8.3.1
- [Qemu-devel] [PATCH v2 0/2] QMP full introspection, Amos Kong, 2013/07/16
- [Qemu-devel] [PATCH v2 1/2] qapi: change qapi to convert schema json, Amos Kong, 2013/07/16
- [Qemu-devel] [PATCH v2 2/2] full introspection support for QMP,
Amos Kong <=
- Re: [Qemu-devel] [PATCH v2 2/2] full introspection support for QMP, Paolo Bonzini, 2013/07/16
- Re: [Qemu-devel] [PATCH v2 2/2] full introspection support for QMP, Amos Kong, 2013/07/16
- Re: [Qemu-devel] [PATCH v2 2/2] full introspection support for QMP, Paolo Bonzini, 2013/07/16
- Re: [Qemu-devel] [PATCH v2 2/2] full introspection support for QMP, Amos Kong, 2013/07/16
- Re: [Qemu-devel] [PATCH v2 2/2] full introspection support for QMP, Paolo Bonzini, 2013/07/16
- Re: [Qemu-devel] [PATCH v2 2/2] full introspection support for QMP, Amos Kong, 2013/07/26
Re: [Qemu-devel] [PATCH v2 2/2] full introspection support for QMP, Luiz Capitulino, 2013/07/17
Re: [Qemu-devel] [PATCH v2 2/2] full introspection support for QMP, Eric Blake, 2013/07/19