feat: add 5 new SNMP vendor modules

Add vendor-specific SNMP handling for:
- Aerohive (HiveOS wireless APs): client counts, CPU/memory, temperature
- Baicells (LTE CPE): RSRP, RSRQ, SINR, RSSI, tx power, temperature
- Cambium PTP (point-to-point radios): RSL, tx power, data rates, temperature
- Cisco WAP (standalone APs): clients, channel utilization, noise floor
- Netonix (WISP switches): temperatures, fan speed, PSU voltages, PoE power

Each module implements the Vendor behaviour with:
- profile_names/0 for profile matching
- detect_hardware/1 for model identification
- discover_wireless_sensors/1 for sensor discovery
- wireless_oid_defs/0 for debug data collection

All modules registered in vendor registry with comprehensive tests.
This commit is contained in:
Graham McIntire 2026-01-22 09:42:48 -06:00
parent 0a4482f974
commit 7c2014842d
No known key found for this signature in database
11 changed files with 1132 additions and 12 deletions

View file

@ -0,0 +1,95 @@
defmodule Towerops.Snmp.Profiles.Vendors.Aerohive do
@moduledoc """
Aerohive/Extreme Networks HiveOS device-specific SNMP handling.
Supports Aerohive wireless access points running HiveOS or IQ Engine.
Supported devices:
- AP121
- AP130
- AP230
- AP250
- AP550
- AP650/AP650X
"""
@behaviour Towerops.Snmp.Profiles.Vendors.Vendor
alias Towerops.Snmp.Client
alias Towerops.Snmp.Profiles.Vendors.Vendor
# Aerohive Enterprise OID: 1.3.6.1.4.1.26928
@model_oid "1.3.6.1.4.1.26928.1.1.1.1.0"
@impl true
def profile_names do
["hiveos-wireless"]
end
@impl true
def detect_hardware(client_opts) do
case Client.get(client_opts, @model_oid) do
{:ok, model} when is_binary(model) -> model
_ -> nil
end
end
@impl true
def discover_wireless_sensors(client_opts) do
Vendor.fetch_sensors(wireless_oid_defs(), client_opts)
end
@impl true
def wireless_oid_defs do
[
# Total Associated Clients
%{
oid: "1.3.6.1.4.1.26928.1.1.1.2.1.1.0",
sensor_type: "clients",
sensor_descr: "Associated Clients",
sensor_unit: "",
sensor_divisor: 1
},
# 2.4GHz Clients
%{
oid: "1.3.6.1.4.1.26928.1.1.1.2.1.2.0",
sensor_type: "clients",
sensor_descr: "2.4GHz Clients",
sensor_unit: "",
sensor_divisor: 1
},
# 5GHz Clients
%{
oid: "1.3.6.1.4.1.26928.1.1.1.2.1.3.0",
sensor_type: "clients",
sensor_descr: "5GHz Clients",
sensor_unit: "",
sensor_divisor: 1
},
# CPU Utilization
%{
oid: "1.3.6.1.4.1.26928.1.1.1.3.1.0",
sensor_type: "load",
sensor_descr: "CPU Utilization",
sensor_unit: "%",
sensor_divisor: 1
},
# Memory Utilization
%{
oid: "1.3.6.1.4.1.26928.1.1.1.3.2.0",
sensor_type: "load",
sensor_descr: "Memory Utilization",
sensor_unit: "%",
sensor_divisor: 1
},
# System Temperature
%{
oid: "1.3.6.1.4.1.26928.1.1.1.3.3.0",
sensor_type: "temperature",
sensor_descr: "System Temperature",
sensor_unit: "C",
sensor_divisor: 1
}
]
end
end

View file

@ -0,0 +1,95 @@
defmodule Towerops.Snmp.Profiles.Vendors.Baicells do
@moduledoc """
Baicells device-specific SNMP handling.
Supports Baicells LTE/5G equipment including eNodeBs and CPEs.
Supported devices:
- Nova 227 CPE
- Nova 233 CPE
- Nova 243 CPE
- Atom OD04 CPE
- Nova 436Q eNodeB
- Nova 446 eNodeB
"""
@behaviour Towerops.Snmp.Profiles.Vendors.Vendor
alias Towerops.Snmp.Client
alias Towerops.Snmp.Profiles.Vendors.Vendor
# Baicells CPE MIB
@model_oid "1.3.6.1.4.1.48290.1.1.1.0"
@impl true
def profile_names do
["baicells-od04", "baicells"]
end
@impl true
def detect_hardware(client_opts) do
case Client.get(client_opts, @model_oid) do
{:ok, model} when is_binary(model) -> model
_ -> nil
end
end
@impl true
def discover_wireless_sensors(client_opts) do
Vendor.fetch_sensors(wireless_oid_defs(), client_opts)
end
@impl true
def wireless_oid_defs do
[
# RSRP (Reference Signal Received Power)
%{
oid: "1.3.6.1.4.1.48290.2.1.1.0",
sensor_type: "rssi",
sensor_descr: "RSRP",
sensor_unit: "dBm",
sensor_divisor: 1
},
# RSRQ (Reference Signal Received Quality)
%{
oid: "1.3.6.1.4.1.48290.2.1.2.0",
sensor_type: "quality",
sensor_descr: "RSRQ",
sensor_unit: "dB",
sensor_divisor: 1
},
# SINR (Signal to Interference plus Noise Ratio)
%{
oid: "1.3.6.1.4.1.48290.2.1.3.0",
sensor_type: "snr",
sensor_descr: "SINR",
sensor_unit: "dB",
sensor_divisor: 1
},
# RSSI
%{
oid: "1.3.6.1.4.1.48290.2.1.4.0",
sensor_type: "rssi",
sensor_descr: "RSSI",
sensor_unit: "dBm",
sensor_divisor: 1
},
# Tx Power
%{
oid: "1.3.6.1.4.1.48290.2.1.5.0",
sensor_type: "power",
sensor_descr: "Tx Power",
sensor_unit: "dBm",
sensor_divisor: 1
},
# Device Temperature
%{
oid: "1.3.6.1.4.1.48290.1.2.1.0",
sensor_type: "temperature",
sensor_descr: "Device Temperature",
sensor_unit: "C",
sensor_divisor: 1
}
]
end
end

View file

@ -0,0 +1,106 @@
defmodule Towerops.Snmp.Profiles.Vendors.CambiumPtp do
@moduledoc """
Cambium PTP (Point-to-Point) device-specific SNMP handling.
Supports Cambium licensed and unlicensed PTP backhaul radios.
Supported devices:
- PTP 250
- PTP 450/450i
- PTP 550
- PTP 600
- PTP 650
- PTP 670
- PTP 700
- PTP 800
"""
@behaviour Towerops.Snmp.Profiles.Vendors.Vendor
alias Towerops.Snmp.Client
alias Towerops.Snmp.Profiles.Vendors.Vendor
# Cambium PTP Enterprise OID: 1.3.6.1.4.1.17713
# PTP 650/670 use .7, others may vary
@model_oid "1.3.6.1.4.1.17713.7.1.1.0"
@impl true
def profile_names do
["ptp250", "ptp450", "ptp500", "ptp550", "ptp600", "ptp650", "ptp670", "ptp700", "ptp800"]
end
@impl true
def detect_hardware(client_opts) do
case Client.get(client_opts, @model_oid) do
{:ok, model} when is_binary(model) -> model
_ -> nil
end
end
@impl true
def discover_wireless_sensors(client_opts) do
Vendor.fetch_sensors(wireless_oid_defs(), client_opts)
end
@impl true
def wireless_oid_defs do
[
# Receive Power (RSL)
%{
oid: "1.3.6.1.4.1.17713.7.12.6.0",
sensor_type: "rssi",
sensor_descr: "Receive Power",
sensor_unit: "dBm",
sensor_divisor: 10
},
# Transmit Power
%{
oid: "1.3.6.1.4.1.17713.7.12.3.0",
sensor_type: "power",
sensor_descr: "Transmit Power",
sensor_unit: "dBm",
sensor_divisor: 10
},
# Vector Error
%{
oid: "1.3.6.1.4.1.17713.7.12.8.0",
sensor_type: "snr",
sensor_descr: "Vector Error",
sensor_unit: "dB",
sensor_divisor: 10
},
# Link Loss
%{
oid: "1.3.6.1.4.1.17713.7.12.9.0",
sensor_type: "quality",
sensor_descr: "Link Loss",
sensor_unit: "dB",
sensor_divisor: 10
},
# Receive Data Rate
%{
oid: "1.3.6.1.4.1.17713.7.12.14.0",
sensor_type: "capacity",
sensor_descr: "Rx Data Rate",
sensor_unit: "kbps",
sensor_divisor: 1
},
# Transmit Data Rate
%{
oid: "1.3.6.1.4.1.17713.7.12.15.0",
sensor_type: "capacity",
sensor_descr: "Tx Data Rate",
sensor_unit: "kbps",
sensor_divisor: 1
},
# Radio Temperature
%{
oid: "1.3.6.1.4.1.17713.7.13.1.0",
sensor_type: "temperature",
sensor_descr: "Radio Temperature",
sensor_unit: "C",
sensor_divisor: 1
}
]
end
end

