mirror of
https://github.com/hansjone/netx.git
synced 2026-10-18 19:55:56 +08:00
Add BGP as <local_as> binding for multi-process ZTE collects.
Stamp local_as on peer/route/evpn results and discover (local_as, neighbor/vrf) pairs from config_bgp_peer so each router bgp process is collected separately. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
b453c6871e
commit
262082ef08
10 changed files with 690 additions and 318 deletions
|
|
@ -28,6 +28,8 @@ _CONT_RE = re.compile(
|
|||
re.I,
|
||||
)
|
||||
_VRF_IN_CMD_RE = re.compile(r"(?i)\bvrf\s+(\S+)")
|
||||
# ``as <local_as>`` sits before ``| one-line`` (optional for legacy cmds).
|
||||
_LOCAL_AS_IN_CMD_RE = re.compile(r"(?i)(?:^|\s)as\s+(\S+)(?:\s*\||\s*$)")
|
||||
|
||||
|
||||
def _detect_bgp_afi(command: str, params: dict[str, str] | None) -> str:
|
||||
|
|
@ -56,6 +58,13 @@ def _detect_vrf(command: str, params: dict[str, str] | None) -> str:
|
|||
return m.group(1).strip() if m else ""
|
||||
|
||||
|
||||
def _detect_local_as(command: str, params: dict[str, str] | None) -> str:
|
||||
if params and params.get("local_as"):
|
||||
return str(params.get("local_as") or "").strip()
|
||||
m = _LOCAL_AS_IN_CMD_RE.search(str(command or ""))
|
||||
return m.group(1).strip() if m else ""
|
||||
|
||||
|
||||
def _state_and_pfx(state_raw: str) -> tuple[str, str]:
|
||||
raw = str(state_raw or "").strip()
|
||||
if raw.isdigit():
|
||||
|
|
@ -66,6 +75,7 @@ def _state_and_pfx(state_raw: str) -> tuple[str, str]:
|
|||
|
||||
def _peer_row(
|
||||
*,
|
||||
local_as: str,
|
||||
afi: str,
|
||||
vrf: str,
|
||||
nei: str,
|
||||
|
|
@ -78,6 +88,7 @@ def _peer_row(
|
|||
) -> dict[str, Any]:
|
||||
state, pfx = _state_and_pfx(state_raw)
|
||||
return {
|
||||
"local_as": local_as[:16],
|
||||
"afi": afi[:32],
|
||||
"vrf": vrf[:128],
|
||||
"neighbor": nei[:128],
|
||||
|
|
@ -94,6 +105,7 @@ def _peer_row(
|
|||
def _map_fsm_rows(
|
||||
rows: list[dict[str, Any]],
|
||||
*,
|
||||
local_as: str,
|
||||
afi: str,
|
||||
vrf: str,
|
||||
) -> list[dict[str, Any]]:
|
||||
|
|
@ -123,6 +135,7 @@ def _map_fsm_rows(
|
|||
state_raw = row_get(r, "STATE_PFX", "STATE", "state", "pfx_rcd")
|
||||
out.append(
|
||||
_peer_row(
|
||||
local_as=local_as,
|
||||
afi=afi,
|
||||
vrf=vrf,
|
||||
nei=nei,
|
||||
|
|
@ -140,6 +153,7 @@ def _map_fsm_rows(
|
|||
def _hand_parse(
|
||||
*,
|
||||
raw_text: str,
|
||||
local_as: str = "",
|
||||
afi: str = "unknown",
|
||||
vrf: str = "",
|
||||
**_kw: Any,
|
||||
|
|
@ -165,6 +179,7 @@ def _hand_parse(
|
|||
pending = ""
|
||||
out.append(
|
||||
_peer_row(
|
||||
local_as=local_as,
|
||||
afi=afi,
|
||||
vrf=vrf,
|
||||
nei=nei,
|
||||
|
|
@ -190,6 +205,7 @@ def _hand_parse(
|
|||
seen.add(nei)
|
||||
out.append(
|
||||
_peer_row(
|
||||
local_as=local_as,
|
||||
afi=afi,
|
||||
vrf=vrf,
|
||||
nei=nei,
|
||||
|
|
@ -213,6 +229,7 @@ def normalize_bgp_peer(
|
|||
command: str = "",
|
||||
params: dict[str, str] | None = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
local_as = _detect_local_as(command, params)
|
||||
afi = _detect_bgp_afi(command, params)
|
||||
vrf = _detect_vrf(command, params)
|
||||
tables = dict(fsm_tables or {})
|
||||
|
|
@ -229,10 +246,10 @@ def normalize_bgp_peer(
|
|||
)
|
||||
|
||||
def _map(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
return _map_fsm_rows(rows, afi=afi, vrf=vrf)
|
||||
return _map_fsm_rows(rows, local_as=local_as, afi=afi, vrf=vrf)
|
||||
|
||||
def _hand(*, raw_text: str, **kw: Any) -> list[dict[str, Any]]:
|
||||
return _hand_parse(raw_text=raw_text, afi=afi, vrf=vrf, **kw)
|
||||
return _hand_parse(raw_text=raw_text, local_as=local_as, afi=afi, vrf=vrf, **kw)
|
||||
|
||||
return prefer_fsm(tables, RULE_KEYS, _map, _hand, raw_text=raw_text)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from typing import Any, Mapping
|
|||
from ....lldp_shared import resolve_vendor_key
|
||||
from ....ntc_parse import apply_rules, resolve_cli_platform, row_get
|
||||
from ..common.pipeline import prefer_fsm
|
||||
from .bgp_peer import _detect_bgp_afi, _detect_vrf
|
||||
from .bgp_peer import _detect_bgp_afi, _detect_local_as, _detect_vrf
|
||||
|
||||
RULE_KEYS = ("zte_zxros_show_bgp_neighbor_routes",)
|
||||
|
||||
|
|
@ -152,6 +152,7 @@ def _emit_route(
|
|||
out: list[dict[str, Any]],
|
||||
seen: set[str],
|
||||
*,
|
||||
local_as: str,
|
||||
afi: str,
|
||||
vrf: str,
|
||||
neighbor: str,
|
||||
|
|
@ -172,6 +173,7 @@ def _emit_route(
|
|||
metric, loc, tag, path = _split_rest(rest, path_continuation=path_continuation)
|
||||
out.append(
|
||||
{
|
||||
"local_as": local_as[:16],
|
||||
"afi": afi[:32],
|
||||
"vrf": vrf[:128],
|
||||
"neighbor": neighbor[:128],
|
||||
|
|
@ -193,6 +195,7 @@ def _emit_route(
|
|||
def _map_fsm_rows(
|
||||
rows: list[dict[str, Any]],
|
||||
*,
|
||||
local_as: str,
|
||||
afi: str,
|
||||
vrf: str,
|
||||
neighbor: str,
|
||||
|
|
@ -214,6 +217,7 @@ def _map_fsm_rows(
|
|||
path = row_get(r, "PATH", "path")
|
||||
out.append(
|
||||
{
|
||||
"local_as": local_as[:16],
|
||||
"afi": afi[:32],
|
||||
"vrf": vrf[:128],
|
||||
"neighbor": neighbor[:128],
|
||||
|
|
@ -236,6 +240,7 @@ def _map_fsm_rows(
|
|||
def _hand_parse(
|
||||
*,
|
||||
raw_text: str,
|
||||
local_as: str = "",
|
||||
afi: str = "unknown",
|
||||
vrf: str = "",
|
||||
neighbor: str = "",
|
||||
|
|
@ -256,6 +261,7 @@ def _hand_parse(
|
|||
_emit_route(
|
||||
out,
|
||||
seen,
|
||||
local_as=local_as,
|
||||
afi=afi,
|
||||
vrf=vrf,
|
||||
neighbor=neighbor,
|
||||
|
|
@ -300,6 +306,7 @@ def _hand_parse(
|
|||
_emit_route(
|
||||
out,
|
||||
seen,
|
||||
local_as=local_as,
|
||||
afi=afi,
|
||||
vrf=vrf,
|
||||
neighbor=neighbor,
|
||||
|
|
@ -333,6 +340,7 @@ def normalize_bgp_route(
|
|||
command: str = "",
|
||||
params: dict[str, str] | None = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
local_as = _detect_local_as(command, params)
|
||||
afi = _detect_bgp_afi(command, params)
|
||||
vrf = _detect_vrf(command, params)
|
||||
neighbor = _detect_neighbor(command, params)
|
||||
|
|
@ -355,12 +363,18 @@ def normalize_bgp_route(
|
|||
|
||||
def _map(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
return _map_fsm_rows(
|
||||
rows, afi=afi, vrf=vrf, neighbor=neighbor, direction=direction
|
||||
rows,
|
||||
local_as=local_as,
|
||||
afi=afi,
|
||||
vrf=vrf,
|
||||
neighbor=neighbor,
|
||||
direction=direction,
|
||||
)
|
||||
|
||||
def _hand(*, raw_text: str, **kw: Any) -> list[dict[str, Any]]:
|
||||
return _hand_parse(
|
||||
raw_text=raw_text,
|
||||
local_as=local_as,
|
||||
afi=afi,
|
||||
vrf=vrf,
|
||||
neighbor=neighbor,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
"""ZTE config intent: ``show running-config bgp`` / MIM ``!<bgp>``.
|
||||
|
||||
Emits one row per ``(afi, vrf, neighbor|peer_group)`` activation under
|
||||
Emits one row per ``(local_as, afi, vrf, neighbor|peer_group)`` activation under
|
||||
address-family. IP literals go in ``neighbor``; non-IP names (peer-groups)
|
||||
go in ``peer_group``. Never captures password / secret lines.
|
||||
|
||||
Supports **multiple** ``router bgp <asn>`` instances: each peer is scoped to
|
||||
its enclosing local AS (meta / peer-group expand do not cross AS boundaries).
|
||||
|
||||
For **global** (non-VRF) address-families, a peer-group ``activate`` is also
|
||||
expanded into one row per global member ``neighbor <ip> peer-group <name>``
|
||||
so discover/bind sees the real Neighbor IPs (direct activates ∪ group
|
||||
|
|
@ -23,6 +26,7 @@ from .config_common import extract_mim_section, is_secret_line
|
|||
|
||||
RULE_KEYS: tuple[str, ...] = ()
|
||||
|
||||
_ROUTER_BGP_RE = re.compile(r"^\s*router\s+bgp\s+(\S+)\s*$", re.I)
|
||||
_NEI_REMOTE_RE = re.compile(r"^\s*neighbor\s+(\S+)\s+remote-as\s+(\S+)\s*$", re.I)
|
||||
_NEI_UPD_RE = re.compile(r"^\s*neighbor\s+(\S+)\s+update-source\s+(\S+)\s*$", re.I)
|
||||
_NEI_PG_RE = re.compile(r"^\s*neighbor\s+(\S+)\s+peer-group(?:\s+(\S+))?\s*$", re.I)
|
||||
|
|
@ -32,9 +36,18 @@ _NEI_RM_RE = re.compile(
|
|||
_NEI_ACT_RE = re.compile(
|
||||
r"^\s*neighbor\s+(\S+)\s+activate(?:\s+(disable))?\s*$", re.I
|
||||
)
|
||||
_AF_V4_VRF_RE = re.compile(r"^\s*address-family\s+ipv4\s+vrf\s+(\S+)\s*$", re.I)
|
||||
_AF_V6_VRF_RE = re.compile(r"^\s*address-family\s+ipv6\s+vrf\s+(\S+)\s*$", re.I)
|
||||
_AF_L2VPN_RE = re.compile(r"^\s*address-family\s+l2vpn\s+(\S+)\s*$", re.I)
|
||||
# Multi-word AFs first (e.g. ``ipv6 sr-policy``); then single token.
|
||||
_AF_MULTI_RE = re.compile(
|
||||
r"^\s*address-family\s+(ipv6\s+sr-policy|ipv4\s+sr-policy)\s*$", re.I
|
||||
)
|
||||
_AF_SINGLE_RE = re.compile(r"^\s*address-family\s+(\S+)\s*$", re.I)
|
||||
_IPV4_RE = re.compile(r"^\d{1,3}(?:\.\d{1,3}){3}$")
|
||||
|
||||
_AfKey = tuple[str, str, str] # (afi, vrf, token)
|
||||
_AfKey = tuple[str, str, str, str] # (local_as, afi, vrf, token)
|
||||
_MetaKey = tuple[str, str] # (local_as, token)
|
||||
|
||||
|
||||
def _is_ip_neighbor(token: str) -> bool:
|
||||
|
|
@ -68,14 +81,15 @@ def _split_neighbor_cols(token: str, info: Mapping[str, str]) -> tuple[str, str]
|
|||
def _merge_info(
|
||||
token: str,
|
||||
*,
|
||||
local_as: str,
|
||||
afi: str,
|
||||
vrf: str,
|
||||
meta: Mapping[str, dict[str, str]],
|
||||
meta: Mapping[_MetaKey, dict[str, str]],
|
||||
af_rm: Mapping[_AfKey, dict[str, str]],
|
||||
) -> dict[str, str]:
|
||||
"""Global neighbor meta + AF-scoped route-maps (AF wins on conflict)."""
|
||||
info = dict(meta.get(token) or {})
|
||||
scoped = af_rm.get((afi, vrf, token)) or {}
|
||||
info = dict(meta.get((local_as, token)) or {})
|
||||
scoped = af_rm.get((local_as, afi, vrf, token)) or {}
|
||||
for key in ("route_map_in", "route_map_out"):
|
||||
if scoped.get(key):
|
||||
info[key] = scoped[key]
|
||||
|
|
@ -84,6 +98,7 @@ def _merge_info(
|
|||
|
||||
def _row(
|
||||
*,
|
||||
local_as: str,
|
||||
afi: str,
|
||||
vrf: str,
|
||||
token: str,
|
||||
|
|
@ -92,6 +107,7 @@ def _row(
|
|||
) -> dict[str, Any]:
|
||||
neighbor, peer_group = _split_neighbor_cols(token, info)
|
||||
return {
|
||||
"local_as": str(local_as or "")[:16],
|
||||
"afi": afi[:32],
|
||||
"vrf": vrf[:128],
|
||||
"neighbor": neighbor[:128],
|
||||
|
|
@ -104,6 +120,12 @@ def _row(
|
|||
}
|
||||
|
||||
|
||||
def _normalize_afi_token(raw: str) -> str:
|
||||
"""Collapse multi-word AF names to a stable token (``ipv6 sr-policy`` → ``ipv6-sr-policy``)."""
|
||||
parts = [p for p in re.split(r"\s+", str(raw or "").strip().lower()) if p]
|
||||
return "-".join(parts)
|
||||
|
||||
|
||||
def normalize_config_bgp_peer(
|
||||
*,
|
||||
raw_text: str,
|
||||
|
|
@ -115,96 +137,133 @@ def normalize_config_bgp_peer(
|
|||
) -> list[dict[str, Any]]:
|
||||
_ = (fsm_tables, vendor, device_type, command, params)
|
||||
body = extract_mim_section(raw_text, "bgp")
|
||||
meta: dict[str, dict[str, str]] = {}
|
||||
meta: dict[_MetaKey, dict[str, str]] = {}
|
||||
af_rm: dict[_AfKey, dict[str, str]] = {}
|
||||
local_as = ""
|
||||
afi = ""
|
||||
vrf = ""
|
||||
activations: list[tuple[str, str, str, str]] = []
|
||||
# (afi, vrf, token, activate)
|
||||
activations: list[tuple[str, str, str, str, str]] = []
|
||||
# (local_as, afi, vrf, token, activate)
|
||||
|
||||
def _meta(token: str) -> dict[str, str]:
|
||||
key = (local_as, token)
|
||||
slot = meta.get(key)
|
||||
if slot is None:
|
||||
slot = {}
|
||||
meta[key] = slot
|
||||
return slot
|
||||
|
||||
for raw in body.splitlines():
|
||||
line = raw.rstrip()
|
||||
if is_secret_line(line):
|
||||
continue
|
||||
|
||||
m = _ROUTER_BGP_RE.match(line)
|
||||
if m:
|
||||
local_as = m.group(1).strip()
|
||||
afi, vrf = "", ""
|
||||
continue
|
||||
|
||||
# End of ``router bgp`` block (column-0 ``$``).
|
||||
if re.match(r"^\$\s*$", line):
|
||||
afi, vrf = "", ""
|
||||
local_as = ""
|
||||
continue
|
||||
|
||||
# End of address-family (exactly two-space indent ``$``).
|
||||
if re.match(r"^ \$\s*$", line) and afi:
|
||||
afi, vrf = "", ""
|
||||
continue
|
||||
|
||||
if not local_as:
|
||||
continue
|
||||
|
||||
m = _NEI_REMOTE_RE.match(line)
|
||||
if m:
|
||||
nei = m.group(1).strip()
|
||||
meta.setdefault(nei, {})
|
||||
meta[nei]["remote_as"] = m.group(2).strip()
|
||||
_meta(nei)["remote_as"] = m.group(2).strip()
|
||||
continue
|
||||
m = _NEI_UPD_RE.match(line)
|
||||
if m:
|
||||
nei = m.group(1).strip()
|
||||
meta.setdefault(nei, {})
|
||||
meta[nei]["update_source"] = m.group(2).strip()
|
||||
_meta(nei)["update_source"] = m.group(2).strip()
|
||||
continue
|
||||
m = _NEI_PG_RE.match(line)
|
||||
if m:
|
||||
nei = m.group(1).strip()
|
||||
meta.setdefault(nei, {})
|
||||
slot = _meta(nei)
|
||||
group = (m.group(2) or "").strip()
|
||||
if group:
|
||||
# neighbor <ip> peer-group <name>
|
||||
meta[nei]["peer_group"] = group
|
||||
slot["peer_group"] = group
|
||||
else:
|
||||
# neighbor <name> peer-group → peer-group definition
|
||||
meta[nei]["is_group"] = "1"
|
||||
slot["is_group"] = "1"
|
||||
continue
|
||||
m = _NEI_RM_RE.match(line)
|
||||
if m and not afi:
|
||||
nei = m.group(1).strip()
|
||||
meta.setdefault(nei, {})
|
||||
direction = m.group(3).lower()
|
||||
key = "route_map_in" if direction == "in" else "route_map_out"
|
||||
meta[nei][key] = m.group(2).strip()
|
||||
_meta(nei)[key] = m.group(2).strip()
|
||||
continue
|
||||
m = re.match(r"^\s*address-family\s+ipv4\s+vrf\s+(\S+)\s*$", line, re.I)
|
||||
m = _AF_V4_VRF_RE.match(line)
|
||||
if m:
|
||||
afi, vrf = "ipv4", m.group(1).strip()
|
||||
continue
|
||||
m = re.match(r"^\s*address-family\s+ipv6\s+vrf\s+(\S+)\s*$", line, re.I)
|
||||
m = _AF_V6_VRF_RE.match(line)
|
||||
if m:
|
||||
afi, vrf = "ipv6", m.group(1).strip()
|
||||
continue
|
||||
m = re.match(r"^\s*address-family\s+l2vpn\s+(\S+)\s*$", line, re.I)
|
||||
m = _AF_L2VPN_RE.match(line)
|
||||
if m:
|
||||
afi, vrf = f"l2vpn-{m.group(1).lower()}", ""
|
||||
continue
|
||||
m = re.match(r"^\s*address-family\s+(\S+)\s*$", line, re.I)
|
||||
m = _AF_MULTI_RE.match(line)
|
||||
if m:
|
||||
afi, vrf = _normalize_afi_token(m.group(1)), ""
|
||||
continue
|
||||
m = _AF_SINGLE_RE.match(line)
|
||||
if m:
|
||||
afi, vrf = m.group(1).lower(), ""
|
||||
continue
|
||||
if re.match(r"^\s*\$\s*$", line) and afi:
|
||||
if line.startswith(" ") and not line.startswith(" "):
|
||||
afi, vrf = "", ""
|
||||
continue
|
||||
m = _NEI_ACT_RE.match(line)
|
||||
if m and afi:
|
||||
nei = m.group(1).strip()
|
||||
act = "disable" if m.group(2) else "enable"
|
||||
activations.append((afi, vrf, nei, act))
|
||||
activations.append((local_as, afi, vrf, nei, act))
|
||||
continue
|
||||
m = _NEI_RM_RE.match(line)
|
||||
if m and afi:
|
||||
nei = m.group(1).strip()
|
||||
direction = m.group(3).lower()
|
||||
rk = "route_map_in" if direction == "in" else "route_map_out"
|
||||
slot = af_rm.setdefault((afi, vrf, nei), {})
|
||||
slot = af_rm.setdefault((local_as, afi, vrf, nei), {})
|
||||
slot[rk] = m.group(2).strip()
|
||||
|
||||
out: list[dict[str, Any]] = []
|
||||
seen: set[tuple[str, str, str, str]] = set()
|
||||
seen: set[tuple[str, str, str, str, str]] = set()
|
||||
|
||||
def _append(row: dict[str, Any]) -> None:
|
||||
key = (row["afi"], row["vrf"], row["neighbor"], row["peer_group"])
|
||||
key = (
|
||||
row["local_as"],
|
||||
row["afi"],
|
||||
row["vrf"],
|
||||
row["neighbor"],
|
||||
row["peer_group"],
|
||||
)
|
||||
if key in seen:
|
||||
return
|
||||
seen.add(key)
|
||||
out.append(row)
|
||||
|
||||
for afi_s, vrf_s, token, act in activations:
|
||||
info = _merge_info(token, afi=afi_s, vrf=vrf_s, meta=meta, af_rm=af_rm)
|
||||
row = _row(afi=afi_s, vrf=vrf_s, token=token, act=act, info=info)
|
||||
for las, afi_s, vrf_s, token, act in activations:
|
||||
info = _merge_info(
|
||||
token, local_as=las, afi=afi_s, vrf=vrf_s, meta=meta, af_rm=af_rm
|
||||
)
|
||||
row = _row(
|
||||
local_as=las, afi=afi_s, vrf=vrf_s, token=token, act=act, info=info
|
||||
)
|
||||
_append(row)
|
||||
|
||||
# Global AF: peer-group activate → expand to member Neighbor IPs.
|
||||
|
|
@ -214,27 +273,45 @@ def normalize_config_bgp_peer(
|
|||
pg = str(row.get("peer_group") or "").strip()
|
||||
if row.get("neighbor") or not pg:
|
||||
continue
|
||||
for member, minfo in meta.items():
|
||||
for (m_as, member), minfo in meta.items():
|
||||
if m_as != las:
|
||||
continue
|
||||
if not _is_ip_neighbor(member):
|
||||
continue
|
||||
if str(minfo.get("peer_group") or "").strip() != pg:
|
||||
continue
|
||||
m_info = _merge_info(
|
||||
member, afi=afi_s, vrf=vrf_s, meta=meta, af_rm=af_rm
|
||||
member, local_as=las, afi=afi_s, vrf=vrf_s, meta=meta, af_rm=af_rm
|
||||
)
|
||||
# AF route-maps on the peer-group name apply to expanded members
|
||||
# unless the member already has an AF-specific map.
|
||||
pg_info = _merge_info(
|
||||
pg, local_as=las, afi=afi_s, vrf=vrf_s, meta=meta, af_rm=af_rm
|
||||
)
|
||||
for key in ("route_map_in", "route_map_out"):
|
||||
if pg_info.get(key) and not m_info.get(key):
|
||||
m_info[key] = pg_info[key]
|
||||
m_row = _row(
|
||||
afi=afi_s, vrf=vrf_s, token=member, act=act, info=m_info
|
||||
local_as=las,
|
||||
afi=afi_s,
|
||||
vrf=vrf_s,
|
||||
token=member,
|
||||
act=act,
|
||||
info=m_info,
|
||||
)
|
||||
m_row["peer_group"] = pg[:64]
|
||||
_append(m_row)
|
||||
|
||||
# Global peers with remote-as but no AF activate
|
||||
for token, info in meta.items():
|
||||
# Peers with remote-as but no AF activate (scoped per local AS)
|
||||
activated_tokens = {(las, n) for las, _, _, n, _ in activations}
|
||||
for (las, token), info in meta.items():
|
||||
if not info.get("remote_as"):
|
||||
continue
|
||||
if any(n == token for _, _, n, _ in activations):
|
||||
if (las, token) in activated_tokens:
|
||||
continue
|
||||
row = _row(afi="global", vrf="", token=token, act="", info=info)
|
||||
row = _row(
|
||||
local_as=las, afi="global", vrf="", token=token, act="", info=info
|
||||
)
|
||||
_append(row)
|
||||
return out
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from typing import Any, Mapping
|
|||
from ....lldp_shared import resolve_vendor_key
|
||||
from ....ntc_parse import apply_rules, resolve_cli_platform, row_get
|
||||
from ..common.pipeline import prefer_fsm
|
||||
from .bgp_peer import _detect_local_as
|
||||
|
||||
RULE_KEYS = ("zte_zxros_show_bgp_evpn_mac",)
|
||||
|
||||
|
|
@ -17,7 +18,11 @@ _ROUTE_RE = re.compile(
|
|||
)
|
||||
|
||||
|
||||
def _map_fsm_rows(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
def _map_fsm_rows(
|
||||
rows: list[dict[str, Any]],
|
||||
*,
|
||||
local_as: str,
|
||||
) -> list[dict[str, Any]]:
|
||||
out: list[dict[str, Any]] = []
|
||||
seen: set[str] = set()
|
||||
for r in rows:
|
||||
|
|
@ -27,6 +32,7 @@ def _map_fsm_rows(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|||
seen.add(net)
|
||||
out.append(
|
||||
{
|
||||
"local_as": local_as[:16],
|
||||
"network": net[:256],
|
||||
"next_hop": row_get(r, "NEXT_HOP", "next_hop")[:128],
|
||||
"metric": row_get(r, "METRIC", "metric")[:32],
|
||||
|
|
@ -39,7 +45,7 @@ def _map_fsm_rows(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|||
return out
|
||||
|
||||
|
||||
def _hand_parse(*, raw_text: str, **_kw: Any) -> list[dict[str, Any]]:
|
||||
def _hand_parse(*, raw_text: str, local_as: str = "", **_kw: Any) -> list[dict[str, Any]]:
|
||||
out: list[dict[str, Any]] = []
|
||||
seen: set[str] = set()
|
||||
for raw in str(raw_text or "").splitlines():
|
||||
|
|
@ -79,6 +85,7 @@ def _hand_parse(*, raw_text: str, **_kw: Any) -> list[dict[str, Any]]:
|
|||
path_parts.append(p)
|
||||
out.append(
|
||||
{
|
||||
"local_as": local_as[:16],
|
||||
"network": net[:256],
|
||||
"next_hop": m.group("nh")[:128],
|
||||
"metric": metric[:32],
|
||||
|
|
@ -100,7 +107,7 @@ def normalize_evpn_mac(
|
|||
command: str = "",
|
||||
params: dict[str, str] | None = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
_ = params
|
||||
local_as = _detect_local_as(command, params)
|
||||
tables = dict(fsm_tables or {})
|
||||
if not any(tables.get(k) for k in RULE_KEYS):
|
||||
platform = resolve_cli_platform(
|
||||
|
|
@ -113,7 +120,14 @@ def normalize_evpn_mac(
|
|||
tables = apply_rules(
|
||||
platform=platform, text=raw_text, rule_keys=RULE_KEYS, command=cmd
|
||||
)
|
||||
return prefer_fsm(tables, RULE_KEYS, _map_fsm_rows, _hand_parse, raw_text=raw_text)
|
||||
|
||||
def _map(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
return _map_fsm_rows(rows, local_as=local_as)
|
||||
|
||||
def _hand(*, raw_text: str, **kw: Any) -> list[dict[str, Any]]:
|
||||
return _hand_parse(raw_text=raw_text, local_as=local_as, **kw)
|
||||
|
||||
return prefer_fsm(tables, RULE_KEYS, _map, _hand, raw_text=raw_text)
|
||||
|
||||
|
||||
normalize_evpn_mac.RULE_KEYS = RULE_KEYS
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue