Implement the XML parser and formatter for overriding of device properties such as: <qemu:override> <qemu:device alias='ua-disk'> <qemu:frontend> <qemu:property name='prop1' type='string' value='propval1'/> <qemu:property name='prop2' type='signed' value='-321'/> <qemu:property name='prop3' type='unsigned' value='123'/> <qemu:property name='prop4' type='bool' value='true'/> <qemu:property name='prop5' type='bool' value='false'/> <qemu:property name='prop6' type='bool' value='false'/> <qemu:property name='prop6' type='remove'/> </qemu:frontend> </qemu:device> </qemu:override> Signed-off-by: Peter Krempa <pkrempa@xxxxxxxxxx> --- src/conf/schemas/domaincommon.rng | 42 ++++ src/qemu/qemu_domain.c | 218 ++++++++++++++++++ src/qemu/qemu_domain.h | 33 +++ .../qemu-ns.x86_64-4.0.0.args | 2 +- .../qemu-ns.x86_64-latest.args | 2 +- tests/qemuxml2argvdata/qemu-ns.xml | 14 ++ .../qemu-ns.x86_64-latest.xml | 14 ++ 7 files changed, 323 insertions(+), 2 deletions(-) diff --git a/src/conf/schemas/domaincommon.rng b/src/conf/schemas/domaincommon.rng index 9c1b64a644..8ca6bee81f 100644 --- a/src/conf/schemas/domaincommon.rng +++ b/src/conf/schemas/domaincommon.rng @@ -80,6 +80,9 @@ <optional> <ref name="qemudeprecation"/> </optional> + <optional> + <ref name="qemuoverride"/> + </optional> <optional> <ref name="lxcsharens"/> </optional> @@ -7553,6 +7556,45 @@ </element> </define> + <define name="qemuoverrideproperty"> + <element name="property" ns="http://libvirt.org/schemas/domain/qemu/1.0"> + <attribute name="name"/> + <attribute name="type"> + <choice> + <value>string</value> + <value>signed</value> + <value>unsigned</value> + <value>bool</value> + <value>remove</value> + </choice> + </attribute> + <optional> + <attribute name="value"/> + </optional> + </element> + </define> + + <define name="qemuoverride"> + <element name="override" ns="http://libvirt.org/schemas/domain/qemu/1.0"> + <interleave> + <zeroOrMore> + <element name="device"> + <attribute name="alias"/> + <interleave> + <optional> + <element name="frontend"> + <zeroOrMore> + <ref name="qemuoverrideproperty"/> + </zeroOrMore> + </element> + </optional> + </interleave> + </element> + </zeroOrMore> + </interleave> + </element> + </define> + <!-- Optional hypervisor extensions in their own namespace: diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 3bf864bc5d..636e58061e 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -3181,6 +3181,23 @@ qemuDomainXmlNsDefFree(qemuDomainXmlNsDef *def) g_free(def->deprecationBehavior); + for (i = 0; i < def->ndeviceOverride; i++) { + size_t j; + g_free(def->deviceOverride[i].alias); + + for (j = 0; j < def->deviceOverride[i].nfrontend; j++) { + qemuDomainXmlNsOverrideProperty *prop = def->deviceOverride[i].frontend + j; + + g_free(prop->name); + g_free(prop->value); + virJSONValueFree(prop->json); + } + + g_free(def->deviceOverride[i].frontend); + } + + g_free(def->deviceOverride); + g_free(def); } @@ -3319,6 +3336,159 @@ qemuDomainDefNamespaceParseCaps(qemuDomainXmlNsDef *nsdef, return 0; } +VIR_ENUM_IMPL(qemuDomainXmlNsOverride, + QEMU_DOMAIN_XML_NS_OVERRIDE_LAST, + "", + "string", + "signed", + "unsigned", + "bool", + "remove", + ); + + +static int +qemuDomainDefNamespaceParseOverrideProperties(qemuDomainXmlNsOverrideProperty *props, + xmlNodePtr *propnodes, + size_t npropnodes) +{ + size_t i; + + for (i = 0; i < npropnodes; i++) { + qemuDomainXmlNsOverrideProperty *prop = props + i; + unsigned long long ull; + long long ll; + bool b; + + if (!(prop->name = virXMLPropString(propnodes[i], "name"))) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing 'name' attribute for qemu:property")); + return -1; + } + + if (STREQ(prop->name, "id")) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("property with name 'id' can't be overriden")); + return -1; + } + + if (virXMLPropEnum(propnodes[i], "type", + qemuDomainXmlNsOverrideTypeFromString, + VIR_XML_PROP_REQUIRED | VIR_XML_PROP_NONZERO, + &prop->type) < 0) + return -1; + + if (!(prop->value = virXMLPropString(propnodes[i], "value"))) { + if (prop->type != QEMU_DOMAIN_XML_NS_OVERRIDE_REMOVE) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing 'value' attribute for 'qemu:property'")); + return -1; + } + } + + switch (prop->type) { + case QEMU_DOMAIN_XML_NS_OVERRIDE_STRING: + prop->json = virJSONValueNewString(g_strdup(prop->value)); + break; + + case QEMU_DOMAIN_XML_NS_OVERRIDE_SIGNED: + if (virStrToLong_ll(prop->value, NULL, 10, &ll) < 0) { + virReportError(VIR_ERR_XML_ERROR, + _("invalid value '%s' of 'value' attribute of 'qemu:property'"), + prop->value); + return -1; + } + prop->json = virJSONValueNewNumberLong(ll); + break; + + case QEMU_DOMAIN_XML_NS_OVERRIDE_UNSIGNED: + if (virStrToLong_ullp(prop->value, NULL, 10, &ull) < 0) { + virReportError(VIR_ERR_XML_ERROR, + _("invalid value '%s' of 'value' attribute of 'qemu:property'"), + prop->value); + return -1; + } + prop->json = virJSONValueNewNumberUlong(ull); + break; + + case QEMU_DOMAIN_XML_NS_OVERRIDE_BOOL: + if (STRNEQ(prop->value, "true") && STRNEQ(prop->value, "false")) { + virReportError(VIR_ERR_XML_ERROR, + _("invalid value '%s' of 'value' attribute of 'qemu:property'"), + prop->value); + return -1; + } + + b = STREQ(prop->value, "true"); + prop->json = virJSONValueNewBoolean(b); + break; + + case QEMU_DOMAIN_XML_NS_OVERRIDE_REMOVE: + if (prop->value) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("setting 'value' attribute of 'qemu:property' doesn't make sense with 'remove' type")); + return -1; + } + break; + + case QEMU_DOMAIN_XML_NS_OVERRIDE_NONE: + case QEMU_DOMAIN_XML_NS_OVERRIDE_LAST: + virReportEnumRangeError(qemuDomainXmlNsOverrideType, prop->type); + return -1; + } + } + + return 0; +} + + +static int +qemuDomainDefNamespaceParseDeviceOverride(qemuDomainXmlNsDef *nsdef, + xmlXPathContextPtr ctxt) +{ + g_autofree xmlNodePtr *devicenodes = NULL; + ssize_t ndevicenodes; + size_t i; + + if ((ndevicenodes = virXPathNodeSet("./qemu:override/qemu:device", ctxt, &devicenodes)) < 0) + return -1; + + if (ndevicenodes == 0) + return 0; + + nsdef->deviceOverride = g_new0(qemuDomainXmlNsDeviceOverride, ndevicenodes); + nsdef->ndeviceOverride = ndevicenodes; + + for (i = 0; i < ndevicenodes; i++) { + qemuDomainXmlNsDeviceOverride *n = nsdef->deviceOverride + i; + g_autofree xmlNodePtr *propnodes = NULL; + ssize_t npropnodes; + VIR_XPATH_NODE_AUTORESTORE(ctxt); + + ctxt->node = devicenodes[i]; + + if (!(n->alias = virXMLPropString(devicenodes[i], "alias"))) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing 'alias' attribute for qemu:device")); + return -1; + } + + if ((npropnodes = virXPathNodeSet("./qemu:frontend/qemu:property", ctxt, &propnodes)) < 0) + return -1; + + if (npropnodes == 0) + continue; + + n->frontend = g_new0(qemuDomainXmlNsOverrideProperty, npropnodes); + n->nfrontend = npropnodes; + + if (qemuDomainDefNamespaceParseOverrideProperties(n->frontend, propnodes, npropnodes) < 0) + return -1; + } + + return 0; +} + static int qemuDomainDefNamespaceParse(xmlXPathContextPtr ctxt, @@ -3331,6 +3501,7 @@ qemuDomainDefNamespaceParse(xmlXPathContextPtr ctxt, if (qemuDomainDefNamespaceParseCommandlineArgs(nsdata, ctxt) < 0 || qemuDomainDefNamespaceParseCommandlineEnv(nsdata, ctxt) < 0 || + qemuDomainDefNamespaceParseDeviceOverride(nsdata, ctxt) < 0 || qemuDomainDefNamespaceParseCaps(nsdata, ctxt) < 0) goto cleanup; @@ -3386,6 +3557,52 @@ qemuDomainDefNamespaceFormatXMLCaps(virBuffer *buf, } +static void +qemuDomainDefNamespaceFormatXMLOverrideProperties(virBuffer *buf, + qemuDomainXmlNsOverrideProperty *props, + size_t nprops) +{ + size_t i; + + for (i = 0; i < nprops; i++) { + g_auto(virBuffer) propAttrBuf = VIR_BUFFER_INITIALIZER; + qemuDomainXmlNsOverrideProperty *prop = props + i; + + virBufferEscapeString(&propAttrBuf, " name='%s'", prop->name); + virBufferAsprintf(&propAttrBuf, " type='%s'", + qemuDomainXmlNsOverrideTypeToString(prop->type)); + virBufferEscapeString(&propAttrBuf, " value='%s'", prop->value); + + virXMLFormatElement(buf, "qemu:property", &propAttrBuf, NULL); + } +} + + +static void +qemuDomainDefNamespaceFormatXMLOverride(virBuffer *buf, + qemuDomainXmlNsDef *xmlns) +{ + g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf); + size_t i; + + for (i = 0; i < xmlns->ndeviceOverride; i++) { + qemuDomainXmlNsDeviceOverride *device = xmlns->deviceOverride + i; + g_auto(virBuffer) deviceAttrBuf = VIR_BUFFER_INITIALIZER; + g_auto(virBuffer) deviceChildBuf = VIR_BUFFER_INIT_CHILD(&childBuf); + g_auto(virBuffer) frontendChildBuf = VIR_BUFFER_INIT_CHILD(&deviceChildBuf); + + virBufferEscapeString(&deviceAttrBuf, " alias='%s'", device->alias); + + qemuDomainDefNamespaceFormatXMLOverrideProperties(&frontendChildBuf, device->frontend, device->nfrontend); + virXMLFormatElement(&deviceChildBuf, "qemu:frontend", NULL, &frontendChildBuf); + + virXMLFormatElement(&childBuf, "qemu:device", &deviceAttrBuf, &deviceChildBuf); + } + + virXMLFormatElement(buf, "qemu:override", NULL, &childBuf); +} + + static int qemuDomainDefNamespaceFormatXML(virBuffer *buf, void *nsdata) @@ -3394,6 +3611,7 @@ qemuDomainDefNamespaceFormatXML(virBuffer *buf, qemuDomainDefNamespaceFormatXMLCommandline(buf, cmd); qemuDomainDefNamespaceFormatXMLCaps(buf, cmd); + qemuDomainDefNamespaceFormatXMLOverride(buf, cmd); virBufferEscapeString(buf, "<qemu:deprecation behavior='%s'/>\n", cmd->deprecationBehavior); diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index edafb585b3..85adb6a5ba 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -443,6 +443,36 @@ struct _qemuDomainXmlNsEnvTuple { char *value; }; + +typedef enum { + QEMU_DOMAIN_XML_NS_OVERRIDE_NONE, + QEMU_DOMAIN_XML_NS_OVERRIDE_STRING, + QEMU_DOMAIN_XML_NS_OVERRIDE_SIGNED, + QEMU_DOMAIN_XML_NS_OVERRIDE_UNSIGNED, + QEMU_DOMAIN_XML_NS_OVERRIDE_BOOL, + QEMU_DOMAIN_XML_NS_OVERRIDE_REMOVE, + + QEMU_DOMAIN_XML_NS_OVERRIDE_LAST +} qemuDomainXmlNsOverrideType; +VIR_ENUM_DECL(qemuDomainXmlNsOverride); + +typedef struct _qemuDomainXmlNsOverrideProperty qemuDomainXmlNsOverrideProperty; +struct _qemuDomainXmlNsOverrideProperty { + char *name; + qemuDomainXmlNsOverrideType type; + char *value; + virJSONValue *json; +}; + +typedef struct _qemuDomainXmlNsDeviceOverride qemuDomainXmlNsDeviceOverride; +struct _qemuDomainXmlNsDeviceOverride { + char *alias; + + size_t nfrontend; + qemuDomainXmlNsOverrideProperty *frontend; +}; + + typedef struct _qemuDomainXmlNsDef qemuDomainXmlNsDef; struct _qemuDomainXmlNsDef { char **args; @@ -458,6 +488,9 @@ struct _qemuDomainXmlNsDef { * starting the VM to avoid any form of errors in the parser or when * changing qemu versions. The knob is mainly for development/CI purposes */ char *deprecationBehavior; + + size_t ndeviceOverride; + qemuDomainXmlNsDeviceOverride *deviceOverride; }; diff --git a/tests/qemuxml2argvdata/qemu-ns.x86_64-4.0.0.args b/tests/qemuxml2argvdata/qemu-ns.x86_64-4.0.0.args index 5105f410ce..236f984a90 100644 --- a/tests/qemuxml2argvdata/qemu-ns.x86_64-4.0.0.args +++ b/tests/qemuxml2argvdata/qemu-ns.x86_64-4.0.0.args @@ -31,7 +31,7 @@ BAR='' \ -device piix3-usb-uhci,id=usb,bus=pci.0,addr=0x1.0x2 \ -blockdev '{"driver":"host_device","filename":"/dev/HostVG/QEMUGuest1","node-name":"libvirt-1-storage","auto-read-only":true,"discard":"unmap"}' \ -blockdev '{"node-name":"libvirt-1-format","read-only":false,"driver":"raw","file":"libvirt-1-storage"}' \ --device ide-hd,bus=ide.0,unit=0,drive=libvirt-1-format,id=ide0-0-0,bootindex=1 \ +-device ide-hd,bus=ide.0,unit=0,drive=libvirt-1-format,id=ua-disk,bootindex=1 \ -device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x2 \ -unknown parameter \ -sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \ diff --git a/tests/qemuxml2argvdata/qemu-ns.x86_64-latest.args b/tests/qemuxml2argvdata/qemu-ns.x86_64-latest.args index 05ccade7e4..c0bf45000f 100644 --- a/tests/qemuxml2argvdata/qemu-ns.x86_64-latest.args +++ b/tests/qemuxml2argvdata/qemu-ns.x86_64-latest.args @@ -33,7 +33,7 @@ BAR='' \ -device '{"driver":"piix3-usb-uhci","id":"usb","bus":"pci.0","addr":"0x1.0x2"}' \ -blockdev '{"driver":"host_device","filename":"/dev/HostVG/QEMUGuest1","node-name":"libvirt-1-storage","auto-read-only":true,"discard":"unmap"}' \ -blockdev '{"node-name":"libvirt-1-format","read-only":false,"driver":"raw","file":"libvirt-1-storage"}' \ --device '{"driver":"ide-hd","bus":"ide.0","unit":0,"drive":"libvirt-1-format","id":"ide0-0-0","bootindex":1}' \ +-device '{"driver":"ide-hd","bus":"ide.0","unit":0,"drive":"libvirt-1-format","id":"ua-disk","bootindex":1}' \ -audiodev '{"id":"audio1","driver":"none"}' \ -device '{"driver":"virtio-balloon-pci","id":"balloon0","bus":"pci.0","addr":"0x2"}' \ -unknown parameter \ diff --git a/tests/qemuxml2argvdata/qemu-ns.xml b/tests/qemuxml2argvdata/qemu-ns.xml index 9d2f5502e9..36bf582dec 100644 --- a/tests/qemuxml2argvdata/qemu-ns.xml +++ b/tests/qemuxml2argvdata/qemu-ns.xml @@ -17,6 +17,7 @@ <disk type='block' device='disk'> <source dev='/dev/HostVG/QEMUGuest1'/> <target dev='hda' bus='ide'/> + <alias name='ua-disk'/> <address type='drive' controller='0' bus='0' target='0' unit='0'/> </disk> <controller type='ide' index='0'/> @@ -32,5 +33,18 @@ <qemu:add capability="blockdev"/> <qemu:del capability="name"/> </qemu:capabilities> + <qemu:override> + <qemu:device alias='ua-disk'> + <qemu:frontend> + <qemu:property name='prop1' type='string' value='propval1'/> + <qemu:property name='prop2' type='signed' value='-321'/> + <qemu:property name='prop3' type='unsigned' value='123'/> + <qemu:property name='prop4' type='bool' value='true'/> + <qemu:property name='prop5' type='bool' value='false'/> + <qemu:property name='prop6' type='bool' value='false'/> + <qemu:property name='prop6' type='remove'/> + </qemu:frontend> + </qemu:device> + </qemu:override> <qemu:deprecation behavior='crash'/> </domain> diff --git a/tests/qemuxml2xmloutdata/qemu-ns.x86_64-latest.xml b/tests/qemuxml2xmloutdata/qemu-ns.x86_64-latest.xml index 674525d033..a8998ae582 100644 --- a/tests/qemuxml2xmloutdata/qemu-ns.x86_64-latest.xml +++ b/tests/qemuxml2xmloutdata/qemu-ns.x86_64-latest.xml @@ -21,6 +21,7 @@ <driver name='qemu' type='raw'/> <source dev='/dev/HostVG/QEMUGuest1'/> <target dev='hda' bus='ide'/> + <alias name='ua-disk'/> <address type='drive' controller='0' bus='0' target='0' unit='0'/> </disk> <controller type='ide' index='0'> @@ -48,5 +49,18 @@ <qemu:add capability='blockdev'/> <qemu:del capability='name'/> </qemu:capabilities> + <qemu:override> + <qemu:device alias='ua-disk'> + <qemu:frontend> + <qemu:property name='prop1' type='string' value='propval1'/> + <qemu:property name='prop2' type='signed' value='-321'/> + <qemu:property name='prop3' type='unsigned' value='123'/> + <qemu:property name='prop4' type='bool' value='true'/> + <qemu:property name='prop5' type='bool' value='false'/> + <qemu:property name='prop6' type='bool' value='false'/> + <qemu:property name='prop6' type='remove'/> + </qemu:frontend> + </qemu:device> + </qemu:override> <qemu:deprecation behavior='crash'/> </domain> -- 2.35.1