View file

@ -0,0 +1,87 @@
defmodule Towerops.Snmp.Profiles.Vendors.Ciscowap do
@moduledoc """
Cisco Wireless Access Point (standalone) device-specific SNMP handling.
Supports Cisco standalone wireless access points (not controller-managed).
Supported devices:
- Cisco WAP121
- Cisco WAP321
- Cisco WAP371
- Cisco WAP561
- Cisco WAP571
- Cisco Small Business WAP Series
"""
@behaviour Towerops.Snmp.Profiles.Vendors.Vendor
alias Towerops.Snmp.Client
alias Towerops.Snmp.Profiles.Vendors.Vendor
# Cisco WAP uses standard Cisco enterprise OID: 1.3.6.1.4.1.9
@model_oid "1.3.6.1.4.1.9.9.618.1.1.1.0"
@impl true
def profile_names do
["ciscowap"]
end
@impl true
def detect_hardware(client_opts) do
case Client.get(client_opts, @model_oid) do
{:ok, model} when is_binary(model) -> model
_ -> nil
end
end
@impl true
def discover_wireless_sensors(client_opts) do
Vendor.fetch_sensors(wireless_oid_defs(), client_opts)
end
@impl true
def wireless_oid_defs do
[
# Total Associated Clients
%{
oid: "1.3.6.1.4.1.9.9.618.1.8.1.0",
sensor_type: "clients",
sensor_descr: "Associated Clients",
sensor_unit: "",
sensor_divisor: 1
},
# 2.4GHz Channel Utilization
%{
oid: "1.3.6.1.4.1.9.9.618.1.4.1.1.8.1",
sensor_type: "load",
sensor_descr: "2.4GHz Channel Utilization",
sensor_unit: "%",
sensor_divisor: 1
},
# 5GHz Channel Utilization
%{
oid: "1.3.6.1.4.1.9.9.618.1.4.1.1.8.2",
sensor_type: "load",
sensor_descr: "5GHz Channel Utilization",
sensor_unit: "%",
sensor_divisor: 1
},
# Noise Floor 2.4GHz
%{
oid: "1.3.6.1.4.1.9.9.618.1.4.1.1.5.1",
sensor_type: "noise",
sensor_descr: "2.4GHz Noise Floor",
sensor_unit: "dBm",
sensor_divisor: 1
},
# Noise Floor 5GHz
%{
oid: "1.3.6.1.4.1.9.9.618.1.4.1.1.5.2",
sensor_type: "noise",
sensor_descr: "5GHz Noise Floor",
sensor_unit: "dBm",
sensor_divisor: 1
}
]
end
end

View file

@ -0,0 +1,95 @@
defmodule Towerops.Snmp.Profiles.Vendors.Netonix do
@moduledoc """
Netonix device-specific SNMP handling.
Supports Netonix WISP switches popular in wireless ISP deployments.
Supported devices:
- WS-6-MINI
- WS-8-150-AC
- WS-12-250-AC
- WS-12-400-AC
- WS-12-DC
- WS-26-400-AC
"""
@behaviour Towerops.Snmp.Profiles.Vendors.Vendor
alias Towerops.Snmp.Client
alias Towerops.Snmp.Profiles.Vendors.Vendor
# Netonix Enterprise OID: 1.3.6.1.4.1.46242
@model_oid "1.3.6.1.4.1.46242.1.1.0"
@impl true
def profile_names do
["netonix"]
end
@impl true
def detect_hardware(client_opts) do
case Client.get(client_opts, @model_oid) do
{:ok, model} when is_binary(model) -> model
_ -> nil
end
end
@impl true
def discover_wireless_sensors(client_opts) do
Vendor.fetch_sensors(wireless_oid_defs(), client_opts)
end
@impl true
def wireless_oid_defs do
[
# Board Temperature
%{
oid: "1.3.6.1.4.1.46242.2.1.0",
sensor_type: "temperature",
sensor_descr: "Board Temperature",
sensor_unit: "C",
sensor_divisor: 1
},
# CPU Temperature
%{
oid: "1.3.6.1.4.1.46242.2.2.0",
sensor_type: "temperature",
sensor_descr: "CPU Temperature",
sensor_unit: "C",
sensor_divisor: 1
},
# Fan Speed
%{
oid: "1.3.6.1.4.1.46242.2.3.0",
sensor_type: "fanspeed",
sensor_descr: "Fan Speed",
sensor_unit: "RPM",
sensor_divisor: 1
},
# PSU1 Voltage
%{
oid: "1.3.6.1.4.1.46242.3.1.0",
sensor_type: "voltage",
sensor_descr: "PSU1 Voltage",
sensor_unit: "V",
sensor_divisor: 1000
},
# PSU2 Voltage
%{
oid: "1.3.6.1.4.1.46242.3.2.0",
sensor_type: "voltage",
sensor_descr: "PSU2 Voltage",
sensor_unit: "V",
sensor_divisor: 1000
},
# Total PoE Power
%{
oid: "1.3.6.1.4.1.46242.4.1.0",
sensor_type: "power",
sensor_descr: "Total PoE Power",
sensor_unit: "W",
sensor_divisor: 1000
}
]
end
end

