Merge duplicate if-intf checks into Config Interface Intent.

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>
This commit is contained in:
oliver 2026-09-20 09:48:48 +08:00
parent c1b1c0f75d
commit 1642a0adec
8 changed files with 37 additions and 21 deletions

View file

@ -21,7 +21,7 @@ Cross-command (multi aux)
1. Implement each aux as a normal status profile (steps 1–4) 1. Implement each aux as a normal status profile (steps 1–4)
2. On the primary profile:: 2. On the primary profile::
aux_commands=[AuxCommand(key="if_intf", profile_id="zte.if_intf")] aux_commands=[AuxCommand(key="if_intf", profile_id="zte.config_interface")]
enrich_joins=[EnrichJoin(from_aux="if_intf", on="interface", take=("vrf",))] enrich_joins=[EnrichJoin(from_aux="if_intf", on="interface", take=("vrf",))]
3. CollectSession caches identical concrete CLI in one batch; enrich runs after 3. CollectSession caches identical concrete CLI in one batch; enrich runs after

View file

@ -1,7 +1,7 @@
"""ZTE: show arp [| one-line]. """ZTE: show arp [| one-line].
Pipeline: TextFSM ``zte_zxros_show_arp`` → map → hand fallback. Pipeline: TextFSM ``zte_zxros_show_arp`` → map → hand fallback.
Cross-command VRF comes from profile ``enrich_joins`` (if_intf), not here. Cross-command VRF comes from profile ``enrich_joins`` (config_interface aux), not here.
""" """
from __future__ import annotations from __future__ import annotations

View file

