Remove fake VRF Route Summary collect item.

Drop the route-summary profiles/parser/persist path; keep VRF list discover for bindings and strip it from default cutover templates.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-20 09:58:26 +08:00
parent 1642a0adec
commit a769e603b7
10 changed files with 18 additions and 320 deletions

View file

@ -24,7 +24,6 @@ from ..models import (
BizStateTask,
BizStateTaskItem,
BizStateTaskItemBinding,
BizStateVrfRouteSummary,
)
from ..ne_netmiko import disable_target_paging, send_show_command
from ..ne_session_factory import close_netmiko_connection, open_netmiko_connection
@ -116,45 +115,6 @@ def _persist_lldp_rows(
return n
def _persist_vrf_route_summary(
db,
*,
batch: BizStateBatch,
cmd_row: BizStateBatchCommand,
records: list[dict[str, Any]],
) -> int:
n = 0
seen: set[tuple[str, str]] = set()
for rec in records:
vrf = str(rec.get("vrf") or "").strip()[:128]
source = str(rec.get("source") or "").strip()[:64]
if not vrf and not source:
continue
key = (vrf, source)
if key in seen:
continue
seen.add(key)
try:
networks = int(rec.get("networks") or 0)
except (TypeError, ValueError):
networks = 0
db.add(
BizStateVrfRouteSummary(
id=uuid4().hex,
batch_id=batch.id,
batch_command_id=cmd_row.id,
task_id=batch.task_id,
ne_id=batch.ne_id,
vrf=vrf,
source=source,
networks=networks,
collected_at=_utcnow(),
)
)
n += 1
return n
_GENERIC_METRICS = {
"isis_adjacency",
"interface_brief",
@ -636,10 +596,6 @@ def _run_collect_session(
n = _persist_lldp_rows(
sdb, batch=batch_row, cmd_row=cmd_row, records=records
)
elif hit.profile.metric_id == "vrf_route_summary":
n = _persist_vrf_route_summary(
sdb, batch=batch_row, cmd_row=cmd_row, records=records
)
elif hit.profile.metric_id in _GENERIC_METRICS:
n = _persist_metric_rows(
sdb,

View file

@ -371,21 +371,6 @@ def _default_lldp_sheet() -> dict[str, Any]:
)
def _default_vrf_sheet() -> dict[str, Any]:
fields = metric_field_map().get("vrf_route_summary") or []
keys = [f.name for f in fields if f.is_key] or ["vrf", "source"]
ifaces = [f.name for f in fields if f.is_interface]
compare = [f.name for f in fields if not f.is_key and f.role in ("state", "meta", "identity")]
if not compare:
compare = [n for n in ("networks",) if n not in keys]
return _sheet_def(
metric_id="vrf_route_summary",
key_fields=keys,
iface_fields=ifaces,
compare_fields=compare,
)
def _default_sheet_for_metric(metric_id: str, *, compare_roles: tuple[str, ...] = ("state",)) -> dict[str, Any]:
fields = metric_field_map().get(metric_id) or []
keys = [f.name for f in fields if f.is_key]
@ -724,20 +709,22 @@ def ensure_default_cutover_template(db: Session) -> BizCompareTemplate:
.one_or_none()
)
if row:
# Upgrade legacy single-sheet cutover if needed
sheets = template_metrics(row)
if len(sheets) < 2:
_apply_sheets_to_row(row, [_default_lldp_sheet(), _default_vrf_sheet()])
row.note = "Built-in multi-metric cutover template (LLDP + VRF)"
cleaned = [s for s in sheets if str(s.get("metric_id") or "") != "vrf_route_summary"]
if not cleaned:
cleaned = [_default_lldp_sheet()]
if cleaned != sheets:
_apply_sheets_to_row(row, cleaned)
row.note = "Built-in cutover template (LLDP)"
row.updated_at = _utcnow()
db.commit()
db.refresh(row)
return row
sheets = [_default_lldp_sheet(), _default_vrf_sheet()]
sheets = [_default_lldp_sheet()]
row = BizCompareTemplate(
id=uuid4().hex,
name="Cutover default",
note="Built-in multi-metric cutover template (LLDP + VRF)",
note="Built-in cutover template (LLDP)",
created_at=_utcnow(),
updated_at=_utcnow(),
)
@ -775,33 +762,6 @@ def ensure_default_lldp_template(db: Session) -> BizCompareTemplate:
return row
def ensure_default_vrf_template(db: Session) -> BizCompareTemplate:
row = (
db.query(BizCompareTemplate)
.filter(BizCompareTemplate.name == "VRF route summary default")
.one_or_none()
)
if row:
if not template_metrics(row):
_apply_sheets_to_row(row, [_default_vrf_sheet()])
row.updated_at = _utcnow()
db.commit()
db.refresh(row)
return row
row = BizCompareTemplate(
id=uuid4().hex,
name="VRF route summary default",
note="Built-in template for per-VRF route summary cutover compare",
created_at=_utcnow(),
updated_at=_utcnow(),
)
_apply_sheets_to_row(row, [_default_vrf_sheet()])
db.add(row)
db.commit()
db.refresh(row)
return row
def ensure_default_zte_status_template(db: Session) -> BizCompareTemplate:
name = "ZTE status default"
row = db.query(BizCompareTemplate).filter(BizCompareTemplate.name == name).one_or_none()
@ -1202,30 +1162,6 @@ def _load_metric_rows(db: Session, *, batch_id: str, metric_id: str) -> list[dic
}
for n in rows
]
if metric_id == "vrf_route_summary":
from ..models import BizStateVrfRouteSummary
rows = (
db.query(BizStateVrfRouteSummary)
.filter(BizStateVrfRouteSummary.batch_id == batch_id)
.all()
)
return [
{
"vrf": r.vrf,
"source": r.source,
"networks": r.networks,
"_netx": {
"batch_id": batch_id,
"batch_command_id": r.batch_command_id or "",
"task_id": r.task_id or "",
"ne_id": r.ne_id or "",
"collected_at": r.collected_at.isoformat() + "Z" if r.collected_at else None,
"row_id": r.id,
},
}
for r in rows
]
# Generic tabular metrics (ISIS / interface / ARP / ND6 / BGP …)
from ..models import BizStateMetricRow

View file

@ -41,7 +41,6 @@ from ...ntc_parse import apply_rules, resolve_cli_platform, rules_for_command
from .cisco import PARSERS as _CISCO_PARSERS
from .common.lldp_neighbors import normalize_lldp_neighbors
from .common.vrf_list import normalize_vrf_list
from .common.vrf_route_summary import normalize_vrf_route_summary
from .ericsson import PARSERS as _ERICSSON_PARSERS
from .h3c import PARSERS as _H3C_PARSERS
from .huawei import PARSERS as _HUAWEI_PARSERS
@ -64,7 +63,6 @@ _VENDOR_PARSERS: list[dict[str, NormalizeFn]] = [
_REGISTRY: dict[str, NormalizeFn] = {
"lldp_neighbors": normalize_lldp_neighbors,
"vrf_list": normalize_vrf_list,
"vrf_route_summary": normalize_vrf_route_summary,
}
for _pack in _VENDOR_PARSERS:
_REGISTRY.update(_pack)

View file

@ -1,60 +0,0 @@
"""Per-VRF route summary counts (multi-vendor line scrape)."""
from __future__ import annotations
import re
from typing import Any
_SOURCE_PATTERNS: list[tuple[str, re.Pattern[str]]] = [
("connected", re.compile(r"(?i)^\s*connected\s+(\d+)\s*$")),
("static", re.compile(r"(?i)^\s*static\s+(\d+)\s*$")),
("local", re.compile(r"(?i)^\s*local\s+(\d+)\s*$")),
("ospf", re.compile(r"(?i)^\s*ospf(?:\s+\S+)?\s+(\d+)\s*$")),
("isis", re.compile(r"(?i)^\s*isis(?:\s+\S+)?\s+(\d+)\s*$")),
("bgp", re.compile(r"(?i)^\s*bgp(?:\s+\S+)?\s+(\d+)\s*$")),
("rip", re.compile(r"(?i)^\s*rip(?:\s+\S+)?\s+(\d+)\s*$")),
("total", re.compile(r"(?i)^\s*(?:total|totals?)\s+(?:routes?\s+)?(\d+)\s*$")),
]
RULE_KEYS: tuple[str, ...] = ()
def normalize_vrf_route_summary(
*,
raw_text: str,
fsm_tables=None,
vendor: str = "",
device_type: str = "",
command: str = "",
params: dict[str, str] | None = None,
) -> list[dict[str, Any]]:
_ = (vendor, device_type, command, fsm_tables)
vrf = str((params or {}).get("vrf") or (params or {}).get("vrf_name") or "").strip()
text = str(raw_text or "")
found: dict[str, int] = {}
for src, pat in _SOURCE_PATTERNS:
for line in text.splitlines():
m = pat.match(line.strip())
if m:
found[src] = int(m.group(1))
break
if not found:
n = 0
for line in text.splitlines():
s = line.strip()
if not s or s.startswith("-") or s.lower().startswith(("code", "codes", "gateway", "routing table")):
continue
if re.match(r"^[A-Z*+]>?\s+\S+", s) or re.match(r"^\S+\s+\d+\.\d+\.\d+\.\d+", s):
n += 1
if n:
found["routes"] = n
rows: list[dict[str, Any]] = []
for src, count in found.items():
rows.append({"vrf": vrf[:128], "source": src[:64], "networks": count})
if vrf and not rows:
rows.append({"vrf": vrf[:128], "source": "empty", "networks": 0})
return rows
normalize_vrf_route_summary.RULE_KEYS = RULE_KEYS

View file

@ -1,8 +1,7 @@
"""Compat shim — prefer ``parsers.common.vrf_list`` / ``vrf_route_summary``."""
"""Compat shim — prefer ``parsers.common.vrf_list``."""
from __future__ import annotations
from .common.vrf_list import normalize_vrf_list
from .common.vrf_route_summary import normalize_vrf_route_summary
__all__ = ["normalize_vrf_list", "normalize_vrf_route_summary"]
__all__ = ["normalize_vrf_list"]

View file

@ -133,15 +133,8 @@ def _lldp_profiles() -> list[ParseProfile]:
return out
_VRF_ROUTE_FIELDS: list[FieldDef] = [
FieldDef("vrf", length=128, indexed=True, is_key=True, display_name="VRF", from_command_param=True),
FieldDef("source", length=64, indexed=True, is_key=True, display_name="路由来源"),
FieldDef("networks", dtype="int", role="state", display_name="路由条数"),
]
def _vrf_profiles() -> list[ParseProfile]:
"""Discover VRF list + parameterized route-summary collect (Phase3)."""
"""Discover VRF list for parameterized collect bindings (e.g. BGP VRF)."""
discover_cmds: dict[str, tuple[str, str, str]] = {
# vendor_key: (command, match, textfsm_command)
"cisco": ("show vrf", r"(?i)^\s*show\s+vrf\s*$", "show vrf"),
@ -161,24 +154,6 @@ def _vrf_profiles() -> list[ParseProfile]:
"show ip vrf",
),
}
collect_cmds: dict[str, tuple[str, str]] = {
"cisco": (
"show ip route vrf <vrf> summary",
r"(?i)^\s*show\s+ip\s+route\s+vrf\s+(?P<vrf>\S+)\s+summary\s*$",
),
"huawei": (
"display ip routing-table vpn-instance <vrf> statistics",
r"(?i)^\s*display\s+ip\s+routing-table\s+vpn-instance\s+(?P<vrf>\S+)\s+statistics\s*$",
),
"h3c": (
"display ip routing-table vpn-instance <vrf> statistics",
r"(?i)^\s*display\s+ip\s+routing-table\s+vpn-instance\s+(?P<vrf>\S+)\s+statistics\s*$",
),
"zte": (
"show ip route vrf <vrf> summary",
r"(?i)^\s*show\s+ip\s+route\s+vrf\s+(?P<vrf>\S+)\s+summary\s*$",
),
}
out: list[ParseProfile] = []
order = 200
for key, (cmd, match, fsm_cmd) in discover_cmds.items():
@ -207,40 +182,6 @@ def _vrf_profiles() -> list[ParseProfile]:
)
)
order += 5
order = 220
for key, (tmpl, match) in collect_cmds.items():
disc_id = f"{key}.vrf_list"
out.append(
ParseProfile(
profile_id=f"{key}.route_vrf_summary",
vendor_key=key,
metric_id="vrf_route_summary",
parser_id="vrf_route_summary",
title="VRF Route Summary",
command_template=tmpl,
match=match,
textfsm_command="",
description="Per-VRF route source counts (discover VRF → select bindings → collect).",
placeholders=[
PlaceholderDef(
name="vrf",
schema_field="vrf",
required=True,
bind_mode="discover_select",
discover_profile_id=disc_id,
discover_value_field="vrf_name",
discover_label_field="vrf_name",
)
],
fields=list(_VRF_ROUTE_FIELDS),
tags=["vrf", "route", "l3"],
sort_order=order,
enabled=True,
kind="collect",
)
)
order += 5
return out