View file

@ -7,10 +7,14 @@ defmodule Towerops.Snmp.Profiles.Vendors.Registry do
"""
alias Towerops.Snmp.Client
alias Towerops.Snmp.Profiles.Vendors.Aerohive
alias Towerops.Snmp.Profiles.Vendors.Airfiber
alias Towerops.Snmp.Profiles.Vendors.Airos
alias Towerops.Snmp.Profiles.Vendors.Aruba
alias Towerops.Snmp.Profiles.Vendors.Aviat
alias Towerops.Snmp.Profiles.Vendors.Baicells
alias Towerops.Snmp.Profiles.Vendors.CambiumPtp
alias Towerops.Snmp.Profiles.Vendors.Ciscowap
alias Towerops.Snmp.Profiles.Vendors.Ciscowlc
alias Towerops.Snmp.Profiles.Vendors.Cnpilot
alias Towerops.Snmp.Profiles.Vendors.Dragonwave
@ -18,6 +22,7 @@ defmodule Towerops.Snmp.Profiles.Vendors.Registry do
alias Towerops.Snmp.Profiles.Vendors.Exalt
alias Towerops.Snmp.Profiles.Vendors.Ligoos
alias Towerops.Snmp.Profiles.Vendors.Mimosa
alias Towerops.Snmp.Profiles.Vendors.Netonix
alias Towerops.Snmp.Profiles.Vendors.Pmp
alias Towerops.Snmp.Profiles.Vendors.Racom
alias Towerops.Snmp.Profiles.Vendors.Radwin
@ -31,28 +36,33 @@ defmodule Towerops.Snmp.Profiles.Vendors.Registry do
alias Towerops.Snmp.Profiles.Vendors.Unifi
@vendors [
Epmp,
Aerohive,
Airos,
Airfiber,
Aruba,
Aviat,
Baicells,
CambiumPtp,
Ciscowap,
Ciscowlc,
Routeros,
Pmp,
Mimosa,
Siklu,
Radwin,
Unifi,
Ligoos,
Cnpilot,
Saf,
Ruckus,
Dragonwave,
Racom,
Epmp,
Exalt,
Ligoos,
Mimosa,
Netonix,
Pmp,
Racom,
Radwin,
Routeros,
Ruckus,
Saf,
Siae,
Siklu,
Sub10,
Teltonika
Teltonika,
Unifi
]
@doc """

View file

