[PATCH v2 2/6] qemu: domain: Add XML namespace code for overriding device config

Peter Krempa pkrempa at redhat.com
Tue Mar 22 11:27:21 UTC 2022


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 at redhat.com>
---
 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



More information about the libvir-list mailing list