summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/op_mode/vpp.py142
1 files changed, 142 insertions, 0 deletions
diff --git a/src/op_mode/vpp.py b/src/op_mode/vpp.py
index fea874909..006ea79ff 100755
--- a/src/op_mode/vpp.py
+++ b/src/op_mode/vpp.py
@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
+import json
import typing
from tabulate import tabulate
@@ -23,7 +24,46 @@ from vyos.configquery import ConfigTreeQuery
import vyos.opmode
+def _verify(target: typing.Optional[str]):
+ """Decorator checks if config for VPP feature exists"""
+ from functools import wraps
+
+ target = target.split() if target else []
+
+ def _verify_target(func):
+ @wraps(func)
+ def _wrapper(*args, **kwargs):
+ config = ConfigTreeQuery()
+ path = ['vpp'] + target
+ if not config.exists(path):
+ raise vyos.opmode.UnconfiguredSubsystem(
+ f'"{" ".join(path)}" is not configured'
+ )
+ return func(*args, **kwargs)
+
+ return _wrapper
+
+ return _verify_target
+
+
class VPPShow:
+ RX_STATES = {
+ 0: 'INITIALIZE',
+ 1: 'PORT_DISABLED',
+ 2: 'EXPIRED',
+ 3: 'LACP_DISABLED',
+ 4: 'DEFAULTED',
+ 5: 'CURRENT',
+ }
+ TX_STATES = {0: 'TRANSMIT'}
+ MUX_STATES = {
+ 0: 'DETACHED',
+ 1: 'WAITING',
+ 2: 'ATTACHED',
+ 3: 'COLLECTING_DISTRIBUTING',
+ }
+ PTX_STATES = {0: 'NO_PERIODIC', 1: 'FAST', 2: 'SLOW', 3: 'PERIODIC_TX'}
+
def __init__(self):
self.config = ConfigTreeQuery()
self.vpp = VPPControl()
@@ -139,6 +179,95 @@ class VPPShow:
data = self._get_ipfix_table_raw()
return data if raw else self._show_ipfix_table_formatted()
+ # -----------------------------
+ # LACP information
+ # -----------------------------
+ def _get_raw_output(self, data_dump: typing.List[dict]) -> list[dict]:
+ data = [json.loads(json.dumps(d._asdict(), default=str)) for d in data_dump]
+ return data
+
+ def _get_lacp_raw(self, ifname: typing.Optional[str]) -> list[dict]:
+ lacp_dump = self.vpp.api.sw_interface_lacp_dump()
+ data = self._get_raw_output(lacp_dump)
+
+ if ifname:
+ res = next((d for d in data if d['interface_name'] == ifname), None)
+ if not res:
+ raise vyos.opmode.IncorrectValue(
+ f'Interface {ifname} is not a member of any LACP bond'
+ )
+ data = [res]
+
+ return data
+
+ def _get_lacp_info_formatted(self, data):
+
+ def bit(x, n):
+ return (x >> n) & 1
+
+ def bits_to_str(x):
+ return ' '.join(f'{bit(x, n):3d}' for n in range(7, -1, -1))
+
+ # Headers (exactly like VPP)
+ print(f'{"":55} {"actor state":32} {"partner state":32}')
+ print(
+ 'interface name'.ljust(26)
+ + 'sw_if_index'.ljust(13)
+ + 'bond interface'.ljust(17)
+ + 'exp/def/dis/col/syn/agg/tim/act'.ljust(33)
+ + 'exp/def/dis/col/syn/agg/tim/act'.ljust(32)
+ )
+
+ for d in data:
+ iface = d['interface_name']
+ sw_if = str(d['sw_if_index'])
+ bond_if = d['bond_interface_name']
+ actor_bits = bits_to_str(d['actor_state'])
+ partner_bits = bits_to_str(d['partner_state'])
+
+ print(
+ f'{iface:25} {sw_if:12} {bond_if:16} {actor_bits:32} {partner_bits:32}'
+ )
+
+ # LAG ID formatting
+ lag_line = (
+ f' LAG ID: '
+ f'[({d["actor_system_priority"]:04x},{d["actor_system"].replace(":", "-")},'
+ f'{d["actor_key"]:04x},{d["actor_port_priority"]:04x},{d["actor_port_number"]:04x}), '
+ f'({d["partner_system_priority"]:04x},{d["partner_system"].replace(":", "-")},'
+ f'{d["partner_key"]:04x},{d["partner_port_priority"]:04x},{d["partner_port_number"]:04x})]'
+ )
+ print(lag_line)
+
+ # State machine line
+ print(
+ f' RX-state: {self.RX_STATES[d["rx_state"]]}, '
+ f'TX-state: {self.TX_STATES[d["tx_state"]]}, '
+ f'MUX-state: {self.MUX_STATES[d["mux_state"]]}, '
+ f'PTX-state: {self.PTX_STATES[d["ptx_state"]]}'
+ )
+
+ def lacp_info(self, raw: bool, ifname: typing.Optional[str]):
+ data = self._get_lacp_raw(ifname)
+
+ if raw:
+ return data
+
+ return self._get_lacp_info_formatted(data)
+
+ def lacp_details(self, raw: bool, ifname: typing.Optional[str]) -> str:
+ # Check if interface is a part of any LACP bond
+ self._get_lacp_raw(ifname)
+
+ # VPP does not have API call to get this data
+ cmd_command = f'show lacp{f" {ifname}" if ifname else ""} details'
+ data = self.vpp.cli_cmd(cmd_command)
+
+ if raw:
+ return [data.reply]
+
+ return data.reply
+
# -----------------------------
# VyOS IPFIX op-mode entries
@@ -155,6 +284,19 @@ def show_ipfix_table(raw: bool):
return VPPShow().ipfix_table(raw)
+# -----------------------------
+# VPP LACP information
+# -----------------------------
+@_verify('interfaces bonding')
+def show_lacp(raw: bool, ifname: typing.Optional[str]):
+ return VPPShow().lacp_info(raw, ifname)
+
+
+@_verify('interfaces bonding')
+def show_lacp_details(raw: bool, ifname: typing.Optional[str]):
+ return VPPShow().lacp_details(raw, ifname)
+
+
if __name__ == '__main__':
try:
res = vyos.opmode.run(sys.modules[__name__])