@ -0,0 +1,127 @@
defmodule Towerops.Snmp.Profiles.Vendors.AerohiveTest do
use Towerops.DataCase, async: true
import Mox
alias Towerops.Snmp.Profiles.Vendors.Aerohive
alias Towerops.Snmp.SnmpMock
setup :verify_on_exit!
@client_opts [
ip: "192.168.1.1",
community: "public",
version: "2c",
port: 161,
timeout: 5000
]
describe "profile_names/0" do
test "returns hiveos profile name" do
assert Aerohive.profile_names() == ["hiveos-wireless"]
end
end
describe "detect_hardware/1" do
test "detects hardware from model OID" do
expect(SnmpMock, :get, fn _, "1.3.6.1.4.1.26928.1.1.1.1.0", _ ->
{:ok, "AP650"}
end)
assert Aerohive.detect_hardware(@client_opts) == "AP650"
end
test "returns nil when OID unavailable" do
expect(SnmpMock, :get, fn _, _, _ ->
{:error, :no_such_object}
end)
assert Aerohive.detect_hardware(@client_opts) == nil
end
end
describe "wireless_oid_defs/0" do
test "returns list of wireless sensor definitions" do
defs = Aerohive.wireless_oid_defs()
assert is_list(defs)
assert [_ | _] = defs
end
test "includes total client count sensor" do
defs = Aerohive.wireless_oid_defs()
clients = Enum.find(defs, &(&1.sensor_descr == "Associated Clients"))
assert clients
assert clients.sensor_type == "clients"
end
test "includes band-specific client sensors" do
defs = Aerohive.wireless_oid_defs()
clients_2g = Enum.find(defs, &(&1.sensor_descr == "2.4GHz Clients"))
clients_5g = Enum.find(defs, &(&1.sensor_descr == "5GHz Clients"))
assert clients_2g
assert clients_2g.sensor_type == "clients"
assert clients_5g
assert clients_5g.sensor_type == "clients"
end
test "includes CPU utilization sensor" do
defs = Aerohive.wireless_oid_defs()
cpu = Enum.find(defs, &(&1.sensor_descr == "CPU Utilization"))
assert cpu
assert cpu.sensor_type == "load"
assert cpu.sensor_unit == "%"
end
test "includes memory utilization sensor" do
defs = Aerohive.wireless_oid_defs()
mem = Enum.find(defs, &(&1.sensor_descr == "Memory Utilization"))
assert mem
assert mem.sensor_type == "load"
assert mem.sensor_unit == "%"
end
test "includes temperature sensor" do
defs = Aerohive.wireless_oid_defs()
temp = Enum.find(defs, &(&1.sensor_type == "temperature"))
assert temp
assert temp.sensor_descr == "System Temperature"
end
end
describe "discover_wireless_sensors/1" do
test "discovers sensors when SNMP responds" do
expect(SnmpMock, :get, 6, fn _, oid, _ ->
cond do
String.contains?(oid, "26928.1.1.1.2.1.1.0") -> {:ok, 45}
String.contains?(oid, "26928.1.1.1.2.1.2.0") -> {:ok, 20}
String.contains?(oid, "26928.1.1.1.2.1.3.0") -> {:ok, 25}
String.contains?(oid, "26928.1.1.1.3.1.0") -> {:ok, 35}
String.contains?(oid, "26928.1.1.1.3.2.0") -> {:ok, 68}
String.contains?(oid, "26928.1.1.1.3.3.0") -> {:ok, 42}
true -> {:error, :no_such_object}
end
end)
sensors = Aerohive.discover_wireless_sensors(@client_opts)
assert is_list(sensors)
assert length(sensors) == 6
end
test "returns empty list when no sensors respond" do
expect(SnmpMock, :get, 6, fn _, _, _ ->
{:error, :no_such_object}
end)
sensors = Aerohive.discover_wireless_sensors(@client_opts)
assert sensors == []
end
end
end

View file

