On 6/28/21 11:18 AM, zhangjl02 wrote:
From: zhangjl02 <zhangjl02(a)inspur.com>
Introduce qos setting and cleaning method. Use ovs command to set qos
parameters on specific interface of qemu virtual machine.
When an ovs port is created, we add 'ifname' to external-ids. When setting
qos on an ovs port, query its qos and queue. If found, change qos on queried
queue and qos, otherwise create new queue and qos. When cleaning qos, query
and clean queues and qos in ovs table record by 'ifname' and 'vmid'.
---
src/libvirt_private.syms | 2 +
src/util/virnetdevopenvswitch.c | 269 ++++++++++++++++++++++++++++++++
src/util/virnetdevopenvswitch.h | 11 ++
3 files changed, 282 insertions(+)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index 68e4b6aab8..775f6706b3 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -2805,8 +2805,10 @@ virNetDevMidonetUnbindPort;
virNetDevOpenvswitchAddPort;
virNetDevOpenvswitchGetMigrateData;
virNetDevOpenvswitchGetVhostuserIfname;
+virNetDevOpenvswitchInterfaceClearQos;
virNetDevOpenvswitchInterfaceGetMaster;
virNetDevOpenvswitchInterfaceParseStats;
+virNetDevOpenvswitchInterfaceSetQos;
virNetDevOpenvswitchInterfaceStats;
virNetDevOpenvswitchMaybeUnescapeReply;
virNetDevOpenvswitchRemovePort;
diff --git a/src/util/virnetdevopenvswitch.c b/src/util/virnetdevopenvswitch.c
index eac68d9556..f27b74f35f 100644
--- a/src/util/virnetdevopenvswitch.c
+++ b/src/util/virnetdevopenvswitch.c
@@ -30,6 +30,7 @@
#include "virlog.h"
#include "virjson.h"
#include "virfile.h"
+#include "virutil.h"
#define VIR_FROM_THIS VIR_FROM_NONE
@@ -140,6 +141,7 @@ int virNetDevOpenvswitchAddPort(const char *brname, const char
*ifname,
g_autofree char *ifaceid_ex_id = NULL;
g_autofree char *profile_ex_id = NULL;
g_autofree char *vmid_ex_id = NULL;
+ g_autofree char *ifname_ex_id = NULL;
virMacAddrFormat(macaddr, macaddrstr);
virUUIDFormat(ovsport->interfaceID, ifuuidstr);
@@ -149,6 +151,7 @@ int virNetDevOpenvswitchAddPort(const char *brname, const char
*ifname,
macaddrstr);
ifaceid_ex_id = g_strdup_printf("external-ids:iface-id=\"%s\"",
ifuuidstr);
vmid_ex_id = g_strdup_printf("external-ids:vm-id=\"%s\"",
vmuuidstr);
+ ifname_ex_id = g_strdup_printf("external-ids:ifname=\"%s\"",
ifname);
if (ovsport->profileID[0] != '\0') {
profile_ex_id =
g_strdup_printf("external-ids:port-profile=\"%s\"",
ovsport->profileID);
@@ -174,6 +177,7 @@ int virNetDevOpenvswitchAddPort(const char *brname, const char
*ifname,
"--", "set", "Interface",
ifname, ifaceid_ex_id,
"--", "set", "Interface",
ifname, vmid_ex_id,
"--", "set", "Interface",
ifname, profile_ex_id,
+ "--", "set", "Interface",
ifname, ifname_ex_id,
"--", "set", "Interface",
ifname,
"external-ids:iface-status=active",
NULL);
@@ -614,3 +618,268 @@ int virNetDevOpenvswitchUpdateVlan(const char *ifname,
return 0;
}
+
+
+/**
+ * virNetDevOpenvswitchInterfaceSetQos:
+ * @ifname: on which interface
+ * @bandwidth: rates to set (may be NULL)
+ * @swapped: true if IN/OUT should be set contrariwise
+ *
+ * Update qos configuration of an OVS port.
+ *
+ * If @swapped is set, the IN part of @bandwidth is set on
+ * @ifname's TX, and vice versa. If it is not set, IN is set on
+ * RX and OUT on TX. This is because for some types of interfaces
+ * domain and the host live on the same side of the interface (so
+ * domain's RX/TX is host's RX/TX), and for some it's swapped
+ * (domain's RX/TX is hosts's TX/RX).
+ *
+ * Return 0 on success, -1 otherwise.
+ */
+int virNetDevOpenvswitchInterfaceSetQos(const char *ifname,
+ const virNetDevBandwidth *bandwidth,
+ const unsigned char *vmid,
+ bool swapped)
+{
+ int ret = -1;
+ char vmuuidstr[VIR_UUID_STRING_BUFLEN];
+ virNetDevBandwidthRate *rx = NULL; /* From domain POV */
+ virNetDevBandwidthRate *tx = NULL; /* From domain POV */
+ virCommand *cmd = NULL;
+ char *vmid_ex_id = NULL;
+ char *ifname_ex_id = NULL;
+ char *average = NULL;
+ char *peak = NULL;
+ char *burst = NULL;
+ char *qos_uuid = NULL;
+ char *queue_uuid = NULL;
+ char **lines = NULL;
+
+ if (!bandwidth) {
+ /* nothing to be enabled */
+ ret = 0;
+ goto cleanup;
+ }
+
+ if (geteuid() != 0) {
+ virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
+ _("Network bandwidth tuning is not available"
+ " in session mode"));
+ return -1;
+ }
+
+ if (!ifname) {
+ virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
+ _("Unable to set bandwidth for interface because "
+ "device name is unknown"));
+ return -1;
+ }
+
+ if (swapped) {
+ rx = bandwidth->out;
+ tx = bandwidth->in;
+ } else {
+ rx = bandwidth->in;
+ tx = bandwidth->out;
+ }
+ if(!bandwidth->out && !bandwidth->in) {
+ ret = virNetDevOpenvswitchInterfaceClearQos(ifname, vmid);
+ // virNetDevBandwidthClear(iframe);
A leftover probably? Also, we don't really use C99 like comments. We're old school
and use C89 style.
+ }
+ if (tx && tx->average) {
+ average = g_strdup_printf("%llu", tx->average * 8192);
+ if (tx->burst)
+ burst = g_strdup_printf("%llu", tx->burst * 8192);
+ if (tx->peak)
+ peak = g_strdup_printf("%llu", tx->peak * 8192);
Let me just say that finding ANY documentation for this part of OVS wasn't easy.
Initially, I though that these multiplications (to get bits/s) were wrong, but after
digging through OVS code - they are indeed correct. Which is not at all consistent with ..
[1]
+
+ // find queue
+ cmd = virNetDevOpenvswitchCreateCmd();
+ virUUIDFormat(vmid, vmuuidstr);
+ vmid_ex_id = g_strdup_printf("external-ids:vm-id=\"%s\"",
vmuuidstr);
+ ifname_ex_id = g_strdup_printf("external-ids:ifname=\"%s\"",
ifname);
+ virCommandAddArgList(cmd, "--no-heading", "--columns=_uuid",
"find", "queue", vmid_ex_id, ifname_ex_id,NULL);
+ virCommandSetOutputBuffer(cmd, &queue_uuid);
+ if (virCommandRun(cmd, NULL) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Unable to find queue on port %s"), ifname);
+ }
+
+ // find qos
+ cmd = virNetDevOpenvswitchCreateCmd();
You need to free cmd before reusing it, like this: virCommandFree(cmd);
The same applies for other variables, that are reused within the funcion (average, burst,
peak, ...).
+ virCommandAddArgList(cmd, "--no-heading",
"--columns=_uuid", "find", "qos", vmid_ex_id,
ifname_ex_id,NULL);
+ virCommandSetOutputBuffer(cmd, &qos_uuid);
+ if (virCommandRun(cmd, NULL) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Unable to find qos on port %s"), ifname);
+ }
+
+ // create qos and set
+ cmd = virNetDevOpenvswitchCreateCmd();
+ if (queue_uuid && *queue_uuid) {
+ lines = g_strsplit(queue_uuid, "\n", 0);
+ virCommandAddArgList(cmd, "set", "queue", lines[0],
NULL);
+ }else{
+ virCommandAddArgList(cmd, "set", "port", ifname,
"qos=@qos1", vmid_ex_id, ifname_ex_id,
+ "--", "--id=@qos1",
"create", "qos", "type=linux-htb", NULL);
+ virCommandAddArgFormat(cmd, "other_config:min-rate=%s", average);
+ if(burst){
+ virCommandAddArgFormat(cmd, "other_config:burst=%s", burst);
+ }
+ if(peak){
+ virCommandAddArgFormat(cmd, "other_config:max-rate=%s",
peak);
+ }
+ virCommandAddArgList(cmd, "queues:0=@queue0", vmid_ex_id,
ifname_ex_id,
+ "--", "--id=@queue0",
"create", "queue", NULL);
+ }
+ virCommandAddArgFormat(cmd, "other_config:min-rate=%s", average);
+ if(burst){
+ virCommandAddArgFormat(cmd, "other_config:burst=%s", burst);
+ }
+ if(peak){
+ virCommandAddArgFormat(cmd, "other_config:max-rate=%s", peak);
+ }
+ virCommandAddArgList(cmd, vmid_ex_id, ifname_ex_id, NULL);
+ if (virCommandRun(cmd, NULL) < 0) {
+ if(*queue_uuid){
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Unable to set queue configuration on port
%s"), ifname);
+ }
+ else
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Unable to create and set qos configuration on
port %s"), ifname);
If one branch of if() has curly brackets then the other must have them too. Moreover, if a
branch doesn't fit into one line then it must have curly braces. So effectivelly, this
should be written as:
if (*queue_uuid) {
virReportError(..
..);
} else {
virReportError(..
..);
}
+ goto cleanup;
+ }
+
+ if(qos_uuid && *qos_uuid){
+ cmd = virNetDevOpenvswitchCreateCmd();
+ lines = g_strsplit(qos_uuid, "\n", 0);
+ virCommandAddArgList(cmd, "set", "qos", lines[0],
NULL);
+ virCommandAddArgFormat(cmd, "other_config:min-rate=%s", average);
+ if(burst){
+ virCommandAddArgFormat(cmd, "other_config:burst=%s", burst);
+ }
+ if(peak){
+ virCommandAddArgFormat(cmd, "other_config:max-rate=%s",
peak);
+ }
+ virCommandAddArgList(cmd, vmid_ex_id, ifname_ex_id, NULL);
+ if (virCommandRun(cmd, NULL) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Unable to set qos configuration on port
%s"), ifname);
+ goto cleanup;
+ }
+ }
+
+ // ret = virNetDevBandwidthSet(ifname,bandwidth,false,swapped);
+ }
+
+ if (rx) {
+ average = g_strdup_printf("%llu", rx->average * 8);
+ if (rx->burst)
+ burst = g_strdup_printf("%llu", rx->burst * 8);
1: this. Here the values are in Kbps. Thus we need to multiply by 8. Le sigh.
+ cmd = virNetDevOpenvswitchCreateCmd();
+ virCommandAddArgList(cmd, "set", "Interface", ifname,
NULL);
+ virCommandAddArgFormat(cmd, "ingress_policing_rate=%s", average);
+ if (burst)
+ virCommandAddArgFormat(cmd, "ingress_policing_burst=%s", burst);
+
+ if (virCommandRun(cmd, NULL) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Unable to set vlan configuration on port %s"),
ifname);
+ goto cleanup;
+ }
+ }
+
+ return 0;
+
+ cleanup:
+ virCommandFree(cmd);
+ VIR_FREE(average);
+ VIR_FREE(peak);
+ VIR_FREE(burst);
+ VIR_FREE(qos_uuid);
+ VIR_FREE(queue_uuid);
+ VIR_FREE(vmid_ex_id);
+ VIR_FREE(ifname_ex_id);
+ VIR_FREE(lines);
All these free calls can be left out if you'd use g_auto* for those variables. Then,
this cleanup label won't be needed really, becuase it would contain just one line ..
+ return ret;
.. return ret;
+}
+
+int virNetDevOpenvswitchInterfaceClearQos(const char *ifname,
+ const unsigned char *vmid){
My comments from above apply for this function too.
+ char vmuuidstr[VIR_UUID_STRING_BUFLEN];
+ virCommand *cmd = NULL;
+ char *vmid_ex_id = NULL;
+ char *qos_uuid = NULL;
+ char *queue_uuid = NULL;
+ char **lines = NULL;
+ size_t i;
+
+ // find qos
+ cmd = virNetDevOpenvswitchCreateCmd();
+ virUUIDFormat(vmid, vmuuidstr);
+ vmid_ex_id = g_strdup_printf("external-ids:vm-id=\"%s\"",
vmuuidstr);
+ virCommandAddArgList(cmd, "--no-heading", "--columns=_uuid",
"find", "qos", vmid_ex_id, NULL);
+ virCommandSetOutputBuffer(cmd, &qos_uuid);
+ if (virCommandRun(cmd, NULL) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Unable to find qos on port %s"), ifname);
Neither of these virReportError() are coupled with return -1. I kind of understand that,
because you want this to be best effort, but maybe this function should be void then?
+ }
+
+ // find queue
+ cmd = virNetDevOpenvswitchCreateCmd();
+ vmid_ex_id = g_strdup_printf("external-ids:vm-id=\"%s\"",
vmuuidstr);
+ virCommandAddArgList(cmd, "--no-heading", "--columns=_uuid",
"find", "queue", vmid_ex_id, NULL);
+ virCommandSetOutputBuffer(cmd, &queue_uuid);
+ if (virCommandRun(cmd, NULL) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Unable to find queue on port %s"), ifname);
+ }
+
+ if (qos_uuid && *qos_uuid) {
+ lines = g_strsplit(qos_uuid, "\n", 0);
+ // destroy qos
+ for (i = 0; lines[i] != NULL; i++) {
+ const char *line = lines[i];
+ if (!*line) {
+ continue;
+ }
+ cmd = virNetDevOpenvswitchCreateCmd();
+ virCommandAddArgList(cmd, "remove", "port", ifname,
"qos", line, NULL);
+ if (virCommandRun(cmd, NULL) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Unable to remove port qos on port %s"),
ifname);
This is slightly weird. After 4/4 this code is executed when a guest is shut off. However,
I see that the port is removed sonned than this function is called thus running this
command fails. This is what I see in the log:
2021-06-29 12:52:32.658+0000: 15442: debug : virCommandRunAsync:2627 : About to run
/usr/bin/ovs-vsctl --timeout=5 -- --if-exists del-port vnet2
2021-06-29 12:52:32.662+0000: 15442: debug : virCommandRunAsync:2629 : Command result 0,
with PID 29124
2021-06-29 12:52:32.689+0000: 15442: debug : virCommandRun:2473 : Result status 0, stdout:
'' stderr: ''
2021-06-29 12:52:32.689+0000: 15442: debug : virCommandRunAsync:2627 : About to run
/usr/bin/ovs-vsctl --timeout=5 --no-heading --columns=_uuid find qos
'external-ids:vm-id="4c42854e-3c84-43ed-ae87-9af0df0ecd16"'
2021-06-29 12:52:32.693+0000: 15442: debug : virCommandRunAsync:2629 : Command result 0,
with PID 29125
2021-06-29 12:52:32.717+0000: 15442: debug : virCommandRun:2473 : Result status 0, stdout:
'f8b63e05-018d-44bb-9e74-a1ee4a40de79
' stderr: ''
2021-06-29 12:52:32.717+0000: 15442: debug : virCommandRunAsync:2627 : About to run
/usr/bin/ovs-vsctl --timeout=5 --no-heading --columns=_uuid find queue
'external-ids:vm-id="4c42854e-3c84-43ed-ae87-9af0df0ecd16"'
2021-06-29 12:52:32.721+0000: 15442: debug : virCommandRunAsync:2629 : Command result 0,
with PID 29126
2021-06-29 12:52:32.745+0000: 15442: debug : virCommandRun:2473 : Result status 0, stdout:
'f098939d-c404-49aa-a9b0-b72631b662a2
' stderr: ''
2021-06-29 12:52:32.745+0000: 15442: debug : virCommandRunAsync:2627 : About to run
/usr/bin/ovs-vsctl --timeout=5 remove port vnet2 qos f8b63e05-018d-44bb-9e74-a1ee4a40de79
2021-06-29 12:52:32.749+0000: 15442: debug : virCommandRunAsync:2629 : Command result 0,
with PID 29127
2021-06-29 12:52:32.772+0000: 15442: error : virCommandWait:2745 : internal error: Child
process (/usr/bin/ovs-vsctl --timeout=5 remove port vnet2 qos
f8b63e05-018d-44bb-9e74-a1ee4a40de79) unexpected exit status 1: ovs-vsctl: no row
"vnet2" in table Port
2021-06-29 12:52:32.772+0000: 15442: debug : virCommandRun:2473 : Result status 0, stdout:
'' stderr: 'ovs-vsctl: no row "vnet2" in table Port
'
2021-06-29 12:52:32.772+0000: 15442: error : virNetDevOpenvswitchInterfaceClearQos:851 :
internal error: Unable to remove port qos on port vnet2
+ }
+ cmd = virNetDevOpenvswitchCreateCmd();
+ virCommandAddArgList(cmd, "destroy", "qos", line,
NULL);
+ if (virCommandRun(cmd, NULL) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Unable to destroy qos on port %s"),
ifname);
+ }
+ }
+ }
+ // destroy queue
+ if (queue_uuid && *queue_uuid) {
+ lines = g_strsplit(queue_uuid, "\n", 0);
+ for (i = 0; lines[i] != NULL; i++) {
+ const char *line = lines[i];
+ if (!*line) {
+ continue;
+ }
+ cmd = virNetDevOpenvswitchCreateCmd();
+ virCommandAddArgList(cmd, "destroy", "queue", line,
NULL);
+ if (virCommandRun(cmd, NULL) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Unable to destroy queue on port %s"),
ifname);
+ }
+ }
+ }
+
+ virCommandFree(cmd);
+ VIR_FREE(qos_uuid);
+ VIR_FREE(queue_uuid);
+ VIR_FREE(vmid_ex_id);
+ VIR_FREE(lines);
+ return 0;
+}
\ No newline at end of file
diff --git a/src/util/virnetdevopenvswitch.h b/src/util/virnetdevopenvswitch.h
index 7525376855..2dcd1aec6b 100644
--- a/src/util/virnetdevopenvswitch.h
+++ b/src/util/virnetdevopenvswitch.h
@@ -21,6 +21,7 @@
#pragma once
#include "internal.h"
+#include "virnetdevbandwidth.h"
#include "virnetdevvportprofile.h"
#include "virnetdevvlan.h"
@@ -69,3 +70,13 @@ int virNetDevOpenvswitchGetVhostuserIfname(const char *path,
int virNetDevOpenvswitchUpdateVlan(const char *ifname,
const virNetDevVlan *virtVlan)
ATTRIBUTE_NONNULL(1) G_GNUC_WARN_UNUSED_RESULT;
+
+int virNetDevOpenvswitchInterfaceSetQos(const char *ifname,
+ const virNetDevBandwidth *bandwidth,
+ const unsigned char *vmid,
+ bool swapped)
+ ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3) G_GNUC_WARN_UNUSED_RESULT;
+
+int virNetDevOpenvswitchInterfaceClearQos(const char *ifname,
+ const unsigned char *vmid)
+ ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) G_GNUC_WARN_UNUSED_RESULT;
Michal