mirror of
https://github.com/hansjone/netx.git
synced 2026-10-18 21:03:57 +08:00
Decouple cutover HF from portrait and tighten ops UI.
Portrait stays on biz-monitor; cutover create/ensure owns purpose=cutover_hf slots with project collect override, multi-interval bindings, window pause, and industrial detail UX. Also expand ZTE collect/config parsers and TextFSM for route/BGP/L2VPN/VRRP coverage. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
a65c9fc420
commit
285c19e5a3
67 changed files with 9510 additions and 723 deletions
210
netx_api/biz_state/parsers/zte/config_isis.py
Normal file
210
netx_api/biz_state/parsers/zte/config_isis.py
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
"""ZTE config intent: IS-IS from ``!<isis>`` / ``show running-config isis``.
|
||||
|
||||
One row per ``(process, vrf, interface)``. Process-level attrs (area, system-id,
|
||||
router-id, is-type) are copied onto each interface row.
|
||||
Never captures hello-authentication / encrypted secrets.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Any, Mapping
|
||||
|
||||
from .config_common import extract_mim_section, is_secret_line
|
||||
|
||||
RULE_KEYS: tuple[str, ...] = ()
|
||||
|
||||
_PROC_RE = re.compile(
|
||||
r"(?i)^\s*router\s+isis\s+(\S+)(?:\s+vrf\s+(\S+))?\s*$"
|
||||
)
|
||||
_AREA_RE = re.compile(r"(?i)^\s*area\s+(\S+)\s*$")
|
||||
_SYS_RE = re.compile(r"(?i)^\s*system-id\s+(\S+)\s*$")
|
||||
_RID_RE = re.compile(r"(?i)^\s*router-id\s+(\S+)\s*$")
|
||||
_ISTYPE_RE = re.compile(r"(?i)^\s*is-type\s+(\S+)\s*$")
|
||||
_IFACE_RE = re.compile(r"(?i)^\s*interface\s+(\S+)\s*$")
|
||||
_CIRCUIT_RE = re.compile(r"(?i)^\s*circuit-type\s+(\S+)\s*$")
|
||||
_NET_RE = re.compile(r"(?i)^\s*network\s+(\S+)\s*$")
|
||||
_METRIC_RE = re.compile(r"(?i)^\s*metric\s+(\d+)\s*$")
|
||||
_V6_METRIC_RE = re.compile(r"(?i)^\s*ipv6\s+metric\s+(\d+)\s*$")
|
||||
_DOLLAR_RE = re.compile(r"^(\s*)\$\s*$")
|
||||
|
||||
|
||||
def _indent(spaces: str) -> int:
|
||||
return len(spaces.expandtabs(2))
|
||||
|
||||
|
||||
def _flush(
|
||||
proc: dict[str, Any] | None,
|
||||
iface: dict[str, Any] | None,
|
||||
out: list[dict[str, Any]],
|
||||
seen: set[tuple[str, ...]],
|
||||
) -> None:
|
||||
if not proc or not iface:
|
||||
return
|
||||
key = (proc["process_id"], proc["vrf"], iface["interface"])
|
||||
if key in seen:
|
||||
return
|
||||
seen.add(key)
|
||||
out.append(
|
||||
{
|
||||
"process_id": str(proc["process_id"])[:32],
|
||||
"vrf": str(proc.get("vrf") or "")[:128],
|
||||
"area": str(proc.get("area") or "")[:64],
|
||||
"system_id": str(proc.get("system_id") or "")[:64],
|
||||
"router_id": str(proc.get("router_id") or "")[:64],
|
||||
"is_type": str(proc.get("is_type") or "")[:32],
|
||||
"interface": str(iface.get("interface") or "")[:128],
|
||||
"circuit_type": str(iface.get("circuit_type") or "")[:32],
|
||||
"network_type": str(iface.get("network_type") or "")[:32],
|
||||
"ip_enable": str(iface.get("ip_enable") or "")[:8],
|
||||
"ipv6_enable": str(iface.get("ipv6_enable") or "")[:8],
|
||||
"metric": str(iface.get("metric") or "")[:16],
|
||||
"ipv6_metric": str(iface.get("ipv6_metric") or "")[:16],
|
||||
"passive": str(iface.get("passive") or "")[:8],
|
||||
"bfd": str(iface.get("bfd") or "")[:16],
|
||||
"ipv6_bfd": str(iface.get("ipv6_bfd") or "")[:16],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def normalize_config_isis(
|
||||
*,
|
||||
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,
|
||||
) -> list[dict[str, Any]]:
|
||||
_ = (fsm_tables, vendor, device_type, command, params)
|
||||
body = extract_mim_section(raw_text, "isis")
|
||||
out: list[dict[str, Any]] = []
|
||||
seen: set[tuple[str, ...]] = set()
|
||||
proc: dict[str, Any] | None = None
|
||||
iface: dict[str, Any] | None = None
|
||||
in_af = False
|
||||
af_indent = 0
|
||||
|
||||
for raw in body.splitlines():
|
||||
line = raw.rstrip()
|
||||
if is_secret_line(line):
|
||||
continue
|
||||
m = _PROC_RE.match(line)
|
||||
if m:
|
||||
_flush(proc, iface, out, seen)
|
||||
iface = None
|
||||
in_af = False
|
||||
af_indent = 0
|
||||
proc = {
|
||||
"process_id": m.group(1).strip(),
|
||||
"vrf": (m.group(2) or "").strip(),
|
||||
"area": "",
|
||||
"system_id": "",
|
||||
"router_id": "",
|
||||
"is_type": "",
|
||||
}
|
||||
continue
|
||||
if not proc:
|
||||
continue
|
||||
|
||||
# Skip address-family subtree (nested $ must not close the process).
|
||||
if in_af:
|
||||
m = _DOLLAR_RE.match(line)
|
||||
if m and _indent(m.group(1)) <= af_indent:
|
||||
in_af = False
|
||||
af_indent = 0
|
||||
continue
|
||||
|
||||
if re.match(r"(?i)^\s*address-family\s+", line):
|
||||
in_af = True
|
||||
af_indent = len(line) - len(line.lstrip(" \t"))
|
||||
continue
|
||||
|
||||
m = _DOLLAR_RE.match(line)
|
||||
if m:
|
||||
ind = _indent(m.group(1))
|
||||
if iface is not None and ind >= 2:
|
||||
_flush(proc, iface, out, seen)
|
||||
iface = None
|
||||
continue
|
||||
_flush(proc, iface, out, seen)
|
||||
iface = None
|
||||
proc = None
|
||||
continue
|
||||
|
||||
if iface is not None:
|
||||
m = _CIRCUIT_RE.match(line)
|
||||
if m:
|
||||
iface["circuit_type"] = m.group(1).lower()
|
||||
continue
|
||||
m = _NET_RE.match(line)
|
||||
if m:
|
||||
iface["network_type"] = m.group(1).lower()
|
||||
continue
|
||||
m = _V6_METRIC_RE.match(line)
|
||||
if m:
|
||||
iface["ipv6_metric"] = m.group(1)
|
||||
continue
|
||||
m = _METRIC_RE.match(line)
|
||||
if m:
|
||||
iface["metric"] = m.group(1)
|
||||
continue
|
||||
low = line.strip().lower()
|
||||
if low == "ip router isis" or low.startswith("ip router isis "):
|
||||
iface["ip_enable"] = "yes"
|
||||
continue
|
||||
if low == "ipv6 router isis" or low.startswith("ipv6 router isis "):
|
||||
iface["ipv6_enable"] = "yes"
|
||||
continue
|
||||
if low == "passive-mode" or low == "passive":
|
||||
iface["passive"] = "yes"
|
||||
continue
|
||||
if low == "bfd-enable" or low.startswith("bfd-enable"):
|
||||
iface["bfd"] = "enable"
|
||||
continue
|
||||
if low == "ipv6 bfd-enable" or low.startswith("ipv6 bfd-enable"):
|
||||
iface["ipv6_bfd"] = "enable"
|
||||
continue
|
||||
continue
|
||||
|
||||
m = _IFACE_RE.match(line)
|
||||
if m:
|
||||
_flush(proc, iface, out, seen)
|
||||
iface = {
|
||||
"interface": m.group(1).strip(),
|
||||
"circuit_type": "",
|
||||
"network_type": "",
|
||||
"ip_enable": "",
|
||||
"ipv6_enable": "",
|
||||
"metric": "",
|
||||
"ipv6_metric": "",
|
||||
"passive": "",
|
||||
"bfd": "",
|
||||
"ipv6_bfd": "",
|
||||
}
|
||||
continue
|
||||
m = _AREA_RE.match(line)
|
||||
if m:
|
||||
proc["area"] = m.group(1).strip()
|
||||
continue
|
||||
m = _SYS_RE.match(line)
|
||||
if m:
|
||||
proc["system_id"] = m.group(1).strip()
|
||||
continue
|
||||
m = _RID_RE.match(line)
|
||||
if m:
|
||||
# prefer IPv4-looking router-id at process level
|
||||
rid = m.group(1).strip()
|
||||
if ":" not in rid or not proc.get("router_id"):
|
||||
proc["router_id"] = rid
|
||||
continue
|
||||
m = _ISTYPE_RE.match(line)
|
||||
if m:
|
||||
proc["is_type"] = m.group(1).lower()
|
||||
continue
|
||||
|
||||
_flush(proc, iface, out, seen)
|
||||
return out
|
||||
|
||||
|
||||
normalize_config_isis.RULE_KEYS = RULE_KEYS
|
||||
Loading…
Add table
Add a link
Reference in a new issue