[PATCH 2/4] qemu: interface: add virNetDevOpenvswitchInterfaceSetQos and virNetDevOpenvswitchInterfaceClearQos
Michal Prívozník
mprivozn at redhat.com
Tue Jun 29 12:59:35 UTC 2021
On 6/28/21 11:18 AM, zhangjl02 wrote:
> From: zhangjl02 <zhangjl02 at 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
More information about the libvir-list
mailing list