Allow detecting capabilities according to the qemu QMP schema. This is necessary as sometimes the availability of certain options depends on the presence of a field in the schema. This patch adds support for loading the QMP schema when detecting qemu capabilities and adds a very simple query language to allow traversing the schema and selecting a certain element from it. The infrastructure in this patch uses a query path to set a specific capability flag according to the availability of the given element in the schema. --- Notes: v2: - fixed typos - added docs for one of the helper functions and changed param order - added hint for query string format docs src/qemu/qemu_capabilities.c | 191 ++++++++++++++++++++++ src/qemu/qemu_capabilities.h | 1 + tests/qemucapabilitiesdata/caps_2.5.0.x86_64.xml | 1 + tests/qemucapabilitiesdata/caps_2.6.0.ppc64le.xml | 1 + tests/qemucapabilitiesdata/caps_2.6.0.x86_64.xml | 1 + tests/qemucapabilitiesdata/caps_2.7.0.x86_64.xml | 1 + 6 files changed, 196 insertions(+) diff --git a/src/qemu/qemu_capabilities.c b/src/qemu/qemu_capabilities.c index 7a8202a..56c9deb 100644 --- a/src/qemu/qemu_capabilities.c +++ b/src/qemu/qemu_capabilities.c @@ -347,6 +347,7 @@ VIR_ENUM_IMPL(virQEMUCaps, QEMU_CAPS_LAST, "machine-iommu", "virtio-vga", "drive-iotune-max-length", + "query-qmp-schema", ); @@ -1488,6 +1489,7 @@ struct virQEMUCapsStringFlags virQEMUCapsCommands[] = { { "rtc-reset-reinjection", QEMU_CAPS_RTC_RESET_REINJECTION }, { "migrate-incoming", QEMU_CAPS_INCOMING_DEFER }, { "query-hotpluggable-cpus", QEMU_CAPS_QUERY_HOTPLUGGABLE_CPUS }, + { "query-qmp-schema", QEMU_CAPS_QUERY_QMP_SCHEMA } }; struct virQEMUCapsStringFlags virQEMUCapsMigration[] = { @@ -1692,6 +1694,11 @@ static struct virQEMUCapsStringFlags virQEMUCapsObjectPropsUSBNECXHCI[] = { { "p3", QEMU_CAPS_NEC_USB_XHCI_PORTS }, }; +/* see documentation for virQEMUCapsQMPSchemaGetByPath for the query format */ +static struct virQEMUCapsStringFlags virQEMUCapsQMPSchemaQueries[] = { + { "bogus/path/to/satisfy/compiler", 0 }, +}; + struct virQEMUCapsObjectTypeProps { const char *type; struct virQEMUCapsStringFlags *props; @@ -3696,6 +3703,187 @@ virQEMUCapsInitArchQMPBasic(virQEMUCapsPtr qemuCaps, return ret; } + +/** + * virQEMUCapsQMPSchemaObjectGetType: + * @field: name of the object containing the requested type + * @name: name of the requested type + * @namefield: name of the object property holding @name + * + * Helper that selects the type of a QMP schema object or it's variant. Returns + * the type string on success or NULL on error. + */ +static const char * +virQEMUCapsQMPSchemaObjectGetType(const char *field, + const char *name, + const char *namefield, + virJSONValuePtr elem) +{ + virJSONValuePtr arr; + virJSONValuePtr cur; + const char *curname; + const char *type; + size_t i; + + if (!(arr = virJSONValueObjectGetArray(elem, field))) + return NULL; + + for (i = 0; i < virJSONValueArraySize(arr); i++) { + if (!(cur = virJSONValueArrayGet(arr, i)) || + !(curname = virJSONValueObjectGetString(cur, namefield)) || + !(type = virJSONValueObjectGetString(cur, "type"))) + continue; + + if (STREQ(name, curname)) + return type; + } + + return NULL; +} + + +static virJSONValuePtr +virQEMUCapsQMPSchemaTraverse(const char *basename, + char **query, + virHashTablePtr schema) +{ + virJSONValuePtr base; + const char *metatype; + + do { + if (!(base = virHashLookup(schema, basename))) + return NULL; + + if (!*query) + return base; + + if (!(metatype = virJSONValueObjectGetString(base, "meta-type"))) + return NULL; + + /* flatten arrays by default */ + if (STREQ(metatype, "array")) { + if (!(basename = virJSONValueObjectGetString(base, "element-type"))) + return NULL; + + continue; + } else if (STREQ(metatype, "object")) { + if (**query == '+') + basename = virQEMUCapsQMPSchemaObjectGetType("variants", + *query + 1, + "case", base); + else + basename = virQEMUCapsQMPSchemaObjectGetType("members", + *query, + "name", base); + + if (!basename) + return NULL; + } else if (STREQ(metatype, "command") || + STREQ(metatype, "event")) { + if (!(basename = virJSONValueObjectGetString(base, *query))) + return NULL; + } else { + /* alternates, basic types and enums can't be entered */ + return NULL; + } + + query++; + } while (*query); + + return base; +} + + +/** + * virQEMUCapsQMPSchemaGetByPath: + * @query: string specifying the required data type (see below) + * @schema: hash table containing the schema data + * @entry: filled with the located schema object requested by @query + * + * Retrieves the requested schema entry specified by @query to @entry. The + * @query parameter has the following syntax which is very closely tied to the + * qemu schema syntax entries separated by slashes with a few special characters: + * + * "command_or_event/attribute/subattribute/+variant_discriminator/subattribute" + * + * command_or_event: name of the event or attribute to introspect + * attribute: selects whether arguments or return type should be introspected + * ("arg-type" or "ret-type" for commands, "arg-type" for events) + * subattribute: specifies member name of object types + * +variant_discriminator: In the case of unionized objects, select a + * specific case to introspect. + * + * Array types are automatically flattened to the singular type. Alternate + * types are currently not supported. + * + * The above types can be chained arbitrarily using slashes to construct any + * path into the schema tree. + * + * Returns 0 on success (including if the requested schema was not found) and + * fills @entry appropriately. On failure returns -1 and sets an appropriate + * error message. + */ +static int +virQEMUCapsQMPSchemaGetByPath(const char *query, + virHashTablePtr schema, + virJSONValuePtr *entry) +{ + char **elems = NULL; + + *entry = NULL; + + if (!(elems = virStringSplit(query, "/", 0))) + return -1; + + if (!*elems) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("malformed query string")); + virStringFreeList(elems); + return -1; + } + + *entry = virQEMUCapsQMPSchemaTraverse(*elems, elems + 1, schema); + + virStringFreeList(elems); + return 0; +} + + +static bool +virQEMUCapsQMPSchemaQueryPath(const char *query, + virHashTablePtr schema) +{ + virJSONValuePtr entry; + + if (virQEMUCapsQMPSchemaGetByPath(query, schema, &entry)) + return false; + + return !!entry; +} + + +static int +virQEMUCapsProbeQMPSchemaCapabilities(virQEMUCapsPtr qemuCaps, + qemuMonitorPtr mon) +{ + struct virQEMUCapsStringFlags *entry; + virHashTablePtr schema; + size_t i; + + if (!(schema = qemuMonitorQueryQMPSchema(mon))) + return -1; + + for (i = 0; i < ARRAY_CARDINALITY(virQEMUCapsQMPSchemaQueries); i++) { + entry = virQEMUCapsQMPSchemaQueries + i; + + if (virQEMUCapsQMPSchemaQueryPath(entry->value, schema)) + virQEMUCapsSet(qemuCaps, entry->flag); + } + + virHashFree(schema); + return 0; +} + + int virQEMUCapsInitQMPMonitor(virQEMUCapsPtr qemuCaps, qemuMonitorPtr mon) @@ -3798,6 +3986,9 @@ virQEMUCapsInitQMPMonitor(virQEMUCapsPtr qemuCaps, goto cleanup; if (virQEMUCapsProbeQMPMigrationCapabilities(qemuCaps, mon) < 0) goto cleanup; + if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_QUERY_QMP_SCHEMA) && + virQEMUCapsProbeQMPSchemaCapabilities(qemuCaps, mon) < 0) + goto cleanup; /* 'intel-iommu' shows up as a device since 2.2.0, but can * not be used with -device until 2.7.0. Before that it diff --git a/src/qemu/qemu_capabilities.h b/src/qemu/qemu_capabilities.h index 6e7a855..4bb3dd1 100644 --- a/src/qemu/qemu_capabilities.h +++ b/src/qemu/qemu_capabilities.h @@ -381,6 +381,7 @@ typedef enum { QEMU_CAPS_MACHINE_IOMMU, /* -machine iommu=on */ QEMU_CAPS_DEVICE_VIRTIO_VGA, /* -device virtio-vga */ QEMU_CAPS_DRIVE_IOTUNE_MAX_LENGTH, /* -drive bps_max_length = and friends */ + QEMU_CAPS_QUERY_QMP_SCHEMA, /* query-qmp-schema command */ QEMU_CAPS_LAST /* this must always be the last item */ } virQEMUCapsFlags; diff --git a/tests/qemucapabilitiesdata/caps_2.5.0.x86_64.xml b/tests/qemucapabilitiesdata/caps_2.5.0.x86_64.xml index 62d42c3..bea01c7 100644 --- a/tests/qemucapabilitiesdata/caps_2.5.0.x86_64.xml +++ b/tests/qemucapabilitiesdata/caps_2.5.0.x86_64.xml @@ -186,6 +186,7 @@ <flag name='virtio-pci-disable-legacy'/> <flag name='machine-iommu'/> <flag name='virtio-vga'/> + <flag name='query-qmp-schema'/> <version>2005000</version> <kvmVersion>0</kvmVersion> <package></package> diff --git a/tests/qemucapabilitiesdata/caps_2.6.0.ppc64le.xml b/tests/qemucapabilitiesdata/caps_2.6.0.ppc64le.xml index 2b147fc..baada32 100644 --- a/tests/qemucapabilitiesdata/caps_2.6.0.ppc64le.xml +++ b/tests/qemucapabilitiesdata/caps_2.6.0.ppc64le.xml @@ -156,6 +156,7 @@ <flag name='virtio-pci-disable-legacy'/> <flag name='virtio-vga'/> <flag name='drive-iotune-max-length'/> + <flag name='query-qmp-schema'/> <version>2006000</version> <kvmVersion>0</kvmVersion> <package></package> diff --git a/tests/qemucapabilitiesdata/caps_2.6.0.x86_64.xml b/tests/qemucapabilitiesdata/caps_2.6.0.x86_64.xml index 1c309df..ed8f286 100644 --- a/tests/qemucapabilitiesdata/caps_2.6.0.x86_64.xml +++ b/tests/qemucapabilitiesdata/caps_2.6.0.x86_64.xml @@ -193,6 +193,7 @@ <flag name='machine-iommu'/> <flag name='virtio-vga'/> <flag name='drive-iotune-max-length'/> + <flag name='query-qmp-schema'/> <version>2006000</version> <kvmVersion>0</kvmVersion> <package></package> diff --git a/tests/qemucapabilitiesdata/caps_2.7.0.x86_64.xml b/tests/qemucapabilitiesdata/caps_2.7.0.x86_64.xml index 2f168da..6ebb670 100644 --- a/tests/qemucapabilitiesdata/caps_2.7.0.x86_64.xml +++ b/tests/qemucapabilitiesdata/caps_2.7.0.x86_64.xml @@ -194,6 +194,7 @@ <flag name='query-hotpluggable-cpus'/> <flag name='virtio-vga'/> <flag name='drive-iotune-max-length'/> + <flag name='query-qmp-schema'/> <version>2007000</version> <kvmVersion>0</kvmVersion> <package> (v2.7.0)</package> -- 2.10.2 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list