@ -0,0 +1,128 @@
defmodule Towerops.Snmp.Profiles.Vendors.BaicellsTest do
use Towerops.DataCase, async: true
import Mox
alias Towerops.Snmp.Profiles.Vendors.Baicells
alias Towerops.Snmp.SnmpMock
setup :verify_on_exit!
@client_opts [
ip: "192.168.1.1",
community: "public",
version: "2c",
port: 161,
timeout: 5000
]
describe "profile_names/0" do
test "returns baicells profile names" do
profiles = Baicells.profile_names()
assert "baicells" in profiles
assert "baicells-od04" in profiles
end
end
describe "detect_hardware/1" do
test "detects hardware from model OID" do
expect(SnmpMock, :get, fn _, "1.3.6.1.4.1.48290.1.1.1.0", _ ->
{:ok, "Atom OD04"}
end)
assert Baicells.detect_hardware(@client_opts) == "Atom OD04"
end
test "returns nil when OID unavailable" do
expect(SnmpMock, :get, fn _, _, _ ->
{:error, :no_such_object}
end)
assert Baicells.detect_hardware(@client_opts) == nil
end
end
describe "wireless_oid_defs/0" do
test "returns list of wireless sensor definitions" do
defs = Baicells.wireless_oid_defs()
assert is_list(defs)
assert [_ | _] = defs
end
test "includes RSRP sensor" do
defs = Baicells.wireless_oid_defs()
rsrp = Enum.find(defs, &(&1.sensor_descr == "RSRP"))
assert rsrp
assert rsrp.sensor_type == "rssi"
assert rsrp.sensor_unit == "dBm"
end
test "includes RSRQ sensor" do
defs = Baicells.wireless_oid_defs()
rsrq = Enum.find(defs, &(&1.sensor_descr == "RSRQ"))
assert rsrq
assert rsrq.sensor_type == "quality"
assert rsrq.sensor_unit == "dB"
end
test "includes SINR sensor" do
defs = Baicells.wireless_oid_defs()
sinr = Enum.find(defs, &(&1.sensor_descr == "SINR"))
assert sinr
assert sinr.sensor_type == "snr"
assert sinr.sensor_unit == "dB"
end
test "includes tx power sensor" do
defs = Baicells.wireless_oid_defs()
tx = Enum.find(defs, &(&1.sensor_descr == "Tx Power"))
assert tx
assert tx.sensor_type == "power"
end
test "includes temperature sensor" do
defs = Baicells.wireless_oid_defs()
temp = Enum.find(defs, &(&1.sensor_type == "temperature"))
assert temp
assert temp.sensor_descr == "Device Temperature"
end
end
describe "discover_wireless_sensors/1" do
test "discovers sensors when SNMP responds" do
expect(SnmpMock, :get, 6, fn _, oid, _ ->
cond do
String.contains?(oid, "48290.2.1.1.0") -> {:ok, -95}
String.contains?(oid, "48290.2.1.2.0") -> {:ok, -12}
String.contains?(oid, "48290.2.1.3.0") -> {:ok, 8}
String.contains?(oid, "48290.2.1.4.0") -> {:ok, -70}
String.contains?(oid, "48290.2.1.5.0") -> {:ok, 23}
String.contains?(oid, "48290.1.2.1.0") -> {:ok, 38}
true -> {:error, :no_such_object}
end
end)
sensors = Baicells.discover_wireless_sensors(@client_opts)
assert is_list(sensors)
assert length(sensors) == 6
end
test "returns empty list when no sensors respond" do
expect(SnmpMock, :get, 6, fn _, _, _ ->
{:error, :no_such_object}
end)
sensors = Baicells.discover_wireless_sensors(@client_opts)
assert sensors == []
end
end
end

View file

@ -0,0 +1,135 @@
defmodule Towerops.Snmp.Profiles.Vendors.CambiumPtpTest do
use Towerops.DataCase, async: true
import Mox
alias Towerops.Snmp.Profiles.Vendors.CambiumPtp
alias Towerops.Snmp.SnmpMock
setup :verify_on_exit!
@client_opts [
ip: "192.168.1.1",
community: "public",
version: "2c",
port: 161,
timeout: 5000
]
describe "profile_names/0" do
test "returns all PTP profile names" do
profiles = CambiumPtp.profile_names()
assert "ptp650" in profiles
assert "ptp670" in profiles
assert "ptp450" in profiles
assert "ptp550" in profiles
assert "ptp800" in profiles
end
end
describe "detect_hardware/1" do
test "detects hardware from model OID" do
expect(SnmpMock, :get, fn _, "1.3.6.1.4.1.17713.7.1.1.0", _ ->
{:ok, "PTP 670"}
end)
assert CambiumPtp.detect_hardware(@client_opts) == "PTP 670"
end
test "returns nil when OID unavailable" do
expect(SnmpMock, :get, fn _, _, _ ->
{:error, :no_such_object}
end)
assert CambiumPtp.detect_hardware(@client_opts) == nil
end
end
describe "wireless_oid_defs/0" do
test "returns list of wireless sensor definitions" do
defs = CambiumPtp.wireless_oid_defs()
assert is_list(defs)
assert [_ | _] = defs
end
test "includes receive power (RSL) sensor" do
defs = CambiumPtp.wireless_oid_defs()
rsl = Enum.find(defs, &(&1.sensor_descr == "Receive Power"))
assert rsl
assert rsl.sensor_type == "rssi"
assert rsl.sensor_unit == "dBm"
assert rsl.sensor_divisor == 10
end
test "includes transmit power sensor" do
defs = CambiumPtp.wireless_oid_defs()
tx = Enum.find(defs, &(&1.sensor_descr == "Transmit Power"))
assert tx
assert tx.sensor_type == "power"
assert tx.sensor_divisor == 10
end
test "includes vector error sensor" do
defs = CambiumPtp.wireless_oid_defs()
ve = Enum.find(defs, &(&1.sensor_descr == "Vector Error"))
assert ve
assert ve.sensor_type == "snr"
end
test "includes data rate sensors" do
defs = CambiumPtp.wireless_oid_defs()
rx_rate = Enum.find(defs, &(&1.sensor_descr == "Rx Data Rate"))
tx_rate = Enum.find(defs, &(&1.sensor_descr == "Tx Data Rate"))
assert rx_rate
assert rx_rate.sensor_type == "capacity"
assert tx_rate
assert tx_rate.sensor_type == "capacity"
end
test "includes temperature sensor" do
defs = CambiumPtp.wireless_oid_defs()
temp = Enum.find(defs, &(&1.sensor_type == "temperature"))
assert temp
assert temp.sensor_descr == "Radio Temperature"
end
end
describe "discover_wireless_sensors/1" do
test "discovers sensors when SNMP responds" do
expect(SnmpMock, :get, 7, fn _, oid, _ ->
cond do
String.contains?(oid, "17713.7.12.6.0") -> {:ok, -550}
String.contains?(oid, "17713.7.12.3.0") -> {:ok, 230}
String.contains?(oid, "17713.7.12.8.0") -> {:ok, 350}
String.contains?(oid, "17713.7.12.9.0") -> {:ok, 15}
String.contains?(oid, "17713.7.12.14.0") -> {:ok, 350_000}
String.contains?(oid, "17713.7.12.15.0") -> {:ok, 350_000}
String.contains?(oid, "17713.7.13.1.0") -> {:ok, 42}
true -> {:error, :no_such_object}
end
end)
sensors = CambiumPtp.discover_wireless_sensors(@client_opts)
assert is_list(sensors)
assert length(sensors) == 7
end
test "returns empty list when no sensors respond" do
expect(SnmpMock, :get, 7, fn _, _, _ ->
{:error, :no_such_object}
end)
sensors = CambiumPtp.discover_wireless_sensors(@client_opts)
assert sensors == []
end
end
end

