Skip LLDP placeholder NEs from discovery until credentials are filled.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-08-04 20:11:43 +08:00
parent 90bd323115
commit 2cbdc896f6
2 changed files with 38 additions and 31 deletions

View file

@ -7,6 +7,7 @@ from fastapi import HTTPException
from sqlalchemy.orm import Session
from .cli_resolve import get_default_profile, infer_device_type_vendor
from .device_types import LLDP_DISCOVERED_NE_SOURCE
from .models import ManagedNE, TopoDiscoverJob, TopoDiscoverJobItem, UmeInventoryNE
from .topology_common import _RAW_PREVIEW_MAX
from .topology_schemas import (
@ -24,6 +25,22 @@ def _raw_preview(raw: str, *, limit: int = _RAW_PREVIEW_MAX) -> str:
return f"{text[:limit]}\n...[truncated preview {limit}/{len(text)} chars]"
def _is_lldp_placeholder_ne(ne: ManagedNE) -> bool:
"""LLDP SSH placeholders have no credentials until the operator promotes them."""
return str(ne.source or "").strip().lower() in {LLDP_DISCOVERED_NE_SOURCE, "lldp"}
def _managed_target_dict(ne: ManagedNE) -> dict[str, str]:
return {
"ne_id": ne.id,
"ume_ne_id": "",
"ne_name": ne.name or "",
"ne_ip": ne.ip_address or "",
"vendor": ne.vendor or "",
"device_type": ne.device_type or "",
}
def _job_out(
db: Session,
job: TopoDiscoverJob,
@ -138,21 +155,15 @@ def _resolve_scan_targets(
targets: list[dict[str, str]] = []
if scope == "all_inventory":
# Managed inventory first; then UME NEs not already covered by the same management IP.
# Skip LLDP placeholders (no login yet); once promoted they join normal inventory.
managed_ips: set[str] = set()
for ne in db.query(ManagedNE).all():
if _is_lldp_placeholder_ne(ne):
continue
ip = str(ne.ip_address or "").strip()
if ip:
managed_ips.add(ip)
targets.append(
{
"ne_id": ne.id,
"ume_ne_id": "",
"ne_name": ne.name or "",
"ne_ip": ne.ip_address or "",
"vendor": ne.vendor or "",
"device_type": ne.device_type or "",
}
)
targets.append(_managed_target_dict(ne))
for ume in db.query(UmeInventoryNE).all():
uid = str(ume.ne_id or "").strip()
if not uid:
@ -173,19 +184,10 @@ def _resolve_scan_targets(
if mid in seen:
continue
ne = db.get(ManagedNE, mid)
if ne is None:
if ne is None or _is_lldp_placeholder_ne(ne):
continue
seen.add(mid)
targets.append(
{
"ne_id": ne.id,
"ume_ne_id": "",
"ne_name": ne.name or "",
"ne_ip": ne.ip_address or "",
"vendor": ne.vendor or "",
"device_type": ne.device_type or "",
}
)
targets.append(_managed_target_dict(ne))
for uid in ume_ids:
key = f"ume:{uid}"
if key in seen:
@ -206,17 +208,10 @@ def _resolve_scan_targets(
for mid in list(filter_ids):
ne = db.get(ManagedNE, mid)
if ne is not None:
targets.append(
{
"ne_id": ne.id,
"ume_ne_id": "",
"ne_name": ne.name or "",
"ne_ip": ne.ip_address or "",
"vendor": ne.vendor or "",
"device_type": ne.device_type or "",
}
)
filter_ids.discard(mid)
if _is_lldp_placeholder_ne(ne):
continue
targets.append(_managed_target_dict(ne))
for uid in list(filter_ids):
row = _ume_target_dict(db, uid, default_profile)
if row is not None:

View file

@ -7,6 +7,7 @@ from datetime import datetime, timedelta
from uuid import uuid4
from netx_api.db import Base, SessionLocal, engine
from netx_api.device_types import LLDP_DISCOVERED_NE_SOURCE
from netx_api.lldp_collect_schemas import LldpCollectPolicyUpdate
from netx_api.lldp_collect_service import (
build_discover_request,
@ -255,6 +256,14 @@ class LldpCollectTests(unittest.TestCase):
device_type="cisco_ios",
ip_address="10.20.30.1",
)
placeholder = ManagedNE(
id=f"ph-{suffix}",
name=f"PH-{suffix}",
vendor="Other",
device_type="generic",
ip_address="",
source=LLDP_DISCOVERED_NE_SOURCE,
)
ume_only = UmeInventoryNE(
ne_id=f"u-{suffix}",
ne_name=f"U-{suffix}",
@ -271,6 +280,7 @@ class LldpCollectTests(unittest.TestCase):
ne_type="ZXCTN",
)
self.db.add(managed)
self.db.add(placeholder)
self.db.add(ume_only)
self.db.add(ume_dup)
self.db.commit()
@ -280,9 +290,11 @@ class LldpCollectTests(unittest.TestCase):
self.assertIn((managed.id, ""), ids)
self.assertIn((ume_only.ne_id, ume_only.ne_id), ids)
self.assertNotIn((ume_dup.ne_id, ume_dup.ne_id), ids)
self.assertNotIn((placeholder.id, ""), ids)
# Cleanup shared DB rows created by this test.
self.db.delete(managed)
self.db.delete(placeholder)
self.db.delete(ume_only)
self.db.delete(ume_dup)
self.db.commit()