diff options
| -rw-r--r-- | op-mode-definitions/vpp_lacp.xml.in | 42 | ||||
| -rwxr-xr-x | src/op_mode/vpp.py | 142 |
2 files changed, 184 insertions, 0 deletions
diff --git a/op-mode-definitions/vpp_lacp.xml.in b/op-mode-definitions/vpp_lacp.xml.in new file mode 100644 index 000000000..60d18647f --- /dev/null +++ b/op-mode-definitions/vpp_lacp.xml.in @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="vpp"> + <children> + <node name="lacp"> + <properties> + <help>Show VPP LACP information</help> + </properties> + <command>${vyos_op_scripts_dir}/vpp.py show_lacp</command> + <children> + <virtualTagNode> + <properties> + <help>Show specific interface LACP information</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --bondable</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/vpp.py show_lacp --ifname=$4</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show interface LACP details</help> + </properties> + <command>${vyos_op_scripts_dir}/vpp.py show_lacp_details --ifname=$4</command> + </leafNode> + </children> + </virtualTagNode> + <leafNode name="detail"> + <properties> + <help>Show LACP detailed information</help> + </properties> + <command>${vyos_op_scripts_dir}/vpp.py show_lacp_details</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition>
\ No newline at end of file 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__]) |
