mirror of
https://github.com/kernelkit/infix.git
synced 2026-08-04 06:43:00 +02:00
statd: extend ospf operational data to replace vtysh commands in cli
Collect more data from FRR to the OSPF and BFD operational datastore. Refactor CLI commands to use the YANG datastore instead of vtysh. Additionally, the BFD command(s) have been moved to the top level of admin-exec, which is where vtysh has them. Finally, preparing for future OSPFv3 support, all commands have been given a new context under 'show ip ospf'. Deprecation warnings have been added to the existing 'show ospf' commands. Also, for consistency, all commands names in plural have been changed to their singular form: routes -> route, interfaes -> interface, etc. This is what vtysh, Cisco, and others do as well. Fixes #1190 Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
@@ -81,6 +81,9 @@ def main():
|
||||
elif args.model == 'infix-firewall':
|
||||
from . import infix_firewall
|
||||
yang_data = infix_firewall.operational()
|
||||
elif args.model == 'ietf-bfd-ip-sh':
|
||||
from . import ietf_bfd_ip_sh
|
||||
yang_data = ietf_bfd_ip_sh.operational()
|
||||
else:
|
||||
common.LOG.warning("Unsupported model %s", args.model)
|
||||
sys.exit(1)
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
from .common import insert
|
||||
from .host import HOST
|
||||
|
||||
|
||||
def frr_to_ietf_state(state):
|
||||
"""Convert FRR BFD state to IETF BFD state"""
|
||||
state_map = {
|
||||
"up": "up",
|
||||
"down": "down",
|
||||
"init": "init",
|
||||
"adminDown": "adminDown"
|
||||
}
|
||||
return state_map.get(state, "down")
|
||||
|
||||
|
||||
def add_sessions(control_protocols):
|
||||
"""Fetch BFD session data from FRR"""
|
||||
cmd = ['vtysh', '-c', 'show bfd peers json']
|
||||
data = HOST.run_json(cmd, default=[])
|
||||
if not data:
|
||||
return # No BFD sessions available
|
||||
|
||||
control_protocol = {}
|
||||
control_protocol["type"] = "infix-routing:bfdv1"
|
||||
control_protocol["name"] = "bfd"
|
||||
control_protocol["ietf-bfd:bfd"] = {}
|
||||
control_protocol["ietf-bfd:bfd"]["ietf-bfd-ip-sh:ip-sh"] = {}
|
||||
control_protocol["ietf-bfd:bfd"]["ietf-bfd-ip-sh:ip-sh"]["sessions"] = {}
|
||||
|
||||
sessions = []
|
||||
|
||||
# FRR returns a list of BFD peers
|
||||
for peer in data:
|
||||
# Only process single-hop sessions (multihop == false)
|
||||
if peer.get("multihop", False):
|
||||
continue
|
||||
|
||||
session = {}
|
||||
|
||||
# Key fields: interface and dest-addr
|
||||
session["interface"] = peer.get("interface", "unknown")
|
||||
session["dest-addr"] = peer.get("peer", "0.0.0.0")
|
||||
|
||||
# Operational state fields (config false)
|
||||
# Local and remote discriminators
|
||||
if peer.get("id") is not None:
|
||||
session["local-discriminator"] = peer["id"]
|
||||
if peer.get("remote-id") is not None:
|
||||
session["remote-discriminator"] = peer["remote-id"]
|
||||
|
||||
# Session running state
|
||||
session["session-running"] = {}
|
||||
|
||||
# Local and remote state
|
||||
state = peer.get("status", "down")
|
||||
session["session-running"]["local-state"] = frr_to_ietf_state(state)
|
||||
# Remote state not directly available in FRR output, infer from status
|
||||
session["session-running"]["remote-state"] = frr_to_ietf_state(state)
|
||||
|
||||
# Local diagnostic - not directly available, use "none"
|
||||
session["session-running"]["local-diagnostic"] = "none"
|
||||
|
||||
# Detection mode - FRR uses async mode for OSPF-created sessions
|
||||
session["session-running"]["detection-mode"] = "async-without-echo"
|
||||
|
||||
# Timing intervals (convert milliseconds to microseconds for YANG)
|
||||
if peer.get("receive-interval") is not None:
|
||||
session["session-running"]["negotiated-rx-interval"] = peer["receive-interval"] * 1000
|
||||
if peer.get("transmit-interval") is not None:
|
||||
session["session-running"]["negotiated-tx-interval"] = peer["transmit-interval"] * 1000
|
||||
|
||||
# Detection time (in microseconds)
|
||||
if peer.get("detect-multiplier") is not None and peer.get("receive-interval") is not None:
|
||||
detection_time_ms = peer["detect-multiplier"] * peer["receive-interval"]
|
||||
session["session-running"]["detection-time"] = detection_time_ms * 1000
|
||||
|
||||
# Path type
|
||||
session["path-type"] = "ietf-bfd-types:path-ip-sh"
|
||||
session["ip-encapsulation"] = True
|
||||
|
||||
sessions.append(session)
|
||||
|
||||
if sessions:
|
||||
control_protocol["ietf-bfd:bfd"]["ietf-bfd-ip-sh:ip-sh"]["sessions"]["session"] = sessions
|
||||
insert(control_protocols, "control-plane-protocol", [control_protocol])
|
||||
|
||||
|
||||
def operational():
|
||||
out = {
|
||||
"ietf-routing:routing": {
|
||||
"control-plane-protocols": {}
|
||||
}
|
||||
}
|
||||
|
||||
add_sessions(out['ietf-routing:routing']['control-plane-protocols'])
|
||||
return out
|
||||
@@ -10,6 +10,14 @@ def frr_to_ietf_neighbor_state(state):
|
||||
return state.lower()
|
||||
|
||||
|
||||
def frr_to_ietf_neighbor_role(role):
|
||||
"""Translate FRR neighbor role to YANG enumeration values"""
|
||||
if role == "Backup":
|
||||
return "BDR"
|
||||
# DR and DROther are already correct
|
||||
return role
|
||||
|
||||
|
||||
def add_routes(ospf):
|
||||
"""Fetch OSPF routes from Frr"""
|
||||
cmd = ['vtysh', '-c', "show ip ospf route json"]
|
||||
@@ -38,6 +46,22 @@ def add_routes(ospf):
|
||||
elif routetype[0] == "N":
|
||||
route["route-type"] = "intra-area"
|
||||
|
||||
# Add area information if available
|
||||
# Note: augmented by infix-routing.yang since standard ietf-ospf doesn't include it
|
||||
# Must use the augmenting module's prefix
|
||||
if info.get("area") is not None:
|
||||
route["infix-routing:area-id"] = info["area"]
|
||||
|
||||
# Add metric (cost) if available
|
||||
if info.get("cost") is not None:
|
||||
route["metric"] = info["cost"]
|
||||
elif info.get("metric") is not None:
|
||||
route["metric"] = info["metric"]
|
||||
|
||||
# Add route-tag for external routes
|
||||
if info.get("tag") is not None:
|
||||
route["route-tag"] = info["tag"]
|
||||
|
||||
for hop in info["nexthops"]:
|
||||
nexthop = {}
|
||||
if hop["ip"] != " ":
|
||||
@@ -119,13 +143,85 @@ def add_areas(control_protocols):
|
||||
val = xlate.get(iface["state"], "unknown")
|
||||
interface["state"] = val
|
||||
|
||||
# Interface priority (for DR/BDR election)
|
||||
if iface.get("priority") is not None:
|
||||
interface["priority"] = iface["priority"]
|
||||
|
||||
# Interface cost
|
||||
if iface.get("cost") is not None:
|
||||
interface["cost"] = iface["cost"]
|
||||
|
||||
# Configuration timers (in seconds)
|
||||
if iface.get("timerDeadSecs") is not None:
|
||||
interface["dead-interval"] = iface["timerDeadSecs"]
|
||||
|
||||
if iface.get("timerRetransmitSecs") is not None:
|
||||
interface["retransmit-interval"] = iface["timerRetransmitSecs"]
|
||||
|
||||
if iface.get("transmitDelaySecs") is not None:
|
||||
interface["transmit-delay"] = iface["transmitDelaySecs"]
|
||||
|
||||
# Hello interval - convert from milliseconds to seconds
|
||||
if iface.get("timerMsecs") is not None:
|
||||
hello_sec = iface["timerMsecs"] // 1000
|
||||
# timer-value-seconds16 requires range 1..65535, use max(1, value)
|
||||
if hello_sec >= 1:
|
||||
interface["hello-interval"] = hello_sec
|
||||
|
||||
# Operational state timers (config false)
|
||||
# Hello timer - time remaining until next Hello (convert ms to seconds)
|
||||
if iface.get("timerHelloInMsecs") is not None:
|
||||
hello_timer_sec = iface["timerHelloInMsecs"] // 1000
|
||||
# timer-value-seconds16 requires range 1..65535, use max(1, value)
|
||||
if hello_timer_sec >= 1:
|
||||
interface["hello-timer"] = hello_timer_sec
|
||||
|
||||
# Wait timer - time until interface exits Waiting state
|
||||
if iface.get("timerWaitSecs") is not None:
|
||||
wait_sec = iface["timerWaitSecs"]
|
||||
# timer-value-seconds16 requires range 1..65535
|
||||
if wait_sec >= 1:
|
||||
interface["wait-timer"] = wait_sec
|
||||
|
||||
neighbors = []
|
||||
for neigh in iface["neighbors"]:
|
||||
neighbor = {}
|
||||
neighbor["neighbor-router-id"] = neigh["neighborIp"]
|
||||
neighbor["address"] = neigh["ifaceAddress"]
|
||||
neighbor["dead-timer"] = neigh["routerDeadIntervalTimerDueMsec"]
|
||||
|
||||
# Priority - use existing YANG leaf for operational data
|
||||
if neigh.get("nbrPriority") is not None:
|
||||
neighbor["priority"] = neigh["nbrPriority"]
|
||||
|
||||
# Uptime - convert from milliseconds to seconds
|
||||
# Note: augmented by infix-routing.yang
|
||||
# Use lastPrgrsvChangeMsec from detail output (time since last progressive state change)
|
||||
if neigh.get("lastPrgrsvChangeMsec") is not None:
|
||||
uptime_sec = neigh["lastPrgrsvChangeMsec"] // 1000
|
||||
neighbor["infix-routing:uptime"] = uptime_sec
|
||||
|
||||
# Dead timer - convert from milliseconds to seconds
|
||||
# timer-value-seconds16 requires range 1..65535
|
||||
if neigh.get("routerDeadIntervalTimerDueMsec") is not None:
|
||||
dead_timer_sec = neigh["routerDeadIntervalTimerDueMsec"] // 1000
|
||||
if dead_timer_sec >= 1:
|
||||
neighbor["dead-timer"] = dead_timer_sec
|
||||
|
||||
neighbor["state"] = frr_to_ietf_neighbor_state(neigh["nbrState"])
|
||||
|
||||
# Store role (DR/BDR/DROther) for display
|
||||
# Note: augmented by infix-routing.yang
|
||||
if neigh.get("role"):
|
||||
neighbor["infix-routing:role"] = frr_to_ietf_neighbor_role(neigh["role"])
|
||||
|
||||
# Store interface name with local address (e.g., "e5:10.0.23.1")
|
||||
# Note: augmented by infix-routing.yang
|
||||
# Compose from ifaceName and localIfaceAddress
|
||||
if neigh.get("ifaceName") and neigh.get("localIfaceAddress"):
|
||||
neighbor["infix-routing:interface-name"] = f"{neigh['ifaceName']}:{neigh['localIfaceAddress']}"
|
||||
elif neigh.get("ifaceName"):
|
||||
neighbor["infix-routing:interface-name"] = neigh["ifaceName"]
|
||||
|
||||
if neigh.get("routerDesignatedId"):
|
||||
neighbor["dr-router-id"] = neigh["routerDesignatedId"]
|
||||
if neigh.get("routerDesignatedBackupId"):
|
||||
|
||||
Reference in New Issue
Block a user