@ -605,14 +605,14 @@ def _zte_status_profiles() -> list[ParseProfile]:
command_template="show arp | one-line", command_template="show arp | one-line",
match=r"(?i)^\s*show\s+arp(?:\s*\|\s*one-line)?\s*$", match=r"(?i)^\s*show\s+arp(?:\s*\|\s*one-line)?\s*$",
textfsm_command="show arp", textfsm_command="show arp",
description="ARP entries (IP/MAC/interface); VRF from if-intf aux.", description="ARP entries (IP/MAC/interface); VRF from config_interface aux.",
fields=list(_ARP_FIELDS), fields=list(_ARP_FIELDS),
tags=["arp", "l3", "status"], tags=["arp", "l3", "status"],
sort_order=320, sort_order=320,
enabled=True, enabled=True,
kind="collect", kind="collect",
aux_commands=[ aux_commands=[
AuxCommand(key="if_intf", profile_id="zte.if_intf"), AuxCommand(key="if_intf", profile_id="zte.config_interface"),
], ],
enrich_joins=[ enrich_joins=[
EnrichJoin(from_aux="if_intf", on="interface", take=("vrf",)), EnrichJoin(from_aux="if_intf", on="interface", take=("vrf",)),
@ -628,13 +628,13 @@ def _zte_status_profiles() -> list[ParseProfile]:
match=r"(?i)^\s*show\s+running-config\s+if-intf\s*$", match=r"(?i)^\s*show\s+running-config\s+if-intf\s*$",
textfsm_command="show running-config if-intf", textfsm_command="show running-config if-intf",
description=( description=(
"Interface VRF map for ARP enrich (aux). " "Compat/aux-only interface→VRF map (disabled in catalog). "
"Free-form CLI match prefers zte.config_interface (longer regex)." "Use Config Interface Intent (zte.config_interface) instead."
), ),
fields=list(_IF_INTF_FIELDS), fields=list(_IF_INTF_FIELDS),
tags=["interface", "vrf", "config", "status", "aux"], tags=["interface", "vrf", "config", "status", "aux"],
sort_order=325, sort_order=325,
enabled=True, enabled=False,
kind="collect", kind="collect",
), ),
ParseProfile( ParseProfile(
@ -1202,7 +1202,10 @@ def _zte_status_profiles() -> list[ParseProfile]:
command_template="show running-config if-intf", command_template="show running-config if-intf",
match=r"(?i)^\s*show\s+running-config\s+if-intf(?:\s*\|\s*one-line)?\s*$", match=r"(?i)^\s*show\s+running-config\s+if-intf(?:\s*\|\s*one-line)?\s*$",
textfsm_command="show running-config if-intf", textfsm_command="show running-config if-intf",
description="Interface VRF/IP/admin intent (richer than if_intf aux map).", description=(
"Interface VRF/IP/admin intent; also ARP VRF enrich source "
"(replaces standalone IF VRF / if_intf check)."
),
fields=list(_CONFIG_IFACE_FIELDS), fields=list(_CONFIG_IFACE_FIELDS),
tags=["config", "interface", "intent"], tags=["config", "interface", "intent"],
sort_order=510, sort_order=510,

View file

@ -36,7 +36,7 @@ ParseProfile (命令 + schema + aux + enrich)
| `netx_api/biz_state/enrich.py` | 声明式等值 join | | `netx_api/biz_state/enrich.py` | 声明式等值 join |
| `netx_api/biz_state/collect_runner.py` | 任务会话采集落库 | | `netx_api/biz_state/collect_runner.py` | 任务会话采集落库 |
样板:`zte/interface_brief`(单命令)、`zte/if_intf`(可复用辅表)、`zte.arp`(主+辅+enrich)。 样板:`zte/interface_brief`(单命令)、`zte.config_interface`(可复用辅表)、`zte.arp`(主+辅+enrich)。
--- ---
@ -98,7 +98,7 @@ ParseProfile (命令 + schema + aux + enrich)
```python ```python
aux_commands=[ aux_commands=[
AuxCommand(key="if_intf", profile_id="zte.if_intf"), AuxCommand(key="if_intf", profile_id="zte.config_interface"),
# 多辅:再 append;key 唯一 # 多辅:再 append;key 唯一
], ],
enrich_joins=[ enrich_joins=[
@ -159,5 +159,5 @@ Sheet(`metrics_json[]`)能力:
- Enrich:`biz_state/enrich.py` - Enrich:`biz_state/enrich.py`
- Session:`biz_state/collect_session.py` - Session:`biz_state/collect_session.py`
- Compare:`biz_state/compare_rules.py`、`compare_engine.py`、`compare_service.py` - Compare:`biz_state/compare_rules.py`、`compare_engine.py`、`compare_service.py`
- ARP+VRF:`profiles.py` → `zte.arp`;`parsers/zte/arp.py`;`parsers/zte/if_intf.py` - ARP+VRF:`profiles.py` → `zte.arp`;`parsers/zte/arp.py`;辅表 `zte.config_interface`(`zte.if_intf` 已禁用仅兼容)
- 模板:`cli_templates/zte/zte_zxros_show_arp.textfsm`、`..._if_intf.textfsm` - 模板:`cli_templates/zte/zte_zxros_show_arp.textfsm`、`..._if_intf.textfsm`

View file

@ -59,18 +59,20 @@ class AuxResolveTests(unittest.TestCase):
reload_profiles() reload_profiles()
def test_resolve_from_profile_id(self) -> None: def test_resolve_from_profile_id(self) -> None:
ra = resolve_aux_command(AuxCommand(key="if_intf", profile_id="zte.if_intf")) ra = resolve_aux_command(AuxCommand(key="if_intf", profile_id="zte.config_interface"))
self.assertEqual(ra.command, "show running-config if-intf") self.assertEqual(ra.command, "show running-config if-intf")
self.assertEqual(ra.parser_id, "if_intf") self.assertEqual(ra.parser_id, "config_interface")
self.assertIn("zte_zxros_show_running_config_if_intf", ra.rule_keys)
def test_arp_profile_slim(self) -> None: def test_arp_profile_slim(self) -> None:
p = get_profile("zte.arp") p = get_profile("zte.arp")
assert p is not None assert p is not None
self.assertEqual(p.aux_commands[0].key, "if_intf") self.assertEqual(p.aux_commands[0].key, "if_intf")
self.assertEqual(p.aux_commands[0].profile_id, "zte.if_intf") self.assertEqual(p.aux_commands[0].profile_id, "zte.config_interface")
self.assertEqual(len(p.enrich_joins), 1) self.assertEqual(len(p.enrich_joins), 1)
self.assertEqual(p.enrich_joins[0].take, ("vrf",)) self.assertEqual(p.enrich_joins[0].take, ("vrf",))
ii = get_profile("zte.if_intf")
assert ii is not None
self.assertFalse(ii.enabled)
class CollectSessionCacheTests(unittest.TestCase): class CollectSessionCacheTests(unittest.TestCase):
@ -98,13 +100,13 @@ class ArpEnrichPipelineTests(unittest.TestCase):
def test_run_primary_with_enrich(self) -> None: def test_run_primary_with_enrich(self) -> None:
if_recs, if_fsm, _ = run_parser( if_recs, if_fsm, _ = run_parser(
"if_intf", "config_interface",
raw_text=IF_INTF_SAMPLE, raw_text=IF_INTF_SAMPLE,
vendor="zte", vendor="zte",
device_type="zte_zxros", device_type="zte_zxros",
command="show running-config if-intf", command="show running-config if-intf",
) )
ra = resolve_aux_command(AuxCommand(key="if_intf", profile_id="zte.if_intf")) ra = resolve_aux_command(AuxCommand(key="if_intf", profile_id="zte.config_interface"))
bundle = build_parse_bundle( bundle = build_parse_bundle(
primary_raw=ARP_MATCHING, primary_raw=ARP_MATCHING,
primary_parser_id="arp", primary_parser_id="arp",

View file

@ -97,7 +97,10 @@ class ArpMultiCommandTests(unittest.TestCase):
assert p is not None assert p is not None
self.assertEqual(len(p.aux_commands), 1) self.assertEqual(len(p.aux_commands), 1)
self.assertEqual(p.aux_commands[0].key, "if_intf") self.assertEqual(p.aux_commands[0].key, "if_intf")
self.assertEqual(p.aux_commands[0].profile_id, "zte.if_intf") self.assertEqual(p.aux_commands[0].profile_id, "zte.config_interface")
ii = get_profile("zte.if_intf")
assert ii is not None
self.assertFalse(ii.enabled)
self.assertEqual(get_parser_meta("if_intf")["rule_keys"], ("zte_zxros_show_running_config_if_intf",)) self.assertEqual(get_parser_meta("if_intf")["rule_keys"], ("zte_zxros_show_running_config_if_intf",))
def test_arp_enriches_vrf_via_aux_records(self) -> None: def test_arp_enriches_vrf_via_aux_records(self) -> None:
@ -110,13 +113,13 @@ class ArpMultiCommandTests(unittest.TestCase):
from netx_api.biz_state.profiles import AuxCommand from netx_api.biz_state.profiles import AuxCommand
if_recs, if_fsm, _ = run_parser( if_recs, if_fsm, _ = run_parser(
"if_intf", "config_interface",
raw_text=IF_INTF_SAMPLE, raw_text=IF_INTF_SAMPLE,
vendor="zte", vendor="zte",
device_type="zte_zxros", device_type="zte_zxros",
command="show running-config if-intf", command="show running-config if-intf",
) )
ra = resolve_aux_command(AuxCommand(key="if_intf", profile_id="zte.if_intf")) ra = resolve_aux_command(AuxCommand(key="if_intf", profile_id="zte.config_interface"))
bundle = build_parse_bundle( bundle = build_parse_bundle(
primary_raw=ARP_MATCHING, primary_raw=ARP_MATCHING,
primary_parser_id="arp", primary_parser_id="arp",

View file

@ -233,6 +233,9 @@ class ZteConfigIntentTests(unittest.TestCase):
self.assertIsNotNone(hit) self.assertIsNotNone(hit)
assert hit is not None assert hit is not None
self.assertEqual(hit.profile.profile_id, "zte.config_interface") self.assertEqual(hit.profile.profile_id, "zte.config_interface")
ii = get_profile("zte.if_intf")
assert ii is not None
self.assertFalse(ii.enabled)
def test_config_vrf(self) -> None: def test_config_vrf(self) -> None:
rows = normalize_config_vrf(raw_text=_CFG_VRF, command="show running-config vrf") rows = normalize_config_vrf(raw_text=_CFG_VRF, command="show running-config vrf")

View file

@ -54,7 +54,12 @@ class ZteStatusParserTests(unittest.TestCase):
for mid in ("isis_adjacency", "interface_brief", "arp", "nd6_cache", "bgp_peer", "if_intf"): for mid in ("isis_adjacency", "interface_brief", "arp", "nd6_cache", "bgp_peer", "if_intf"):
self.assertIn(mid, metric_field_map()) self.assertIn(mid, metric_field_map())
self.assertIsNotNone(get_profile("zte.isis_adjacency")) self.assertIsNotNone(get_profile("zte.isis_adjacency"))
self.assertIsNotNone(get_profile("zte.if_intf")) ii = get_profile("zte.if_intf")
self.assertIsNotNone(ii)
assert ii is not None
self.assertFalse(ii.enabled)
self.assertNotIn("zte.if_intf", zte)
self.assertIn("zte.config_interface", zte)
def test_isis_adjacency(self) -> None: def test_isis_adjacency(self) -> None:
text = _section( text = _section(