[PATCH 1/5] util: make it optional to clear existing tc qdiscs/filters in virNetDevBandwidthSet()

Laine Stump posted 5 patches 1 month ago
There is a newer version of this series
[PATCH 1/5] util: make it optional to clear existing tc qdiscs/filters in virNetDevBandwidthSet()
Posted by Laine Stump 1 month ago
virNetDevBandwidthSet() always clears all existing qdiscs and their
subordinate filters before adding all the new qdiscs/filters. This is
normally exactly what we want, but there is one case (the network
driver) where the Qdisc added by virNetDevBandwidthSet() may already
be in use by the nftables backend (which will add a rule to fix the
checksum of dhcp packets); in that case, we *don't* want
virNetDevBandwidthSet() to clear out the qdisc that was already added
for nftables, and none of the bandwidth filters have been added yet,
so there already aren't any "old" filters that need to be removed
either - it is safe to just skip virNetDevBandwidthClear() in this
case.

To allow the network driver to set bandwidth without first clearing
it, this patch adds a "clear" bool to the args of
virNetDevBandwidthSet() - if clear-true (for almost all usages this is
the case) virNetDevBandwidth() will call virNetDevBandwidthClear()
just as it always has. But if clear=false it *won't* call
virNetDevBandwidthClear().

As suggested above, clear is set to false for all calls to
virNetdevBandwidthSet() except for two places in the network driver.

Signed-off-by: Laine Stump <laine@redhat.com>
---
 src/lxc/lxc_driver.c           |  2 +-
 src/lxc/lxc_process.c          |  2 +-
 src/network/bridge_driver.c    |  4 ++--
 src/qemu/qemu_command.c        |  2 +-
 src/qemu/qemu_driver.c         |  3 ++-
 src/qemu/qemu_hotplug.c        |  4 ++--
 src/util/virnetdevbandwidth.c  | 19 ++++++++++++++++++-
 src/util/virnetdevbandwidth.h  |  1 +
 tests/virnetdevbandwidthtest.c |  2 +-
 9 files changed, 29 insertions(+), 10 deletions(-)