View file

@ -0,0 +1,113 @@
defmodule Towerops.Snmp.Profiles.Vendors.CiscowapTest do
use Towerops.DataCase, async: true
import Mox
alias Towerops.Snmp.Profiles.Vendors.Ciscowap
alias Towerops.Snmp.SnmpMock
setup :verify_on_exit!
@client_opts [
ip: "192.168.1.1",
community: "public",
version: "2c",
port: 161,
timeout: 5000
]
describe "profile_names/0" do
test "returns ciscowap profile name" do
assert Ciscowap.profile_names() == ["ciscowap"]
end
end
describe "detect_hardware/1" do
test "detects hardware from model OID" do
expect(SnmpMock, :get, fn _, "1.3.6.1.4.1.9.9.618.1.1.1.0", _ ->
{:ok, "WAP371"}
end)
assert Ciscowap.detect_hardware(@client_opts) == "WAP371"
end
test "returns nil when OID unavailable" do
expect(SnmpMock, :get, fn _, _, _ ->
{:error, :no_such_object}
end)
assert Ciscowap.detect_hardware(@client_opts) == nil
end
end
describe "wireless_oid_defs/0" do
test "returns list of wireless sensor definitions" do
defs = Ciscowap.wireless_oid_defs()
assert is_list(defs)
assert [_ | _] = defs
end
test "includes client count sensor" do
defs = Ciscowap.wireless_oid_defs()
clients = Enum.find(defs, &(&1.sensor_descr == "Associated Clients"))
assert clients
assert clients.sensor_type == "clients"
end
test "includes channel utilization sensors" do
defs = Ciscowap.wireless_oid_defs()
util_2g = Enum.find(defs, &(&1.sensor_descr == "2.4GHz Channel Utilization"))
util_5g = Enum.find(defs, &(&1.sensor_descr == "5GHz Channel Utilization"))
assert util_2g
assert util_2g.sensor_type == "load"
assert util_2g.sensor_unit == "%"
assert util_5g
assert util_5g.sensor_type == "load"
end
test "includes noise floor sensors" do
defs = Ciscowap.wireless_oid_defs()
noise_2g = Enum.find(defs, &(&1.sensor_descr == "2.4GHz Noise Floor"))
noise_5g = Enum.find(defs, &(&1.sensor_descr == "5GHz Noise Floor"))
assert noise_2g
assert noise_2g.sensor_type == "noise"
assert noise_2g.sensor_unit == "dBm"
assert noise_5g
assert noise_5g.sensor_type == "noise"
end
end
describe "discover_wireless_sensors/1" do
test "discovers sensors when SNMP responds" do
expect(SnmpMock, :get, 5, fn _, oid, _ ->
cond do
String.contains?(oid, "9.9.618.1.8.1.0") -> {:ok, 28}
String.contains?(oid, "9.9.618.1.4.1.1.8.1") -> {:ok, 45}
String.contains?(oid, "9.9.618.1.4.1.1.8.2") -> {:ok, 30}
String.contains?(oid, "9.9.618.1.4.1.1.5.1") -> {:ok, -95}
String.contains?(oid, "9.9.618.1.4.1.1.5.2") -> {:ok, -92}
true -> {:error, :no_such_object}
end
end)
sensors = Ciscowap.discover_wireless_sensors(@client_opts)
assert is_list(sensors)
assert length(sensors) == 5
end
test "returns empty list when no sensors respond" do
expect(SnmpMock, :get, 5, fn _, _, _ ->
{:error, :no_such_object}
end)
sensors = Ciscowap.discover_wireless_sensors(@client_opts)
assert sensors == []
end
end
end

