diff --git a/lib/towerops/snmp/profiles/vendors/aerohive.ex b/lib/towerops/snmp/profiles/vendors/aerohive.ex new file mode 100644 index 00000000..e6d94a6f --- /dev/null +++ b/lib/towerops/snmp/profiles/vendors/aerohive.ex @@ -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 diff --git a/lib/towerops/snmp/profiles/vendors/baicells.ex b/lib/towerops/snmp/profiles/vendors/baicells.ex new file mode 100644 index 00000000..5a68298a --- /dev/null +++ b/lib/towerops/snmp/profiles/vendors/baicells.ex @@ -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 diff --git a/lib/towerops/snmp/profiles/vendors/cambium_ptp.ex b/lib/towerops/snmp/profiles/vendors/cambium_ptp.ex new file mode 100644 index 00000000..d1ffb8f9 --- /dev/null +++ b/lib/towerops/snmp/profiles/vendors/cambium_ptp.ex @@ -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 diff --git a/lib/towerops/snmp/profiles/vendors/ciscowap.ex b/lib/towerops/snmp/profiles/vendors/ciscowap.ex new file mode 100644 index 00000000..3fefa8a4 --- /dev/null +++ b/lib/towerops/snmp/profiles/vendors/ciscowap.ex @@ -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 diff --git a/lib/towerops/snmp/profiles/vendors/netonix.ex b/lib/towerops/snmp/profiles/vendors/netonix.ex new file mode 100644 index 00000000..ddb03f90 --- /dev/null +++ b/lib/towerops/snmp/profiles/vendors/netonix.ex @@ -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 diff --git a/lib/towerops/snmp/profiles/vendors/registry.ex b/lib/towerops/snmp/profiles/vendors/registry.ex index 6d7422ba..5d7cda4c 100644 --- a/lib/towerops/snmp/profiles/vendors/registry.ex +++ b/lib/towerops/snmp/profiles/vendors/registry.ex @@ -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 """ diff --git a/test/towerops/snmp/profiles/vendors/aerohive_test.exs b/test/towerops/snmp/profiles/vendors/aerohive_test.exs new file mode 100644 index 00000000..323bd0f7 --- /dev/null +++ b/test/towerops/snmp/profiles/vendors/aerohive_test.exs @@ -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 diff --git a/test/towerops/snmp/profiles/vendors/baicells_test.exs b/test/towerops/snmp/profiles/vendors/baicells_test.exs new file mode 100644 index 00000000..f68b710d --- /dev/null +++ b/test/towerops/snmp/profiles/vendors/baicells_test.exs @@ -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 diff --git a/test/towerops/snmp/profiles/vendors/cambium_ptp_test.exs b/test/towerops/snmp/profiles/vendors/cambium_ptp_test.exs new file mode 100644 index 00000000..b3c54fb3 --- /dev/null +++ b/test/towerops/snmp/profiles/vendors/cambium_ptp_test.exs @@ -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 diff --git a/test/towerops/snmp/profiles/vendors/ciscowap_test.exs b/test/towerops/snmp/profiles/vendors/ciscowap_test.exs new file mode 100644 index 00000000..3c604b56 --- /dev/null +++ b/test/towerops/snmp/profiles/vendors/ciscowap_test.exs @@ -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 diff --git a/test/towerops/snmp/profiles/vendors/netonix_test.exs b/test/towerops/snmp/profiles/vendors/netonix_test.exs new file mode 100644 index 00000000..9ceaabb3 --- /dev/null +++ b/test/towerops/snmp/profiles/vendors/netonix_test.exs @@ -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