diff --git a/src/lxc/lxc_driver.c b/src/lxc/lxc_driver.c
index 0e31e5e4b9..6910651f95 100644
--- a/src/lxc/lxc_driver.c
+++ b/src/lxc/lxc_driver.c
@@ -3570,7 +3570,7 @@ lxcDomainAttachDeviceNetLive(virLXCDriver *driver,
     actualBandwidth = virDomainNetGetActualBandwidth(net);
     if (actualBandwidth) {
         if (virNetDevSupportsBandwidth(actualType)) {
-            if (virNetDevBandwidthSet(net->ifname, actualBandwidth, false,
+            if (virNetDevBandwidthSet(net->ifname, actualBandwidth, true, false,
                                       !virDomainNetTypeSharesHostView(net)) < 0)
                 goto cleanup;
         } else {
diff --git a/src/lxc/lxc_process.c b/src/lxc/lxc_process.c
index 083ab83ec6..e1a310029d 100644
--- a/src/lxc/lxc_process.c
+++ b/src/lxc/lxc_process.c
@@ -609,7 +609,7 @@ virLXCProcessSetupInterfaces(virLXCDriver *driver,
         actualBandwidth = virDomainNetGetActualBandwidth(net);
         if (actualBandwidth) {
             if (virNetDevSupportsBandwidth(type)) {
-                if (virNetDevBandwidthSet(net->ifname, actualBandwidth, false,
+                if (virNetDevBandwidthSet(net->ifname, actualBandwidth, true, false,
                                           !virDomainNetTypeSharesHostView(net)) < 0)
                     goto cleanup;
             } else {
diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c
index d408f17de7..698146dd8c 100644
--- a/src/network/bridge_driver.c
+++ b/src/network/bridge_driver.c
@@ -2109,7 +2109,7 @@ networkStartNetworkVirtual(virNetworkDriverState *driver,
         }
     }
 
-    if (virNetDevBandwidthSet(def->bridge, def->bandwidth, true, true) < 0)
+    if (virNetDevBandwidthSet(def->bridge, def->bandwidth, false, true, true) < 0)
         goto error;
 
     return 0;
@@ -2190,7 +2190,7 @@ networkStartNetworkBridge(virNetworkObj *obj)
      * type BRIDGE, is started. On failure, undo anything you've done,
      * and return -1. On success return 0.
      */
-    if (virNetDevBandwidthSet(def->bridge, def->bandwidth, true, true) < 0)
+    if (virNetDevBandwidthSet(def->bridge, def->bandwidth, false, true, true) < 0)
         goto error;
 
     if (networkStartHandleMACTableManagerMode(obj) < 0)
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c
index f4430275dc..3afdc72d05 100644
--- a/src/qemu/qemu_command.c
+++ b/src/qemu/qemu_command.c
@@ -8694,7 +8694,7 @@ qemuBuildInterfaceCommandLine(virQEMUDriver *driver,
                                                         def->uuid,
                                                         !virDomainNetTypeSharesHostView(net)) < 0)
                     goto cleanup;
-            } else if (virNetDevBandwidthSet(net->ifname, actualBandwidth, false,
+            } else if (virNetDevBandwidthSet(net->ifname, actualBandwidth, true, false,
                                              !virDomainNetTypeSharesHostView(net)) < 0) {
                 goto cleanup;
             }
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 5b9c55f704..abea0799f2 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -9938,13 +9938,14 @@ qemuDomainSetInterfaceParameters(virDomainPtr dom,
                 virErrorRestore(&orig_err);
                 goto endjob;
             }
-        } else if (virNetDevBandwidthSet(net->ifname, newBandwidth, false,
+        } else if (virNetDevBandwidthSet(net->ifname, newBandwidth, true, false,
                                          !virDomainNetTypeSharesHostView(net)) < 0) {
             virErrorPtr orig_err;
 
             virErrorPreserveLast(&orig_err);
             ignore_value(virNetDevBandwidthSet(net->ifname,
                                                net->bandwidth,
+                                               true,
                                                false,
                                                !virDomainNetTypeSharesHostView(net)));
             if (net->bandwidth) {
diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c
index 55512476e4..5b35f724dd 100644
--- a/src/qemu/qemu_hotplug.c
+++ b/src/qemu/qemu_hotplug.c
@@ -1331,7 +1331,7 @@ qemuDomainAttachNetDevice(virQEMUDriver *driver,
                                                         vm->def->uuid,
                                                         !virDomainNetTypeSharesHostView(net)) < 0)
                     goto cleanup;
-            } else if (virNetDevBandwidthSet(net->ifname, actualBandwidth, false,
+            } else if (virNetDevBandwidthSet(net->ifname, actualBandwidth, true, false,
                                              !virDomainNetTypeSharesHostView(net)) < 0) {
                 goto cleanup;
             }
@@ -4181,7 +4181,7 @@ qemuDomainChangeNet(virQEMUDriver *driver,
                                                         vm->def->uuid,
                                                         !virDomainNetTypeSharesHostView(newdev)) < 0)
                     goto cleanup;
-            } else if (virNetDevBandwidthSet(newdev->ifname, newb, false,
+            } else if (virNetDevBandwidthSet(newdev->ifname, newb, true, false,
                                              !virDomainNetTypeSharesHostView(newdev)) < 0) {
                 goto cleanup;
             }
diff --git a/src/util/virnetdevbandwidth.c b/src/util/virnetdevbandwidth.c
index 2b58c58d3e..d62a85a06c 100644
--- a/src/util/virnetdevbandwidth.c
+++ b/src/util/virnetdevbandwidth.c
@@ -173,6 +173,7 @@ virNetDevBandwidthManipulateFilter(const char *ifname,
  * virNetDevBandwidthSet:
  * @ifname: on which interface
  * @bandwidth: rates to set (may be NULL)
+ * @clear: true if we should first clear all tc qdiscs/filters already on the interface
  * @hierarchical_class: whether to create hierarchical class
  * @swapped: true if IN/OUT should be set contrariwise
  *
@@ -183,6 +184,17 @@ virNetDevBandwidthManipulateFilter(const char *ifname,
  * hierarchical class. It is used to guarantee minimal
  * throughput ('floor' attribute in NIC).
  *
+ * If @clear is true, then the root qdisc is deleted, which causes any
+ * already existing filters to also be deleted. If false, then it's
+ * assumed that there are no existing rules. The caller should use
+ * clear=true for an existing interface that is having its bandwidth
+ * setting modified, but can use clear=false if the interface was
+ * newly created, and this is the first time bandwidth has been set,
+ * but someone else might have already added the qdisc (e.g. this is
+ * the case when the network driver is setting bandwidth for a virtual
+ * network bridge device - the nftables backend may have already added
+ * qdisc handle 1:0 and a filter, and we don't want to delete them)
+ *
  * 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
@@ -195,6 +207,7 @@ virNetDevBandwidthManipulateFilter(const char *ifname,
 int
 virNetDevBandwidthSet(const char *ifname,
                       const virNetDevBandwidth *bandwidth,
+                      bool clear,
                       bool hierarchical_class,
                       bool swapped)
 {
@@ -232,7 +245,11 @@ virNetDevBandwidthSet(const char *ifname,
         tx = bandwidth->out;
     }
 
-    virNetDevBandwidthClear(ifname);
+    /* Only if the caller requests, clear everything including root
+     * qdisc and all filters before adding everything.
+     */
+    if (clear)
+        virNetDevBandwidthClear(ifname);
 
     if (tx && tx->average) {
         average = g_strdup_printf("%llukbps", tx->average);
diff --git a/src/util/virnetdevbandwidth.h b/src/util/virnetdevbandwidth.h
index 6d268fb119..68344016c5 100644
--- a/src/util/virnetdevbandwidth.h
+++ b/src/util/virnetdevbandwidth.h
@@ -41,6 +41,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(virNetDevBandwidth, virNetDevBandwidthFree);
 
 int virNetDevBandwidthSet(const char *ifname,
                           const virNetDevBandwidth *bandwidth,
+                          bool clear,
                           bool hierarchical_class,
                           bool swapped)
     G_GNUC_WARN_UNUSED_RESULT;
diff --git a/tests/virnetdevbandwidthtest.c b/tests/virnetdevbandwidthtest.c
index f7c38faa2e..75f960e402 100644
--- a/tests/virnetdevbandwidthtest.c
+++ b/tests/virnetdevbandwidthtest.c
@@ -83,7 +83,7 @@ testVirNetDevBandwidthSet(const void *data)
             return -1;
     } else {
         exp_cmd = info->exp_cmd_tc;
-        if (virNetDevBandwidthSet(iface, band, info->hierarchical_class, true) < 0)
+        if (virNetDevBandwidthSet(iface, band, true, info->hierarchical_class, true) < 0)
             return -1;
     }
 
-- 
2.47.0
Re: [PATCH 1/5] util: make it optional to clear existing tc qdiscs/filters in virNetDevBandwidthSet()
Posted by Daniel P. Berrangé 1 month ago
On Fri, Nov 22, 2024 at 04:16:35PM -0500, Laine Stump wrote:
> virNetDevBandwidthSet() always clears all existing qdiscs and their
> subordinate filters before adding all the new qdiscs/filters. This is
> normally exactly what we want, but there is one case (the network
> driver) where the Qdisc added by virNetDevBandwidthSet() may already
> be in use by the nftables backend (which will add a rule to fix the
> checksum of dhcp packets); in that case, we *don't* want
> virNetDevBandwidthSet() to clear out the qdisc that was already added
> for nftables, and none of the bandwidth filters have been added yet,
> so there already aren't any "old" filters that need to be removed
> either - it is safe to just skip virNetDevBandwidthClear() in this
> case.
> 
> To allow the network driver to set bandwidth without first clearing
> it, this patch adds a "clear" bool to the args of
> virNetDevBandwidthSet() - if clear-true (for almost all usages this is
> the case) virNetDevBandwidth() will call virNetDevBandwidthClear()
> just as it always has. But if clear=false it *won't* call
> virNetDevBandwidthClear().
> 
> As suggested above, clear is set to false for all calls to
> virNetdevBandwidthSet() except for two places in the network driver.
> 
> Signed-off-by: Laine Stump <laine@redhat.com>
> ---

> diff --git a/src/util/virnetdevbandwidth.h b/src/util/virnetdevbandwidth.h
> index 6d268fb119..68344016c5 100644
> --- a/src/util/virnetdevbandwidth.h
> +++ b/src/util/virnetdevbandwidth.h
> @@ -41,6 +41,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(virNetDevBandwidth, virNetDevBandwidthFree);
>  
>  int virNetDevBandwidthSet(const char *ifname,
>                            const virNetDevBandwidth *bandwidth,
> +                          bool clear,
>                            bool hierarchical_class,
>                            bool swapped)
>      G_GNUC_WARN_UNUSED_RESULT;

I'm not such a fan of magic "bool" parameters that have signifcant
behaviour changes, because when reading the code it is far from
clear what effect passing "true" is having. This is compounded
when we have 3 "bool" parameters in a row.

I think this would be  better change to have 'int flags', and then
define an enum bitset....

   VIR_NETDEV_BANDWIDTH_CLEAR_CLASSES = (1 << 0)
   VIR_NETDEV_BANDWIDTH_HIERARCHICAL_CLASS = (1 << 1)
   VIR_NETDEV_BANDWIDTH_DIR_SWAPPED = (1 << 2)
   

With regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|
Re: [PATCH 1/5] util: make it optional to clear existing tc qdiscs/filters in virNetDevBandwidthSet()
Posted by Laine Stump 1 month ago
On 11/25/24 5:38 AM, Daniel P. Berrangé wrote:
> On Fri, Nov 22, 2024 at 04:16:35PM -0500, Laine Stump wrote:
>> virNetDevBandwidthSet() always clears all existing qdiscs and their
>> subordinate filters before adding all the new qdiscs/filters. This is
>> normally exactly what we want, but there is one case (the network
>> driver) where the Qdisc added by virNetDevBandwidthSet() may already
>> be in use by the nftables backend (which will add a rule to fix the
>> checksum of dhcp packets); in that case, we *don't* want
>> virNetDevBandwidthSet() to clear out the qdisc that was already added
>> for nftables, and none of the bandwidth filters have been added yet,
>> so there already aren't any "old" filters that need to be removed
>> either - it is safe to just skip virNetDevBandwidthClear() in this
>> case.
>>
>> To allow the network driver to set bandwidth without first clearing
>> it, this patch adds a "clear" bool to the args of
>> virNetDevBandwidthSet() - if clear-true (for almost all usages this is
>> the case) virNetDevBandwidth() will call virNetDevBandwidthClear()
>> just as it always has. But if clear=false it *won't* call
>> virNetDevBandwidthClear().
>>
>> As suggested above, clear is set to false for all calls to
>> virNetdevBandwidthSet() except for two places in the network driver.
>>
>> Signed-off-by: Laine Stump <laine@redhat.com>
>> ---
> 
>> diff --git a/src/util/virnetdevbandwidth.h b/src/util/virnetdevbandwidth.h
>> index 6d268fb119..68344016c5 100644
>> --- a/src/util/virnetdevbandwidth.h
>> +++ b/src/util/virnetdevbandwidth.h
>> @@ -41,6 +41,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(virNetDevBandwidth, virNetDevBandwidthFree);
>>   
>>   int virNetDevBandwidthSet(const char *ifname,
>>                             const virNetDevBandwidth *bandwidth,
>> +                          bool clear,
>>                             bool hierarchical_class,
>>                             bool swapped)
>>       G_GNUC_WARN_UNUSED_RESULT;
> 
> I'm not such a fan of magic "bool" parameters that have signifcant
> behaviour changes, because when reading the code it is far from
> clear what effect passing "true" is having. This is compounded
> when we have 3 "bool" parameters in a row.

Yeah, me too. The only reason I didn't do that is because I was 
concerned about reducing change in order to avoid a regression, and also 
because I was lazy. Your mentioning it is enough to put it over the top 
though, so I'll redo this patch using a single flags arg.

> I think this would be  better change to have 'int flags', and then
> define an enum bitset....
> 
>     VIR_NETDEV_BANDWIDTH_CLEAR_CLASSES = (1 << 0)
>     VIR_NETDEV_BANDWIDTH_HIERARCHICAL_CLASS = (1 << 1)
>     VIR_NETDEV_BANDWIDTH_DIR_SWAPPED = (1 << 2)
>     
> 
> With regards,
> Daniel