Servicio monitoring · provisioning — auto-provisionamiento de objetivos SNMP
Descripción
Módulo centralizado de auto-provisionamiento del Observatory: encapsula la lógica de descubrir DeviceProfile sin MonitoringTarget asociado y crear los objetivos correspondientes (idempotente mediante bulk_create(ignore_conflicts=True)).
Antes: la lógica estaba duplicada (~110 LOC casi idéntica) en las 4 vistas HTMX del Observatory (wireless_view, observatory_view, ups_view, signage_view). Ahora vive aquí, permitiendo que el caller gate la escritura detrás de observatory:edit.
Archivo: monitoring/services/provisioning.py (89 LOC públicas)
Introducido en: commit f263a03 (sesión s108, auditoría sa6)
API Pública
provision_profile_targets(org, profiles, scope) → None
Auto-provisiona un MonitoringTarget para cada entrada en profiles que:
- Tenga un
ip_addressválido (no nulo). - No tenga ya
linked_monitoring_target.
Parámetros:
org: Organization— organización propietaria de los objetivos creados (non-null).profiles: List[DeviceProfile]— perfiles de dispositivo a vincular.scope: str— alcance del objetivo ('wireless','ups','signage','network'). Se usa para filtrar objetivos existentes y asignar a los nuevos.
Comportamiento:
- Busca objetivos existentes en la org + scope con un único
filter(organization=org, scope=scope). - Itera los perfiles: para cada uno sin
linked_monitoring_target, crea un nuevoMonitoringTargetcon:ip_addressdel perfil.name= hostname del perfil o IP en fallback.ping_enabled=True.snmp_enabled= verdadero solo si el perfil tiene credenciales SNMP.http_enabled=False.enabled=True.config= dict SNMP compilado porbuild_snmp_config(profile).device= device vinculado del perfil (si existe).
- Persiste con
bulk_create(new_targets, ignore_conflicts=True)— idempotente: si el objetivo ya existe (por una carrera concurrente), se ignora sin error. - Re-busca objetivos recién creados y vincula sus PKs (asignado por la BD) a los perfiles.
- Actualiza todos los perfiles con
bulk_update(profiles_to_link, ["linked_monitoring_target"]).
Fuente de datos:
- Input:
DeviceProfile(modelonetwork.models.DeviceProfile) - Escribe:
MonitoringTarget(modelomonitoring.models.MonitoringTarget) - Lee: existentes de
MonitoringTarget(single scan, O(n) en memoria)
Restricciones de seguridad:
- DEBE llamarse solo si el usuario tiene permiso
observatory:edit(no validado aquí; responsabilidad del caller, típicamentemonitoring/views.py). orgdebe ser no-nulo (valida antes de llamar).
Idempotencia: sí. Múltiples llamadas sucesivas con los mismos perfiles y scope no crean duplicados; perfil con linked_monitoring_target ya seteado se salta.
build_snmp_config(profile) → dict
Arma el dict de configuración SNMP desde las credenciales de un DeviceProfile.
Parámetros:
profile: DeviceProfile— perfil a convertir.
Devuelve: dict con claves:
snmp_community(si existe en el perfil).snmp_version,snmp_v3_username,snmp_v3_auth_protocol,snmp_v3_auth_key,snmp_v3_priv_protocol,snmp_v3_priv_key(si v3 y credenciales presentes).
Propósito: centralizar la compilación de config para evitar duplication entre vistas y hacer más fácil futuros cambios (p.ej., secretos cifrados, validación de claves).
Helpers privados
_profile_port_map(interno): mapa de alcance → puertos por defecto del perfil (usado antes, ahora centralizado aquí si se reutiliza entre vistas).
Decisiones de diseño
-
Idempotencia mediante
bulk_create(..., ignore_conflicts=True): si dos requests concurrentes detectan el mismo perfil sin objetivo, ambos crean, pero solo uno persiste (basado en índice único(organization, ip_address, scope)). El otro se ignora sin error. -
Escrituras en dos queries: primero
bulk_create, luego re-scan ybulk_update. Esto es más eficiente que un loop desave()por perfil (lo que ocurría antes en las vistas). -
Vinculación de PKs post-bulk: tras
bulk_create(..., ignore_conflicts=True), los PKs de nuevos objetivos no están siempre asignados (depende del backend). Se re-buscan confilter(organization=org, scope=scope, ip_address__in=created_ips)y se vinculan a los perfiles. -
Caller es responsable del gate de permisos: este servicio NO valida
observatory:edit. Las vistas lo validan antes de llamar. Esto mantiene el módulo enfocado en la lógica de provisión.
Contexto: Broken Access Control auditado (sa6)
Antes de este cambio, cada una de las 4 vistas hacía if user.is_authenticated: (sin gate de rol) y luego llamaba a la lógica de provisión inline. Un usuario readonly podía provocar escrituras abriendo la página.
Ahora: la lógica de provisión está aquí, y el caller (vistas) valida has_permission(user, "observatory", "edit") antes de llamar. El modelo refuerza la garantía: la escritura no puede ocurrir sin permiso.
Véase también
- [[feature—monitoring—auditoria-suprema-sa6-observatory-realtime]]
- [[entity—monitoring—view—wireless]]
- [[entity—monitoring—view—observatory]]
- [[concept—security—role-based-access-control]]
- [[concept—saas—multi-tenancy]]