View file

@ -0,0 +1,129 @@
defmodule Towerops.Snmp.Profiles.Vendors.NetonixTest do
use Towerops.DataCase, async: true
import Mox
alias Towerops.Snmp.Profiles.Vendors.Netonix
alias Towerops.Snmp.SnmpMock
setup :verify_on_exit!
@client_opts [
ip: "192.168.1.1",
community: "public",
version: "2c",
port: 161,
timeout: 5000
]
describe "profile_names/0" do
test "returns netonix profile name" do
assert Netonix.profile_names() == ["netonix"]
end
end
describe "detect_hardware/1" do
test "detects hardware from model OID" do
expect(SnmpMock, :get, fn _, "1.3.6.1.4.1.46242.1.1.0", _ ->
{:ok, "WS-12-250-AC"}
end)
assert Netonix.detect_hardware(@client_opts) == "WS-12-250-AC"
end
test "returns nil when OID unavailable" do
expect(SnmpMock, :get, fn _, _, _ ->
{:error, :no_such_object}
end)
assert Netonix.detect_hardware(@client_opts) == nil
end
end
describe "wireless_oid_defs/0" do
test "returns list of sensor definitions" do
defs = Netonix.wireless_oid_defs()
assert is_list(defs)
assert [_ | _] = defs
end
test "includes board temperature sensor" do
defs = Netonix.wireless_oid_defs()
temp = Enum.find(defs, &(&1.sensor_descr == "Board Temperature"))
assert temp
assert temp.sensor_type == "temperature"
assert temp.sensor_unit == "C"
end
test "includes CPU temperature sensor" do
defs = Netonix.wireless_oid_defs()
temp = Enum.find(defs, &(&1.sensor_descr == "CPU Temperature"))
assert temp
assert temp.sensor_type == "temperature"
end
test "includes fan speed sensor" do
defs = Netonix.wireless_oid_defs()
fan = Enum.find(defs, &(&1.sensor_descr == "Fan Speed"))
assert fan
assert fan.sensor_type == "fanspeed"
assert fan.sensor_unit == "RPM"
end
test "includes voltage sensors" do
defs = Netonix.wireless_oid_defs()
psu1 = Enum.find(defs, &(&1.sensor_descr == "PSU1 Voltage"))
psu2 = Enum.find(defs, &(&1.sensor_descr == "PSU2 Voltage"))
assert psu1
assert psu1.sensor_type == "voltage"
assert psu1.sensor_divisor == 1000
assert psu2
assert psu2.sensor_type == "voltage"
end
test "includes PoE power sensor" do
defs = Netonix.wireless_oid_defs()
poe = Enum.find(defs, &(&1.sensor_descr == "Total PoE Power"))
assert poe
assert poe.sensor_type == "power"
assert poe.sensor_unit == "W"
end
end
describe "discover_wireless_sensors/1" do
test "discovers sensors when SNMP responds" do
expect(SnmpMock, :get, 6, fn _, oid, _ ->
cond do
String.contains?(oid, "46242.2.1.0") -> {:ok, 45}
String.contains?(oid, "46242.2.2.0") -> {:ok, 55}
String.contains?(oid, "46242.2.3.0") -> {:ok, 3500}
String.contains?(oid, "46242.3.1.0") -> {:ok, 54_000}
String.contains?(oid, "46242.3.2.0") -> {:ok, 48_000}
String.contains?(oid, "46242.4.1.0") -> {:ok, 75_000}
true -> {:error, :no_such_object}
end
end)
sensors = Netonix.discover_wireless_sensors(@client_opts)
assert is_list(sensors)
assert length(sensors) == 6
end
test "returns empty list when no sensors respond" do
expect(SnmpMock, :get, 6, fn _, _, _ ->
{:error, :no_such_object}
end)
sensors = Netonix.discover_wireless_sensors(@client_opts)
assert sensors == []
end
end
end