Add SNMP monitor collection

This commit is contained in:
Keith Smith
2026-05-24 00:44:02 -06:00
parent bd6c508c94
commit fe7157fdad
7 changed files with 650 additions and 15 deletions
+45 -4
View File
@@ -7,11 +7,12 @@ from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session
import httpx
from app.collectors.snmp import SnmpCheckConfig, SnmpCheckResult, run_snmp_check
from app.collectors.website import WebsiteCheckConfig, run_website_check
from app.collectors.network import PingCheckConfig, TcpCheckConfig, run_ping_check, run_tcp_check
from app.config import settings
from app.db import session_scope
from app.models import AlertRule, Asset, CheckResult, Incident, Monitor, NotificationChannel
from app.models import AlertRule, Asset, CheckResult, Credential, Incident, Metric, Monitor, NotificationChannel
from app.secrets import decrypt_secret
logger = logging.getLogger(__name__)
@@ -47,7 +48,7 @@ class Scheduler:
def _load_due_monitors(self, db: Session) -> list[Monitor]:
now = datetime.now(UTC)
monitors = db.scalars(
select(Monitor).where(Monitor.monitor_type.in_(["http", "ping", "tcp"])).order_by(Monitor.id).limit(50)
select(Monitor).where(Monitor.monitor_type.in_(["http", "ping", "tcp", "snmp"])).order_by(Monitor.id).limit(50)
).all()
due: list[Monitor] = []
for monitor in monitors:
@@ -60,7 +61,7 @@ class Scheduler:
return due
async def _run_monitor(self, db: Session, monitor: Monitor) -> None:
result = await self._collect_monitor_result(monitor)
result = await self._collect_monitor_result(db, monitor)
now = datetime.now(UTC)
monitor.status = result.status
@@ -76,6 +77,18 @@ class Scheduler:
)
db.flush()
for metric in getattr(result, "metrics", []):
db.add(
Metric(
monitor_id=monitor.id,
name=metric.name,
value=metric.value,
unit=metric.unit,
observed_at=now,
)
)
db.flush()
if monitor.asset_id is not None:
asset = db.get(Asset, monitor.asset_id)
if asset is not None:
@@ -87,7 +100,7 @@ class Scheduler:
logger.info("Checked %s: %s (%s ms)", monitor.name, result.status, result.response_time_ms)
async def _collect_monitor_result(self, monitor: Monitor):
async def _collect_monitor_result(self, db: Session, monitor: Monitor):
if monitor.monitor_type == "http":
config = WebsiteCheckConfig(
url=monitor.target,
@@ -115,8 +128,36 @@ class Scheduler:
)
return await run_tcp_check(config)
if monitor.monitor_type == "snmp":
return await self._collect_snmp_monitor_result(db, monitor)
raise ValueError(f"Unsupported monitor type: {monitor.monitor_type}")
async def _collect_snmp_monitor_result(self, db: Session, monitor: Monitor) -> SnmpCheckResult:
profile_id = monitor.config.get("credential_profile_id")
if not isinstance(profile_id, int):
return SnmpCheckResult(status="down", response_time_ms=None, message="SNMP credential profile is not configured")
profile = db.get(Credential, profile_id)
if profile is None or profile.credential_type != "snmp":
return SnmpCheckResult(status="down", response_time_ms=None, message="SNMP credential profile was not found")
community = decrypt_secret(profile.encrypted_secret)
if not community:
return SnmpCheckResult(status="down", response_time_ms=None, message="SNMP credential profile has no usable community string")
extra = dict(profile.extra or {})
config = SnmpCheckConfig(
host=monitor.target,
community=community,
item_id=str(monitor.config.get("item_id") or ""),
item_type=str(monitor.config.get("item_type") or ""),
port=int(extra.get("port") or 161),
timeout_seconds=float(extra.get("timeout_seconds") or 5),
retries=int(extra.get("retries") or 1),
)
return await run_snmp_check(config)
async def _evaluate_rule(self, db: Session, monitor: Monitor, rule: AlertRule, now: datetime, message: str) -> None:
open_incident = db.scalar(
select(Incident).where(