From b740f3e3a7ac78dab0ffb294837df6fb64520742 Mon Sep 17 00:00:00 2001
From: sarthurdev <965089+sarthurdev@users.noreply.github.com>
Date: Tue, 8 Oct 2024 23:17:23 +0200
Subject: [PATCH] wlb: T4470: Support WLB op-mode commands
---
data/op-mode-standardized.json | 1 +
op-mode-definitions/load-balancing_wan.xml.in | 37 ++++++
src/op_mode/load-balancing_wan.py | 117 ++++++++++++++++++
src/op_mode/restart.py | 5 +
4 files changed, 160 insertions(+)
create mode 100644 op-mode-definitions/load-balancing_wan.xml.in
create mode 100755 src/op_mode/load-balancing_wan.py
diff --git a/data/op-mode-standardized.json b/data/op-mode-standardized.json
index 35587b63c9..345081f9fb 100644
--- a/data/op-mode-standardized.json
+++ b/data/op-mode-standardized.json
@@ -13,6 +13,7 @@
"evpn.py",
"interfaces.py",
"ipsec.py",
+"load-balancing_wan.py",
"lldp.py",
"log.py",
"memory.py",
diff --git a/op-mode-definitions/load-balancing_wan.xml.in b/op-mode-definitions/load-balancing_wan.xml.in
new file mode 100644
index 0000000000..91c57c1f41
--- /dev/null
+++ b/op-mode-definitions/load-balancing_wan.xml.in
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+ Restart Wide Area Network (WAN) load-balancing daemon
+
+ sudo ${vyos_op_scripts_dir}/restart.py restart_service --name load-balancing_wan
+
+
+
+
+
+
+
+ Show Wide Area Network (WAN) load-balancing information
+
+ ${vyos_op_scripts_dir}/load-balancing_wan.py show_summary
+
+
+
+ Show Wide Area Network (WAN) load-balancing flow
+
+ ${vyos_op_scripts_dir}/load-balancing_wan.py show_connection
+
+
+
+ Show WAN load-balancing statistics
+
+ ${vyos_op_scripts_dir}/load-balancing_wan.py show_status
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/op_mode/load-balancing_wan.py b/src/op_mode/load-balancing_wan.py
new file mode 100755
index 0000000000..9fa4738025
--- /dev/null
+++ b/src/op_mode/load-balancing_wan.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import json
+import re
+import sys
+
+from datetime import datetime
+
+from vyos.config import Config
+from vyos.utils.process import cmd
+
+import vyos.opmode
+
+wlb_status_file = '/run/wlb_status.json'
+
+status_format = '''Interface: {ifname}
+Status: {status}
+Last Status Change: {last_change}
+Last Interface Success: {last_success}
+Last Interface Failure: {last_failure}
+Interface Failures: {failures}
+'''
+
+def _verify(func):
+ """Decorator checks if WLB config exists"""
+ from functools import wraps
+
+ @wraps(func)
+ def _wrapper(*args, **kwargs):
+ config = Config()
+ if not config.exists(['load-balancing', 'wan']):
+ unconf_message = 'WAN load-balancing is not configured'
+ raise vyos.opmode.UnconfiguredSubsystem(unconf_message)
+ return func(*args, **kwargs)
+ return _wrapper
+
+def _get_raw_data():
+ with open(wlb_status_file, 'r') as f:
+ data = json.loads(f.read())
+ if not data:
+ return {}
+ return data
+
+def _get_formatted_output(raw_data):
+ for ifname, if_data in raw_data.items():
+ latest_change = if_data['last_success'] if if_data['last_success'] > if_data['last_failure'] else if_data['last_failure']
+
+ change_dt = datetime.fromtimestamp(latest_change) if latest_change > 0 else None
+ success_dt = datetime.fromtimestamp(if_data['last_success']) if if_data['last_success'] > 0 else None
+ failure_dt = datetime.fromtimestamp(if_data['last_failure']) if if_data['last_failure'] > 0 else None
+ now = datetime.utcnow()
+
+ fmt_data = {
+ 'ifname': ifname,
+ 'status': "active" if if_data['state'] else "failed",
+ 'last_change': change_dt.strftime("%Y-%m-%d %H:%M:%S") if change_dt else 'N/A',
+ 'last_success': str(now - success_dt) if success_dt else 'N/A',
+ 'last_failure': str(now - failure_dt) if failure_dt else 'N/A',
+ 'failures': if_data['failure_count']
+ }
+ print(status_format.format(**fmt_data))
+
+@_verify
+def show_summary(raw: bool):
+ data = _get_raw_data()
+
+ if raw:
+ return data
+ else:
+ return _get_formatted_output(data)
+
+@_verify
+def show_connection(raw: bool):
+ res = cmd('sudo conntrack -L -n')
+ lines = res.split("\n")
+ filtered_lines = [line for line in lines if re.search(r' mark=[1-9]', line)]
+
+ if raw:
+ return filtered_lines
+
+ for line in lines:
+ print(line)
+
+@_verify
+def show_status(raw: bool):
+ res = cmd('sudo nft list chain ip vyos_wanloadbalance wlb_mangle_prerouting')
+ lines = res.split("\n")
+ filtered_lines = [line.replace("\t", "") for line in lines[3:-2] if 'meta mark set' not in line]
+
+ if raw:
+ return filtered_lines
+
+ for line in filtered_lines:
+ print(line)
+
+if __name__ == "__main__":
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/restart.py b/src/op_mode/restart.py
index 3b0031f347..efa8354851 100755
--- a/src/op_mode/restart.py
+++ b/src/op_mode/restart.py
@@ -53,6 +53,10 @@
'systemd_service': 'strongswan',
'path': ['vpn', 'ipsec'],
},
+ 'load-balancing_wan': {
+ 'systemd_service': 'vyos-wan-load-balance',
+ 'path': ['load-balancing', 'wan'],
+ },
'mdns_repeater': {
'systemd_service': 'avahi-daemon',
'path': ['service', 'mdns', 'repeater'],
@@ -86,6 +90,7 @@
'haproxy',
'igmp_proxy',
'ipsec',
+ 'load-balancing_wan',
'mdns_repeater',
'router_advert',
'snmp',