summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorl0crian1 <ryan.claridge13@gmail.com>2025-10-31 18:12:46 -0400
committerl0crian1 <ryan.claridge13@gmail.com>2025-10-31 18:12:46 -0400
commit559dd60a3bdb5866eb81c332d594a8b91529c4a4 (patch)
treece7bb452c162b1737a019eabbc6c6d3c4c8664a6
parent07936657062ce1fe1b9185b17e9230f20e96e365 (diff)
downloadvyos-1x-559dd60a3bdb5866eb81c332d594a8b91529c4a4.tar.gz
vyos-1x-559dd60a3bdb5866eb81c332d594a8b91529c4a4.zip
wlb: T7977: Fix weight calculation for multiple interfaces
- Fixed issue in T7977 - Added smoketest for 3 or more interfaces in rule - Added wait_for function to utils/misc.py
-rw-r--r--python/vyos/utils/misc.py23
-rw-r--r--python/vyos/wanloadbalance.py2
-rwxr-xr-xsmoketest/scripts/cli/test_load-balancing_wan.py76
3 files changed, 100 insertions, 1 deletions
diff --git a/python/vyos/utils/misc.py b/python/vyos/utils/misc.py
index c7f7e7343..b8b25b860 100644
--- a/python/vyos/utils/misc.py
+++ b/python/vyos/utils/misc.py
@@ -12,6 +12,9 @@
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+import time
+
+from typing import Callable, Any
def begin(*args):
"""
@@ -64,3 +67,23 @@ def install_into_config(conf, config_paths, override_prompt=True):
if count > 0:
print(f'{count} value(s) installed. Use "compare" to see the pending changes, and "commit" to apply.')
+
+def wait_for(func: Callable[[], Any], interval: float = 1.0, timeout: float = 5.0) -> bool:
+ """
+ Repeatedly calls `func()` until it returns True or the timeout expires.
+
+ Args:
+ func: A function with no arguments that returns a truthy value when ready.
+ interval: Seconds to wait between calls (default: 1.0).
+ timeout: Maximum time to wait in seconds (default: 5.0).
+
+ Returns:
+ True if the function returned True within the timeout, otherwise False.
+ """
+ start = time.monotonic()
+ while True:
+ if func():
+ return True
+ if (time.monotonic() - start) >= timeout:
+ return False
+ time.sleep(interval)
diff --git a/python/vyos/wanloadbalance.py b/python/vyos/wanloadbalance.py
index afe005731..05ae6b536 100644
--- a/python/vyos/wanloadbalance.py
+++ b/python/vyos/wanloadbalance.py
@@ -154,7 +154,7 @@ def wlb_weight_interfaces(rule_conf, health_state):
for ifname, weight in sorted(interfaces, key=lambda i: i[1]): # build weight ranges
end = start + weight - 1
out.append((ifname, f'{start}-{end}' if end > start else start))
- start = weight
+ start += weight
return out, total_weight
diff --git a/smoketest/scripts/cli/test_load-balancing_wan.py b/smoketest/scripts/cli/test_load-balancing_wan.py
index 8dc0c2622..255203e91 100755
--- a/smoketest/scripts/cli/test_load-balancing_wan.py
+++ b/smoketest/scripts/cli/test_load-balancing_wan.py
@@ -21,8 +21,10 @@ import time
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.utils.file import chmod_755
from vyos.utils.file import write_file
+from vyos.utils.misc import wait_for
from vyos.utils.process import call
from vyos.utils.process import cmd
+from vyos.utils.process import rc_cmd
base_path = ['load-balancing']
@@ -428,6 +430,80 @@ echo "$ifname - $state" > {hook_output_path}
self.verify_nftables_chain(nftables_search, 'ip vyos_wanloadbalance', 'wlb_mangle_prerouting')
+ def test_3_or_more_interfaces_in_rule(self):
+ lan_iface = 'eth1'
+
+ # Interfaces for equal weight test
+ self.cli_set(['interfaces', 'ethernet', 'eth0', 'vif', '101', 'address', '203.0.113.2/30'])
+ self.cli_set(['interfaces', 'ethernet', 'eth0', 'vif', '102', 'address', '203.0.113.6/30'])
+ self.cli_set(['interfaces', 'ethernet', 'eth0', 'vif', '103', 'address', '203.0.113.10/30'])
+
+ # Interfaces for unequal weight test
+ self.cli_set(['interfaces', 'ethernet', 'eth0', 'vif', '201', 'address', '203.0.113.14/30'])
+ self.cli_set(['interfaces', 'ethernet', 'eth0', 'vif', '202', 'address', '203.0.113.18/30'])
+ self.cli_set(['interfaces', 'ethernet', 'eth0', 'vif', '203', 'address', '203.0.113.22/30'])
+
+ self.cli_set(['interfaces', 'ethernet', lan_iface, 'vif', '100', 'address', '198.51.100.2/30'])
+ self.cli_set(['interfaces', 'ethernet', lan_iface, 'vif', '200', 'address', '198.51.100.6/30'])
+
+ # Health checks for equal weight test
+ self.cli_set(base_path + ['wan', 'interface-health', 'eth0.101', 'nexthop', '203.0.113.2'])
+ self.cli_set(base_path + ['wan', 'interface-health', 'eth0.101', 'success-count', '1'])
+ self.cli_set(base_path + ['wan', 'interface-health', 'eth0.101', 'failure-count', '1'])
+ self.cli_set(base_path + ['wan', 'interface-health', 'eth0.102', 'nexthop', '203.0.113.6'])
+ self.cli_set(base_path + ['wan', 'interface-health', 'eth0.102', 'success-count', '1'])
+ self.cli_set(base_path + ['wan', 'interface-health', 'eth0.102', 'failure-count', '1'])
+ self.cli_set(base_path + ['wan', 'interface-health', 'eth0.103', 'nexthop', '203.0.113.10'])
+ self.cli_set(base_path + ['wan', 'interface-health', 'eth0.103', 'success-count', '1'])
+ self.cli_set(base_path + ['wan', 'interface-health', 'eth0.103', 'failure-count', '1'])
+ self.cli_set(base_path + ['wan', 'rule', '10', 'inbound-interface', f'{lan_iface}.100'])
+ self.cli_set(base_path + ['wan', 'rule', '10', 'interface', 'eth0.101'])
+ self.cli_set(base_path + ['wan', 'rule', '10', 'interface', 'eth0.102'])
+ self.cli_set(base_path + ['wan', 'rule', '10', 'interface', 'eth0.103'])
+
+ # Health checks for unequal weight test
+ self.cli_set(base_path + ['wan', 'interface-health', 'eth0.201', 'nexthop', '203.0.113.14'])
+ self.cli_set(base_path + ['wan', 'interface-health', 'eth0.201', 'success-count', '1'])
+ self.cli_set(base_path + ['wan', 'interface-health', 'eth0.201', 'failure-count', '1'])
+ self.cli_set(base_path + ['wan', 'interface-health', 'eth0.202', 'nexthop', '203.0.113.18'])
+ self.cli_set(base_path + ['wan', 'interface-health', 'eth0.202', 'success-count', '1'])
+ self.cli_set(base_path + ['wan', 'interface-health', 'eth0.202', 'failure-count', '1'])
+ self.cli_set(base_path + ['wan', 'interface-health', 'eth0.203', 'nexthop', '203.0.113.22'])
+ self.cli_set(base_path + ['wan', 'interface-health', 'eth0.203', 'success-count', '1'])
+ self.cli_set(base_path + ['wan', 'interface-health', 'eth0.203', 'failure-count', '1'])
+ self.cli_set(base_path + ['wan', 'rule', '20', 'inbound-interface', f'{lan_iface}.200'])
+ self.cli_set(base_path + ['wan', 'rule', '20', 'interface', 'eth0.201'])
+ self.cli_set(base_path + ['wan', 'rule', '20', 'interface', 'eth0.202', 'weight', '2'])
+ self.cli_set(base_path + ['wan', 'rule', '20', 'interface', 'eth0.203', 'weight', '3'])
+
+ # commit changes
+ self.cli_commit()
+
+ def check_wlb_status():
+ rc, wlb_status = rc_cmd('nft list chain ip vyos_wanloadbalance wlb_mangle_prerouting')
+ if rc != 0:
+ return False
+
+ # get all lines containing 'jump'
+ lines = [l for l in wlb_status.splitlines() if 'jump' in l]
+
+ # check total count of 'jump' across all matching lines
+ total_jumps = sum(l.count('jump') for l in lines)
+
+ return total_jumps == 6
+
+ wait_for(check_wlb_status)
+
+ nftables_search = [
+ ['jump wlb_mangle_isp_eth0.101'],
+ ['jump wlb_mangle_isp_eth0.102'],
+ ['jump wlb_mangle_isp_eth0.103'],
+ ['jump wlb_mangle_isp_eth0.201'],
+ ['jump wlb_mangle_isp_eth0.202'],
+ ['jump wlb_mangle_isp_eth0.203'],
+ ]
+
+ self.verify_nftables_chain(nftables_search, 'ip vyos_wanloadbalance', 'wlb_mangle_prerouting')
if __name__ == '__main__':
unittest.main(verbosity=2, failfast=VyOSUnitTestSHIM.TestCase.debug_on())