mirror of
https://github.com/hansjone/netx.git
synced 2026-10-19 14:20:47 +08:00
Prefer vendor label for LLDP command when it conflicts with device_type.
Huawei NEs still tagged zte_zxros were getting show lldp neighbor brief and timing out; use display lldp neighbor and align UME inventory vendor before the jump. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
b36174bfa4
commit
3adf8d8b75
4 changed files with 82 additions and 18 deletions
|
|
@ -175,17 +175,35 @@ def _ume_target_dict(db: Session, uid: str, default_profile: Any) -> dict[str, s
|
||||||
ume = db.query(UmeInventoryNE).filter(UmeInventoryNE.ne_id == uid).one_or_none()
|
ume = db.query(UmeInventoryNE).filter(UmeInventoryNE.ne_id == uid).one_or_none()
|
||||||
if ume is None:
|
if ume is None:
|
||||||
return None
|
return None
|
||||||
|
inv_vendor = str(ume.vendor or "").strip()
|
||||||
if default_profile is not None:
|
if default_profile is not None:
|
||||||
dtype, vendor = infer_device_type_vendor(str(ume.ne_type or ""), default_profile)
|
dtype, vendor = infer_device_type_vendor(str(ume.ne_type or ""), default_profile)
|
||||||
else:
|
else:
|
||||||
dtype, vendor = "zte_zxros", (ume.vendor or "ZTE")
|
dtype, vendor = "zte_zxros", (inv_vendor or "ZTE")
|
||||||
|
# Prefer UME inventory vendor over CLI-profile defaults so Huawei is not
|
||||||
|
# treated as ZTE (show lldp neighbor brief) when ne_type rules miss.
|
||||||
|
if inv_vendor:
|
||||||
|
vendor = inv_vendor
|
||||||
|
from .topology_lldp import _key_from_device_type, _key_from_vendor_label
|
||||||
|
|
||||||
|
label_key = _key_from_vendor_label(inv_vendor)
|
||||||
|
dtype_key = _key_from_device_type(str(dtype or ""))
|
||||||
|
if label_key and label_key != dtype_key:
|
||||||
|
dtype = {
|
||||||
|
"huawei": "huawei",
|
||||||
|
"cisco": "cisco_ios",
|
||||||
|
"zte": "zte_zxros",
|
||||||
|
"h3c": "hp_comware",
|
||||||
|
"juniper": "juniper_junos",
|
||||||
|
"nokia": "nokia_sros",
|
||||||
|
}.get(label_key, dtype)
|
||||||
name = (ume.host_name or ume.ne_name or ume.user_label or ume.ip_address or uid).strip()
|
name = (ume.host_name or ume.ne_name or ume.user_label or ume.ip_address or uid).strip()
|
||||||
return {
|
return {
|
||||||
"ne_id": uid,
|
"ne_id": uid,
|
||||||
"ume_ne_id": uid,
|
"ume_ne_id": uid,
|
||||||
"ne_name": name,
|
"ne_name": name,
|
||||||
"ne_ip": ume.ip_address or "",
|
"ne_ip": ume.ip_address or "",
|
||||||
"vendor": vendor or (ume.vendor or "ZTE"),
|
"vendor": vendor or (inv_vendor or "ZTE"),
|
||||||
"device_type": dtype or "zte_zxros",
|
"device_type": dtype or "zte_zxros",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,6 @@ def _discover_one_target(
|
||||||
"raw_preview": "",
|
"raw_preview": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd, _proto = pick_neighbor_command(vendor=vendor, device_type=device_type)
|
|
||||||
exec_kwargs: dict[str, Any] = {"read_timeout_sec": 60}
|
exec_kwargs: dict[str, Any] = {"read_timeout_sec": 60}
|
||||||
if target.get("ume_ne_id") and not db.get(ManagedNE, target["ne_id"]):
|
if target.get("ume_ne_id") and not db.get(ManagedNE, target["ne_id"]):
|
||||||
exec_kwargs["ume_ne_id"] = target["ume_ne_id"]
|
exec_kwargs["ume_ne_id"] = target["ume_ne_id"]
|
||||||
|
|
@ -108,9 +107,14 @@ def _discover_one_target(
|
||||||
return {
|
return {
|
||||||
**base,
|
**base,
|
||||||
"ok": False,
|
"ok": False,
|
||||||
"command": cmd,
|
"command": "",
|
||||||
"error": str(exc.detail or "resolve_failed")[:500],
|
"error": str(exc.detail or "resolve_failed")[:500],
|
||||||
}
|
}
|
||||||
|
# Prefer resolved session identity (overrides / profile) for the LLDP verb.
|
||||||
|
vendor = str(creds.get("vendor") or vendor or "")
|
||||||
|
device_type = str(creds.get("device_type") or device_type or "")
|
||||||
|
pkey, is_stub = parser_meta(vendor=vendor, device_type=device_type)
|
||||||
|
cmd, _proto = pick_neighbor_command(vendor=vendor, device_type=device_type)
|
||||||
skip = cli_creds_skip_reason(creds, interactive=False)
|
skip = cli_creds_skip_reason(creds, interactive=False)
|
||||||
if skip:
|
if skip:
|
||||||
return {**base, "ok": False, "command": cmd, "error": skip}
|
return {**base, "ok": False, "command": cmd, "error": skip}
|
||||||
|
|
|
||||||
|
|
@ -52,8 +52,11 @@ _DEVICE_TYPE_PREFIX_RULES: tuple[tuple[str, str], ...] = (
|
||||||
_VENDOR_LABEL_TO_KEY: dict[str, str] = {
|
_VENDOR_LABEL_TO_KEY: dict[str, str] = {
|
||||||
"cisco": "cisco",
|
"cisco": "cisco",
|
||||||
"huawei": "huawei",
|
"huawei": "huawei",
|
||||||
|
"华为": "huawei",
|
||||||
|
"hw": "huawei",
|
||||||
"h3c": "h3c",
|
"h3c": "h3c",
|
||||||
"zte": "zte",
|
"zte": "zte",
|
||||||
|
"中兴": "zte",
|
||||||
"juniper": "juniper",
|
"juniper": "juniper",
|
||||||
"nokia": "nokia",
|
"nokia": "nokia",
|
||||||
"ericsson": "ericsson",
|
"ericsson": "ericsson",
|
||||||
|
|
@ -110,23 +113,49 @@ VENDOR_LLDP_PROFILES: dict[str, VendorLldpProfile] = {
|
||||||
STUB_PARSER_KEYS = frozenset({"nokia", "ericsson", "generic"})
|
STUB_PARSER_KEYS = frozenset({"nokia", "ericsson", "generic"})
|
||||||
|
|
||||||
|
|
||||||
def resolve_vendor_key(vendor: str = "", device_type: str = "") -> str:
|
def _key_from_device_type(device_type: str) -> str:
|
||||||
"""Map inventory device_type (preferred) or vendor label -> profile key."""
|
|
||||||
dtype = str(device_type or "").strip().lower()
|
dtype = str(device_type or "").strip().lower()
|
||||||
if dtype:
|
if not dtype:
|
||||||
|
return ""
|
||||||
for prefix, key in _DEVICE_TYPE_PREFIX_RULES:
|
for prefix, key in _DEVICE_TYPE_PREFIX_RULES:
|
||||||
if dtype == prefix.rstrip("_") or dtype.startswith(prefix):
|
if dtype == prefix.rstrip("_") or dtype.startswith(prefix):
|
||||||
return key
|
return key
|
||||||
if dtype in VENDOR_LLDP_PROFILES and dtype != "generic":
|
if dtype in VENDOR_LLDP_PROFILES and dtype != "generic":
|
||||||
return dtype
|
return dtype
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def _key_from_vendor_label(vendor: str) -> str:
|
||||||
label = str(vendor or "").strip().lower()
|
label = str(vendor or "").strip().lower()
|
||||||
if label:
|
if not label:
|
||||||
|
return ""
|
||||||
if label in _VENDOR_LABEL_TO_KEY:
|
if label in _VENDOR_LABEL_TO_KEY:
|
||||||
return _VENDOR_LABEL_TO_KEY[label]
|
return _VENDOR_LABEL_TO_KEY[label]
|
||||||
for token, key in _VENDOR_LABEL_TO_KEY.items():
|
for token, key in _VENDOR_LABEL_TO_KEY.items():
|
||||||
if label == token or label.startswith(f"{token} ") or label.startswith(f"{token}-"):
|
if label == token or label.startswith(f"{token} ") or label.startswith(f"{token}-"):
|
||||||
return key
|
return key
|
||||||
|
# Substring for values like "Huawei Technologies"
|
||||||
|
if len(token) >= 3 and token in label:
|
||||||
|
return key
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_vendor_key(vendor: str = "", device_type: str = "") -> str:
|
||||||
|
"""Map inventory vendor / device_type -> LLDP profile key.
|
||||||
|
|
||||||
|
Prefer Netmiko ``device_type`` when it is specific. When vendor label and
|
||||||
|
device_type disagree (e.g. vendor=Huawei but dtype still zte_zxros from a
|
||||||
|
default profile), prefer the vendor label so Huawei gets ``display lldp``
|
||||||
|
instead of ZTE's ``show lldp neighbor brief``.
|
||||||
|
"""
|
||||||
|
dtype_key = _key_from_device_type(device_type)
|
||||||
|
label_key = _key_from_vendor_label(vendor)
|
||||||
|
if label_key and dtype_key and label_key != dtype_key:
|
||||||
|
return label_key
|
||||||
|
if dtype_key:
|
||||||
|
return dtype_key
|
||||||
|
if label_key:
|
||||||
|
return label_key
|
||||||
return "generic"
|
return "generic"
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -138,6 +167,7 @@ def get_vendor_profile(vendor: str = "", device_type: str = "") -> VendorLldpPro
|
||||||
def lldp_command_for_vendor(vendor: str = "", device_type: str = "") -> str:
|
def lldp_command_for_vendor(vendor: str = "", device_type: str = "") -> str:
|
||||||
dtype = str(device_type or "").strip().lower()
|
dtype = str(device_type or "").strip().lower()
|
||||||
# AOS has a community template; SROS profile command differs.
|
# AOS has a community template; SROS profile command differs.
|
||||||
|
# Keep AOS command even when vendor label says Nokia/Alcatel.
|
||||||
if dtype.startswith("alcatel_aos"):
|
if dtype.startswith("alcatel_aos"):
|
||||||
return "show lldp remote-system"
|
return "show lldp remote-system"
|
||||||
return get_vendor_profile(vendor, device_type).lldp_command
|
return get_vendor_profile(vendor, device_type).lldp_command
|
||||||
|
|
|
||||||
|
|
@ -157,6 +157,18 @@ class LldpParserTests(unittest.TestCase):
|
||||||
key, stub = lldp.parser_meta(vendor="Ericsson", device_type="ericsson_ipos")
|
key, stub = lldp.parser_meta(vendor="Ericsson", device_type="ericsson_ipos")
|
||||||
self.assertTrue(stub)
|
self.assertTrue(stub)
|
||||||
|
|
||||||
|
def test_pick_command_vendor_wins_on_dtype_conflict(self) -> None:
|
||||||
|
# Stale/default zte_zxros must not force ZTE LLDP on a Huawei-labeled NE.
|
||||||
|
self.assertEqual(
|
||||||
|
lldp.pick_neighbor_command(vendor="Huawei", device_type="zte_zxros")[0],
|
||||||
|
"display lldp neighbor",
|
||||||
|
)
|
||||||
|
self.assertEqual(lldp.resolve_vendor_key("Huawei", "zte_zxros"), "huawei")
|
||||||
|
self.assertEqual(
|
||||||
|
lldp.pick_neighbor_command(vendor="Huawei", device_type="huawei")[0],
|
||||||
|
"display lldp neighbor",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DeadlockHelperTests(unittest.TestCase):
|
class DeadlockHelperTests(unittest.TestCase):
|
||||||
def test_is_deadlock_error_detects_pg_message(self) -> None:
|
def test_is_deadlock_error_detects_pg_message(self) -> None:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue