- POST /snmp-profiles/parse-mib: upload MIB file, subprocess-call tod-mib-parser, return OID tree JSON
- POST /snmp-profiles/{id}/test: test profile connectivity via NATS discovery probe to poller
- New snmp_proxy service module following routeros_proxy.py lazy NATS pattern
- Pydantic schemas: MIBParseResponse, ProfileTestRequest, ProfileTestResponse, ProfileTestOIDResult
- MIB_PARSER_PATH config setting with /app/tod-mib-parser default
- MIB parse errors return 422, not 500; temp file cleanup in finally block
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
113 lines
3.5 KiB
Python
113 lines
3.5 KiB
Python
"""SNMP proxy via NATS request-reply.
|
|
|
|
Sends SNMP discovery/test requests to the Go poller's DiscoveryResponder
|
|
subscription (device.discover.snmp) and returns structured response data.
|
|
|
|
Used by:
|
|
- SNMP profile test endpoint (verify device connectivity with credentials)
|
|
"""
|
|
|
|
import json
|
|
import logging
|
|
from typing import Any, Optional
|
|
|
|
import nats
|
|
import nats.aio.client
|
|
|
|
from app.config import settings
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Module-level NATS connection (lazy initialized)
|
|
_nc: nats.aio.client.Client | None = None
|
|
|
|
|
|
async def _get_nats() -> nats.aio.client.Client:
|
|
"""Get or create a NATS connection for SNMP proxy requests."""
|
|
global _nc
|
|
if _nc is None or _nc.is_closed:
|
|
_nc = await nats.connect(settings.NATS_URL)
|
|
logger.info("SNMP proxy NATS connection established")
|
|
return _nc
|
|
|
|
|
|
async def snmp_discover(
|
|
ip_address: str,
|
|
snmp_port: int = 161,
|
|
snmp_version: str = "v2c",
|
|
community: Optional[str] = None,
|
|
security_level: Optional[str] = None,
|
|
username: Optional[str] = None,
|
|
auth_protocol: Optional[str] = None,
|
|
auth_passphrase: Optional[str] = None,
|
|
priv_protocol: Optional[str] = None,
|
|
priv_passphrase: Optional[str] = None,
|
|
) -> dict[str, Any]:
|
|
"""Send an SNMP discovery probe to a device via the Go poller.
|
|
|
|
Builds a DiscoveryRequest payload matching the Go DiscoveryRequest struct
|
|
and sends it to the poller's DiscoveryResponder via NATS request-reply.
|
|
|
|
Args:
|
|
ip_address: Target device IP address.
|
|
snmp_port: SNMP port (default 161).
|
|
snmp_version: "v1", "v2c", or "v3".
|
|
community: Community string for v1/v2c.
|
|
security_level: SNMPv3 security level.
|
|
username: SNMPv3 username.
|
|
auth_protocol: SNMPv3 auth protocol (e.g. "SHA").
|
|
auth_passphrase: SNMPv3 auth passphrase.
|
|
priv_protocol: SNMPv3 privacy protocol (e.g. "AES").
|
|
priv_passphrase: SNMPv3 privacy passphrase.
|
|
|
|
Returns:
|
|
{"sys_object_id": str, "sys_descr": str, "sys_name": str, "error": str|None}
|
|
"""
|
|
nc = await _get_nats()
|
|
|
|
payload: dict[str, Any] = {
|
|
"ip_address": ip_address,
|
|
"snmp_port": snmp_port,
|
|
"snmp_version": snmp_version,
|
|
}
|
|
|
|
# v1/v2c credentials
|
|
if community is not None:
|
|
payload["community"] = community
|
|
|
|
# v3 credentials
|
|
if security_level is not None:
|
|
payload["security_level"] = security_level
|
|
if username is not None:
|
|
payload["username"] = username
|
|
if auth_protocol is not None:
|
|
payload["auth_protocol"] = auth_protocol
|
|
if auth_passphrase is not None:
|
|
payload["auth_passphrase"] = auth_passphrase
|
|
if priv_protocol is not None:
|
|
payload["priv_protocol"] = priv_protocol
|
|
if priv_passphrase is not None:
|
|
payload["priv_passphrase"] = priv_passphrase
|
|
|
|
try:
|
|
reply = await nc.request(
|
|
"device.discover.snmp",
|
|
json.dumps(payload).encode(),
|
|
timeout=10.0,
|
|
)
|
|
return json.loads(reply.data)
|
|
except nats.errors.TimeoutError:
|
|
return {"error": "Device unreachable or SNMP timeout"}
|
|
except Exception as exc:
|
|
logger.error("SNMP discovery NATS request failed for %s: %s", ip_address, exc)
|
|
return {"error": str(exc)}
|
|
|
|
|
|
async def close() -> None:
|
|
"""Close the NATS connection. Called on application shutdown."""
|
|
global _nc
|
|
if _nc and not _nc.is_closed:
|
|
await _nc.drain()
|
|
_nc = None
|
|
logger.info("SNMP proxy NATS connection closed")
|