From e1bc56e0658a30490107b503353cce13b25e93c2 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Thu, 22 Jan 2026 09:53:33 -0600 Subject: [PATCH] feat(snmp): add 6 new vendor modules (cnwave60, cnmatrix, ruckus_sz, deliberant, engenius, grandstream) --- .../snmp/profiles/vendors/cnmatrix.ex | 87 ++++++++++++++ .../snmp/profiles/vendors/cnwave60.ex | 85 ++++++++++++++ .../snmp/profiles/vendors/deliberant.ex | 85 ++++++++++++++ .../snmp/profiles/vendors/engenius.ex | 102 ++++++++++++++++ .../snmp/profiles/vendors/grandstream.ex | 86 ++++++++++++++ lib/towerops/snmp/profiles/vendors/ligoos.ex | 3 +- .../snmp/profiles/vendors/registry.ex | 14 ++- .../snmp/profiles/vendors/ruckus_sz.ex | 85 ++++++++++++++ .../snmp/profiles/vendors/cnmatrix_test.exs | 106 +++++++++++++++++ .../snmp/profiles/vendors/cnwave60_test.exs | 106 +++++++++++++++++ .../snmp/profiles/vendors/deliberant_test.exs | 106 +++++++++++++++++ .../snmp/profiles/vendors/engenius_test.exs | 110 ++++++++++++++++++ .../profiles/vendors/grandstream_test.exs | 109 +++++++++++++++++ .../snmp/profiles/vendors/ligoos_test.exs | 4 +- .../snmp/profiles/vendors/registry_test.exs | 5 +- .../snmp/profiles/vendors/ruckus_sz_test.exs | 105 +++++++++++++++++ 16 files changed, 1191 insertions(+), 7 deletions(-) create mode 100644 lib/towerops/snmp/profiles/vendors/cnmatrix.ex create mode 100644 lib/towerops/snmp/profiles/vendors/cnwave60.ex create mode 100644 lib/towerops/snmp/profiles/vendors/deliberant.ex create mode 100644 lib/towerops/snmp/profiles/vendors/engenius.ex create mode 100644 lib/towerops/snmp/profiles/vendors/grandstream.ex create mode 100644 lib/towerops/snmp/profiles/vendors/ruckus_sz.ex create mode 100644 test/towerops/snmp/profiles/vendors/cnmatrix_test.exs create mode 100644 test/towerops/snmp/profiles/vendors/cnwave60_test.exs create mode 100644 test/towerops/snmp/profiles/vendors/deliberant_test.exs create mode 100644 test/towerops/snmp/profiles/vendors/engenius_test.exs create mode 100644 test/towerops/snmp/profiles/vendors/grandstream_test.exs create mode 100644 test/towerops/snmp/profiles/vendors/ruckus_sz_test.exs diff --git a/lib/towerops/snmp/profiles/vendors/cnmatrix.ex b/lib/towerops/snmp/profiles/vendors/cnmatrix.ex new file mode 100644 index 00000000..37c54d7d --- /dev/null +++ b/lib/towerops/snmp/profiles/vendors/cnmatrix.ex @@ -0,0 +1,87 @@ +defmodule Towerops.Snmp.Profiles.Vendors.Cnmatrix do + @moduledoc """ + Cambium cnMatrix device-specific SNMP handling. + + Supports Cambium cnMatrix managed Ethernet switches. + + Supported devices: + - cnMatrix EX1010 + - cnMatrix EX1028 + - cnMatrix EX2010 + - cnMatrix EX2028 + - cnMatrix TX1012 + - cnMatrix TX2012 + """ + + @behaviour Towerops.Snmp.Profiles.Vendors.Vendor + + alias Towerops.Snmp.Client + alias Towerops.Snmp.Profiles.Vendors.Vendor + + # Cambium cnMatrix Enterprise OID: 1.3.6.1.4.1.17713.24 + @model_oid "1.3.6.1.4.1.17713.24.1.1.1.0" + + @impl true + def profile_names do + ["cnmatrix"] + 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 + [ + # CPU Utilization + %{ + oid: "1.3.6.1.4.1.17713.24.2.1.0", + sensor_type: "load", + sensor_descr: "CPU Utilization", + sensor_unit: "%", + sensor_divisor: 1 + }, + # Memory Utilization + %{ + oid: "1.3.6.1.4.1.17713.24.2.2.0", + sensor_type: "load", + sensor_descr: "Memory Utilization", + sensor_unit: "%", + sensor_divisor: 1 + }, + # System Temperature + %{ + oid: "1.3.6.1.4.1.17713.24.3.1.0", + sensor_type: "temperature", + sensor_descr: "System Temperature", + sensor_unit: "C", + sensor_divisor: 1 + }, + # Fan Speed + %{ + oid: "1.3.6.1.4.1.17713.24.3.2.0", + sensor_type: "fanspeed", + sensor_descr: "Fan Speed", + sensor_unit: "RPM", + sensor_divisor: 1 + }, + # Total PoE Power + %{ + oid: "1.3.6.1.4.1.17713.24.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/cnwave60.ex b/lib/towerops/snmp/profiles/vendors/cnwave60.ex new file mode 100644 index 00000000..59849356 --- /dev/null +++ b/lib/towerops/snmp/profiles/vendors/cnwave60.ex @@ -0,0 +1,85 @@ +defmodule Towerops.Snmp.Profiles.Vendors.Cnwave60 do + @moduledoc """ + Cambium cnWave 60GHz device-specific SNMP handling. + + Supports Cambium cnWave 60GHz fixed wireless equipment. + + Supported devices: + - cnWave V1000 + - cnWave V3000 + - cnWave V5000 + - cnWave E500/E600 + """ + + @behaviour Towerops.Snmp.Profiles.Vendors.Vendor + + alias Towerops.Snmp.Client + alias Towerops.Snmp.Profiles.Vendors.Vendor + + # Cambium cnWave 60 Enterprise OID: 1.3.6.1.4.1.17713.60 + @model_oid "1.3.6.1.4.1.17713.60.1.1.1.0" + + @impl true + def profile_names do + ["cnwave60"] + 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 Signal Level (RSL) + %{ + oid: "1.3.6.1.4.1.17713.60.12.1.0", + sensor_type: "rssi", + sensor_descr: "Receive Signal Level", + sensor_unit: "dBm", + sensor_divisor: 10 + }, + # Transmit Power + %{ + oid: "1.3.6.1.4.1.17713.60.12.2.0", + sensor_type: "power", + sensor_descr: "Transmit Power", + sensor_unit: "dBm", + sensor_divisor: 10 + }, + # Signal to Noise Ratio + %{ + oid: "1.3.6.1.4.1.17713.60.12.3.0", + sensor_type: "snr", + sensor_descr: "Signal to Noise Ratio", + sensor_unit: "dB", + sensor_divisor: 10 + }, + # Link Capacity + %{ + oid: "1.3.6.1.4.1.17713.60.12.4.0", + sensor_type: "capacity", + sensor_descr: "Link Capacity", + sensor_unit: "Mbps", + sensor_divisor: 1 + }, + # System Temperature + %{ + oid: "1.3.6.1.4.1.17713.60.13.1.0", + sensor_type: "temperature", + sensor_descr: "System Temperature", + sensor_unit: "C", + sensor_divisor: 1 + } + ] + end +end diff --git a/lib/towerops/snmp/profiles/vendors/deliberant.ex b/lib/towerops/snmp/profiles/vendors/deliberant.ex new file mode 100644 index 00000000..46e0d0a4 --- /dev/null +++ b/lib/towerops/snmp/profiles/vendors/deliberant.ex @@ -0,0 +1,85 @@ +defmodule Towerops.Snmp.Profiles.Vendors.Deliberant do + @moduledoc """ + Deliberant (LigoWave) device-specific SNMP handling. + + Supports Deliberant/LigoWave wireless equipment. + + Supported devices: + - DLB APC Series + - DLB APC 5M + - DLB APC 2M + - LigoPTP RapidFire + """ + + @behaviour Towerops.Snmp.Profiles.Vendors.Vendor + + alias Towerops.Snmp.Client + alias Towerops.Snmp.Profiles.Vendors.Vendor + + # Deliberant/LigoWave Enterprise OID: 1.3.6.1.4.1.32761 + @model_oid "1.3.6.1.4.1.32761.1.1.1.0" + + @impl true + def profile_names do + ["deliberant"] + 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 Signal Level + %{ + oid: "1.3.6.1.4.1.32761.3.5.1.2.1.6.1", + sensor_type: "rssi", + sensor_descr: "Signal Level", + sensor_unit: "dBm", + sensor_divisor: 1 + }, + # Noise Floor + %{ + oid: "1.3.6.1.4.1.32761.3.5.1.2.1.7.1", + sensor_type: "noise", + sensor_descr: "Noise Floor", + sensor_unit: "dBm", + sensor_divisor: 1 + }, + # Transmit CCQ + %{ + oid: "1.3.6.1.4.1.32761.3.5.1.2.1.8.1", + sensor_type: "quality", + sensor_descr: "Transmit CCQ", + sensor_unit: "%", + sensor_divisor: 1 + }, + # Connected Clients + %{ + oid: "1.3.6.1.4.1.32761.3.5.1.2.1.9.1", + sensor_type: "clients", + sensor_descr: "Connected Clients", + sensor_unit: "", + sensor_divisor: 1 + }, + # CPU Temperature + %{ + oid: "1.3.6.1.4.1.32761.3.1.1.2.0", + sensor_type: "temperature", + sensor_descr: "CPU Temperature", + sensor_unit: "C", + sensor_divisor: 1 + } + ] + end +end diff --git a/lib/towerops/snmp/profiles/vendors/engenius.ex b/lib/towerops/snmp/profiles/vendors/engenius.ex new file mode 100644 index 00000000..55ef4527 --- /dev/null +++ b/lib/towerops/snmp/profiles/vendors/engenius.ex @@ -0,0 +1,102 @@ +defmodule Towerops.Snmp.Profiles.Vendors.Engenius do + @moduledoc """ + EnGenius device-specific SNMP handling. + + Supports EnGenius wireless access points and outdoor equipment. + + Supported devices: + - ENS Series (ENS500, ENS620EXT) + - EWS Series (EWS357AP, EWS377AP) + - ECW Series (ECW230, ECW260) + - EnStationAC + - EnJet Series + """ + + @behaviour Towerops.Snmp.Profiles.Vendors.Vendor + + alias Towerops.Snmp.Client + alias Towerops.Snmp.Profiles.Vendors.Vendor + + # EnGenius Enterprise OID: 1.3.6.1.4.1.14125 + @model_oid "1.3.6.1.4.1.14125.1.1.1.0" + + @impl true + def profile_names do + ["engenius"] + 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 Connected Clients + %{ + oid: "1.3.6.1.4.1.14125.100.1.4.1.0", + sensor_type: "clients", + sensor_descr: "Connected Clients", + sensor_unit: "", + sensor_divisor: 1 + }, + # 2.4GHz Clients + %{ + oid: "1.3.6.1.4.1.14125.100.1.4.2.0", + sensor_type: "clients", + sensor_descr: "2.4GHz Clients", + sensor_unit: "", + sensor_divisor: 1 + }, + # 5GHz Clients + %{ + oid: "1.3.6.1.4.1.14125.100.1.4.3.0", + sensor_type: "clients", + sensor_descr: "5GHz Clients", + sensor_unit: "", + sensor_divisor: 1 + }, + # CPU Utilization + %{ + oid: "1.3.6.1.4.1.14125.100.1.5.1.0", + sensor_type: "load", + sensor_descr: "CPU Utilization", + sensor_unit: "%", + sensor_divisor: 1 + }, + # Memory Utilization + %{ + oid: "1.3.6.1.4.1.14125.100.1.5.2.0", + sensor_type: "load", + sensor_descr: "Memory Utilization", + sensor_unit: "%", + sensor_divisor: 1 + }, + # Channel Utilization 2.4GHz + %{ + oid: "1.3.6.1.4.1.14125.100.1.6.1.0", + sensor_type: "load", + sensor_descr: "2.4GHz Channel Utilization", + sensor_unit: "%", + sensor_divisor: 1 + }, + # Channel Utilization 5GHz + %{ + oid: "1.3.6.1.4.1.14125.100.1.6.2.0", + sensor_type: "load", + sensor_descr: "5GHz Channel Utilization", + sensor_unit: "%", + sensor_divisor: 1 + } + ] + end +end diff --git a/lib/towerops/snmp/profiles/vendors/grandstream.ex b/lib/towerops/snmp/profiles/vendors/grandstream.ex new file mode 100644 index 00000000..dd56129f --- /dev/null +++ b/lib/towerops/snmp/profiles/vendors/grandstream.ex @@ -0,0 +1,86 @@ +defmodule Towerops.Snmp.Profiles.Vendors.Grandstream do + @moduledoc """ + Grandstream device-specific SNMP handling. + + Supports Grandstream wireless access points. + + Supported devices: + - GWN7600 Series + - GWN7610 + - GWN7615 + - GWN7660 + - GWN7664 + """ + + @behaviour Towerops.Snmp.Profiles.Vendors.Vendor + + alias Towerops.Snmp.Client + alias Towerops.Snmp.Profiles.Vendors.Vendor + + # Grandstream Enterprise OID: 1.3.6.1.4.1.42397 + @model_oid "1.3.6.1.4.1.42397.1.1.2.1.0" + + @impl true + def profile_names do + ["grandstream-ap"] + 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 Connected Clients + %{ + oid: "1.3.6.1.4.1.42397.1.1.3.1.0", + sensor_type: "clients", + sensor_descr: "Connected Clients", + sensor_unit: "", + sensor_divisor: 1 + }, + # 2.4GHz Clients + %{ + oid: "1.3.6.1.4.1.42397.1.1.3.2.0", + sensor_type: "clients", + sensor_descr: "2.4GHz Clients", + sensor_unit: "", + sensor_divisor: 1 + }, + # 5GHz Clients + %{ + oid: "1.3.6.1.4.1.42397.1.1.3.3.0", + sensor_type: "clients", + sensor_descr: "5GHz Clients", + sensor_unit: "", + sensor_divisor: 1 + }, + # CPU Utilization + %{ + oid: "1.3.6.1.4.1.42397.1.1.4.1.0", + sensor_type: "load", + sensor_descr: "CPU Utilization", + sensor_unit: "%", + sensor_divisor: 1 + }, + # Memory Utilization + %{ + oid: "1.3.6.1.4.1.42397.1.1.4.2.0", + sensor_type: "load", + sensor_descr: "Memory Utilization", + sensor_unit: "%", + sensor_divisor: 1 + } + ] + end +end diff --git a/lib/towerops/snmp/profiles/vendors/ligoos.ex b/lib/towerops/snmp/profiles/vendors/ligoos.ex index eb76a5ac..11652dce 100644 --- a/lib/towerops/snmp/profiles/vendors/ligoos.ex +++ b/lib/towerops/snmp/profiles/vendors/ligoos.ex @@ -7,7 +7,6 @@ defmodule Towerops.Snmp.Profiles.Vendors.Ligoos do Supported devices: - LigoWave LigoPTP series - LigoWave DLB series - - Deliberant devices (share same firmware) """ @behaviour Towerops.Snmp.Profiles.Vendors.Vendor @@ -19,7 +18,7 @@ defmodule Towerops.Snmp.Profiles.Vendors.Ligoos do @wl_if_table "1.3.6.1.4.1.32750.3.10.1.2.1.1" @impl true - def profile_names, do: ["ligoos", "deliberant"] + def profile_names, do: ["ligoos"] @impl true def detect_hardware(_client_opts) do diff --git a/lib/towerops/snmp/profiles/vendors/registry.ex b/lib/towerops/snmp/profiles/vendors/registry.ex index 5d7cda4c..58491474 100644 --- a/lib/towerops/snmp/profiles/vendors/registry.ex +++ b/lib/towerops/snmp/profiles/vendors/registry.ex @@ -16,10 +16,15 @@ defmodule Towerops.Snmp.Profiles.Vendors.Registry do alias Towerops.Snmp.Profiles.Vendors.CambiumPtp alias Towerops.Snmp.Profiles.Vendors.Ciscowap alias Towerops.Snmp.Profiles.Vendors.Ciscowlc + alias Towerops.Snmp.Profiles.Vendors.Cnmatrix alias Towerops.Snmp.Profiles.Vendors.Cnpilot + alias Towerops.Snmp.Profiles.Vendors.Cnwave60 + alias Towerops.Snmp.Profiles.Vendors.Deliberant alias Towerops.Snmp.Profiles.Vendors.Dragonwave + alias Towerops.Snmp.Profiles.Vendors.Engenius alias Towerops.Snmp.Profiles.Vendors.Epmp alias Towerops.Snmp.Profiles.Vendors.Exalt + alias Towerops.Snmp.Profiles.Vendors.Grandstream alias Towerops.Snmp.Profiles.Vendors.Ligoos alias Towerops.Snmp.Profiles.Vendors.Mimosa alias Towerops.Snmp.Profiles.Vendors.Netonix @@ -28,6 +33,7 @@ defmodule Towerops.Snmp.Profiles.Vendors.Registry do alias Towerops.Snmp.Profiles.Vendors.Radwin alias Towerops.Snmp.Profiles.Vendors.Routeros alias Towerops.Snmp.Profiles.Vendors.Ruckus + alias Towerops.Snmp.Profiles.Vendors.RuckusSz alias Towerops.Snmp.Profiles.Vendors.Saf alias Towerops.Snmp.Profiles.Vendors.Siae alias Towerops.Snmp.Profiles.Vendors.Siklu @@ -37,18 +43,23 @@ defmodule Towerops.Snmp.Profiles.Vendors.Registry do @vendors [ Aerohive, - Airos, Airfiber, + Airos, Aruba, Aviat, Baicells, CambiumPtp, Ciscowap, Ciscowlc, + Cnmatrix, Cnpilot, + Cnwave60, + Deliberant, Dragonwave, + Engenius, Epmp, Exalt, + Grandstream, Ligoos, Mimosa, Netonix, @@ -57,6 +68,7 @@ defmodule Towerops.Snmp.Profiles.Vendors.Registry do Radwin, Routeros, Ruckus, + RuckusSz, Saf, Siae, Siklu, diff --git a/lib/towerops/snmp/profiles/vendors/ruckus_sz.ex b/lib/towerops/snmp/profiles/vendors/ruckus_sz.ex new file mode 100644 index 00000000..6b72f116 --- /dev/null +++ b/lib/towerops/snmp/profiles/vendors/ruckus_sz.ex @@ -0,0 +1,85 @@ +defmodule Towerops.Snmp.Profiles.Vendors.RuckusSz do + @moduledoc """ + Ruckus SmartZone wireless controller device-specific SNMP handling. + + Supports Ruckus SmartZone wireless LAN controllers. + + Supported devices: + - SmartZone 100 (SZ-100) + - SmartZone 144 (SZ-144) + - SmartZone 300 (SZ-300) + - Virtual SmartZone (vSZ) + """ + + @behaviour Towerops.Snmp.Profiles.Vendors.Vendor + + alias Towerops.Snmp.Client + alias Towerops.Snmp.Profiles.Vendors.Vendor + + # Ruckus Enterprise OID: 1.3.6.1.4.1.25053 + @model_oid "1.3.6.1.4.1.25053.1.1.1.0" + + @impl true + def profile_names do + ["ruckuswireless-sz"] + 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 Access Points + %{ + oid: "1.3.6.1.4.1.25053.1.2.1.1.1.1.1.0", + sensor_type: "count", + sensor_descr: "Total Access Points", + sensor_unit: "", + sensor_divisor: 1 + }, + # Connected Access Points + %{ + oid: "1.3.6.1.4.1.25053.1.2.1.1.1.1.2.0", + sensor_type: "count", + sensor_descr: "Connected Access Points", + sensor_unit: "", + sensor_divisor: 1 + }, + # Total Wireless Clients + %{ + oid: "1.3.6.1.4.1.25053.1.2.1.1.1.1.3.0", + sensor_type: "clients", + sensor_descr: "Total Wireless Clients", + sensor_unit: "", + sensor_divisor: 1 + }, + # CPU Utilization + %{ + oid: "1.3.6.1.4.1.25053.1.2.1.1.1.2.1.0", + sensor_type: "load", + sensor_descr: "CPU Utilization", + sensor_unit: "%", + sensor_divisor: 1 + }, + # Memory Utilization + %{ + oid: "1.3.6.1.4.1.25053.1.2.1.1.1.2.2.0", + sensor_type: "load", + sensor_descr: "Memory Utilization", + sensor_unit: "%", + sensor_divisor: 1 + } + ] + end +end diff --git a/test/towerops/snmp/profiles/vendors/cnmatrix_test.exs b/test/towerops/snmp/profiles/vendors/cnmatrix_test.exs new file mode 100644 index 00000000..e621dad5 --- /dev/null +++ b/test/towerops/snmp/profiles/vendors/cnmatrix_test.exs @@ -0,0 +1,106 @@ +defmodule Towerops.Snmp.Profiles.Vendors.CnmatrixTest do + use Towerops.DataCase, async: true + + import Mox + + alias Towerops.Snmp.Profiles.Vendors.Cnmatrix + 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 cnmatrix profile name" do + assert Cnmatrix.profile_names() == ["cnmatrix"] + 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.24.1.1.1.0", _ -> + {:ok, "cnMatrix EX2028"} + end) + + assert Cnmatrix.detect_hardware(@client_opts) == "cnMatrix EX2028" + end + + test "returns nil when OID unavailable" do + expect(SnmpMock, :get, fn _, _, _ -> + {:error, :no_such_object} + end) + + assert Cnmatrix.detect_hardware(@client_opts) == nil + end + end + + describe "wireless_oid_defs/0" do + test "returns list of sensor definitions" do + defs = Cnmatrix.wireless_oid_defs() + + assert is_list(defs) + assert [_ | _] = defs + end + + test "includes CPU utilization sensor" do + defs = Cnmatrix.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 temperature sensor" do + defs = Cnmatrix.wireless_oid_defs() + temp = Enum.find(defs, &(&1.sensor_type == "temperature")) + + assert temp + assert temp.sensor_descr == "System Temperature" + end + + test "includes PoE power sensor" do + defs = Cnmatrix.wireless_oid_defs() + poe = Enum.find(defs, &(&1.sensor_descr == "Total PoE Power")) + + assert poe + assert poe.sensor_type == "power" + 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, "17713.24.2.1.0") -> {:ok, 35} + String.contains?(oid, "17713.24.2.2.0") -> {:ok, 68} + String.contains?(oid, "17713.24.3.1.0") -> {:ok, 42} + String.contains?(oid, "17713.24.3.2.0") -> {:ok, 3500} + String.contains?(oid, "17713.24.4.1.0") -> {:ok, 75_000} + true -> {:error, :no_such_object} + end + end) + + sensors = Cnmatrix.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 = Cnmatrix.discover_wireless_sensors(@client_opts) + + assert sensors == [] + end + end +end diff --git a/test/towerops/snmp/profiles/vendors/cnwave60_test.exs b/test/towerops/snmp/profiles/vendors/cnwave60_test.exs new file mode 100644 index 00000000..de5c9d58 --- /dev/null +++ b/test/towerops/snmp/profiles/vendors/cnwave60_test.exs @@ -0,0 +1,106 @@ +defmodule Towerops.Snmp.Profiles.Vendors.Cnwave60Test do + use Towerops.DataCase, async: true + + import Mox + + alias Towerops.Snmp.Profiles.Vendors.Cnwave60 + 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 cnwave60 profile name" do + assert Cnwave60.profile_names() == ["cnwave60"] + 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.60.1.1.1.0", _ -> + {:ok, "cnWave V3000"} + end) + + assert Cnwave60.detect_hardware(@client_opts) == "cnWave V3000" + end + + test "returns nil when OID unavailable" do + expect(SnmpMock, :get, fn _, _, _ -> + {:error, :no_such_object} + end) + + assert Cnwave60.detect_hardware(@client_opts) == nil + end + end + + describe "wireless_oid_defs/0" do + test "returns list of wireless sensor definitions" do + defs = Cnwave60.wireless_oid_defs() + + assert is_list(defs) + assert [_ | _] = defs + end + + test "includes receive signal level sensor" do + defs = Cnwave60.wireless_oid_defs() + rsl = Enum.find(defs, &(&1.sensor_descr == "Receive Signal Level")) + + assert rsl + assert rsl.sensor_type == "rssi" + assert rsl.sensor_divisor == 10 + end + + test "includes SNR sensor" do + defs = Cnwave60.wireless_oid_defs() + snr = Enum.find(defs, &(&1.sensor_descr == "Signal to Noise Ratio")) + + assert snr + assert snr.sensor_type == "snr" + end + + test "includes temperature sensor" do + defs = Cnwave60.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, 5, fn _, oid, _ -> + cond do + String.contains?(oid, "17713.60.12.1.0") -> {:ok, -550} + String.contains?(oid, "17713.60.12.2.0") -> {:ok, 200} + String.contains?(oid, "17713.60.12.3.0") -> {:ok, 280} + String.contains?(oid, "17713.60.12.4.0") -> {:ok, 1000} + String.contains?(oid, "17713.60.13.1.0") -> {:ok, 45} + true -> {:error, :no_such_object} + end + end) + + sensors = Cnwave60.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 = Cnwave60.discover_wireless_sensors(@client_opts) + + assert sensors == [] + end + end +end diff --git a/test/towerops/snmp/profiles/vendors/deliberant_test.exs b/test/towerops/snmp/profiles/vendors/deliberant_test.exs new file mode 100644 index 00000000..e8adc123 --- /dev/null +++ b/test/towerops/snmp/profiles/vendors/deliberant_test.exs @@ -0,0 +1,106 @@ +defmodule Towerops.Snmp.Profiles.Vendors.DeliberantTest do + use Towerops.DataCase, async: true + + import Mox + + alias Towerops.Snmp.Profiles.Vendors.Deliberant + 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 deliberant profile name" do + assert Deliberant.profile_names() == ["deliberant"] + end + end + + describe "detect_hardware/1" do + test "detects hardware from model OID" do + expect(SnmpMock, :get, fn _, "1.3.6.1.4.1.32761.1.1.1.0", _ -> + {:ok, "DLB APC 5M"} + end) + + assert Deliberant.detect_hardware(@client_opts) == "DLB APC 5M" + end + + test "returns nil when OID unavailable" do + expect(SnmpMock, :get, fn _, _, _ -> + {:error, :no_such_object} + end) + + assert Deliberant.detect_hardware(@client_opts) == nil + end + end + + describe "wireless_oid_defs/0" do + test "returns list of wireless sensor definitions" do + defs = Deliberant.wireless_oid_defs() + + assert is_list(defs) + assert [_ | _] = defs + end + + test "includes signal level sensor" do + defs = Deliberant.wireless_oid_defs() + signal = Enum.find(defs, &(&1.sensor_descr == "Signal Level")) + + assert signal + assert signal.sensor_type == "rssi" + assert signal.sensor_unit == "dBm" + end + + test "includes noise floor sensor" do + defs = Deliberant.wireless_oid_defs() + noise = Enum.find(defs, &(&1.sensor_descr == "Noise Floor")) + + assert noise + assert noise.sensor_type == "noise" + end + + test "includes temperature sensor" do + defs = Deliberant.wireless_oid_defs() + temp = Enum.find(defs, &(&1.sensor_type == "temperature")) + + assert temp + assert temp.sensor_descr == "CPU Temperature" + 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, "32761.3.5.1.2.1.6.1") -> {:ok, -65} + String.contains?(oid, "32761.3.5.1.2.1.7.1") -> {:ok, -95} + String.contains?(oid, "32761.3.5.1.2.1.8.1") -> {:ok, 85} + String.contains?(oid, "32761.3.5.1.2.1.9.1") -> {:ok, 12} + String.contains?(oid, "32761.3.1.1.2.0") -> {:ok, 55} + true -> {:error, :no_such_object} + end + end) + + sensors = Deliberant.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 = Deliberant.discover_wireless_sensors(@client_opts) + + assert sensors == [] + end + end +end diff --git a/test/towerops/snmp/profiles/vendors/engenius_test.exs b/test/towerops/snmp/profiles/vendors/engenius_test.exs new file mode 100644 index 00000000..d7ee7749 --- /dev/null +++ b/test/towerops/snmp/profiles/vendors/engenius_test.exs @@ -0,0 +1,110 @@ +defmodule Towerops.Snmp.Profiles.Vendors.EngeniusTest do + use Towerops.DataCase, async: true + + import Mox + + alias Towerops.Snmp.Profiles.Vendors.Engenius + 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 engenius profile name" do + assert Engenius.profile_names() == ["engenius"] + end + end + + describe "detect_hardware/1" do + test "detects hardware from model OID" do + expect(SnmpMock, :get, fn _, "1.3.6.1.4.1.14125.1.1.1.0", _ -> + {:ok, "EWS377AP"} + end) + + assert Engenius.detect_hardware(@client_opts) == "EWS377AP" + end + + test "returns nil when OID unavailable" do + expect(SnmpMock, :get, fn _, _, _ -> + {:error, :no_such_object} + end) + + assert Engenius.detect_hardware(@client_opts) == nil + end + end + + describe "wireless_oid_defs/0" do + test "returns list of wireless sensor definitions" do + defs = Engenius.wireless_oid_defs() + + assert is_list(defs) + assert [_ | _] = defs + end + + test "includes total client count sensor" do + defs = Engenius.wireless_oid_defs() + clients = Enum.find(defs, &(&1.sensor_descr == "Connected Clients")) + + assert clients + assert clients.sensor_type == "clients" + end + + test "includes band-specific client sensors" do + defs = Engenius.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_5g + end + + test "includes channel utilization sensors" do + defs = Engenius.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_5g + 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, "14125.100.1.4.1.0") -> {:ok, 45} + String.contains?(oid, "14125.100.1.4.2.0") -> {:ok, 20} + String.contains?(oid, "14125.100.1.4.3.0") -> {:ok, 25} + String.contains?(oid, "14125.100.1.5.1.0") -> {:ok, 35} + String.contains?(oid, "14125.100.1.5.2.0") -> {:ok, 68} + String.contains?(oid, "14125.100.1.6.1.0") -> {:ok, 45} + String.contains?(oid, "14125.100.1.6.2.0") -> {:ok, 30} + true -> {:error, :no_such_object} + end + end) + + sensors = Engenius.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 = Engenius.discover_wireless_sensors(@client_opts) + + assert sensors == [] + end + end +end diff --git a/test/towerops/snmp/profiles/vendors/grandstream_test.exs b/test/towerops/snmp/profiles/vendors/grandstream_test.exs new file mode 100644 index 00000000..ad6f2beb --- /dev/null +++ b/test/towerops/snmp/profiles/vendors/grandstream_test.exs @@ -0,0 +1,109 @@ +defmodule Towerops.Snmp.Profiles.Vendors.GrandstreamTest do + use Towerops.DataCase, async: true + + import Mox + + alias Towerops.Snmp.Profiles.Vendors.Grandstream + 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 grandstream-ap profile name" do + assert Grandstream.profile_names() == ["grandstream-ap"] + end + end + + describe "detect_hardware/1" do + test "detects hardware from model OID" do + expect(SnmpMock, :get, fn _, "1.3.6.1.4.1.42397.1.1.2.1.0", _ -> + {:ok, "GWN7660"} + end) + + assert Grandstream.detect_hardware(@client_opts) == "GWN7660" + end + + test "returns nil when OID unavailable" do + expect(SnmpMock, :get, fn _, _, _ -> + {:error, :no_such_object} + end) + + assert Grandstream.detect_hardware(@client_opts) == nil + end + end + + describe "wireless_oid_defs/0" do + test "returns list of wireless sensor definitions" do + defs = Grandstream.wireless_oid_defs() + + assert is_list(defs) + assert [_ | _] = defs + end + + test "includes total client count sensor" do + defs = Grandstream.wireless_oid_defs() + clients = Enum.find(defs, &(&1.sensor_descr == "Connected Clients")) + + assert clients + assert clients.sensor_type == "clients" + end + + test "includes band-specific client sensors" do + defs = Grandstream.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_5g + end + + test "includes CPU and memory sensors" do + defs = Grandstream.wireless_oid_defs() + cpu = Enum.find(defs, &(&1.sensor_descr == "CPU Utilization")) + mem = Enum.find(defs, &(&1.sensor_descr == "Memory Utilization")) + + assert cpu + assert cpu.sensor_type == "load" + assert mem + assert mem.sensor_type == "load" + 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, "42397.1.1.3.1.0") -> {:ok, 45} + String.contains?(oid, "42397.1.1.3.2.0") -> {:ok, 20} + String.contains?(oid, "42397.1.1.3.3.0") -> {:ok, 25} + String.contains?(oid, "42397.1.1.4.1.0") -> {:ok, 35} + String.contains?(oid, "42397.1.1.4.2.0") -> {:ok, 68} + true -> {:error, :no_such_object} + end + end) + + sensors = Grandstream.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 = Grandstream.discover_wireless_sensors(@client_opts) + + assert sensors == [] + end + end +end diff --git a/test/towerops/snmp/profiles/vendors/ligoos_test.exs b/test/towerops/snmp/profiles/vendors/ligoos_test.exs index 951dfddb..9195953d 100644 --- a/test/towerops/snmp/profiles/vendors/ligoos_test.exs +++ b/test/towerops/snmp/profiles/vendors/ligoos_test.exs @@ -17,8 +17,8 @@ defmodule Towerops.Snmp.Profiles.Vendors.LigoosTest do ] describe "profile_names/0" do - test "returns ligoos and deliberant profile names" do - assert Ligoos.profile_names() == ["ligoos", "deliberant"] + test "returns ligoos profile name" do + assert Ligoos.profile_names() == ["ligoos"] end end diff --git a/test/towerops/snmp/profiles/vendors/registry_test.exs b/test/towerops/snmp/profiles/vendors/registry_test.exs index 18f1a5b2..d708eda4 100644 --- a/test/towerops/snmp/profiles/vendors/registry_test.exs +++ b/test/towerops/snmp/profiles/vendors/registry_test.exs @@ -6,6 +6,7 @@ defmodule Towerops.Snmp.Profiles.Vendors.RegistryTest do alias Towerops.Snmp.Profiles.Vendors.Airfiber alias Towerops.Snmp.Profiles.Vendors.Airos alias Towerops.Snmp.Profiles.Vendors.Cnpilot + alias Towerops.Snmp.Profiles.Vendors.Deliberant alias Towerops.Snmp.Profiles.Vendors.Dragonwave alias Towerops.Snmp.Profiles.Vendors.Epmp alias Towerops.Snmp.Profiles.Vendors.Exalt @@ -122,8 +123,8 @@ defmodule Towerops.Snmp.Profiles.Vendors.RegistryTest do assert Registry.get_vendor("ligoos") == Ligoos end - test "returns Ligoos for deliberant profile" do - assert Registry.get_vendor("deliberant") == Ligoos + test "returns Deliberant for deliberant profile" do + assert Registry.get_vendor("deliberant") == Deliberant end test "returns Cnpilot for cnpilot profile" do diff --git a/test/towerops/snmp/profiles/vendors/ruckus_sz_test.exs b/test/towerops/snmp/profiles/vendors/ruckus_sz_test.exs new file mode 100644 index 00000000..2166e794 --- /dev/null +++ b/test/towerops/snmp/profiles/vendors/ruckus_sz_test.exs @@ -0,0 +1,105 @@ +defmodule Towerops.Snmp.Profiles.Vendors.RuckusSzTest do + use Towerops.DataCase, async: true + + import Mox + + alias Towerops.Snmp.Profiles.Vendors.RuckusSz + 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 ruckuswireless-sz profile name" do + assert RuckusSz.profile_names() == ["ruckuswireless-sz"] + end + end + + describe "detect_hardware/1" do + test "detects hardware from model OID" do + expect(SnmpMock, :get, fn _, "1.3.6.1.4.1.25053.1.1.1.0", _ -> + {:ok, "SmartZone 300"} + end) + + assert RuckusSz.detect_hardware(@client_opts) == "SmartZone 300" + end + + test "returns nil when OID unavailable" do + expect(SnmpMock, :get, fn _, _, _ -> + {:error, :no_such_object} + end) + + assert RuckusSz.detect_hardware(@client_opts) == nil + end + end + + describe "wireless_oid_defs/0" do + test "returns list of sensor definitions" do + defs = RuckusSz.wireless_oid_defs() + + assert is_list(defs) + assert [_ | _] = defs + end + + test "includes total APs sensor" do + defs = RuckusSz.wireless_oid_defs() + aps = Enum.find(defs, &(&1.sensor_descr == "Total Access Points")) + + assert aps + assert aps.sensor_type == "count" + end + + test "includes client count sensor" do + defs = RuckusSz.wireless_oid_defs() + clients = Enum.find(defs, &(&1.sensor_descr == "Total Wireless Clients")) + + assert clients + assert clients.sensor_type == "clients" + end + + test "includes CPU utilization sensor" do + defs = RuckusSz.wireless_oid_defs() + cpu = Enum.find(defs, &(&1.sensor_descr == "CPU Utilization")) + + assert cpu + assert cpu.sensor_type == "load" + 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, "25053.1.2.1.1.1.1.1.0") -> {:ok, 150} + String.contains?(oid, "25053.1.2.1.1.1.1.2.0") -> {:ok, 145} + String.contains?(oid, "25053.1.2.1.1.1.1.3.0") -> {:ok, 2500} + String.contains?(oid, "25053.1.2.1.1.1.2.1.0") -> {:ok, 45} + String.contains?(oid, "25053.1.2.1.1.1.2.2.0") -> {:ok, 72} + true -> {:error, :no_such_object} + end + end) + + sensors = RuckusSz.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 = RuckusSz.discover_wireless_sensors(@client_opts) + + assert sensors == [] + end + end +end