mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 05:30:46 +08:00
Wire BGP neighbor in/out discover to config BGP peer intent.
Filter peers by AFI (and VRF pairs for CE), support zip/pair bindings in discover UI, and tag FIB rows with address_families. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
3c3013dd73
commit
dd8d46aebc
10 changed files with 490 additions and 110 deletions
|
|
@ -62,7 +62,12 @@ def normalize_binding_dicts(
|
|||
*,
|
||||
placeholders: list | None = None,
|
||||
) -> list[dict[str, str]]:
|
||||
"""Accept ``{vrf: X}`` or ``{placeholder, value}`` rows; expand multi-value placeholders."""
|
||||
"""Accept ``{vrf: X}`` or ``{placeholder, value}`` rows; expand multi-value placeholders.
|
||||
|
||||
Multi-placeholder rows stored as interleaved ``{placeholder,value}`` pairs are
|
||||
zipped in order (e.g. vrf/neighbor/vrf/neighbor → combined dicts). Already
|
||||
combined dicts (multiple keys) are kept as-is.
|
||||
"""
|
||||
raw = list(bindings or [])
|
||||
converted: list[dict[str, str]] = []
|
||||
for b in raw:
|
||||
|
|
@ -85,6 +90,33 @@ def normalize_binding_dicts(
|
|||
if val:
|
||||
out.append({name: val})
|
||||
return out
|
||||
|
||||
if len(phs) >= 2:
|
||||
ph_names = [str(getattr(ph, "name", "") or "").strip() for ph in phs]
|
||||
ph_names = [n for n in ph_names if n]
|
||||
combined: list[dict[str, str]] = []
|
||||
singles: list[dict[str, str]] = []
|
||||
for c in converted:
|
||||
if len(c) > 1:
|
||||
combined.append(dict(c))
|
||||
else:
|
||||
singles.append(c)
|
||||
if combined and not singles:
|
||||
return combined
|
||||
if singles and ph_names:
|
||||
from collections import defaultdict
|
||||
|
||||
by_ph: dict[str, list[str]] = defaultdict(list)
|
||||
for c in singles:
|
||||
k, v = next(iter(c.items()))
|
||||
by_ph[k].append(v)
|
||||
lists = [by_ph.get(n, []) for n in ph_names]
|
||||
n0 = len(lists[0]) if lists else 0
|
||||
if n0 and all(len(L) == n0 for L in lists):
|
||||
zipped = [dict(zip(ph_names, vals)) for vals in zip(*lists)]
|
||||
return combined + zipped if combined else zipped
|
||||
if combined:
|
||||
return combined
|
||||
return converted
|
||||
|
||||
|
||||
|
|
@ -100,23 +132,32 @@ def _optional_discover_placeholders(profile: ParseProfile) -> list[PlaceholderDe
|
|||
return [ph for ph in _discover_placeholders(profile) if not ph.required]
|
||||
|
||||
|
||||
def _record_passes_discover_filter(rec: dict[str, Any], ph: PlaceholderDef) -> bool:
|
||||
filt_field = str(ph.discover_filter_field or "").strip()
|
||||
filt_contains = str(ph.discover_filter_contains or "").strip().lower()
|
||||
if filt_field and filt_contains:
|
||||
hay = str(rec.get(filt_field) or "").strip().lower()
|
||||
if filt_contains not in hay:
|
||||
return False
|
||||
require = str(ph.discover_require_nonempty or "").strip()
|
||||
if require and not str(rec.get(require) or "").strip():
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def filter_discover_records(
|
||||
records: list[dict[str, Any]] | None,
|
||||
ph: PlaceholderDef,
|
||||
) -> list[str]:
|
||||
"""Apply placeholder discover filter; return unique values for ``ph``."""
|
||||
value_field = str(ph.discover_value_field or ph.name or "").strip() or "vrf_name"
|
||||
filt_field = str(ph.discover_filter_field or "").strip()
|
||||
filt_contains = str(ph.discover_filter_contains or "").strip().lower()
|
||||
out: list[str] = []
|
||||
seen: set[str] = set()
|
||||
for rec in records or []:
|
||||
if not isinstance(rec, dict):
|
||||
continue
|
||||
if filt_field and filt_contains:
|
||||
hay = str(rec.get(filt_field) or "").strip().lower()
|
||||
if filt_contains not in hay:
|
||||
continue
|
||||
if not _record_passes_discover_filter(rec, ph):
|
||||
continue
|
||||
val = str(rec.get(value_field) or "").strip()
|
||||
if not val or val in seen:
|
||||
continue
|
||||
|
|
@ -125,6 +166,17 @@ def filter_discover_records(
|
|||
return out
|
||||
|
||||
|
||||
def shared_discover_placeholders(profile: ParseProfile) -> list[PlaceholderDef]:
|
||||
"""Placeholders that share one discover_profile_id (pair/tuple bind)."""
|
||||
discover_phs = _discover_placeholders(profile)
|
||||
if len(discover_phs) < 2:
|
||||
return []
|
||||
disc_ids = {str(ph.discover_profile_id or "").strip() for ph in discover_phs}
|
||||
if len(disc_ids) != 1 or not next(iter(disc_ids)):
|
||||
return []
|
||||
return discover_phs
|
||||
|
||||
|
||||
def expand_from_bindings(
|
||||
*,
|
||||
profile: ParseProfile,
|
||||
|
|
@ -176,12 +228,52 @@ def expand_bindings_from_discover_records(
|
|||
profile: ParseProfile,
|
||||
records: list[dict[str, Any]] | None,
|
||||
) -> list[tuple[str, dict[str, str]]]:
|
||||
"""Build concrete commands from discover/parser records (e.g. config_vrf)."""
|
||||
"""Build concrete commands from discover/parser records (e.g. config_vrf).
|
||||
|
||||
Supports a single discover placeholder, or multiple placeholders that share
|
||||
the same discover_profile_id (zipped row-wise from each record).
|
||||
"""
|
||||
discover_phs = _discover_placeholders(profile)
|
||||
if not discover_phs:
|
||||
raise ValueError(f"profile {profile.profile_id} has no discover placeholders")
|
||||
|
||||
shared = shared_discover_placeholders(profile)
|
||||
if shared and len(shared) == len(profile.placeholders):
|
||||
ph0 = shared[0]
|
||||
bindings: list[dict[str, str]] = []
|
||||
seen: set[tuple[str, ...]] = set()
|
||||
for rec in records or []:
|
||||
if not isinstance(rec, dict):
|
||||
continue
|
||||
if not _record_passes_discover_filter(rec, ph0):
|
||||
continue
|
||||
params: dict[str, str] = {}
|
||||
ok = True
|
||||
for ph in shared:
|
||||
vf = str(ph.discover_value_field or ph.name or "").strip()
|
||||
val = str(rec.get(vf) or "").strip()
|
||||
if not val:
|
||||
ok = False
|
||||
break
|
||||
params[ph.name] = val
|
||||
if not ok:
|
||||
continue
|
||||
key = tuple(params.get(ph.name, "") for ph in shared)
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
bindings.append(params)
|
||||
if not bindings:
|
||||
raise ValueError(
|
||||
f"no discover values for {profile.profile_id} ({ph0.discover_profile_id})"
|
||||
)
|
||||
return expand_from_bindings(profile=profile, bindings=bindings)
|
||||
|
||||
if len(discover_phs) != 1 or len(profile.placeholders) != 1:
|
||||
raise ValueError(f"expand-from-discover only supports a single placeholder: {profile.profile_id}")
|
||||
raise ValueError(
|
||||
f"expand-from-discover only supports a single placeholder "
|
||||
f"or shared multi-placeholder: {profile.profile_id}"
|
||||
)
|
||||
ph = discover_phs[0]
|
||||
values = filter_discover_records(records, ph)
|
||||
if not values:
|
||||
|
|
|
|||
|
|
@ -14,6 +14,10 @@ from ..lldp_shared import resolve_vendor_key
|
|||
from ..models import BizStateTask
|
||||
from ..ne_netmiko import disable_target_paging, send_show_command
|
||||
from ..ne_session_factory import close_netmiko_connection, open_netmiko_connection
|
||||
from .command_match import (
|
||||
_record_passes_discover_filter,
|
||||
shared_discover_placeholders,
|
||||
)
|
||||
from .parsers import get_parser, run_parser
|
||||
from .profiles import get_profile
|
||||
|
||||
|
|
@ -71,17 +75,24 @@ def discover_params(
|
|||
)
|
||||
value_field = "vrf_name"
|
||||
label_field = "vrf_name"
|
||||
if collect_profile_id:
|
||||
collect = get_profile(collect_profile_id)
|
||||
if collect:
|
||||
for ph in collect.placeholders:
|
||||
if placeholder and ph.name != placeholder:
|
||||
continue
|
||||
if ph.discover_value_field:
|
||||
value_field = ph.discover_value_field
|
||||
if ph.discover_label_field:
|
||||
label_field = ph.discover_label_field
|
||||
break
|
||||
collect = get_profile(collect_profile_id) if collect_profile_id else None
|
||||
pair_phs = shared_discover_placeholders(collect) if collect else []
|
||||
active_ph = None
|
||||
if collect:
|
||||
for ph in collect.placeholders:
|
||||
if placeholder and ph.name != placeholder:
|
||||
continue
|
||||
if ph.discover_value_field:
|
||||
value_field = ph.discover_value_field
|
||||
if ph.discover_label_field:
|
||||
label_field = ph.discover_label_field
|
||||
active_ph = ph
|
||||
break
|
||||
# Pair discover: filter with the first shared placeholder's AF rules.
|
||||
if pair_phs and not placeholder:
|
||||
active_ph = pair_phs[0]
|
||||
value_field = str(active_ph.discover_value_field or active_ph.name or value_field)
|
||||
label_field = str(active_ph.discover_label_field or label_field)
|
||||
|
||||
try:
|
||||
if src == "managed":
|
||||
|
|
@ -148,36 +159,78 @@ def discover_params(
|
|||
|
||||
candidates = []
|
||||
seen: set[str] = set()
|
||||
filt_field = ""
|
||||
filt_contains = ""
|
||||
if collect_profile_id:
|
||||
collect = get_profile(collect_profile_id)
|
||||
if collect:
|
||||
for ph in collect.placeholders:
|
||||
if placeholder and ph.name != placeholder:
|
||||
continue
|
||||
filt_field = str(ph.discover_filter_field or "").strip()
|
||||
filt_contains = str(ph.discover_filter_contains or "").strip().lower()
|
||||
break
|
||||
for rec in records:
|
||||
val = str(rec.get(value_field) or "").strip()
|
||||
if not val or val in seen:
|
||||
continue
|
||||
if filt_field and filt_contains:
|
||||
hay = str(rec.get(filt_field) or "").strip().lower()
|
||||
if filt_contains not in hay:
|
||||
# Shared discover profile → one candidate per (placeholder fields) tuple.
|
||||
if pair_phs and len(pair_phs) >= 2:
|
||||
filter_ph = pair_phs[0]
|
||||
for rec in records:
|
||||
if not _record_passes_discover_filter(rec, filter_ph):
|
||||
# Also require every paired field non-empty.
|
||||
continue
|
||||
seen.add(val)
|
||||
label = str(rec.get(label_field) or val).strip() or val
|
||||
candidates.append(
|
||||
{
|
||||
"value": val,
|
||||
"label": label,
|
||||
"rd": str(rec.get("rd") or ""),
|
||||
"protocols": str(rec.get("protocols") or rec.get("address_families") or ""),
|
||||
"extra": rec,
|
||||
}
|
||||
)
|
||||
bind: dict[str, str] = {}
|
||||
ok = True
|
||||
for ph in pair_phs:
|
||||
if not _record_passes_discover_filter(rec, ph):
|
||||
ok = False
|
||||
break
|
||||
vf = str(ph.discover_value_field or ph.name or "").strip()
|
||||
val = str(rec.get(vf) or "").strip()
|
||||
if not val:
|
||||
ok = False
|
||||
break
|
||||
bind[ph.name] = val
|
||||
if not ok:
|
||||
continue
|
||||
key = "|".join(f"{k}={bind[k]}" for k in sorted(bind))
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
as_num = str(rec.get("remote_as") or "").strip()
|
||||
label_parts = [bind.get(ph.name, "") for ph in pair_phs]
|
||||
label = " / ".join(p for p in label_parts if p)
|
||||
if as_num:
|
||||
label = f"{label} (AS {as_num})"
|
||||
candidates.append(
|
||||
{
|
||||
"value": key,
|
||||
"label": label,
|
||||
"rd": str(rec.get("rd") or ""),
|
||||
"protocols": str(
|
||||
rec.get("protocols")
|
||||
or rec.get("address_families")
|
||||
or rec.get("afi")
|
||||
or ""
|
||||
),
|
||||
"bindings": bind,
|
||||
"extra": rec,
|
||||
}
|
||||
)
|
||||
else:
|
||||
for rec in records:
|
||||
if active_ph and not _record_passes_discover_filter(rec, active_ph):
|
||||
continue
|
||||
val = str(rec.get(value_field) or "").strip()
|
||||
if not val or val in seen:
|
||||
continue
|
||||
seen.add(val)
|
||||
label = str(rec.get(label_field) or val).strip() or val
|
||||
as_num = str(rec.get("remote_as") or "").strip()
|
||||
if as_num and value_field == "neighbor":
|
||||
label = f"{label} (AS {as_num})"
|
||||
candidates.append(
|
||||
{
|
||||
"value": val,
|
||||
"label": label,
|
||||
"rd": str(rec.get("rd") or ""),
|
||||
"protocols": str(
|
||||
rec.get("protocols")
|
||||
or rec.get("address_families")
|
||||
or rec.get("afi")
|
||||
or ""
|
||||
),
|
||||
"bindings": {str(active_ph.name if active_ph else value_field): val},
|
||||
"extra": rec,
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"ok": True,
|
||||
|
|
@ -186,6 +239,7 @@ def discover_params(
|
|||
"command": command,
|
||||
"vendor_key": resolve_vendor_key(vendor, device_type),
|
||||
"value_field": value_field,
|
||||
"pair_mode": bool(pair_phs and len(pair_phs) >= 2),
|
||||
"candidates": candidates,
|
||||
"raw_preview": str(raw or "")[:4000],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ def _map_fsm_rows(rows: list[dict[str, Any]], *, vrf: str) -> list[dict[str, Any
|
|||
"pri": row_get(r, "PRI", "pri")[:16],
|
||||
"metric": row_get(r, "METRIC", "metric")[:32],
|
||||
"flags": row_get(r, "FLAGS", "flags")[:16],
|
||||
"address_families": "ipv4",
|
||||
}
|
||||
)
|
||||
return out
|
||||
|
|
@ -67,6 +68,7 @@ def _hand_parse(*, raw_text: str, vrf: str = "", **_kw: Any) -> list[dict[str, A
|
|||
"pri": m.group("pri")[:16],
|
||||
"metric": m.group("metric")[:32],
|
||||
"flags": (m.group("flags") or "").strip()[:16],
|
||||
"address_families": "ipv4",
|
||||
}
|
||||
)
|
||||
return out
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ def _map_fsm_rows(rows: list[dict[str, Any]], *, vrf: str) -> list[dict[str, Any
|
|||
"pri": row_get(r, "PRI", "pri")[:16],
|
||||
"metric": row_get(r, "METRIC", "metric")[:32],
|
||||
"flags": row_get(r, "FLAGS", "flags")[:16],
|
||||
"address_families": "ipv6",
|
||||
}
|
||||
if gw or iface:
|
||||
cur["gateway"] = gw[:128]
|
||||
|
|
@ -77,6 +78,7 @@ def _hand_parse(*, raw_text: str, vrf: str = "", **_kw: Any) -> list[dict[str, A
|
|||
"pri": m.group("pri")[:16],
|
||||
"metric": m.group("metric")[:32],
|
||||
"flags": (m.group("flags") or "")[:16],
|
||||
"address_families": "ipv6",
|
||||
}
|
||||
continue
|
||||
m2 = _NH_RE.match(line)
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ class PlaceholderDef:
|
|||
# When required=False and no bindings: collect may expand all discover values.
|
||||
discover_filter_field: str = ""
|
||||
discover_filter_contains: str = ""
|
||||
# Skip rows where this field is empty (e.g. CE peers require non-empty vrf).
|
||||
discover_require_nonempty: str = ""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
|
@ -280,17 +282,7 @@ _BGP_PEER_FIELDS: list[FieldDef] = [
|
|||
FieldDef("address_families", length=64, role="meta", display_name="AF"),
|
||||
]
|
||||
|
||||
# Neighbor / dual-placeholder profiles still require an explicit VRF bind.
|
||||
_BGP_VRF_PLACEHOLDER = PlaceholderDef(
|
||||
name="vrf",
|
||||
schema_field="vrf",
|
||||
required=True,
|
||||
bind_mode="discover_select",
|
||||
discover_profile_id="zte.config_vrf",
|
||||
discover_value_field="vrf_name",
|
||||
discover_label_field="vrf_name",
|
||||
)
|
||||
|
||||
# BGP VRF summary still binds VRF from config VRF intent.
|
||||
_VRF_PLACEHOLDER_IPV4 = PlaceholderDef(
|
||||
name="vrf",
|
||||
schema_field="vrf",
|
||||
|
|
@ -315,11 +307,82 @@ _VRF_PLACEHOLDER_IPV6 = PlaceholderDef(
|
|||
discover_filter_contains="ipv6",
|
||||
)
|
||||
|
||||
_BGP_NEIGHBOR_PLACEHOLDER = PlaceholderDef(
|
||||
# BGP neighbor in/out: discover peers from Config BGP Peer Intent, filtered by AF.
|
||||
_BGP_NEIGHBOR_VPNV4 = PlaceholderDef(
|
||||
name="neighbor",
|
||||
schema_field="neighbor",
|
||||
required=True,
|
||||
bind_mode="manual_text",
|
||||
bind_mode="discover_select",
|
||||
discover_profile_id="zte.config_bgp_peer",
|
||||
discover_value_field="neighbor",
|
||||
discover_label_field="neighbor",
|
||||
discover_filter_field="afi",
|
||||
discover_filter_contains="vpnv4",
|
||||
)
|
||||
|
||||
_BGP_NEIGHBOR_VPNV6 = PlaceholderDef(
|
||||
name="neighbor",
|
||||
schema_field="neighbor",
|
||||
required=True,
|
||||
bind_mode="discover_select",
|
||||
discover_profile_id="zte.config_bgp_peer",
|
||||
discover_value_field="neighbor",
|
||||
discover_label_field="neighbor",
|
||||
discover_filter_field="afi",
|
||||
discover_filter_contains="vpnv6",
|
||||
)
|
||||
|
||||
# Per-VRF CE peers live under address-family ipv4/ipv6 vrf <name>.
|
||||
_BGP_VRF_PEER_IPV4 = PlaceholderDef(
|
||||
name="vrf",
|
||||
schema_field="vrf",
|
||||
required=True,
|
||||
bind_mode="discover_select",
|
||||
discover_profile_id="zte.config_bgp_peer",
|
||||
discover_value_field="vrf",
|
||||
discover_label_field="vrf",
|
||||
discover_filter_field="afi",
|
||||
discover_filter_contains="ipv4",
|
||||
discover_require_nonempty="neighbor",
|
||||
)
|
||||
|
||||
_BGP_NEIGHBOR_IPV4_VRF = PlaceholderDef(
|
||||
name="neighbor",
|
||||
schema_field="neighbor",
|
||||
required=True,
|
||||
bind_mode="discover_select",
|
||||
discover_profile_id="zte.config_bgp_peer",
|
||||
discover_value_field="neighbor",
|
||||
discover_label_field="neighbor",
|
||||
discover_filter_field="afi",
|
||||
discover_filter_contains="ipv4",
|
||||
discover_require_nonempty="vrf",
|
||||
)
|
||||
|
||||
_BGP_VRF_PEER_IPV6 = PlaceholderDef(
|
||||
name="vrf",
|
||||
schema_field="vrf",
|
||||
required=True,
|
||||
bind_mode="discover_select",
|
||||
discover_profile_id="zte.config_bgp_peer",
|
||||
discover_value_field="vrf",
|
||||
discover_label_field="vrf",
|
||||
discover_filter_field="afi",
|
||||
discover_filter_contains="ipv6",
|
||||
discover_require_nonempty="neighbor",
|
||||
)
|
||||
|
||||
_BGP_NEIGHBOR_IPV6_VRF = PlaceholderDef(
|
||||
name="neighbor",
|
||||
schema_field="neighbor",
|
||||
required=True,
|
||||
bind_mode="discover_select",
|
||||
discover_profile_id="zte.config_bgp_peer",
|
||||
discover_value_field="neighbor",
|
||||
discover_label_field="neighbor",
|
||||
discover_filter_field="afi",
|
||||
discover_filter_contains="ipv6",
|
||||
discover_require_nonempty="vrf",
|
||||
)
|
||||
|
||||
_OSPF_FIELDS: list[FieldDef] = [
|
||||
|
|
@ -905,7 +968,7 @@ def _zte_status_profiles() -> list[ParseProfile]:
|
|||
match=r"(?i)^\s*show\s+bgp\s+vpnv4\s+unicast\s+neighbor\s+in\s+(?P<neighbor>\S+)(?:\s*\|\s*one-line)?\s*$",
|
||||
textfsm_command="show bgp vpnv4 unicast neighbor in",
|
||||
description="Routes learned from VPNv4 neighbor; summary aux for peer state.",
|
||||
placeholders=[_BGP_NEIGHBOR_PLACEHOLDER],
|
||||
placeholders=[_BGP_NEIGHBOR_VPNV4],
|
||||
fields=list(_BGP_ROUTE_FIELDS),
|
||||
tags=["bgp", "vpnv4", "route"],
|
||||
sort_order=420,
|
||||
|
|
@ -930,7 +993,7 @@ def _zte_status_profiles() -> list[ParseProfile]:
|
|||
match=r"(?i)^\s*show\s+bgp\s+vpnv4\s+unicast\s+neighbor\s+out\s+(?P<neighbor>\S+)(?:\s*\|\s*one-line)?\s*$",
|
||||
textfsm_command="show bgp vpnv4 unicast neighbor out",
|
||||
description="Routes advertised to VPNv4 neighbor (large; bind neighbor).",
|
||||
placeholders=[_BGP_NEIGHBOR_PLACEHOLDER],
|
||||
placeholders=[_BGP_NEIGHBOR_VPNV4],
|
||||
fields=list(_BGP_ROUTE_FIELDS),
|
||||
tags=["bgp", "vpnv4", "route"],
|
||||
sort_order=425,
|
||||
|
|
@ -955,7 +1018,7 @@ def _zte_status_profiles() -> list[ParseProfile]:
|
|||
match=r"(?i)^\s*show\s+bgp\s+vpnv6\s+unicast\s+neighbor\s+in\s+(?P<neighbor>\S+)(?:\s*\|\s*one-line)?\s*$",
|
||||
textfsm_command="show bgp vpnv6 unicast neighbor in",
|
||||
description="Routes learned from VPNv6 neighbor; summary aux for peer state.",
|
||||
placeholders=[_BGP_NEIGHBOR_PLACEHOLDER],
|
||||
placeholders=[_BGP_NEIGHBOR_VPNV6],
|
||||
fields=list(_BGP_ROUTE_FIELDS),
|
||||
tags=["bgp", "vpnv6", "route"],
|
||||
sort_order=426,
|
||||
|
|
@ -980,7 +1043,7 @@ def _zte_status_profiles() -> list[ParseProfile]:
|
|||
match=r"(?i)^\s*show\s+bgp\s+vpnv6\s+unicast\s+neighbor\s+out\s+(?P<neighbor>\S+)(?:\s*\|\s*one-line)?\s*$",
|
||||
textfsm_command="show bgp vpnv6 unicast neighbor out",
|
||||
description="Routes advertised to VPNv6 neighbor (large; bind neighbor).",
|
||||
placeholders=[_BGP_NEIGHBOR_PLACEHOLDER],
|
||||
placeholders=[_BGP_NEIGHBOR_VPNV6],
|
||||
fields=list(_BGP_ROUTE_FIELDS),
|
||||
tags=["bgp", "vpnv6", "route"],
|
||||
sort_order=427,
|
||||
|
|
@ -1007,8 +1070,8 @@ def _zte_status_profiles() -> list[ParseProfile]:
|
|||
r"neighbor\s+in\s+(?P<neighbor>\S+)(?:\s*\|\s*one-line)?\s*$"
|
||||
),
|
||||
textfsm_command="show bgp vpnv4 unicast neighbor in",
|
||||
description="Per-VRF routes learned from neighbor; VRF summary aux.",
|
||||
placeholders=[_BGP_VRF_PLACEHOLDER, _BGP_NEIGHBOR_PLACEHOLDER],
|
||||
description="Per-VRF CE peer routes; discover (vrf,neighbor) from BGP peer intent.",
|
||||
placeholders=[_BGP_VRF_PEER_IPV4, _BGP_NEIGHBOR_IPV4_VRF],
|
||||
fields=list(_BGP_ROUTE_FIELDS),
|
||||
tags=["bgp", "vpnv4", "vrf", "route"],
|
||||
sort_order=430,
|
||||
|
|
@ -1035,8 +1098,8 @@ def _zte_status_profiles() -> list[ParseProfile]:
|
|||
r"neighbor\s+out\s+(?P<neighbor>\S+)(?:\s*\|\s*one-line)?\s*$"
|
||||
),
|
||||
textfsm_command="show bgp vpnv4 unicast neighbor out",
|
||||
description="Per-VRF routes advertised to neighbor (may be large).",
|
||||
placeholders=[_BGP_VRF_PLACEHOLDER, _BGP_NEIGHBOR_PLACEHOLDER],
|
||||
description="Per-VRF CE peer advertised routes; discover pairs from BGP peer intent.",
|
||||
placeholders=[_BGP_VRF_PEER_IPV4, _BGP_NEIGHBOR_IPV4_VRF],
|
||||
fields=list(_BGP_ROUTE_FIELDS),
|
||||
tags=["bgp", "vpnv4", "vrf", "route"],
|
||||
sort_order=435,
|
||||
|
|
@ -1063,8 +1126,8 @@ def _zte_status_profiles() -> list[ParseProfile]:
|
|||
r"neighbor\s+in\s+(?P<neighbor>\S+)(?:\s*\|\s*one-line)?\s*$"
|
||||
),
|
||||
textfsm_command="show bgp vpnv6 unicast neighbor in",
|
||||
description="Per-VRF VPNv6 routes learned from neighbor.",
|
||||
placeholders=[_BGP_VRF_PLACEHOLDER, _BGP_NEIGHBOR_PLACEHOLDER],
|
||||
description="Per-VRF IPv6 CE peer routes; discover pairs from BGP peer intent.",
|
||||
placeholders=[_BGP_VRF_PEER_IPV6, _BGP_NEIGHBOR_IPV6_VRF],
|
||||
fields=list(_BGP_ROUTE_FIELDS),
|
||||
tags=["bgp", "vpnv6", "vrf", "route"],
|
||||
sort_order=440,
|
||||
|
|
@ -1091,8 +1154,8 @@ def _zte_status_profiles() -> list[ParseProfile]:
|
|||
r"neighbor\s+out\s+(?P<neighbor>\S+)(?:\s*\|\s*one-line)?\s*$"
|
||||
),
|
||||
textfsm_command="show bgp vpnv6 unicast neighbor out",
|
||||
description="Per-VRF VPNv6 routes advertised to neighbor.",
|
||||
placeholders=[_BGP_VRF_PLACEHOLDER, _BGP_NEIGHBOR_PLACEHOLDER],
|
||||
description="Per-VRF IPv6 CE advertised routes; discover pairs from BGP peer intent.",
|
||||
placeholders=[_BGP_VRF_PEER_IPV6, _BGP_NEIGHBOR_IPV6_VRF],
|
||||
fields=list(_BGP_ROUTE_FIELDS),
|
||||
tags=["bgp", "vpnv6", "vrf", "route"],
|
||||
sort_order=445,
|
||||
|
|
@ -1130,7 +1193,7 @@ def _zte_status_profiles() -> list[ParseProfile]:
|
|||
from_aux="config_vrf",
|
||||
left_on="vrf",
|
||||
right_on="vrf_name",
|
||||
take=("rd", "address_families"),
|
||||
take=("rd",),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -1172,7 +1235,7 @@ def _zte_status_profiles() -> list[ParseProfile]:
|
|||
from_aux="config_vrf",
|
||||
left_on="vrf",
|
||||
right_on="vrf_name",
|
||||
take=("rd", "address_families"),
|
||||
take=("rd",),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -1501,6 +1564,7 @@ def profile_to_public_dict(p: ParseProfile, *, overrides: dict[str, Any] | None
|
|||
"discover_label_field": ph.discover_label_field,
|
||||
"discover_filter_field": ph.discover_filter_field,
|
||||
"discover_filter_contains": ph.discover_filter_contains,
|
||||
"discover_require_nonempty": ph.discover_require_nonempty,
|
||||
}
|
||||
for ph in p.placeholders
|
||||
],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue