From: Hyman Huang <yong.huang(a)smartx.com>
This implements XML support for automatically attaching a
vhostuser port to an Open vSwitch bridge.
Here is an example of the config for a vhostuser interface
that attached to bridge automatically:
<interface type='vhostuser'>
<mac address='52:54:00:3b:83:1b'/>
<source type='unix' path='/tmp/vhostifname'
mode='server'/>
<virtualport type='openvswitch'>
<parameters interfaceid='9317d6b7-5fae-4464-a7e9-87d90eff2204'/>
</virtualport>
<backend type='openvswitch' autoiface='yes'>
<parameters bridge='ovsbr-ddpbxnhaq'/>
</backend>
<model type='virtio'/>
<driver queues='5'/>
</interface>
This setup offers an automated method to configure a vhostuser
interface in server mode, simplifying integration with DPDK-enabled
Open vSwitch bridges.
---
docs/formatdomain.rst | 37 +++++++++++-
src/conf/domain_conf.c | 59 ++++++++++++++++++-
src/conf/domain_conf.h | 6 ++
src/conf/domain_postparse.c | 46 +++++++++++++++
src/conf/domain_validate.c | 24 ++++++++
src/conf/schemas/domaincommon.rng | 20 +++++++
.../net-vhostuser.x86_64-latest.xml | 10 ++++
tests/qemuxmlconfdata/net-vhostuser.xml | 10 ++++
8 files changed, 209 insertions(+), 3 deletions(-)
diff --git a/docs/formatdomain.rst b/docs/formatdomain.rst
index 9a2f065590..d7051618a7 100644
--- a/docs/formatdomain.rst
+++ b/docs/formatdomain.rst
@@ -6533,6 +6533,12 @@ plane is based on shared memory.
<source type='unix' path='/tmp/vhost2.sock'
mode='client'>
<reconnect enabled='yes' timeout='10'/>
</source>
+ <virtualport type='openvswitch'>
+ <parameters interfaceid='9317d6b7-5fae-4464-a7e9-87d90eff2204'/>
+ </virtualport>
+ <backend type='openvswitch' autoiface='yes'>
+ <parameters bridge='ovsbr-ddpbxnhaq'/>
+ </backend>
<model type='virtio'/>
<driver queues='5'/>
</interface>
@@ -6557,11 +6563,40 @@ two attributes ``enabled`` (which accepts ``yes`` and ``no``) and
``timeout`` which specifies the amount of seconds after which
hypervisor tries to reconnect.
+The ``<backend>`` element specifies the backend implementation of
+the ``vhostuser`` interface type. The ``type`` attribute is required
+and currently supports ``openvswitch`` and ``passt``.
+
+If ``type='openvswitch'``:
+
+ * The ``autoiface`` attribute may be specified (``yes`` or ``no``).
+ * If ``autoiface='yes'``, the ``<parameters>`` element ``bridge``
+ attribute is mandatory. Libvirt will derive the interface name
+ by extracting the substring after the ``/`` character in the
+ vhostuser server path, and attach it to the bridge specified by
+ the ``bridge`` attribute.
+
+If ``type='passt'``:
+
+ * See the `here
+ <formatdomain.html#vhost-user-connection-with-passt-backend>`__
+ section for detailed information.
+
+The optional ``<virtualport>`` element applies only to the
+``openvswitch`` backend (which is both the default and only supported
+type currently). See the `here
+<formatdomain.html#bridge-to-lan>`__ section for detailed information
+about ``<virtualport type='openvswitch'/>``.
+
Note that when ``mode='server'`` is used, the hypervisor will wait for the
incoming connection to be established prior to actually running the VM. This is
not possible when hotplugging an interface with such config so the VM will
continue to run even when no connection is made. It's advised to use
-``mode='client'`` instead.
+``mode='client'`` instead. :since:`Since 11.6.0`, Libvirt can automatically
+attach the vhostuser port to the Open vSwitch bridge (i.e., the ``backend``
+element has ``type='openvswitch'`` and ``autoiface='yes'``). This setup
+offers an automated method to configure a vhostuser interface in server
+mode, simplifying integration with DPDK-enabled Open vSwitch bridges.
vhost-user connection with passt backend
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c
index 9862a76023..2b0b840819 100644
--- a/src/conf/domain_conf.c
+++ b/src/conf/domain_conf.c
@@ -650,6 +650,7 @@ VIR_ENUM_IMPL(virDomainNetBackend,
VIR_DOMAIN_NET_BACKEND_LAST,
"default",
"passt",
+ "openvswitch",
);
VIR_ENUM_IMPL(virDomainNetProto,
@@ -9773,6 +9774,8 @@ virDomainNetBackendParseXML(xmlNodePtr node,
{
g_autofree char *tap = virXMLPropString(node, "tap");
g_autofree char *vhost = virXMLPropString(node, "vhost");
+ virTristateBool autoiface;
+ xmlNodePtr parameters = NULL;
/* In the case of NET_TYPE_USER, backend type can be unspecified
* (i.e. VIR_DOMAIN_NET_BACKEND_DEFAULT) and that means 'use
@@ -9798,8 +9801,33 @@ virDomainNetBackendParseXML(xmlNodePtr node,
return -1;
}
- if (def->backend.type == VIR_DOMAIN_NET_BACKEND_PASST)
+ switch (def->backend.type) {
+ case VIR_DOMAIN_NET_BACKEND_DEFAULT:
+ break;
+
+ case VIR_DOMAIN_NET_BACKEND_PASST:
def->backend.data.passt.logFile = virXMLPropString(node,
"logFile");
+ break;
+
+ case VIR_DOMAIN_NET_BACKEND_OPENVSWITCH:
+ if (virXMLPropTristateBool(node, "autoiface", VIR_XML_PROP_NONE,
+ &autoiface) < 0)
+ return -1;
+ virTristateBoolToBool(autoiface,
&def->backend.data.openvswitch.autoiface);
+
+ if (def->backend.data.openvswitch.autoiface) {
+ if ((parameters = virXMLNodeGetSubelement(node, "parameters"))) {
+ def->backend.data.openvswitch.iface = virXMLPropString(parameters,
"iface");
+ def->backend.data.openvswitch.bridge = virXMLPropString(parameters,
"bridge");
+ }
+ }
+ break;
+
+ case VIR_DOMAIN_NET_BACKEND_LAST:
+ default:
+ virReportEnumRangeError(virDomainNetBackendType, def->backend.type);
+ break;
+ }
if (tap)
def->backend.tap = virFileSanitizePath(tap);
@@ -10142,6 +10170,10 @@ virDomainNetDefParseXML(virDomainXMLOption *xmlopt,
source_node, ctxt) < 0)
return NULL;
}
+
+ virtualport_flags = VIR_VPORT_XML_GENERATE_MISSING_DEFAULTS |
+ VIR_VPORT_XML_REQUIRE_ALL_ATTRIBUTES |
+ VIR_VPORT_XML_REQUIRE_TYPE;
}
break;
@@ -24927,11 +24959,26 @@ virDomainNetTeamingInfoFormat(virDomainNetTeamingInfo *teaming,
}
+static void
+virDomainNetBackendOpenvswitchIfaceFormat(virDomainNetBackend *backend,
+ virBuffer *buf)
+{
+ if (backend->data.openvswitch.autoiface) {
+ virBufferEscapeString(buf, "<parameters iface='%s'",
+ backend->data.openvswitch.iface);
+ virBufferEscapeString(buf, " bridge='%s'",
+ backend->data.openvswitch.bridge);
+ virBufferAddLit(buf, "/>\n");
+ }
+}
+
+
static void
virDomainNetBackendFormat(virBuffer *buf,
virDomainNetBackend *backend)
{
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
+ g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
if (backend->type) {
virBufferAsprintf(&attrBuf, " type='%s'",
@@ -24941,8 +24988,12 @@ virDomainNetBackendFormat(virBuffer *buf,
virBufferEscapeString(&attrBuf, " vhost='%s'",
backend->vhost);
if (backend->type == VIR_DOMAIN_NET_BACKEND_PASST) {
virBufferEscapeString(&attrBuf, " logFile='%s'",
backend->data.passt.logFile);
+ } else if (backend->type == VIR_DOMAIN_NET_BACKEND_OPENVSWITCH) {
+ virBufferAsprintf(&attrBuf, " autoiface='%s'",
+ backend->data.openvswitch.autoiface ? "yes" :
"no");
+ virDomainNetBackendOpenvswitchIfaceFormat(backend, &childBuf);
}
- virXMLFormatElement(buf, "backend", &attrBuf, NULL);
+ virXMLFormatElement(buf, "backend", &attrBuf, &childBuf);
}
@@ -30141,6 +30192,10 @@ virDomainNetGetActualBridgeName(const virDomainNetDef *iface)
(iface->data.network.actual->type == VIR_DOMAIN_NET_TYPE_BRIDGE ||
iface->data.network.actual->type == VIR_DOMAIN_NET_TYPE_NETWORK))
return iface->data.network.actual->data.bridge.brname;
+ if (iface->type == VIR_DOMAIN_NET_TYPE_VHOSTUSER &&
+ iface->backend.type == VIR_DOMAIN_NET_BACKEND_OPENVSWITCH &&
+ iface->backend.data.openvswitch.autoiface)
+ return iface->backend.data.openvswitch.bridge;
return NULL;
}
diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h
index 6d43654f0a..c19a56e375 100644
--- a/src/conf/domain_conf.h
+++ b/src/conf/domain_conf.h
@@ -1030,6 +1030,7 @@ typedef enum {
typedef enum {
VIR_DOMAIN_NET_BACKEND_DEFAULT = 0,
VIR_DOMAIN_NET_BACKEND_PASST,
+ VIR_DOMAIN_NET_BACKEND_OPENVSWITCH,
VIR_DOMAIN_NET_BACKEND_LAST
} virDomainNetBackendType;
@@ -1078,6 +1079,11 @@ struct _virDomainNetBackend {
struct {
char *logFile; /* path to logfile used by passt process */
} passt;
+ struct {
+ bool autoiface; /* create openvswitch interface automatically */
+ char *iface; /* interface name */
+ char *bridge; /* bridge name that interface attached to */
+ } openvswitch;
} data;
};
diff --git a/src/conf/domain_postparse.c b/src/conf/domain_postparse.c
index a07ec8d94e..f618ea3165 100644
--- a/src/conf/domain_postparse.c
+++ b/src/conf/domain_postparse.c
@@ -1235,6 +1235,49 @@ virDomainDefRejectDuplicatePanics(virDomainDef *def)
}
+static int
+virDomainPostParseInterfaceAutoGenerate(virDomainNetDef *net)
+{
+ if (net->type == VIR_DOMAIN_NET_TYPE_VHOSTUSER &&
+ net->backend.type == VIR_DOMAIN_NET_BACKEND_OPENVSWITCH &&
+ net->backend.data.openvswitch.autoiface) {
+ if (!net->virtPortProfile) {
+ virNetDevVPortProfile *virtPort = g_new0(virNetDevVPortProfile, 1);
+ virtPort->virtPortType = VIR_NETDEV_VPORT_PROFILE_OPENVSWITCH;
+ if (virUUIDGenerate(virtPort->instanceID) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("cannot generate a random uuid for
instanceid"));
+ return -1;
+ }
+ virtPort->instanceID_specified = true;
+ net->virtPortProfile = virtPort;
+ }
+
+ if (!net->backend.data.openvswitch.iface) {
+ virDomainChrSourceDef *src = net->data.vhostuser;
+ char *ifname = strrchr(src->data.nix.path, '/');
+ if (++ifname) {
+ net->backend.data.openvswitch.iface = g_strdup(ifname);
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+static int
+virDomainDefPostParseInterface(virDomainDef *def)
+{
+ size_t i;
+ for (i = 0; i < def->nnets; i++) {
+ if (virDomainPostParseInterfaceAutoGenerate(def->nets[i]) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+
static int
virDomainDefPostParseCommon(virDomainDef *def,
struct virDomainDefPostParseDeviceIteratorData *data,
@@ -1315,6 +1358,9 @@ virDomainDefPostParseCommon(virDomainDef *def,
if (virDomainDefPostParseCPU(def) < 0)
return -1;
+ if (virDomainDefPostParseInterface(def) < 0)
+ return -1;
+
return 0;
}
diff --git a/src/conf/domain_validate.c b/src/conf/domain_validate.c
index b28af7fa56..03ebce93a1 100644
--- a/src/conf/domain_validate.c
+++ b/src/conf/domain_validate.c
@@ -2262,6 +2262,30 @@ virDomainNetDefValidate(const virDomainNetDef *net)
}
}
+ if (net->backend.type == VIR_DOMAIN_NET_BACKEND_OPENVSWITCH) {
+ if (net->type != VIR_DOMAIN_NET_TYPE_VHOSTUSER) {
+ virReportError(VIR_ERR_XML_ERROR, "%s",
+ _("The 'openvswitch' backend can only be used
with interface type='vhostuser'"));
+ return -1;
+ }
+
+ if (net->backend.data.openvswitch.autoiface) {
+ if (!net->backend.data.openvswitch.iface ||
+ !net->backend.data.openvswitch.bridge) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("No interface or bridge name provided"));
+ return -1;
+ }
+
+ if ((!net->virtPortProfile) ||
+ (net->virtPortProfile->virtPortType !=
VIR_NETDEV_VPORT_PROFILE_OPENVSWITCH)) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("Invalid virtualport element encountered"));
+ return -1;
+ }
+ }
+ }
+
if (net->sourceDev && net->backend.type !=
VIR_DOMAIN_NET_BACKEND_PASST) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("The 'dev' attribute of the <source> element
can only be used with <interface> type='user' or type='vhostuser' if
the <backend> type='passt'"));
diff --git a/src/conf/schemas/domaincommon.rng b/src/conf/schemas/domaincommon.rng
index 183dd5db5e..50a37fce43 100644
--- a/src/conf/schemas/domaincommon.rng
+++ b/src/conf/schemas/domaincommon.rng
@@ -3534,6 +3534,9 @@
</optional>
<empty/>
</element>
+ <optional>
+ <ref name="virtualPortProfile"/>
+ </optional>
</optional>
<ref name="interface-options"/>
</interleave>
@@ -3913,6 +3916,7 @@
<choice>
<value>default</value>
<value>passt</value>
+ <value>openvswitch</value>
</choice>
</attribute>
</optional>
@@ -3931,6 +3935,22 @@
<ref name="absFilePath"/>
</attribute>
</optional>
+ <optional>
+ <attribute name="autoiface">
+ <ref name="virYesNo"/>
+ </attribute>
+ </optional>
+ <optional>
+ <element name="parameters">
+ <optional>
+ <attribute name="bridge">
+ <data type="string">
+ <param name='pattern'>[a-zA-Z0-9\-_]+</param>
+ </data>
+ </attribute>
+ </optional>
+ </element>
+ </optional>
</element>
</optional>
<optional>
diff --git a/tests/qemuxmlconfdata/net-vhostuser.x86_64-latest.xml
b/tests/qemuxmlconfdata/net-vhostuser.x86_64-latest.xml
index 44bebef2c8..05b85a6df0 100644
--- a/tests/qemuxmlconfdata/net-vhostuser.x86_64-latest.xml
+++ b/tests/qemuxmlconfdata/net-vhostuser.x86_64-latest.xml
@@ -30,12 +30,22 @@
<interface type='vhostuser'>
<mac address='52:54:00:ee:96:6b'/>
<source type='unix' path='/tmp/vhost0.sock'
mode='server'/>
+ <virtualport type='openvswitch'>
+ <parameters interfaceid='9317d6b7-5fae-4464-a7e9-87d90eff2204'/>
+ </virtualport>
+ <backend type='openvswitch' autoiface='no'/>
<model type='virtio'/>
<address type='pci' domain='0x0000' bus='0x00'
slot='0x02' function='0x0'/>
</interface>
<interface type='vhostuser'>
<mac address='52:54:00:ee:96:6c'/>
<source type='unix' path='/tmp/vhost1.sock'
mode='client'/>
+ <virtualport type='openvswitch'>
+ <parameters interfaceid='9317d6b7-5fae-4464-a7e9-87d90eff2204'/>
+ </virtualport>
+ <backend type='openvswitch' autoiface='yes'>
+ <parameters bridge='ovsbr-ddpbxnhaq'/>
+ </backend>
<model type='virtio'/>
<address type='pci' domain='0x0000' bus='0x00'
slot='0x03' function='0x0'/>
</interface>
diff --git a/tests/qemuxmlconfdata/net-vhostuser.xml
b/tests/qemuxmlconfdata/net-vhostuser.xml
index 91d1abc027..dc4d69ba2b 100644
--- a/tests/qemuxmlconfdata/net-vhostuser.xml
+++ b/tests/qemuxmlconfdata/net-vhostuser.xml
@@ -23,11 +23,21 @@
<interface type='vhostuser'>
<mac address='52:54:00:ee:96:6b'/>
<source type='unix' path='/tmp/vhost0.sock'
mode='server'/>
+ <virtualport type='openvswitch'>
+ <parameters interfaceid='9317d6b7-5fae-4464-a7e9-87d90eff2204'/>
+ </virtualport>
+ <backend type='openvswitch' autoiface='no'/>
<model type='virtio'/>
</interface>
<interface type='vhostuser'>
<mac address='52:54:00:ee:96:6c'/>
<source type='unix' path='/tmp/vhost1.sock'
mode='client'/>
+ <virtualport type='openvswitch'>
+ <parameters interfaceid='9317d6b7-5fae-4464-a7e9-87d90eff2204'/>
+ </virtualport>
+ <backend type='openvswitch' autoiface='yes'>
+ <parameters bridge='ovsbr-ddpbxnhaq'/>
+ </backend>
<model type='virtio'/>
</interface>
<interface type='server'>
--
2.27.0