qemu-devel
[Top][All Lists]
Advanced

[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




reply via email to

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