View file

@ -22,7 +22,6 @@ from ..models import (
BizStateTask,
BizStateTaskItem,
BizStateTaskItemBinding,
BizStateVrfRouteSummary,
ManagedNE,
)
from ..timeutil import utcnow_naive
@ -557,13 +556,6 @@ def get_batch(db: Session, batch_id: str) -> dict[str, Any]:
.limit(5000)
.all()
)
vrf_rows = (
db.query(BizStateVrfRouteSummary)
.filter(BizStateVrfRouteSummary.batch_id == batch_id)
.order_by(BizStateVrfRouteSummary.vrf.asc(), BizStateVrfRouteSummary.source.asc())
.limit(5000)
.all()
)
metric_rows = (
db.query(BizStateMetricRow)
.filter(BizStateMetricRow.batch_id == batch_id)
@ -620,9 +612,6 @@ def get_batch(db: Session, batch_id: str) -> dict[str, Any]:
}
for n in neighbors
],
"vrf_route_summary": [
{"vrf": r.vrf, "source": r.source, "networks": r.networks} for r in vrf_rows
],
"metrics": metrics_by_id,
}
@ -707,13 +696,6 @@ def export_batch_zip(db: Session, batch_id: str) -> bytes:
)
zf.writestr("tables/lldp_neighbor.csv", "\n".join(csv_lines) + "\n")
vrf_csv = ["vrf,source,networks"]
for r in detail.get("vrf_route_summary") or []:
vrf_csv.append(
",".join([_csv(r["vrf"]), _csv(r["source"]), _csv(str(r["networks"]))])
)
zf.writestr("tables/vrf_route_summary.csv", "\n".join(vrf_csv) + "\n")
for mid, rows in sorted((detail.get("metrics") or {}).items()):
if not rows:
continue