Files
OrbitWard/backend/app/api/discovery.py
T
2026-05-26 16:34:10 -06:00

129 lines
4.6 KiB
Python

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from app.api.credentials import SNMP_CREDENTIAL_TYPE
from app.auth.dependencies import require_role
from app.core.secrets import decrypt_secret
from app.db.session import get_db
from app.models import Credential, User
from app.schemas.core import (
SnmpDiscoveredInterfaceRead,
SnmpDiscoveryItemRead,
SnmpDiscoveryRead,
SnmpDiscoveryRequest,
)
from app.services.snmp import DiscoveredSnmpDevice, SnmpCredential, SnmpDiscoveryError, discover_snmp_device
router = APIRouter(prefix="/discovery", tags=["discovery"])
@router.post("/snmp", response_model=SnmpDiscoveryRead)
def discover_snmp(
payload: SnmpDiscoveryRequest,
_: User = Depends(require_role("admin")),
db: Session = Depends(get_db),
) -> SnmpDiscoveryRead:
profile = db.get(Credential, payload.credential_profile_id)
if profile is None or profile.credential_type != SNMP_CREDENTIAL_TYPE:
raise HTTPException(status_code=404, detail="SNMP credential profile not found")
extra = dict(profile.extra or {})
version = str(extra.get("version") or "2c")
if version != "2c":
raise HTTPException(status_code=400, detail="SNMP discovery currently supports SNMPv2c profiles")
community = decrypt_secret(profile.encrypted_secret)
if not community:
raise HTTPException(status_code=400, detail="SNMP credential profile has no usable community string")
credential = SnmpCredential(
community=community,
port=int(extra.get("port") or 161),
timeout_seconds=int(extra.get("timeout_seconds") or 5),
retries=int(extra.get("retries") or 1),
)
try:
discovered = discover_snmp_device(payload.host, credential)
except SnmpDiscoveryError as exc:
raise HTTPException(status_code=502, detail="SNMP discovery failed") from exc
return _discovery_to_read(payload.credential_profile_id, discovered)
def _discovery_to_read(credential_profile_id: int, discovered: DiscoveredSnmpDevice) -> SnmpDiscoveryRead:
interfaces = [
SnmpDiscoveredInterfaceRead(
index=interface.index,
name=interface.name,
description=interface.description,
admin_status=interface.admin_status,
oper_status=interface.oper_status,
speed_bps=interface.speed_bps,
)
for interface in discovered.interfaces
]
return SnmpDiscoveryRead(
host=discovered.host,
credential_profile_id=credential_profile_id,
profile_key=discovered.profile_key,
profile_name=discovered.profile_name,
capabilities=discovered.capabilities,
device_name=discovered.device_name,
description=discovered.description,
uptime_seconds=discovered.uptime_seconds,
interfaces=interfaces,
monitorable_items=_monitorable_items(discovered),
)
def _monitorable_items(discovered: DiscoveredSnmpDevice) -> list[SnmpDiscoveryItemRead]:
items = []
if discovered.uptime_seconds is not None:
items.append(
SnmpDiscoveryItemRead(
item_id="device.uptime",
item_type="device_uptime",
group="Device Health",
label="Device uptime",
unit="seconds",
)
)
items.extend(
SnmpDiscoveryItemRead(
item_id=item.item_id,
item_type=item.item_type,
group=item.group,
label=item.label,
unit=item.unit,
)
for item in discovered.health_items
)
for interface in discovered.interfaces:
group = f"Interface {interface.name}"
item_prefix = f"interface.{interface.index}"
items.extend(
[
SnmpDiscoveryItemRead(
item_id=f"{item_prefix}.status",
item_type="interface_status",
group=group,
label=f"{interface.name} status",
),
SnmpDiscoveryItemRead(
item_id=f"{item_prefix}.traffic",
item_type="interface_traffic",
group=group,
label=f"{interface.name} traffic",
unit="bps",
),
SnmpDiscoveryItemRead(
item_id=f"{item_prefix}.errors",
item_type="interface_errors",
group=group,
label=f"{interface.name} errors and discards",
unit="count",
),
]
)
return items