diff --git a/netx_api/topology_discover_common.py b/netx_api/topology_discover_common.py index 02cf107..b6110cc 100644 --- a/netx_api/topology_discover_common.py +++ b/netx_api/topology_discover_common.py @@ -137,7 +137,12 @@ def _resolve_scan_targets( default_profile = get_default_profile(db) targets: list[dict[str, str]] = [] if scope == "all_inventory": + # Managed inventory first; then UME NEs not already covered by the same management IP. + managed_ips: set[str] = set() for ne in db.query(ManagedNE).all(): + ip = str(ne.ip_address or "").strip() + if ip: + managed_ips.add(ip) targets.append( { "ne_id": ne.id, @@ -148,6 +153,16 @@ def _resolve_scan_targets( "device_type": ne.device_type or "", } ) + for ume in db.query(UmeInventoryNE).all(): + uid = str(ume.ne_id or "").strip() + if not uid: + continue + ip = str(ume.ip_address or "").strip() + if ip and ip in managed_ips: + continue + row = _ume_target_dict(db, uid, default_profile) + if row is not None: + targets.append(row) return targets managed_ids = [str(x).strip() for x in (body.managed_ne_ids or []) if str(x).strip()] diff --git a/netx_api/topology_schemas.py b/netx_api/topology_schemas.py index 3dbcb6a..835eb3c 100644 --- a/netx_api/topology_schemas.py +++ b/netx_api/topology_schemas.py @@ -74,7 +74,7 @@ class FabricNeighborhoodOut(BaseModel): class FabricDiscoverRequest(BaseModel): """Start LLDP discovery into fabric (no CDP).""" - scope: str = Field(default="ne_ids", description="all_inventory | ne_ids") + scope: str = Field(default="ne_ids", description="all_inventory (managed+UME) | ne_ids") ne_ids: list[str] = Field( default_factory=list, description="Legacy mixed ids (managed first, then ume). Prefer managed_ne_ids/ume_ne_ids.", diff --git a/tests/test_lldp_collect.py b/tests/test_lldp_collect.py index 8f33631..ee2f780 100644 --- a/tests/test_lldp_collect.py +++ b/tests/test_lldp_collect.py @@ -242,6 +242,51 @@ class LldpCollectTests(unittest.TestCase): self.assertEqual(job.status, "failed") self.assertIn("running_stale_timeout", job.error or "") + def test_all_inventory_includes_ume(self) -> None: + from netx_api.models import UmeInventoryNE + from netx_api.topology_discover_common import _resolve_scan_targets + from netx_api.topology_schemas import FabricDiscoverRequest + + suffix = uuid4().hex[:8] + managed = ManagedNE( + id=f"m-{suffix}", + name=f"M-{suffix}", + vendor="Cisco", + device_type="cisco_ios", + ip_address="10.20.30.1", + ) + ume_only = UmeInventoryNE( + ne_id=f"u-{suffix}", + ne_name=f"U-{suffix}", + ip_address="10.20.30.2", + vendor="ZTE", + ne_type="ZXCTN", + ) + # Same IP as managed — should be skipped to avoid double SSH. + ume_dup = UmeInventoryNE( + ne_id=f"udup-{suffix}", + ne_name=f"UDup-{suffix}", + ip_address="10.20.30.1", + vendor="ZTE", + ne_type="ZXCTN", + ) + self.db.add(managed) + self.db.add(ume_only) + self.db.add(ume_dup) + self.db.commit() + + targets = _resolve_scan_targets(self.db, FabricDiscoverRequest(scope="all_inventory")) + ids = {(t.get("ne_id"), t.get("ume_ne_id")) for t in targets} + 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) + + # Cleanup shared DB rows created by this test. + self.db.delete(managed) + self.db.delete(ume_only) + self.db.delete(ume_dup) + self.db.commit() + if __name__ == "__main__": unittest.main() diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index 8e7c126..64b1f06 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -287,7 +287,7 @@ const en = { concurrency: "Concurrency", historyKeep: "Jobs to keep", scope: "Scope", - scopeAll: "All managed NEs", + scopeAll: "All NEs (managed + UME)", scopeSelected: "Selected NEs", savePolicy: "Save policy", selectedCount: "{{count}} selected", diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index c3a8593..9aafbfa 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -284,7 +284,7 @@ const zh = { concurrency: "并发", historyKeep: "任务保留数", scope: "范围", - scopeAll: "全部纳管网元", + scopeAll: "全部网元(纳管+UME)", scopeSelected: "指定网元", savePolicy: "保存策略", selectedCount: "已选 {{count}} 台",