mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 03:10:46 +08:00
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:
parent
1642a0adec
commit
a769e603b7
10 changed files with 18 additions and 320 deletions
|
|
@ -24,7 +24,6 @@ from ..models import (
|
||||||
BizStateTask,
|
BizStateTask,
|
||||||
BizStateTaskItem,
|
BizStateTaskItem,
|
||||||
BizStateTaskItemBinding,
|
BizStateTaskItemBinding,
|
||||||
BizStateVrfRouteSummary,
|
|
||||||
)
|
)
|
||||||
from ..ne_netmiko import disable_target_paging, send_show_command
|
from ..ne_netmiko import disable_target_paging, send_show_command
|
||||||
from ..ne_session_factory import close_netmiko_connection, open_netmiko_connection
|
from ..ne_session_factory import close_netmiko_connection, open_netmiko_connection
|
||||||
|
|
@ -116,45 +115,6 @@ def _persist_lldp_rows(
|
||||||
return n
|
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 = {
|
_GENERIC_METRICS = {
|
||||||
"isis_adjacency",
|
"isis_adjacency",
|
||||||
"interface_brief",
|
"interface_brief",
|
||||||
|
|
@ -636,10 +596,6 @@ def _run_collect_session(
|
||||||
n = _persist_lldp_rows(
|
n = _persist_lldp_rows(
|
||||||
sdb, batch=batch_row, cmd_row=cmd_row, records=records
|
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:
|
elif hit.profile.metric_id in _GENERIC_METRICS:
|
||||||
n = _persist_metric_rows(
|
n = _persist_metric_rows(
|
||||||
sdb,
|
sdb,
|
||||||
|
|
|
||||||
|
|
@ -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]:
|
def _default_sheet_for_metric(metric_id: str, *, compare_roles: tuple[str, ...] = ("state",)) -> dict[str, Any]:
|
||||||
fields = metric_field_map().get(metric_id) or []
|
fields = metric_field_map().get(metric_id) or []
|
||||||
keys = [f.name for f in fields if f.is_key]
|
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()
|
.one_or_none()
|
||||||
)
|
)
|
||||||
if row:
|
if row:
|
||||||
# Upgrade legacy single-sheet cutover if needed
|
|
||||||
sheets = template_metrics(row)
|
sheets = template_metrics(row)
|
||||||
if len(sheets) < 2:
|
cleaned = [s for s in sheets if str(s.get("metric_id") or "") != "vrf_route_summary"]
|
||||||
_apply_sheets_to_row(row, [_default_lldp_sheet(), _default_vrf_sheet()])
|
if not cleaned:
|
||||||
row.note = "Built-in multi-metric cutover template (LLDP + VRF)"
|
cleaned = [_default_lldp_sheet()]
|
||||||
|
if cleaned != sheets:
|
||||||
|
_apply_sheets_to_row(row, cleaned)
|
||||||
|
row.note = "Built-in cutover template (LLDP)"
|
||||||
row.updated_at = _utcnow()
|
row.updated_at = _utcnow()
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(row)
|
db.refresh(row)
|
||||||
return row
|
return row
|
||||||
sheets = [_default_lldp_sheet(), _default_vrf_sheet()]
|
sheets = [_default_lldp_sheet()]
|
||||||
row = BizCompareTemplate(
|
row = BizCompareTemplate(
|
||||||
id=uuid4().hex,
|
id=uuid4().hex,
|
||||||
name="Cutover default",
|
name="Cutover default",
|
||||||
note="Built-in multi-metric cutover template (LLDP + VRF)",
|
note="Built-in cutover template (LLDP)",
|
||||||
created_at=_utcnow(),
|
created_at=_utcnow(),
|
||||||
updated_at=_utcnow(),
|
updated_at=_utcnow(),
|
||||||
)
|
)
|
||||||
|
|
@ -775,33 +762,6 @@ def ensure_default_lldp_template(db: Session) -> BizCompareTemplate:
|
||||||
return row
|
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:
|
def ensure_default_zte_status_template(db: Session) -> BizCompareTemplate:
|
||||||
name = "ZTE status default"
|
name = "ZTE status default"
|
||||||
row = db.query(BizCompareTemplate).filter(BizCompareTemplate.name == name).one_or_none()
|
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
|
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 …)
|
# Generic tabular metrics (ISIS / interface / ARP / ND6 / BGP …)
|
||||||
from ..models import BizStateMetricRow
|
from ..models import BizStateMetricRow
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,6 @@ from ...ntc_parse import apply_rules, resolve_cli_platform, rules_for_command
|
||||||
from .cisco import PARSERS as _CISCO_PARSERS
|
from .cisco import PARSERS as _CISCO_PARSERS
|
||||||
from .common.lldp_neighbors import normalize_lldp_neighbors
|
from .common.lldp_neighbors import normalize_lldp_neighbors
|
||||||
from .common.vrf_list import normalize_vrf_list
|
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 .ericsson import PARSERS as _ERICSSON_PARSERS
|
||||||
from .h3c import PARSERS as _H3C_PARSERS
|
from .h3c import PARSERS as _H3C_PARSERS
|
||||||
from .huawei import PARSERS as _HUAWEI_PARSERS
|
from .huawei import PARSERS as _HUAWEI_PARSERS
|
||||||
|
|
@ -64,7 +63,6 @@ _VENDOR_PARSERS: list[dict[str, NormalizeFn]] = [
|
||||||
_REGISTRY: dict[str, NormalizeFn] = {
|
_REGISTRY: dict[str, NormalizeFn] = {
|
||||||
"lldp_neighbors": normalize_lldp_neighbors,
|
"lldp_neighbors": normalize_lldp_neighbors,
|
||||||
"vrf_list": normalize_vrf_list,
|
"vrf_list": normalize_vrf_list,
|
||||||
"vrf_route_summary": normalize_vrf_route_summary,
|
|
||||||
}
|
}
|
||||||
for _pack in _VENDOR_PARSERS:
|
for _pack in _VENDOR_PARSERS:
|
||||||
_REGISTRY.update(_pack)
|
_REGISTRY.update(_pack)
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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 __future__ import annotations
|
||||||
|
|
||||||
from .common.vrf_list import normalize_vrf_list
|
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"]
|
||||||
|
|
|
||||||
|
|
@ -133,15 +133,8 @@ def _lldp_profiles() -> list[ParseProfile]:
|
||||||
return out
|
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]:
|
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]] = {
|
discover_cmds: dict[str, tuple[str, str, str]] = {
|
||||||
# vendor_key: (command, match, textfsm_command)
|
# vendor_key: (command, match, textfsm_command)
|
||||||
"cisco": ("show vrf", r"(?i)^\s*show\s+vrf\s*$", "show vrf"),
|
"cisco": ("show vrf", r"(?i)^\s*show\s+vrf\s*$", "show vrf"),
|
||||||
|
|
@ -161,24 +154,6 @@ def _vrf_profiles() -> list[ParseProfile]:
|
||||||
"show ip vrf",
|
"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] = []
|
out: list[ParseProfile] = []
|
||||||
order = 200
|
order = 200
|
||||||
for key, (cmd, match, fsm_cmd) in discover_cmds.items():
|
for key, (cmd, match, fsm_cmd) in discover_cmds.items():
|
||||||
|
|
@ -207,40 +182,6 @@ def _vrf_profiles() -> list[ParseProfile]:
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
order += 5
|
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
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ from ..models import (
|
||||||
BizStateTask,
|
BizStateTask,
|
||||||
BizStateTaskItem,
|
BizStateTaskItem,
|
||||||
BizStateTaskItemBinding,
|
BizStateTaskItemBinding,
|
||||||
BizStateVrfRouteSummary,
|
|
||||||
ManagedNE,
|
ManagedNE,
|
||||||
)
|
)
|
||||||
from ..timeutil import utcnow_naive
|
from ..timeutil import utcnow_naive
|
||||||
|
|
@ -557,13 +556,6 @@ def get_batch(db: Session, batch_id: str) -> dict[str, Any]:
|
||||||
.limit(5000)
|
.limit(5000)
|
||||||
.all()
|
.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 = (
|
metric_rows = (
|
||||||
db.query(BizStateMetricRow)
|
db.query(BizStateMetricRow)
|
||||||
.filter(BizStateMetricRow.batch_id == batch_id)
|
.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
|
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,
|
"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")
|
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()):
|
for mid, rows in sorted((detail.get("metrics") or {}).items()):
|
||||||
if not rows:
|
if not rows:
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
"""Unit tests for Phase3 VRF binding expand / route summary parse."""
|
"""Unit tests for VRF list discover profiles."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from netx_api.biz_state.command_match import expand_from_bindings, match_command, preview_task_item
|
from netx_api.biz_state.parsers.vrf import normalize_vrf_list
|
||||||
from netx_api.biz_state.parsers.vrf import normalize_vrf_list, normalize_vrf_route_summary
|
|
||||||
from netx_api.biz_state.profiles import get_profile, profiles_for_vendor, reload_profiles
|
from netx_api.biz_state.profiles import get_profile, profiles_for_vendor, reload_profiles
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -13,60 +12,12 @@ class BizStateVrfTests(unittest.TestCase):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
reload_profiles()
|
reload_profiles()
|
||||||
|
|
||||||
def test_vrf_profiles_registered(self) -> None:
|
def test_vrf_list_profiles_registered(self) -> None:
|
||||||
cisco = profiles_for_vendor("cisco")
|
cisco = profiles_for_vendor("cisco")
|
||||||
self.assertTrue(any(p.profile_id == "cisco.vrf_list" and p.kind == "discover" for p in cisco))
|
self.assertTrue(any(p.profile_id == "cisco.vrf_list" and p.kind == "discover" for p in cisco))
|
||||||
self.assertTrue(any(p.profile_id == "cisco.route_vrf_summary" for p in cisco))
|
self.assertFalse(any(p.metric_id == "vrf_route_summary" for p in cisco))
|
||||||
|
self.assertIsNone(get_profile("cisco.route_vrf_summary"))
|
||||||
def test_expand_multi_vrf_bindings(self) -> None:
|
self.assertIsNone(get_profile("zte.route_vrf_summary"))
|
||||||
p = get_profile("cisco.route_vrf_summary")
|
|
||||||
assert p is not None
|
|
||||||
pairs = expand_from_bindings(
|
|
||||||
profile=p,
|
|
||||||
bindings=[
|
|
||||||
{"placeholder": "vrf", "value": "CUST_A"},
|
|
||||||
{"placeholder": "vrf", "value": "CUST_B"},
|
|
||||||
],
|
|
||||||
)
|
|
||||||
self.assertEqual(len(pairs), 2)
|
|
||||||
self.assertEqual(pairs[0][0], "show ip route vrf CUST_A summary")
|
|
||||||
self.assertEqual(pairs[1][0], "show ip route vrf CUST_B summary")
|
|
||||||
hit = match_command(vendor_key="cisco", command=pairs[0][0])
|
|
||||||
self.assertIsNotNone(hit)
|
|
||||||
assert hit is not None
|
|
||||||
self.assertEqual(hit.params.get("vrf"), "CUST_A")
|
|
||||||
|
|
||||||
def test_expand_requires_bindings(self) -> None:
|
|
||||||
p = get_profile("zte.route_vrf_summary")
|
|
||||||
assert p is not None
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
expand_from_bindings(profile=p, bindings=[])
|
|
||||||
|
|
||||||
def test_preview_with_bindings(self) -> None:
|
|
||||||
prev = preview_task_item(
|
|
||||||
vendor_key="huawei",
|
|
||||||
profile_id="huawei.route_vrf_summary",
|
|
||||||
bindings=[{"vrf": "VPN1"}],
|
|
||||||
)
|
|
||||||
self.assertTrue(prev["ok"])
|
|
||||||
self.assertEqual(len(prev["commands"]), 1)
|
|
||||||
self.assertIn("VPN1", prev["commands"][0]["command"])
|
|
||||||
|
|
||||||
def test_route_summary_parser(self) -> None:
|
|
||||||
raw = """
|
|
||||||
IP routing table name is CUST_A
|
|
||||||
Route Source Networks
|
|
||||||
connected 3
|
|
||||||
static 1
|
|
||||||
bgp 65001 42
|
|
||||||
Total 46
|
|
||||||
"""
|
|
||||||
rows = normalize_vrf_route_summary(raw_text=raw, params={"vrf": "CUST_A"})
|
|
||||||
by_src = {r["source"]: r["networks"] for r in rows}
|
|
||||||
self.assertEqual(by_src["connected"], 3)
|
|
||||||
self.assertEqual(by_src["bgp"], 42)
|
|
||||||
self.assertEqual(by_src["total"], 46)
|
|
||||||
self.assertTrue(all(r["vrf"] == "CUST_A" for r in rows))
|
|
||||||
|
|
||||||
def test_vrf_list_fallback(self) -> None:
|
def test_vrf_list_fallback(self) -> None:
|
||||||
raw = "CUST_A 100:1 ipv4\nCUST_B 100:2 ipv4\n"
|
raw = "CUST_A 100:1 ipv4\nCUST_B 100:2 ipv4\n"
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,7 @@ src/
|
||||||
- Phase1:LLDP 邻居快照;建任务默认启用对应厂商 LLDP profile;支持自定义只采不解析行
|
- Phase1:LLDP 邻居快照;建任务默认启用对应厂商 LLDP profile;支持自定义只采不解析行
|
||||||
- Phase2:比对模板(多监控表 / Key·接口·比对字段;空比对=仅条目)、端口映射、CompareJob 列表与详情、先选批次再按 Excel sheet 看 pre/post、`/network/tasks/biz-compare`
|
- Phase2:比对模板(多监控表 / Key·接口·比对字段;空比对=仅条目)、端口映射、CompareJob 列表与详情、先选批次再按 Excel sheet 看 pre/post、`/network/tasks/biz-compare`
|
||||||
- API 补充:`GET /v1/biz-state/compare/metrics`;模板 `metrics[]` 多表结构
|
- API 补充:`GET /v1/biz-state/compare/metrics`;模板 `metrics[]` 多表结构
|
||||||
- Phase3:VRF 发现→人选关联→带参采集(`*.route_vrf_summary`);`PUT …/items/{id}/bindings`
|
- Phase3:VRF 发现(`*.vrf_list`)供带参采集绑定;`PUT …/items/{id}/bindings`
|
||||||
- 调度:`NETX_BIZ_STATE_SCHEDULER_ENABLED`(默认开),tick `NETX_BIZ_STATE_SCHEDULER_TICK_SEC`
|
- 调度:`NETX_BIZ_STATE_SCHEDULER_ENABLED`(默认开),tick `NETX_BIZ_STATE_SCHEDULER_TICK_SEC`
|
||||||
- 前端:`/network/tasks/biz-state`(勾选监控项 / 发现 VRF / 批次 / 导出 zip)
|
- 前端:`/network/tasks/biz-state`(勾选监控项 / 发现 VRF / 批次 / 导出 zip)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,6 @@ function columnsFromRows(rows: Record<string, unknown>[]): SheetCol[] {
|
||||||
function metricSheetTitle(metricId: string, t: (k: string) => string): string {
|
function metricSheetTitle(metricId: string, t: (k: string) => string): string {
|
||||||
const map: Record<string, string> = {
|
const map: Record<string, string> = {
|
||||||
lldp_neighbor: t("bizState.sheetLldp"),
|
lldp_neighbor: t("bizState.sheetLldp"),
|
||||||
vrf_route_summary: t("bizState.sheetVrfRoute"),
|
|
||||||
isis_adjacency: t("bizState.sheetIsis"),
|
isis_adjacency: t("bizState.sheetIsis"),
|
||||||
interface_brief: t("bizState.sheetIfaceBrief"),
|
interface_brief: t("bizState.sheetIfaceBrief"),
|
||||||
arp: t("bizState.sheetArp"),
|
arp: t("bizState.sheetArp"),
|
||||||
|
|
@ -174,9 +173,6 @@ function rowsForMetric(batch: any, metricId: string): Record<string, unknown>[]
|
||||||
if (mid === "lldp_neighbor") {
|
if (mid === "lldp_neighbor") {
|
||||||
return ((batch?.lldp_neighbors || []) as Record<string, unknown>[]) || [];
|
return ((batch?.lldp_neighbors || []) as Record<string, unknown>[]) || [];
|
||||||
}
|
}
|
||||||
if (mid === "vrf_route_summary") {
|
|
||||||
return ((batch?.vrf_route_summary || []) as Record<string, unknown>[]) || [];
|
|
||||||
}
|
|
||||||
return Array.isArray(fromGeneric) ? (fromGeneric as Record<string, unknown>[]) : [];
|
return Array.isArray(fromGeneric) ? (fromGeneric as Record<string, unknown>[]) : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,7 +211,6 @@ function buildBatchSheets(batch: any, t: (k: string) => string): SheetDef[] {
|
||||||
for (const c of cmds) pushMid(String(c.metric_id || ""));
|
for (const c of cmds) pushMid(String(c.metric_id || ""));
|
||||||
for (const mid of Object.keys(batch?.metrics || {})) pushMid(mid);
|
for (const mid of Object.keys(batch?.metrics || {})) pushMid(mid);
|
||||||
if ((batch?.lldp_neighbors || []).length) pushMid("lldp_neighbor");
|
if ((batch?.lldp_neighbors || []).length) pushMid("lldp_neighbor");
|
||||||
if ((batch?.vrf_route_summary || []).length) pushMid("vrf_route_summary");
|
|
||||||
|
|
||||||
for (const mid of metricOrder) {
|
for (const mid of metricOrder) {
|
||||||
const rows = rowsForMetric(batch, mid);
|
const rows = rowsForMetric(batch, mid);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue