From 74e409d6a634ea8d0c82ef65d7836661baf8d2df Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Thu, 22 Jan 2026 10:22:19 -0600 Subject: [PATCH] feat: add Pepwave, Ceragon, Tranzeo, and CMM vendor modules --- lib/towerops/snmp/profiles/vendors/ceragon.ex | 74 ++++++++++++ lib/towerops/snmp/profiles/vendors/cmm.ex | 74 ++++++++++++ lib/towerops/snmp/profiles/vendors/pepwave.ex | 74 ++++++++++++ .../snmp/profiles/vendors/registry.ex | 8 ++ lib/towerops/snmp/profiles/vendors/tranzeo.ex | 83 ++++++++++++++ .../snmp/profiles/vendors/ceragon_test.exs | 99 ++++++++++++++++ .../snmp/profiles/vendors/cmm_test.exs | 99 ++++++++++++++++ .../snmp/profiles/vendors/pepwave_test.exs | 99 ++++++++++++++++ .../snmp/profiles/vendors/registry_test.exs | 28 +++++ .../snmp/profiles/vendors/tranzeo_test.exs | 107 ++++++++++++++++++ 10 files changed, 745 insertions(+) create mode 100644 lib/towerops/snmp/profiles/vendors/ceragon.ex create mode 100644 lib/towerops/snmp/profiles/vendors/cmm.ex create mode 100644 lib/towerops/snmp/profiles/vendors/pepwave.ex create mode 100644 lib/towerops/snmp/profiles/vendors/tranzeo.ex create mode 100644 test/towerops/snmp/profiles/vendors/ceragon_test.exs create mode 100644 test/towerops/snmp/profiles/vendors/cmm_test.exs create mode 100644 test/towerops/snmp/profiles/vendors/pepwave_test.exs create mode 100644 test/towerops/snmp/profiles/vendors/tranzeo_test.exs diff --git a/lib/towerops/snmp/profiles/vendors/ceragon.ex b/lib/towerops/snmp/profiles/vendors/ceragon.ex new file mode 100644 index 00000000..26dfd441 --- /dev/null +++ b/lib/towerops/snmp/profiles/vendors/ceragon.ex @@ -0,0 +1,74 @@ +defmodule Towerops.Snmp.Profiles.Vendors.Ceragon do + @moduledoc """ + Ceragon CeraOS microwave backhaul device-specific SNMP handling. + + Supports Ceragon FibeAir microwave radio systems. + + Supported devices: + - FibeAir IP-10 series + - FibeAir IP-20 series + - FibeAir RFU-C/H series + """ + + @behaviour Towerops.Snmp.Profiles.Vendors.Vendor + + alias Towerops.Snmp.Client + alias Towerops.Snmp.Profiles.Vendors.Vendor + + # Ceragon Enterprise OID: 1.3.6.1.4.1.2281 + @model_oid "1.3.6.1.4.1.2281.1.1.1.0" + + @impl true + def profile_names, do: ["ceraos"] + + @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 + [ + # TX Power + %{ + oid: "1.3.6.1.4.1.2281.10.4.1.11.1.1.1", + sensor_type: "power", + sensor_descr: "TX Power", + sensor_unit: "dBm", + sensor_divisor: 10 + }, + # RX Level + %{ + oid: "1.3.6.1.4.1.2281.10.4.1.11.1.2.1", + sensor_type: "rssi", + sensor_descr: "RX Level", + sensor_unit: "dBm", + sensor_divisor: 10 + }, + # SNR + %{ + oid: "1.3.6.1.4.1.2281.10.4.1.11.1.3.1", + sensor_type: "snr", + sensor_descr: "Signal to Noise Ratio", + sensor_unit: "dB", + sensor_divisor: 10 + }, + # Link Status (1=up, 2=down) + %{ + oid: "1.3.6.1.4.1.2281.10.4.1.11.1.4.1", + sensor_type: "state", + sensor_descr: "Link Status", + sensor_unit: "", + sensor_divisor: 1 + } + ] + end +end diff --git a/lib/towerops/snmp/profiles/vendors/cmm.ex b/lib/towerops/snmp/profiles/vendors/cmm.ex new file mode 100644 index 00000000..5e118a4d --- /dev/null +++ b/lib/towerops/snmp/profiles/vendors/cmm.ex @@ -0,0 +1,74 @@ +defmodule Towerops.Snmp.Profiles.Vendors.Cmm do + @moduledoc """ + Cambium CMM (Cluster Management Module) device-specific SNMP handling. + + Supports Cambium Networks CMM devices for tower site management. + + Supported devices: + - CMM3 (legacy) + - CMM4 (current generation) + - CMM5 (latest) + """ + + @behaviour Towerops.Snmp.Profiles.Vendors.Vendor + + alias Towerops.Snmp.Client + alias Towerops.Snmp.Profiles.Vendors.Vendor + + # Cambium Enterprise OID: 1.3.6.1.4.1.161.19 + @model_oid "1.3.6.1.4.1.161.19.3.4.4.2.0" + + @impl true + def profile_names, do: ["cmm", "cmm4"] + + @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 + [ + # Power Status (main supply) + %{ + oid: "1.3.6.1.4.1.161.19.3.4.4.1.0", + sensor_type: "state", + sensor_descr: "Power Status", + sensor_unit: "", + sensor_divisor: 1 + }, + # GPS Sync Status + %{ + oid: "1.3.6.1.4.1.161.19.3.4.4.3.0", + sensor_type: "state", + sensor_descr: "GPS Sync Status", + sensor_unit: "", + sensor_divisor: 1 + }, + # System Uptime (seconds) + %{ + oid: "1.3.6.1.4.1.161.19.3.4.4.4.0", + sensor_type: "runtime", + sensor_descr: "System Uptime", + sensor_unit: "s", + sensor_divisor: 1 + }, + # Connected APs count + %{ + oid: "1.3.6.1.4.1.161.19.3.4.4.5.0", + sensor_type: "ap-count", + sensor_descr: "Connected APs", + sensor_unit: "", + sensor_divisor: 1 + } + ] + end +end diff --git a/lib/towerops/snmp/profiles/vendors/pepwave.ex b/lib/towerops/snmp/profiles/vendors/pepwave.ex new file mode 100644 index 00000000..69b76abe --- /dev/null +++ b/lib/towerops/snmp/profiles/vendors/pepwave.ex @@ -0,0 +1,74 @@ +defmodule Towerops.Snmp.Profiles.Vendors.Pepwave do + @moduledoc """ + Peplink/Pepwave device-specific SNMP handling. + + Supports Pepwave cellular routers and bonding devices. + + Supported devices: + - Pepwave MAX series (HD2, HD4, Transit, etc.) + - Pepwave Balance series + - Peplink SpeedFusion appliances + """ + + @behaviour Towerops.Snmp.Profiles.Vendors.Vendor + + alias Towerops.Snmp.Client + alias Towerops.Snmp.Profiles.Vendors.Vendor + + # Peplink Enterprise OID: 1.3.6.1.4.1.27662 + @model_oid "1.3.6.1.4.1.27662.1.1.1.0" + + @impl true + def profile_names, do: ["pepwave"] + + @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 + [ + # WAN1 Signal Strength + %{ + oid: "1.3.6.1.4.1.27662.3.1.1.1.0", + sensor_type: "rssi", + sensor_descr: "WAN1 Signal Strength", + sensor_unit: "dBm", + sensor_divisor: 1 + }, + # WAN1 Connection Status (1=connected) + %{ + oid: "1.3.6.1.4.1.27662.3.1.2.1.0", + sensor_type: "state", + sensor_descr: "WAN1 Status", + sensor_unit: "", + sensor_divisor: 1 + }, + # CPU Utilization + %{ + oid: "1.3.6.1.4.1.27662.2.1.1.0", + sensor_type: "load", + sensor_descr: "CPU Utilization", + sensor_unit: "%", + sensor_divisor: 1 + }, + # Memory Utilization + %{ + oid: "1.3.6.1.4.1.27662.2.1.2.0", + sensor_type: "load", + sensor_descr: "Memory Utilization", + sensor_unit: "%", + sensor_divisor: 1 + } + ] + end +end diff --git a/lib/towerops/snmp/profiles/vendors/registry.ex b/lib/towerops/snmp/profiles/vendors/registry.ex index 962028c5..4c5a7226 100644 --- a/lib/towerops/snmp/profiles/vendors/registry.ex +++ b/lib/towerops/snmp/profiles/vendors/registry.ex @@ -16,8 +16,10 @@ defmodule Towerops.Snmp.Profiles.Vendors.Registry do alias Towerops.Snmp.Profiles.Vendors.Aviat alias Towerops.Snmp.Profiles.Vendors.Baicells alias Towerops.Snmp.Profiles.Vendors.CambiumPtp + alias Towerops.Snmp.Profiles.Vendors.Ceragon alias Towerops.Snmp.Profiles.Vendors.Ciscowap alias Towerops.Snmp.Profiles.Vendors.Ciscowlc + alias Towerops.Snmp.Profiles.Vendors.Cmm alias Towerops.Snmp.Profiles.Vendors.Cnmatrix alias Towerops.Snmp.Profiles.Vendors.Cnpilot alias Towerops.Snmp.Profiles.Vendors.Cnwave60 @@ -33,6 +35,7 @@ defmodule Towerops.Snmp.Profiles.Vendors.Registry do alias Towerops.Snmp.Profiles.Vendors.Mimosa alias Towerops.Snmp.Profiles.Vendors.MoxaAwk alias Towerops.Snmp.Profiles.Vendors.Netonix + alias Towerops.Snmp.Profiles.Vendors.Pepwave alias Towerops.Snmp.Profiles.Vendors.Pmp alias Towerops.Snmp.Profiles.Vendors.Proxim alias Towerops.Snmp.Profiles.Vendors.Racom @@ -46,6 +49,7 @@ defmodule Towerops.Snmp.Profiles.Vendors.Registry do alias Towerops.Snmp.Profiles.Vendors.Stellar alias Towerops.Snmp.Profiles.Vendors.Sub10 alias Towerops.Snmp.Profiles.Vendors.Teltonika + alias Towerops.Snmp.Profiles.Vendors.Tranzeo alias Towerops.Snmp.Profiles.Vendors.Unifi alias Towerops.Snmp.Profiles.Vendors.Xirrus alias Towerops.Snmp.Profiles.Vendors.ZyxelWlc @@ -60,8 +64,10 @@ defmodule Towerops.Snmp.Profiles.Vendors.Registry do Aviat, Baicells, CambiumPtp, + Ceragon, Ciscowap, Ciscowlc, + Cmm, Cnmatrix, Cnpilot, Cnwave60, @@ -77,6 +83,7 @@ defmodule Towerops.Snmp.Profiles.Vendors.Registry do Mimosa, MoxaAwk, Netonix, + Pepwave, Pmp, Proxim, Racom, @@ -90,6 +97,7 @@ defmodule Towerops.Snmp.Profiles.Vendors.Registry do Stellar, Sub10, Teltonika, + Tranzeo, Unifi, Xirrus, ZyxelWlc diff --git a/lib/towerops/snmp/profiles/vendors/tranzeo.ex b/lib/towerops/snmp/profiles/vendors/tranzeo.ex new file mode 100644 index 00000000..9a2c6162 --- /dev/null +++ b/lib/towerops/snmp/profiles/vendors/tranzeo.ex @@ -0,0 +1,83 @@ +defmodule Towerops.Snmp.Profiles.Vendors.Tranzeo do + @moduledoc """ + Tranzeo outdoor wireless device-specific SNMP handling. + + Supports Tranzeo outdoor wireless access points and bridges. + + Supported devices: + - Tranzeo TR-5A series + - Tranzeo TR-5plus series + - Tranzeo Classic series + """ + + @behaviour Towerops.Snmp.Profiles.Vendors.Vendor + + alias Towerops.Snmp.Client + alias Towerops.Snmp.Profiles.Vendors.Vendor + + # Tranzeo uses sysDescr for detection + @model_oid "1.3.6.1.2.1.1.1.0" + + @impl true + def profile_names, do: ["tranzeo"] + + @impl true + def detect_hardware(client_opts) do + case Client.get(client_opts, @model_oid) do + {:ok, descr} when is_binary(descr) -> + if String.contains?(descr, "Tranzeo") do + descr + |> String.split(" ") + |> Enum.drop(1) + |> Enum.take(2) + |> Enum.join(" ") + end + + _ -> + 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 + [ + # Associated Clients + %{ + oid: "1.3.6.1.4.1.24350.2.1.1.0", + sensor_type: "clients", + sensor_descr: "Associated Clients", + sensor_unit: "", + sensor_divisor: 1 + }, + # Signal Strength + %{ + oid: "1.3.6.1.4.1.24350.2.2.1.0", + sensor_type: "rssi", + sensor_descr: "Signal Strength", + sensor_unit: "dBm", + sensor_divisor: 1 + }, + # Noise Floor + %{ + oid: "1.3.6.1.4.1.24350.2.2.2.0", + sensor_type: "rssi", + sensor_descr: "Noise Floor", + sensor_unit: "dBm", + sensor_divisor: 1 + }, + # TX Power + %{ + oid: "1.3.6.1.4.1.24350.2.2.3.0", + sensor_type: "power", + sensor_descr: "TX Power", + sensor_unit: "dBm", + sensor_divisor: 1 + } + ] + end +end diff --git a/test/towerops/snmp/profiles/vendors/ceragon_test.exs b/test/towerops/snmp/profiles/vendors/ceragon_test.exs new file mode 100644 index 00000000..a3f143f4 --- /dev/null +++ b/test/towerops/snmp/profiles/vendors/ceragon_test.exs @@ -0,0 +1,99 @@ +defmodule Towerops.Snmp.Profiles.Vendors.CeragonTest do + use Towerops.DataCase, async: true + + import Mox + + alias Towerops.Snmp.Profiles.Vendors.Ceragon + 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 ceraos profile name" do + assert Ceragon.profile_names() == ["ceraos"] + end + end + + describe "detect_hardware/1" do + test "detects hardware from model OID" do + expect(SnmpMock, :get, fn _, "1.3.6.1.4.1.2281.1.1.1.0", _ -> + {:ok, "FibeAir IP-20C"} + end) + + assert Ceragon.detect_hardware(@client_opts) == "FibeAir IP-20C" + end + + test "returns nil when OID unavailable" do + expect(SnmpMock, :get, fn _, _, _ -> + {:error, :no_such_object} + end) + + assert Ceragon.detect_hardware(@client_opts) == nil + end + end + + describe "wireless_oid_defs/0" do + test "returns list of sensor definitions" do + defs = Ceragon.wireless_oid_defs() + + assert is_list(defs) + assert [_ | _] = defs + end + + test "includes TX and RX power sensors" do + defs = Ceragon.wireless_oid_defs() + tx = Enum.find(defs, &(&1.sensor_descr == "TX Power")) + rx = Enum.find(defs, &(&1.sensor_descr == "RX Level")) + + assert tx + assert tx.sensor_type == "power" + assert rx + assert rx.sensor_type == "rssi" + end + + test "includes SNR sensor" do + defs = Ceragon.wireless_oid_defs() + snr = Enum.find(defs, &(&1.sensor_descr == "Signal to Noise Ratio")) + + assert snr + assert snr.sensor_type == "snr" + end + end + + describe "discover_wireless_sensors/1" do + test "discovers sensors when SNMP responds" do + expect(SnmpMock, :get, 4, fn _, oid, _ -> + cond do + String.contains?(oid, "2281.10.4.1.11.1.1.1") -> {:ok, 200} + String.contains?(oid, "2281.10.4.1.11.1.2.1") -> {:ok, -450} + String.contains?(oid, "2281.10.4.1.11.1.3.1") -> {:ok, 320} + String.contains?(oid, "2281.10.4.1.11.1.4.1") -> {:ok, 1} + true -> {:error, :no_such_object} + end + end) + + sensors = Ceragon.discover_wireless_sensors(@client_opts) + + assert is_list(sensors) + assert length(sensors) == 4 + end + + test "returns empty list when no sensors respond" do + expect(SnmpMock, :get, 4, fn _, _, _ -> + {:error, :no_such_object} + end) + + sensors = Ceragon.discover_wireless_sensors(@client_opts) + + assert sensors == [] + end + end +end diff --git a/test/towerops/snmp/profiles/vendors/cmm_test.exs b/test/towerops/snmp/profiles/vendors/cmm_test.exs new file mode 100644 index 00000000..3a90db8c --- /dev/null +++ b/test/towerops/snmp/profiles/vendors/cmm_test.exs @@ -0,0 +1,99 @@ +defmodule Towerops.Snmp.Profiles.Vendors.CmmTest do + use Towerops.DataCase, async: true + + import Mox + + alias Towerops.Snmp.Profiles.Vendors.Cmm + 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 cmm and cmm4 profile names" do + assert Cmm.profile_names() == ["cmm", "cmm4"] + end + end + + describe "detect_hardware/1" do + test "detects hardware from model OID" do + expect(SnmpMock, :get, fn _, "1.3.6.1.4.1.161.19.3.4.4.2.0", _ -> + {:ok, "CMM4"} + end) + + assert Cmm.detect_hardware(@client_opts) == "CMM4" + end + + test "returns nil when OID unavailable" do + expect(SnmpMock, :get, fn _, _, _ -> + {:error, :no_such_object} + end) + + assert Cmm.detect_hardware(@client_opts) == nil + end + end + + describe "wireless_oid_defs/0" do + test "returns list of sensor definitions" do + defs = Cmm.wireless_oid_defs() + + assert is_list(defs) + assert [_ | _] = defs + end + + test "includes power and GPS status sensors" do + defs = Cmm.wireless_oid_defs() + power = Enum.find(defs, &(&1.sensor_descr == "Power Status")) + gps = Enum.find(defs, &(&1.sensor_descr == "GPS Sync Status")) + + assert power + assert power.sensor_type == "state" + assert gps + assert gps.sensor_type == "state" + end + + test "includes connected APs sensor" do + defs = Cmm.wireless_oid_defs() + aps = Enum.find(defs, &(&1.sensor_descr == "Connected APs")) + + assert aps + assert aps.sensor_type == "ap-count" + end + end + + describe "discover_wireless_sensors/1" do + test "discovers sensors when SNMP responds" do + expect(SnmpMock, :get, 4, fn _, oid, _ -> + cond do + String.contains?(oid, "161.19.3.4.4.1.0") -> {:ok, 1} + String.contains?(oid, "161.19.3.4.4.3.0") -> {:ok, 1} + String.contains?(oid, "161.19.3.4.4.4.0") -> {:ok, 86_400} + String.contains?(oid, "161.19.3.4.4.5.0") -> {:ok, 8} + true -> {:error, :no_such_object} + end + end) + + sensors = Cmm.discover_wireless_sensors(@client_opts) + + assert is_list(sensors) + assert length(sensors) == 4 + end + + test "returns empty list when no sensors respond" do + expect(SnmpMock, :get, 4, fn _, _, _ -> + {:error, :no_such_object} + end) + + sensors = Cmm.discover_wireless_sensors(@client_opts) + + assert sensors == [] + end + end +end diff --git a/test/towerops/snmp/profiles/vendors/pepwave_test.exs b/test/towerops/snmp/profiles/vendors/pepwave_test.exs new file mode 100644 index 00000000..ee736089 --- /dev/null +++ b/test/towerops/snmp/profiles/vendors/pepwave_test.exs @@ -0,0 +1,99 @@ +defmodule Towerops.Snmp.Profiles.Vendors.PepwaveTest do + use Towerops.DataCase, async: true + + import Mox + + alias Towerops.Snmp.Profiles.Vendors.Pepwave + 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 pepwave profile name" do + assert Pepwave.profile_names() == ["pepwave"] + end + end + + describe "detect_hardware/1" do + test "detects hardware from model OID" do + expect(SnmpMock, :get, fn _, "1.3.6.1.4.1.27662.1.1.1.0", _ -> + {:ok, "MAX HD4"} + end) + + assert Pepwave.detect_hardware(@client_opts) == "MAX HD4" + end + + test "returns nil when OID unavailable" do + expect(SnmpMock, :get, fn _, _, _ -> + {:error, :no_such_object} + end) + + assert Pepwave.detect_hardware(@client_opts) == nil + end + end + + describe "wireless_oid_defs/0" do + test "returns list of sensor definitions" do + defs = Pepwave.wireless_oid_defs() + + assert is_list(defs) + assert [_ | _] = defs + end + + test "includes signal strength sensor" do + defs = Pepwave.wireless_oid_defs() + signal = Enum.find(defs, &(&1.sensor_descr == "WAN1 Signal Strength")) + + assert signal + assert signal.sensor_type == "rssi" + end + + test "includes CPU and memory sensors" do + defs = Pepwave.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, 4, fn _, oid, _ -> + cond do + String.contains?(oid, "27662.3.1.1.1.0") -> {:ok, -65} + String.contains?(oid, "27662.3.1.2.1.0") -> {:ok, 1} + String.contains?(oid, "27662.2.1.1.0") -> {:ok, 25} + String.contains?(oid, "27662.2.1.2.0") -> {:ok, 60} + true -> {:error, :no_such_object} + end + end) + + sensors = Pepwave.discover_wireless_sensors(@client_opts) + + assert is_list(sensors) + assert length(sensors) == 4 + end + + test "returns empty list when no sensors respond" do + expect(SnmpMock, :get, 4, fn _, _, _ -> + {:error, :no_such_object} + end) + + sensors = Pepwave.discover_wireless_sensors(@client_opts) + + assert sensors == [] + end + end +end diff --git a/test/towerops/snmp/profiles/vendors/registry_test.exs b/test/towerops/snmp/profiles/vendors/registry_test.exs index 604e1834..ca86e6eb 100644 --- a/test/towerops/snmp/profiles/vendors/registry_test.exs +++ b/test/towerops/snmp/profiles/vendors/registry_test.exs @@ -7,6 +7,8 @@ defmodule Towerops.Snmp.Profiles.Vendors.RegistryTest do alias Towerops.Snmp.Profiles.Vendors.Airos alias Towerops.Snmp.Profiles.Vendors.Altalabs alias Towerops.Snmp.Profiles.Vendors.Alvarion + alias Towerops.Snmp.Profiles.Vendors.Ceragon + alias Towerops.Snmp.Profiles.Vendors.Cmm alias Towerops.Snmp.Profiles.Vendors.Cnpilot alias Towerops.Snmp.Profiles.Vendors.Deliberant alias Towerops.Snmp.Profiles.Vendors.Dlinkap @@ -17,6 +19,7 @@ defmodule Towerops.Snmp.Profiles.Vendors.RegistryTest do alias Towerops.Snmp.Profiles.Vendors.Ligoos alias Towerops.Snmp.Profiles.Vendors.Mimosa alias Towerops.Snmp.Profiles.Vendors.MoxaAwk + alias Towerops.Snmp.Profiles.Vendors.Pepwave alias Towerops.Snmp.Profiles.Vendors.Pmp alias Towerops.Snmp.Profiles.Vendors.Proxim alias Towerops.Snmp.Profiles.Vendors.Racom @@ -28,6 +31,7 @@ defmodule Towerops.Snmp.Profiles.Vendors.RegistryTest do alias Towerops.Snmp.Profiles.Vendors.Siae alias Towerops.Snmp.Profiles.Vendors.Siklu alias Towerops.Snmp.Profiles.Vendors.Stellar + alias Towerops.Snmp.Profiles.Vendors.Tranzeo alias Towerops.Snmp.Profiles.Vendors.Unifi alias Towerops.Snmp.Profiles.Vendors.Xirrus alias Towerops.Snmp.Profiles.Vendors.ZyxelWlc @@ -74,6 +78,10 @@ defmodule Towerops.Snmp.Profiles.Vendors.RegistryTest do assert Dlinkap in vendors assert Fortiap in vendors assert MoxaAwk in vendors + assert Ceragon in vendors + assert Cmm in vendors + assert Pepwave in vendors + assert Tranzeo in vendors end end @@ -110,6 +118,10 @@ defmodule Towerops.Snmp.Profiles.Vendors.RegistryTest do assert "dlinkap" in names assert "fortiap" in names assert "moxa-awk" in names + assert "ceraos" in names + assert "cmm" in names + assert "pepwave" in names + assert "tranzeo" in names end end @@ -222,6 +234,22 @@ defmodule Towerops.Snmp.Profiles.Vendors.RegistryTest do assert Registry.get_vendor("moxa-awk") == MoxaAwk end + test "returns Ceragon for ceraos profile" do + assert Registry.get_vendor("ceraos") == Ceragon + end + + test "returns Cmm for cmm profile" do + assert Registry.get_vendor("cmm") == Cmm + end + + test "returns Pepwave for pepwave profile" do + assert Registry.get_vendor("pepwave") == Pepwave + end + + test "returns Tranzeo for tranzeo profile" do + assert Registry.get_vendor("tranzeo") == Tranzeo + end + test "returns nil for unknown profile" do assert Registry.get_vendor("unknown_vendor") == nil end diff --git a/test/towerops/snmp/profiles/vendors/tranzeo_test.exs b/test/towerops/snmp/profiles/vendors/tranzeo_test.exs new file mode 100644 index 00000000..a927df61 --- /dev/null +++ b/test/towerops/snmp/profiles/vendors/tranzeo_test.exs @@ -0,0 +1,107 @@ +defmodule Towerops.Snmp.Profiles.Vendors.TranzeoTest do + use Towerops.DataCase, async: true + + import Mox + + alias Towerops.Snmp.Profiles.Vendors.Tranzeo + 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 tranzeo profile name" do + assert Tranzeo.profile_names() == ["tranzeo"] + end + end + + describe "detect_hardware/1" do + test "detects hardware from sysDescr containing Tranzeo" do + expect(SnmpMock, :get, fn _, "1.3.6.1.2.1.1.1.0", _ -> + {:ok, "Tranzeo TR-5A Plus"} + end) + + assert Tranzeo.detect_hardware(@client_opts) == "TR-5A Plus" + end + + test "returns nil when sysDescr does not contain Tranzeo" do + expect(SnmpMock, :get, fn _, _, _ -> + {:ok, "Some Other Device"} + end) + + assert Tranzeo.detect_hardware(@client_opts) == nil + end + + test "returns nil when OID unavailable" do + expect(SnmpMock, :get, fn _, _, _ -> + {:error, :no_such_object} + end) + + assert Tranzeo.detect_hardware(@client_opts) == nil + end + end + + describe "wireless_oid_defs/0" do + test "returns list of sensor definitions" do + defs = Tranzeo.wireless_oid_defs() + + assert is_list(defs) + assert [_ | _] = defs + end + + test "includes associated clients sensor" do + defs = Tranzeo.wireless_oid_defs() + clients = Enum.find(defs, &(&1.sensor_descr == "Associated Clients")) + + assert clients + assert clients.sensor_type == "clients" + end + + test "includes signal and power sensors" do + defs = Tranzeo.wireless_oid_defs() + signal = Enum.find(defs, &(&1.sensor_descr == "Signal Strength")) + tx_power = Enum.find(defs, &(&1.sensor_descr == "TX Power")) + + assert signal + assert signal.sensor_type == "rssi" + assert tx_power + assert tx_power.sensor_type == "power" + end + end + + describe "discover_wireless_sensors/1" do + test "discovers sensors when SNMP responds" do + expect(SnmpMock, :get, 4, fn _, oid, _ -> + cond do + String.contains?(oid, "24350.2.1.1.0") -> {:ok, 15} + String.contains?(oid, "24350.2.2.1.0") -> {:ok, -55} + String.contains?(oid, "24350.2.2.2.0") -> {:ok, -95} + String.contains?(oid, "24350.2.2.3.0") -> {:ok, 20} + true -> {:error, :no_such_object} + end + end) + + sensors = Tranzeo.discover_wireless_sensors(@client_opts) + + assert is_list(sensors) + assert length(sensors) == 4 + end + + test "returns empty list when no sensors respond" do + expect(SnmpMock, :get, 4, fn _, _, _ -> + {:error, :no_such_object} + end) + + sensors = Tranzeo.discover_wireless_sensors(@client_opts) + + assert sensors == [] + end + end +end