confd,statd: add support for link aggregates

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2025-02-12 23:21:08 +01:00
committed by Tobias Waldekranz
parent ec2e161649
commit 78499757b0
9 changed files with 644 additions and 120 deletions
+118 -1
View File
@@ -451,6 +451,34 @@ class Iface:
self.pvid = get_json_data('', self.data, 'infix-interfaces:bridge-port', 'pvid')
self.stp_state = get_json_data('', self.data, 'infix-interfaces:bridge-port',
'stp', 'cist', 'state')
self.lag_mode = get_json_data('', self.data, 'infix-interfaces:lag', 'mode')
if self.lag_mode:
self.lag_type = get_json_data('', self.data, 'infix-interfaces:lag', 'static', 'mode')
if self.lag_mode == "lacp":
self.lag_hash = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'hash')
self.lacp_id = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'aggregator-id')
self.lacp_actor_key = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'actor-key')
self.lacp_partner_key = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'partner-key')
self.lacp_partner_mac = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'partner-mac')
self.lacp_sys_prio = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'system-priority')
else:
self.lag_hash = get_json_data('', self.data, 'infix-interfaces:lag', 'static', 'hash')
self.link_updelay = get_json_data('', self.data, 'infix-interfaces:lag', 'link-monitor', 'debounce', 'up')
self.link_downdelay = get_json_data('', self.data, 'infix-interfaces:lag', 'link-monitor', 'debounce', 'down')
self.lacp_mode = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'mode')
rate = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'rate')
self.lacp_rate = "fast (1s)" if rate == "fast" else "slow (30 sec)"
self.lag = get_json_data('', self.data, 'infix-interfaces:lag-port', 'lag')
if self.lag:
self.lag_state = get_json_data('', self.data, 'infix-interfaces:lag-port', 'state')
self.lacp_id = get_json_data('', self.data, 'infix-interfaces:lag-port', 'lacp', 'aggregator-id')
self.lacp_state = get_json_data('', self.data, 'infix-interfaces:lag-port', 'lacp', 'actor-state')
self.lacp_pstate = get_json_data('', self.data, 'infix-interfaces:lag-port', 'lacp', 'partner-state')
self.link_failures = get_json_data('', self.data, 'infix-interfaces:lag-port', 'link-failures')
self.containers = get_json_data('', self.data, 'infix-interfaces:container-network', 'containers')
@@ -491,6 +519,9 @@ class Iface:
def is_bridge(self):
return self.type == "infix-if-type:bridge"
def is_lag(self):
return self.type == "infix-if-type:lag"
def is_veth(self):
return self.data['type'] == "infix-if-type:veth"
@@ -635,6 +666,59 @@ class Iface:
self.pr_proto_ipv4()
self.pr_proto_ipv6()
def pr_proto_lag(self, member=True):
data_str = ""
row = f"{'lag':<{Pad.proto}}"
if member:
state = self.lag_state.upper()
if self.oper() == "up":
row += Decore.green(f"{state:<{Pad.state}}")
else:
row += Decore.yellow(f"{state:<{Pad.state}}")
if self.lacp_state:
lacp = ', '.join(self.lacp_state)
data_str += lacp
else:
dec = Decore.green if self.oper() == "up" else Decore.yellow
row += dec(f"{self.oper().upper():<{Pad.state}}")
data_str += f"{self.lag_mode}"
if self.lag_mode == "lacp":
data_str += f": {self.lacp_mode}"
data_str += f", rate: {self.lacp_rate}"
data_str += f", hash: {self.lag_hash}"
else:
data_str += f": {self.lag_type}"
data_str += f", hash: {self.lag_hash}"
if data_str:
row += f"{data_str:<{Pad.data}}"
print(row)
def pr_lag(self, _ifaces):
self.pr_name(pipe="")
self.pr_proto_lag(member=False)
lowers = []
for _iface in [Iface(data) for data in _ifaces]:
if _iface.lag and _iface.lag == self.name:
lowers.append(_iface)
if lowers:
self.pr_proto_eth(pipe='')
self.pr_proto_ipv4(pipe='')
self.pr_proto_ipv6(pipe='')
else:
self.pr_proto_eth(pipe=' ')
self.pr_proto_ipv4()
self.pr_proto_ipv6()
for i, lower in enumerate(lowers):
pipe = '' if (i == len(lowers) -1) else ''
lower.pr_name(pipe)
lower.pr_proto_lag()
def pr_veth(self, _ifaces):
self.pr_name(pipe="")
self.pr_proto_veth()
@@ -716,6 +800,32 @@ class Iface:
if self.phys_address:
print(f"{'physical address':<{20}}: {self.phys_address}")
if self.lag_mode:
print(f"{'lag mode':<{20}}: {self.lag_mode}")
if self.lag_mode == "lacp":
print(f"{'lag hash':<{20}}: {self.lag_hash}")
print(f"{'lacp mode':<{20}}: {self.lacp_mode}")
print(f"{'lacp rate':<{20}}: {self.lacp_rate}")
print(f"{'lacp aggregate id':<{20}}: {self.lacp_id}")
print(f"{'lacp system priority':<{20}}: {self.lacp_sys_prio}")
print(f"{'lacp actor key':<{20}}: {self.lacp_actor_key}")
print(f"{'lacp partner key':<{20}}: {self.lacp_partner_key}")
print(f"{'lacp partner mac':<{20}}: {self.lacp_partner_mac}")
else:
print(f"{'lag type':<{20}}: {self.lag_type}")
print(f"{'lag hash':<{20}}: {self.lag_hash}")
print(f"{'link debounce up':<{20}}: {self.link_updelay} msec")
print(f"{'link debounce down':<{20}}: {self.link_downdelay} msec")
if self.lag:
print(f"{'lag member':<{20}}: {self.lag}")
print(f"{'lag member state':<{20}}: {self.lag_state}")
if self.lacp_state:
print(f"{'lacp aggregate id':<{20}}: {self.lacp_id}")
print(f"{'lacp actor state':<{20}}: {', '.join(self.lacp_state)}")
print(f"{'lacp partner state':<{20}}: {', '.join(self.lacp_pstate)}")
print(f"{'link failure count':<{20}}: {self.link_failures}")
if self.ipv4_addr:
first = True
for addr in self.ipv4_addr:
@@ -872,6 +982,10 @@ def pr_interface_list(json):
iface.pr_bridge(ifaces)
continue
if iface.is_lag():
iface.pr_lag(ifaces)
continue
if iface.is_veth():
iface.pr_veth(ifaces)
continue
@@ -892,11 +1006,14 @@ def pr_interface_list(json):
iface.pr_vlan(ifaces)
continue
# These interfaces are printed by there parent, such as bridge
# These interfaces are printed by their parent, such as bridge
if iface.lower_if:
continue
if iface.bridge:
continue
if iface.lag:
continue
print_interface(iface)
@@ -0,0 +1,79 @@
"""The lag/bond oper-status always follows the carrier"""
def lower(iplink):
"""Return a dictionary of the status of a lag member"""
port = {
"lag": iplink['master'],
}
info = iplink['linkinfo']['info_slave_data']
if info:
# active or backup link
port['state'] = info['state'].lower()
port['link-failures'] = info['link_failure_count']
# On etherlike interfaces and tap interfaces oper-status lies
# if info['mii_status'] == "DOWN":
# iface['oper-status'] = "down"
# Initialize lacp dict only if we encounter LACP-related fields
if 'ad_aggregator_id' in info:
port['lacp'] = {}
port['lacp']['aggregator-id'] = info['ad_aggregator_id']
port['lacp']['actor-state'] = info['ad_actor_oper_port_state_str']
port['lacp']['partner-state'] = info['ad_partner_oper_port_state_str']
else:
port['state'] = 'backup'
port['link-failures'] = 0
return port
def lag(iplink):
"""Return a dictionary of the status of the lag"""
mode = {
"balance-xor": "static",
"802.3ad": "lacp",
}
hash_policy = {
"layer2": "layer2",
"layer3+4": "layer3-4",
"layer2+3": "layer2-3",
"encap2+3": "encap2-3",
"encap3+4": "encap3-4",
"vlan+srcmac": "vlan-srcmac",
}
bond = {}
info = iplink["linkinfo"]["info_data"]
if info:
bond["mode"] = mode.get(info['mode'], "static")
if bond["mode"] == "lacp":
bond["lacp"] = {
"mode": 'active' if info['ad_lacp_active'] == "on" else 'passive',
"rate": info['ad_lacp_rate'],
"hash": hash_policy.get(info['xmit_hash_policy'], "layer2"),
}
if 'ad_info' in info:
bond["lacp"]["aggregator-id"] = info['ad_info']['aggregator']
bond["lacp"]["actor-key"] = info['ad_info']['actor_key']
bond["lacp"]["partner-key"] = info['ad_info']['partner_key']
bond["lacp"]["partner-mac"] = info['ad_info']['partner_mac']
if 'ad_actor_sys_prio' in info:
bond["lacp"]["system-priority"] = info['ad_actor_sys_prio']
else:
bond["static"] = {
"mode": info['mode'],
"hash": info['xmit_hash_policy']
}
bond["link-monitor"] = {
"debounce": {
"up": info['updelay'],
"down": info['downdelay']
}
}
return bond
@@ -3,6 +3,7 @@ from . import common
from . import bridge
from . import ethernet
from . import ip
from . import lag
from . import tun
from . import veth
from . import vlan
@@ -117,16 +118,16 @@ def interface(iplink, ipaddr):
interface["infix-interfaces:bridge"] = br
if brport := bridge.lower(iplink):
interface["infix-interfaces:bridge-port"] = brport
# case "infix-if-type:lag":
# if l := lag.lag(iplink):
# interface["infix-interfaces:lag"] = l
case "infix-if-type:lag":
if lg := lag.lag(iplink):
interface["infix-interfaces:lag"] = lg
case "infix-if-type:ethernet":
if eth := ethernet.ethernet(iplink):
interface["ieee802-ethernet-interface:ethernet"] = eth
case "infix-if-type:vxlan":
if vxlan := tun.vxlan(iplink):
interface["infix-interfaces:vxlan"] = vxlan
case "infix-if-type:gre"|"infix-if-type:gretap":
case "infix-if-type:gre" | "infix-if-type:gretap":
if gre := tun.gre(iplink):
interface["infix-interfaces:gre"] = gre
case "infix-if-type:veth":
@@ -140,9 +141,9 @@ def interface(iplink, ipaddr):
case "infix-interfaces:bridge-port":
if brport := bridge.lower(iplink):
interface["infix-interfaces:bridge-port"] = brport
# case "infix-interfaces:lag-port":
# if lagport := lag.lower(iplink):
# interface["infix-interfaces:lag-port"] = lagport
case "infix-interfaces:lag-port":
if lagport := lag.lower(iplink):
interface["infix-interfaces:lag-port"] = lagport
return interface