Switches lib/towerops/snmp.ex over to defdelegate calls into the new submodules introduced in the prior commit. Drops the inline definitions and unused aliases. Final size: 450 lines (was 2742).
450 lines
16 KiB
Elixir
450 lines
16 KiB
Elixir
defmodule Towerops.Snmp do
|
|
@moduledoc """
|
|
The SNMP context.
|
|
|
|
Provides the public API for SNMP discovery and monitoring functionality.
|
|
"""
|
|
|
|
import Ecto.Query
|
|
|
|
alias Towerops.Devices.Device, as: DeviceSchema
|
|
alias Towerops.Repo
|
|
alias Towerops.Snmp.ArpEntries
|
|
alias Towerops.Snmp.Client
|
|
alias Towerops.Snmp.Device
|
|
alias Towerops.Snmp.DiscoveredDevices
|
|
alias Towerops.Snmp.Discovery
|
|
alias Towerops.Snmp.EntityPhysicals
|
|
alias Towerops.Snmp.Interfaces
|
|
alias Towerops.Snmp.IpAddresses
|
|
alias Towerops.Snmp.MacAddresses
|
|
alias Towerops.Snmp.Mempools
|
|
alias Towerops.Snmp.Monitoring
|
|
alias Towerops.Snmp.Neighbors
|
|
alias Towerops.Snmp.PrinterSupplies
|
|
alias Towerops.Snmp.Processors
|
|
alias Towerops.Snmp.Queries
|
|
alias Towerops.Snmp.Sensors
|
|
alias Towerops.Snmp.StorageQueries
|
|
alias Towerops.Snmp.Topology
|
|
alias Towerops.Snmp.Transceivers
|
|
alias Towerops.Snmp.Vlans
|
|
alias Towerops.Snmp.WirelessClients
|
|
|
|
require Logger
|
|
|
|
@doc """
|
|
Tests SNMP connectivity to a device.
|
|
|
|
## Examples
|
|
|
|
iex> test_connection("192.168.1.1", "public", "2c")
|
|
{:ok, "Connection successful"}
|
|
|
|
iex> test_connection("192.168.1.99", "wrong", "2c")
|
|
{:error, :timeout}
|
|
"""
|
|
@spec test_connection(String.t(), String.t(), String.t(), integer()) :: {:ok, String.t()} | {:error, term()}
|
|
def test_connection(ip, community, version, port \\ 161) do
|
|
Client.test_connection(
|
|
ip: ip,
|
|
community: community,
|
|
version: version,
|
|
port: port
|
|
)
|
|
end
|
|
|
|
@doc """
|
|
Runs SNMP discovery for a piece of device.
|
|
|
|
Discovers:
|
|
- Device information (manufacturer, model, firmware)
|
|
- Network interfaces
|
|
- Sensors (temperature, power, fans, etc.)
|
|
|
|
Updates the database with discovered data.
|
|
|
|
## Examples
|
|
|
|
iex> discover_device(device)
|
|
{:ok, %Device{}}
|
|
"""
|
|
@spec discover_device(DeviceSchema.t()) :: {:ok, Device.t()} | {:error, term()}
|
|
def discover_device(%DeviceSchema{} = device) do
|
|
Discovery.discover_device(device)
|
|
end
|
|
|
|
@doc """
|
|
Runs SNMP discovery for all SNMP-enabled devices in an organization.
|
|
|
|
Returns a summary of successful and failed discoveries.
|
|
|
|
## Examples
|
|
|
|
iex> discover_all_for_org(org_id)
|
|
{:ok, %{success: 10, failed: 2, errors: [:timeout, :no_response]}}
|
|
"""
|
|
@spec discover_all_for_org(String.t()) ::
|
|
{:ok, %{enqueued: non_neg_integer(), failed: non_neg_integer(), errors: [term()]}}
|
|
def discover_all_for_org(org_id) do
|
|
Discovery.discover_all(org_id)
|
|
end
|
|
|
|
@doc """
|
|
Creates monitoring checks from discovered SNMP entities.
|
|
|
|
After discovery completes, this function creates Check records for:
|
|
- Each sensor (temperature, voltage, power, etc.)
|
|
- Each interface (operational status monitoring)
|
|
- Each processor (CPU load monitoring)
|
|
- Each storage volume (disk usage monitoring)
|
|
|
|
Checks are automatically enabled and scheduled for execution.
|
|
|
|
## Examples
|
|
|
|
iex> create_checks_from_discovery(device, snmp_device)
|
|
{:ok, %{sensors: 45, interfaces: 12, processors: 2, storage: 5}}
|
|
"""
|
|
def create_checks_from_discovery(%DeviceSchema{} = device, %Device{} = snmp_device) do
|
|
snmp_device = Repo.preload(snmp_device, [:sensors, :interfaces, :processors, :storage, :mempools])
|
|
|
|
initial = %{sensors: 0, interfaces: 0, processors: 0, storage: 0, errors: []}
|
|
|
|
results =
|
|
initial
|
|
|> create_discovered_checks(device, snmp_device.sensors, :sensor, :sensors, &create_sensor_check/2)
|
|
|> create_discovered_checks(device, snmp_device.interfaces, :interface, :interfaces, &create_interface_check/2)
|
|
|> create_discovered_checks(device, snmp_device.processors, :processor, :processors, &create_processor_check/2)
|
|
|> create_discovered_checks(device, snmp_device.storage, :storage, :storage, &create_storage_check/2)
|
|
|
|
Logger.info(
|
|
"Created checks for device #{device.id}: #{results.sensors} sensors, #{results.interfaces} interfaces, #{results.processors} processors, #{results.storage} storage"
|
|
)
|
|
|
|
{:ok, results}
|
|
end
|
|
|
|
# Generic check creator: iterates entities, calls check_fun for each, and
|
|
# tallies successes under success_key while collecting errors tagged with
|
|
# error_tag. Eliminates per-entity copy-pasted reduce blocks.
|
|
defp create_discovered_checks(acc, device, entities, error_tag, success_key, check_fun) do
|
|
Enum.reduce(entities, acc, fn entity, acc ->
|
|
case check_fun.(device, entity) do
|
|
{:ok, _check} -> Map.update!(acc, success_key, &(&1 + 1))
|
|
{:error, reason} -> Map.update!(acc, :errors, &[{error_tag, entity.id, reason} | &1])
|
|
end
|
|
end)
|
|
end
|
|
|
|
defp create_sensor_check(device, sensor) do
|
|
alias Towerops.Monitoring
|
|
|
|
attrs = %{
|
|
organization_id: device.organization_id,
|
|
device_id: device.id,
|
|
name: sensor.sensor_descr,
|
|
check_type: "snmp_sensor",
|
|
source_type: "auto_discovery",
|
|
source_id: sensor.id,
|
|
interval_seconds: 60,
|
|
enabled: sensor.monitored,
|
|
config: %{
|
|
"sensor_type" => sensor.sensor_type,
|
|
"sensor_oid" => sensor.sensor_oid,
|
|
"sensor_unit" => sensor.sensor_unit
|
|
}
|
|
}
|
|
|
|
case Monitoring.create_check(attrs) do
|
|
{:ok, check} ->
|
|
# Schedule first execution
|
|
_ = Monitoring.schedule_check(check)
|
|
{:ok, check}
|
|
|
|
error ->
|
|
error
|
|
end
|
|
end
|
|
|
|
defp create_interface_check(device, interface) do
|
|
alias Towerops.Monitoring
|
|
|
|
name = interface.if_alias || interface.if_name || interface.if_descr || "Interface #{interface.if_index}"
|
|
|
|
attrs = %{
|
|
organization_id: device.organization_id,
|
|
device_id: device.id,
|
|
name: "#{name} Status",
|
|
check_type: "snmp_interface",
|
|
source_type: "auto_discovery",
|
|
source_id: interface.id,
|
|
interval_seconds: 60,
|
|
enabled: interface.monitored,
|
|
config: %{
|
|
"if_index" => interface.if_index,
|
|
"if_descr" => interface.if_descr
|
|
}
|
|
}
|
|
|
|
case Monitoring.create_check(attrs) do
|
|
{:ok, check} ->
|
|
_ = Monitoring.schedule_check(check)
|
|
{:ok, check}
|
|
|
|
error ->
|
|
error
|
|
end
|
|
end
|
|
|
|
defp create_processor_check(device, processor) do
|
|
alias Towerops.Monitoring
|
|
|
|
attrs = %{
|
|
organization_id: device.organization_id,
|
|
device_id: device.id,
|
|
name: processor.description || "Processor #{processor.processor_index}",
|
|
check_type: "snmp_processor",
|
|
source_type: "auto_discovery",
|
|
source_id: processor.id,
|
|
interval_seconds: 60,
|
|
enabled: true,
|
|
config: %{
|
|
"processor_type" => processor.processor_type,
|
|
"processor_index" => processor.processor_index
|
|
}
|
|
}
|
|
|
|
case Monitoring.create_check(attrs) do
|
|
{:ok, check} ->
|
|
_ = Monitoring.schedule_check(check)
|
|
{:ok, check}
|
|
|
|
error ->
|
|
error
|
|
end
|
|
end
|
|
|
|
defp create_storage_check(device, storage) do
|
|
alias Towerops.Monitoring
|
|
|
|
name = storage.description || storage.device_name || "Storage #{storage.storage_index}"
|
|
|
|
attrs = %{
|
|
organization_id: device.organization_id,
|
|
device_id: device.id,
|
|
name: "#{name} Usage",
|
|
check_type: "snmp_storage",
|
|
source_type: "auto_discovery",
|
|
source_id: storage.id,
|
|
interval_seconds: 300,
|
|
enabled: true,
|
|
config: %{
|
|
"storage_type" => storage.storage_type,
|
|
"storage_index" => storage.storage_index
|
|
}
|
|
}
|
|
|
|
case Monitoring.create_check(attrs) do
|
|
{:ok, check} ->
|
|
_ = Monitoring.schedule_check(check)
|
|
{:ok, check}
|
|
|
|
error ->
|
|
error
|
|
end
|
|
end
|
|
|
|
# Device queries
|
|
|
|
@doc """
|
|
Gets the SNMP device for a piece of device.
|
|
|
|
## Examples
|
|
|
|
iex> get_device(device_id)
|
|
%Device{}
|
|
|
|
iex> get_device(nonexistent_id)
|
|
nil
|
|
"""
|
|
@spec get_device(String.t()) :: Device.t() | nil
|
|
def get_device(device_id) do
|
|
Repo.get_by(Device, device_id: device_id)
|
|
end
|
|
|
|
@doc """
|
|
Gets the SNMP device for a device with preloaded associations.
|
|
"""
|
|
def get_device_with_associations(device_id) do
|
|
Device
|
|
|> where([d], d.device_id == ^device_id)
|
|
|> preload([:device, :interfaces, :sensors, :state_sensors, :processors, :storage, :mempools])
|
|
|> Repo.one()
|
|
end
|
|
|
|
## Interface metadata delegates (implementation in `Towerops.Snmp.Interfaces`).
|
|
|
|
defdelegate list_interfaces(device_id), to: Interfaces
|
|
defdelegate list_monitored_interfaces(device_id), to: Interfaces
|
|
defdelegate get_interface(interface_id), to: Interfaces
|
|
defdelegate update_interface(interface, attrs), to: Interfaces
|
|
defdelegate set_manual_capacity(interface_id, capacity_bps), to: Interfaces
|
|
defdelegate clear_manual_capacity(interface_id), to: Interfaces
|
|
|
|
## Sensor metadata delegates (implementation in `Towerops.Snmp.Sensors`).
|
|
|
|
defdelegate list_sensors(device_id), to: Sensors
|
|
defdelegate list_monitored_sensors(device_id), to: Sensors
|
|
defdelegate list_sensors_by_type(device_id), to: Sensors
|
|
defdelegate get_sensor(sensor_id), to: Sensors
|
|
defdelegate update_sensor(sensor, attrs), to: Sensors
|
|
defdelegate update_state_sensor(state_sensor, attrs), to: Sensors
|
|
defdelegate list_state_sensors(device_id), to: Sensors
|
|
|
|
## Sensor / interface time-series query delegates
|
|
## (implementation in `Towerops.Snmp.Queries`).
|
|
|
|
defdelegate get_sensor_readings(sensor_id, opts \\ []), to: Queries
|
|
defdelegate get_latest_sensor_reading(sensor_id), to: Queries
|
|
defdelegate get_latest_sensor_readings_batch(sensor_ids), to: Queries
|
|
defdelegate get_interface_stats(interface_id, opts \\ []), to: Queries
|
|
defdelegate get_latest_interface_stat(interface_id), to: Queries
|
|
defdelegate get_latest_interface_stats_batch(interface_ids), to: Queries
|
|
|
|
## Time-series write delegates (implementation in `Towerops.Snmp.Monitoring`).
|
|
|
|
defdelegate create_sensor_reading(attrs), to: Monitoring
|
|
defdelegate create_sensor_readings_batch(entries), to: Monitoring
|
|
defdelegate create_interface_stat(attrs), to: Monitoring
|
|
defdelegate create_interface_stats_batch(entries), to: Monitoring
|
|
|
|
## Neighbor delegates (implementation in `Towerops.Snmp.Neighbors`).
|
|
|
|
defdelegate list_neighbors(device_id), to: Neighbors
|
|
defdelegate list_neighbors_with_equipment(device_id), to: Neighbors
|
|
defdelegate get_neighbor(neighbor_id), to: Neighbors
|
|
defdelegate upsert_neighbor(attrs), to: Neighbors
|
|
defdelegate delete_stale_neighbors(device_id, cutoff_datetime), to: Neighbors
|
|
defdelegate delete_stale_and_upsert_neighbors(device_id, neighbors, cutoff), to: Neighbors
|
|
|
|
## Discovered device aggregation (implementation in `Towerops.Snmp.DiscoveredDevices`).
|
|
|
|
defdelegate list_discovered_devices_for_organization(organization_id), to: DiscoveredDevices
|
|
|
|
## VLAN delegates (implementation in `Towerops.Snmp.Vlans`).
|
|
|
|
defdelegate list_vlans(snmp_device_id), to: Vlans
|
|
defdelegate get_vlan(vlan_id), to: Vlans
|
|
|
|
## IP address delegates (implementation in `Towerops.Snmp.IpAddresses`).
|
|
|
|
defdelegate list_ip_addresses(snmp_device_id), to: IpAddresses
|
|
defdelegate list_interface_ip_addresses(interface_id), to: IpAddresses
|
|
|
|
## Processor metadata + reading delegates
|
|
## (queries in `Towerops.Snmp.Processors`, reading inserts in
|
|
## `Towerops.Snmp.Monitoring`).
|
|
|
|
defdelegate list_processors(snmp_device_id), to: Processors
|
|
defdelegate get_processor(processor_id), to: Processors
|
|
defdelegate update_processor(processor, attrs), to: Processors
|
|
defdelegate get_processor_readings(processor_id, opts \\ []), to: Processors
|
|
defdelegate create_processor_reading(attrs), to: Monitoring
|
|
defdelegate create_processor_readings_batch(entries), to: Monitoring
|
|
|
|
## Storage metadata + reading delegates
|
|
## (implementation in `Towerops.Snmp.StorageQueries`).
|
|
|
|
defdelegate list_storage(snmp_device_id), to: StorageQueries
|
|
defdelegate get_storage(storage_id), to: StorageQueries
|
|
defdelegate update_storage(storage, attrs), to: StorageQueries
|
|
defdelegate get_storage_readings(storage_id, opts \\ []), to: StorageQueries
|
|
defdelegate get_latest_storage_reading(storage_id), to: StorageQueries
|
|
defdelegate get_latest_storage_readings_batch(storage_ids), to: StorageQueries
|
|
defdelegate create_storage_reading(attrs), to: StorageQueries
|
|
defdelegate create_storage_readings_batch(entries), to: StorageQueries
|
|
|
|
## Mempool metadata + reading delegates
|
|
## (implementation in `Towerops.Snmp.Mempools`).
|
|
|
|
defdelegate list_mempools(snmp_device_id), to: Mempools
|
|
defdelegate update_mempool(mempool, attrs), to: Mempools
|
|
defdelegate get_mempool_readings(mempool_id, opts \\ []), to: Mempools
|
|
defdelegate get_latest_mempool_reading(mempool_id), to: Mempools
|
|
defdelegate get_latest_mempool_readings_batch(mempool_ids), to: Mempools
|
|
defdelegate create_mempool_reading(attrs), to: Mempools
|
|
defdelegate create_mempool_readings_batch(entries), to: Mempools
|
|
|
|
## Entity physical delegates (implementation in `Towerops.Snmp.EntityPhysicals`).
|
|
|
|
defdelegate list_entity_physical(snmp_device_id), to: EntityPhysicals
|
|
defdelegate get_entity_physical(id), to: EntityPhysicals
|
|
defdelegate get_entity_physical_by_class(snmp_device_id, entity_class), to: EntityPhysicals
|
|
defdelegate get_entity_physical_tree(snmp_device_id), to: EntityPhysicals
|
|
defdelegate update_entity_physical(entity, attrs), to: EntityPhysicals
|
|
defdelegate create_entity_physical_readings_batch(entries), to: EntityPhysicals
|
|
|
|
## Transceiver delegates (implementation in `Towerops.Snmp.Transceivers`).
|
|
|
|
defdelegate list_transceivers(snmp_device_id), to: Transceivers
|
|
defdelegate get_transceiver(id), to: Transceivers
|
|
defdelegate get_transceivers_by_type(snmp_device_id, transceiver_type), to: Transceivers
|
|
defdelegate list_transceivers_with_dom(snmp_device_id), to: Transceivers
|
|
defdelegate get_transceiver_with_latest_reading(id), to: Transceivers
|
|
defdelegate update_transceiver(transceiver, attrs), to: Transceivers
|
|
defdelegate create_transceiver_readings_batch(entries), to: Transceivers
|
|
|
|
## Printer supply delegates (implementation in `Towerops.Snmp.PrinterSupplies`).
|
|
|
|
defdelegate list_printer_supplies(snmp_device_id), to: PrinterSupplies
|
|
defdelegate get_printer_supply(id), to: PrinterSupplies
|
|
defdelegate get_printer_supplies_by_type(snmp_device_id, supply_type), to: PrinterSupplies
|
|
defdelegate get_low_printer_supplies(snmp_device_id, threshold_percent), to: PrinterSupplies
|
|
defdelegate update_printer_supply(printer_supply, attrs), to: PrinterSupplies
|
|
|
|
## ARP entry delegates (implementation in `Towerops.Snmp.ArpEntries`).
|
|
|
|
defdelegate list_arp_entries(device_id), to: ArpEntries
|
|
defdelegate get_arp_entry(arp_entry_id), to: ArpEntries
|
|
defdelegate upsert_arp_entry(attrs), to: ArpEntries
|
|
defdelegate delete_stale_arp_entries(device_id, cutoff_datetime), to: ArpEntries
|
|
|
|
defdelegate delete_stale_and_upsert_arp_entries(device_id, arp_entries, interfaces, cutoff),
|
|
to: ArpEntries
|
|
|
|
defdelegate upsert_arp_entries(device_id, arp_entries, interfaces), to: ArpEntries
|
|
|
|
## MAC address delegates (implementation in `Towerops.Snmp.MacAddresses`).
|
|
|
|
defdelegate list_mac_addresses(device_id), to: MacAddresses
|
|
defdelegate upsert_mac_address(attrs), to: MacAddresses
|
|
defdelegate delete_stale_mac_addresses(device_id, cutoff_datetime), to: MacAddresses
|
|
|
|
defdelegate delete_stale_and_upsert_mac_addresses(device_id, mac_entries, interfaces, cutoff),
|
|
to: MacAddresses
|
|
|
|
defdelegate upsert_mac_addresses(device_id, mac_entries, interfaces), to: MacAddresses
|
|
|
|
## Network topology delegate (implementation in `Towerops.Snmp.Topology`).
|
|
|
|
defdelegate get_network_topology(organization_id), to: Topology
|
|
|
|
## Wireless client delegates (implementation in `Towerops.Snmp.WirelessClients`).
|
|
|
|
defdelegate list_wireless_clients(device_id), to: WirelessClients
|
|
defdelegate get_wireless_clients_by_mac(organization_id, mac_addresses), to: WirelessClients
|
|
defdelegate upsert_wireless_client(attrs), to: WirelessClients
|
|
defdelegate upsert_wireless_clients(device_id, organization_id, clients), to: WirelessClients
|
|
defdelegate delete_stale_wireless_clients(device_id, cutoff_datetime), to: WirelessClients
|
|
|
|
defdelegate delete_stale_and_upsert_wireless_clients(device_id, organization_id, clients, cutoff),
|
|
to: WirelessClients
|
|
|
|
defdelegate create_wireless_client_readings_batch(entries), to: WirelessClients
|
|
defdelegate list_weak_signal_clients(organization_id, threshold_dbm \\ -75), to: WirelessClients
|
|
defdelegate list_low_snr_clients(organization_id, threshold_db \\ 15), to: WirelessClients
|
|
defdelegate get_wireless_client_count_by_device(organization_id), to: WirelessClients
|
|
defdelegate list_overloaded_aps(organization_id, threshold \\ 50), to: WirelessClients
|
|
defdelegate get_wireless_client_last_seen(organization_id, mac_address), to: WirelessClients
|
|
end
|