mirror of
https://github.com/hansjone/netx.git
synced 2026-10-11 00:32:39 +08:00
Hide standalone IF VRF from the catalog and point ARP enrich at config_interface so one CLI maps to one check item. Co-authored-by: Cursor <cursoragent@cursor.com>
151 lines
4.5 KiB
Python
151 lines
4.5 KiB
Python
"""ZTE: show arp [| one-line].
|
|
|
|
Pipeline: TextFSM ``zte_zxros_show_arp`` → map → hand fallback.
|
|
Cross-command VRF comes from profile ``enrich_joins`` (config_interface aux), not here.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
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
|
|
|
|
RULE_KEYS = ("zte_zxros_show_arp",)
|
|
|
|
_ARP_AGE_TIME_RE = re.compile(r"^\d{1,2}:\d{2}:\d{2}$")
|
|
_IP_RE = re.compile(r"^\d{1,3}(?:\.\d{1,3}){3}$")
|
|
|
|
|
|
def is_valid_arp_age(age: str) -> bool:
|
|
"""True when Age looks like a dynamic timer (HH:MM:SS), not static flags like H."""
|
|
return bool(_ARP_AGE_TIME_RE.match(str(age or "").strip()))
|
|
|
|
|
|
def _row_from_fields(
|
|
*,
|
|
ip: str,
|
|
age: str,
|
|
mac: str,
|
|
iface: str,
|
|
exter: str = "",
|
|
inter: str = "",
|
|
sub: str = "",
|
|
) -> dict[str, Any] | None:
|
|
ip = str(ip or "").strip()
|
|
iface = str(iface or "").strip()
|
|
if not ip or not iface:
|
|
return None
|
|
age = str(age or "").strip()
|
|
dynamic = is_valid_arp_age(age)
|
|
return {
|
|
"ip": ip[:64],
|
|
"age": age[:32],
|
|
"mac": str(mac or "").strip()[:64],
|
|
"interface": iface[:128],
|
|
"exter_vlan": str(exter or "").strip()[:32],
|
|
"inter_vlan": str(inter or "").strip()[:32],
|
|
"sub_interface": str(sub or "").strip()[:128],
|
|
"entry_type": "dynamic" if dynamic else "static",
|
|
"vrf": "",
|
|
}
|
|
|
|
|
|
def _map_fsm_rows(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
out: list[dict[str, Any]] = []
|
|
seen: set[tuple[str, str]] = set()
|
|
for r in rows:
|
|
ip = row_get(r, "IP", "ip")
|
|
iface = row_get(r, "INTERFACE", "interface")
|
|
key = (ip, iface)
|
|
if not ip or not iface or key in seen:
|
|
continue
|
|
mapped = _row_from_fields(
|
|
ip=ip,
|
|
age=row_get(r, "AGE", "age"),
|
|
mac=row_get(r, "MAC", "mac", "HARDWARE", "hardware"),
|
|
iface=iface,
|
|
exter=row_get(r, "EXTER_VLAN", "exter_vlan"),
|
|
inter=row_get(r, "INTER_VLAN", "inter_vlan"),
|
|
sub=row_get(r, "SUB_INTERFACE", "sub_interface"),
|
|
)
|
|
if not mapped:
|
|
continue
|
|
seen.add(key)
|
|
out.append(mapped)
|
|
return out
|
|
|
|
|
|
def _hand_parse(*, raw_text: str, **_kw: Any) -> list[dict[str, Any]]:
|
|
out: list[dict[str, Any]] = []
|
|
seen: set[tuple[str, str]] = set()
|
|
for raw in str(raw_text or "").splitlines():
|
|
line = raw.strip()
|
|
if not line or line.startswith("---") or line.lower().startswith("arp protect"):
|
|
continue
|
|
if line.lower().startswith("the count"):
|
|
continue
|
|
if "hardware" in line.lower() and "address" in line.lower():
|
|
continue
|
|
parts = line.split()
|
|
if len(parts) < 4 or not _IP_RE.match(parts[0]):
|
|
continue
|
|
ip, age, mac, iface = parts[0], parts[1], parts[2], parts[3]
|
|
key = (ip, iface)
|
|
if key in seen:
|
|
continue
|
|
mapped = _row_from_fields(
|
|
ip=ip,
|
|
age=age,
|
|
mac=mac,
|
|
iface=iface,
|
|
exter=parts[4] if len(parts) > 4 else "",
|
|
inter=parts[5] if len(parts) > 5 else "",
|
|
sub=parts[6] if len(parts) > 6 else "",
|
|
)
|
|
if not mapped:
|
|
continue
|
|
seen.add(key)
|
|
out.append(mapped)
|
|
return out
|
|
|
|
|
|
def normalize_arp(
|
|
*,
|
|
raw_text: str,
|
|
fsm_tables: Mapping[str, list[dict[str, Any]]] | None = None,
|
|
vendor: str = "",
|
|
device_type: str = "",
|
|
command: str = "",
|
|
params: dict[str, str] | None = None,
|
|
raws: Mapping[str, str] | None = None,
|
|
aux_records: Mapping[str, list[dict[str, Any]]] | None = None,
|
|
) -> list[dict[str, Any]]:
|
|
_ = (params, raws, aux_records)
|
|
tables = dict(fsm_tables or {})
|
|
if not any(tables.get(k) for k in RULE_KEYS):
|
|
platform = resolve_cli_platform(
|
|
vendor=vendor,
|
|
device_type=device_type,
|
|
vendor_key=resolve_vendor_key(vendor, device_type),
|
|
)
|
|
cmd = str(command or "show arp").strip() or "show arp"
|
|
if platform:
|
|
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,
|
|
vendor=vendor,
|
|
device_type=device_type,
|
|
command=command,
|
|
)
|
|
|
|
|
|
normalize_arp.RULE_KEYS = RULE_KEYS
|