diff --git a/backend/app/services/device_discovery.py b/backend/app/services/device_discovery.py index 9e35b68..266b6f1 100644 --- a/backend/app/services/device_discovery.py +++ b/backend/app/services/device_discovery.py @@ -133,10 +133,10 @@ def _parse_config(config_text: str, rack_host: str, all_rack_hosts: list[str] | logger.info(f"[ssh_discovery] Plugin '{plugin_name}' (cls={cls})") - # Collect all URI-like values in this section - uri_fields = ("uri", "frame_uri", "host") + # uri/frame_uri/host → actual device (camera, io_board, etc.) + device_uri_fields = ("uri", "frame_uri", "host") found_ip_in_plugin = False - for field_name in uri_fields: + for field_name in device_uri_fields: raw = parser.get(section, field_name, fallback=None) if not raw: logger.info(f"[ssh_discovery] {field_name}: (not set)") @@ -164,9 +164,42 @@ def _parse_config(config_text: str, rack_host: str, all_rack_hosts: list[str] | )) found_ip_in_plugin = True + # stream_uri → RTSP server (separate VM, not the camera itself) + stream_raw = parser.get(section, "stream_uri", fallback=None) + if stream_raw: + logger.info(f"[ssh_discovery] stream_uri='{stream_raw}'") + stream_ip = _extract_ip(stream_raw) + if stream_ip and stream_ip not in all_rack_hosts and stream_ip not in seen_ips: + seen_ips.add(stream_ip) + logger.info(f"[ssh_discovery] → EXTRACTED {stream_ip} as RTSP server (category=vm)") + discovered.append(DiscoveredDevice( + ip=stream_ip, + category="vm", + role="other", + plugin_name=f"{plugin_name}_rtsp", + cls="rtsp_server", + )) + found_ip_in_plugin = True + if not found_ip_in_plugin: logger.info(f"[ssh_discovery] → No network devices found in this plugin") + # Parse [caps.voice] section for Asterisk server + voice_addr = parser.get("caps.voice", "server_addr", fallback=None) + if voice_addr: + logger.info(f"[ssh_discovery] [caps.voice] server_addr='{voice_addr}'") + voice_ip = _extract_ip(f"sip://{voice_addr}") or (voice_addr.strip() if re.match(r"^\d{1,3}(\.\d{1,3}){3}$", voice_addr.strip()) else None) + if voice_ip and voice_ip not in all_rack_hosts and voice_ip not in seen_ips: + seen_ips.add(voice_ip) + logger.info(f"[ssh_discovery] → EXTRACTED {voice_ip} as Asterisk (category=vm)") + discovered.append(DiscoveredDevice( + ip=voice_ip, + category="vm", + role="other", + plugin_name="asterisk", + cls="asterisk", + )) + return discovered @@ -505,6 +538,59 @@ async def discover_via_ssh( rack_id_mapping[ext_id] = new_rack.id logger.info(f"[ssh_discovery] Created rack entry '{ext_id}' (id={new_rack.id})") + # Step 1b: Add main CAPS server and DB server as devices + infra_devices = [] + if obj.server_ip: + infra_devices.append((obj.server_ip, "main_server", "caps_server")) + if obj.db_host and obj.db_host != obj.server_ip: + infra_devices.append((obj.db_host, "vm", "db_server")) + + for infra_ip, infra_category, infra_hostname in infra_devices: + try: + existing_infra = (await db.execute( + select(Device).where( + Device.object_id == obj.id, + Device.ip == infra_ip, + ) + )).scalar_one_or_none() + if existing_infra is None: + db.add(Device( + object_id=obj.id, + ip=infra_ip, + hostname=infra_hostname, + category=infra_category, + role="other", + source="auto_ssh", + location="server_room", + )) + logger.info(f"[ssh_discovery] {infra_ip} ({infra_hostname}) - ADDED: category={infra_category}, location=server_room, source=auto_ssh") + result.created += 1 + result.actions.append(DeviceAction( + ip=infra_ip, + hostname=infra_hostname, + action="ADDED", + reason=f"object infrastructure device ({infra_category})", + category=infra_category, + role="other", + )) + else: + logger.info(f"[ssh_discovery] {infra_ip} ({infra_hostname}) - SKIPPED: already exists (category={existing_infra.category}, hostname={existing_infra.hostname})") + result.skipped += 1 + result.actions.append(DeviceAction( + ip=infra_ip, + hostname=existing_infra.hostname or infra_hostname, + action="SKIPPED", + reason=f"already exists (category={existing_infra.category})", + category=existing_infra.category, + role=existing_infra.role, + )) + except Exception as exc: + logger.error(f"[ssh_discovery] {infra_ip} ({infra_hostname}) - ERROR: {exc}") + result.errors.append(f"Error upserting infra device {infra_ip}: {exc}") + + if infra_devices: + await db.flush() + for external_id, rack_host, rack_name in rack_hosts: # Resolve config_path override from the embedded device's connection_params existing_rack_pc = (await db.execute( @@ -552,6 +638,7 @@ async def discover_via_ssh( category="embedded", role="other", source="auto_ssh", + rack_id=rack_id_mapping.get(external_id), )) logger.info(f"[ssh_discovery] {rack_host} ({rack_hostname}) - ADDED: category=main_server, role=other, hostname={rack_hostname}, source=auto_ssh") result.created += 1 @@ -564,16 +651,32 @@ async def discover_via_ssh( role="other", )) else: - logger.info(f"[ssh_discovery] {rack_host} ({existing_rack.hostname}) - SKIPPED: rack already exists in devices") - result.skipped += 1 - result.actions.append(DeviceAction( - ip=rack_host, - hostname=existing_rack.hostname, - action="SKIPPED", - reason="rack already exists in devices", - category=existing_rack.category, - role=existing_rack.role, - )) + rack_changes = [] + if existing_rack.rack_id is None and rack_id_mapping.get(external_id) is not None: + existing_rack.rack_id = rack_id_mapping[external_id] + rack_changes.append(f"rack_id: None→{existing_rack.rack_id}") + if rack_changes: + logger.info(f"[ssh_discovery] {rack_host} ({existing_rack.hostname}) - UPDATED: {', '.join(rack_changes)}") + result.updated += 1 + result.actions.append(DeviceAction( + ip=rack_host, + hostname=existing_rack.hostname, + action="UPDATED", + reason="; ".join(rack_changes), + category=existing_rack.category, + role=existing_rack.role, + )) + else: + logger.info(f"[ssh_discovery] {rack_host} ({existing_rack.hostname}) - SKIPPED: rack already exists in devices") + result.skipped += 1 + result.actions.append(DeviceAction( + ip=rack_host, + hostname=existing_rack.hostname, + action="SKIPPED", + reason="rack already exists in devices", + category=existing_rack.category, + role=existing_rack.role, + )) except Exception as exc: logger.error(f"[ssh_discovery] {rack_host} - ERROR adding rack: {exc}") result.errors.append(f"Error adding rack {rack_host}: {exc}")