qemu: domain: Add XML namespace code for overriding device config

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@redhat.com>
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
This commit is contained in:
Peter Krempa 2022-03-16 11:27:48 +01:00
parent 5747dff5f7
commit b2d4ae0ec3
7 changed files with 323 additions and 2 deletions

View File

@ -80,6 +80,9 @@
<optional> <optional>
<ref name="qemudeprecation"/> <ref name="qemudeprecation"/>
</optional> </optional>
<optional>
<ref name="qemuoverride"/>
</optional>
<optional> <optional>
<ref name="lxcsharens"/> <ref name="lxcsharens"/>
</optional> </optional>
@ -7560,6 +7563,45 @@
</element> </element>
</define> </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: Optional hypervisor extensions in their own namespace:

View File

@ -3181,6 +3181,23 @@ qemuDomainXmlNsDefFree(qemuDomainXmlNsDef *def)
g_free(def->deprecationBehavior); 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); g_free(def);
} }
@ -3319,6 +3336,159 @@ qemuDomainDefNamespaceParseCaps(qemuDomainXmlNsDef *nsdef,
return 0; 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 static int
qemuDomainDefNamespaceParse(xmlXPathContextPtr ctxt, qemuDomainDefNamespaceParse(xmlXPathContextPtr ctxt,
@ -3331,6 +3501,7 @@ qemuDomainDefNamespaceParse(xmlXPathContextPtr ctxt,
if (qemuDomainDefNamespaceParseCommandlineArgs(nsdata, ctxt) < 0 || if (qemuDomainDefNamespaceParseCommandlineArgs(nsdata, ctxt) < 0 ||
qemuDomainDefNamespaceParseCommandlineEnv(nsdata, ctxt) < 0 || qemuDomainDefNamespaceParseCommandlineEnv(nsdata, ctxt) < 0 ||
qemuDomainDefNamespaceParseDeviceOverride(nsdata, ctxt) < 0 ||
qemuDomainDefNamespaceParseCaps(nsdata, ctxt) < 0) qemuDomainDefNamespaceParseCaps(nsdata, ctxt) < 0)
goto cleanup; 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 static int
qemuDomainDefNamespaceFormatXML(virBuffer *buf, qemuDomainDefNamespaceFormatXML(virBuffer *buf,
void *nsdata) void *nsdata)
@ -3394,6 +3611,7 @@ qemuDomainDefNamespaceFormatXML(virBuffer *buf,
qemuDomainDefNamespaceFormatXMLCommandline(buf, cmd); qemuDomainDefNamespaceFormatXMLCommandline(buf, cmd);
qemuDomainDefNamespaceFormatXMLCaps(buf, cmd); qemuDomainDefNamespaceFormatXMLCaps(buf, cmd);
qemuDomainDefNamespaceFormatXMLOverride(buf, cmd);
virBufferEscapeString(buf, "<qemu:deprecation behavior='%s'/>\n", virBufferEscapeString(buf, "<qemu:deprecation behavior='%s'/>\n",
cmd->deprecationBehavior); cmd->deprecationBehavior);

View File

@ -443,6 +443,36 @@ struct _qemuDomainXmlNsEnvTuple {
char *value; 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; typedef struct _qemuDomainXmlNsDef qemuDomainXmlNsDef;
struct _qemuDomainXmlNsDef { struct _qemuDomainXmlNsDef {
char **args; char **args;
@ -458,6 +488,9 @@ struct _qemuDomainXmlNsDef {
* starting the VM to avoid any form of errors in the parser or when * 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 */ * changing qemu versions. The knob is mainly for development/CI purposes */
char *deprecationBehavior; char *deprecationBehavior;
size_t ndeviceOverride;
qemuDomainXmlNsDeviceOverride *deviceOverride;
}; };

View File

@ -31,7 +31,7 @@ BAR='' \
-device piix3-usb-uhci,id=usb,bus=pci.0,addr=0x1.0x2 \ -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 '{"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"}' \ -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 \ -device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x2 \
-unknown parameter \ -unknown parameter \
-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \ -sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \

View File

@ -33,7 +33,7 @@ BAR='' \
-device '{"driver":"piix3-usb-uhci","id":"usb","bus":"pci.0","addr":"0x1.0x2"}' \ -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 '{"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"}' \ -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"}' \ -audiodev '{"id":"audio1","driver":"none"}' \
-device '{"driver":"virtio-balloon-pci","id":"balloon0","bus":"pci.0","addr":"0x2"}' \ -device '{"driver":"virtio-balloon-pci","id":"balloon0","bus":"pci.0","addr":"0x2"}' \
-unknown parameter \ -unknown parameter \

View File

@ -17,6 +17,7 @@
<disk type='block' device='disk'> <disk type='block' device='disk'>
<source dev='/dev/HostVG/QEMUGuest1'/> <source dev='/dev/HostVG/QEMUGuest1'/>
<target dev='hda' bus='ide'/> <target dev='hda' bus='ide'/>
<alias name='ua-disk'/>
<address type='drive' controller='0' bus='0' target='0' unit='0'/> <address type='drive' controller='0' bus='0' target='0' unit='0'/>
</disk> </disk>
<controller type='ide' index='0'/> <controller type='ide' index='0'/>
@ -32,5 +33,18 @@
<qemu:add capability="blockdev"/> <qemu:add capability="blockdev"/>
<qemu:del capability="name"/> <qemu:del capability="name"/>
</qemu:capabilities> </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'/> <qemu:deprecation behavior='crash'/>
</domain> </domain>

View File

@ -21,6 +21,7 @@
<driver name='qemu' type='raw'/> <driver name='qemu' type='raw'/>
<source dev='/dev/HostVG/QEMUGuest1'/> <source dev='/dev/HostVG/QEMUGuest1'/>
<target dev='hda' bus='ide'/> <target dev='hda' bus='ide'/>
<alias name='ua-disk'/>
<address type='drive' controller='0' bus='0' target='0' unit='0'/> <address type='drive' controller='0' bus='0' target='0' unit='0'/>
</disk> </disk>
<controller type='ide' index='0'> <controller type='ide' index='0'>
@ -48,5 +49,18 @@
<qemu:add capability='blockdev'/> <qemu:add capability='blockdev'/>
<qemu:del capability='name'/> <qemu:del capability='name'/>
</qemu:capabilities> </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'/> <qemu:deprecation behavior='crash'/>
</domain> </domain>