diff --git a/backend/app/services/device_discovery.py b/backend/app/services/device_discovery.py index 58a51f6..1c760c6 100644 --- a/backend/app/services/device_discovery.py +++ b/backend/app/services/device_discovery.py @@ -96,8 +96,17 @@ class DiscoveredDevice: cls: str -def _parse_config(config_text: str, rack_host: str) -> list[DiscoveredDevice]: - """Extract discovered network devices from a caps config file.""" +def _parse_config(config_text: str, rack_host: str, all_rack_hosts: list[str] | None = None) -> list[DiscoveredDevice]: + """Extract discovered network devices from a caps config file. + + Args: + config_text: Config file content + rack_host: IP of current rack (will be excluded from results) + all_rack_hosts: All rack IPs (will be excluded to avoid confusing rack servers with devices) + """ + if all_rack_hosts is None: + all_rack_hosts = [rack_host] + parser = RawConfigParser(strict=False) parser.read_string(config_text) @@ -137,8 +146,8 @@ def _parse_config(config_text: str, rack_host: str) -> list[DiscoveredDevice]: if not ip: logger.info(f"[ssh_discovery] → IGNORED (not a valid network IP)") continue - if ip == rack_host: - logger.info(f"[ssh_discovery] → IGNORED (matches rack_host {rack_host})") + if ip in all_rack_hosts: + logger.info(f"[ssh_discovery] → IGNORED (is a rack server IP: {ip})") continue if ip in seen_ips: logger.info(f"[ssh_discovery] → IGNORED (duplicate, already seen)") @@ -567,7 +576,9 @@ async def discover_via_ssh( category="embedded", role="other", )) - devices_found = _parse_config(config_text, rack_host) + # Get all rack IPs to exclude them from device discovery + all_rack_ips = [host for _, host, _ in rack_hosts] + devices_found = _parse_config(config_text, rack_host, all_rack_ips) logger.info(f"[ssh_discovery] Found {len(devices_found)} devices from plugins in {rack_host}") for found in devices_found: @@ -585,6 +596,22 @@ async def discover_via_ssh( if existing is not None: changes = [] + + # PROTECT: Don't overwrite embedded (rack) devices with plugin devices! + # If device is currently embedded (a rack), never change it + if existing.category == "embedded": + logger.info(f"[ssh_discovery] {found.ip} ({found.plugin_name}) - SKIPPED: device is rack server (embedded), cannot overwrite with plugin device") + result.skipped += 1 + result.actions.append(DeviceAction( + ip=found.ip, + hostname=existing.hostname, + action="SKIPPED", + reason=f"device is rack server (embedded), cannot overwrite with plugin device", + category=existing.category, + role=existing.role, + )) + continue + if existing.category != found.category and existing.source == "auto_ssh": changes.append(f"category: {existing.category}→{found.category}") existing.category = found.category @@ -592,7 +619,7 @@ async def discover_via_ssh( changes.append(f"role: other→{found.role}") existing.role = found.role # Update hostname if it was never set or still auto-generated - if not existing.hostname or existing.hostname.startswith("rack") or existing.hostname.startswith("lane"): + if not existing.hostname or existing.hostname.startswith("rack") or (existing.hostname.startswith("lane") and not existing.hostname.startswith(f"{external_id}")): if existing.hostname != hostname: changes.append(f"hostname: '{existing.hostname}'→'{hostname}'") existing.hostname = hostname