feat: add Meraki/FortiWLC vendors, remove AlertNotifier temporarily
Vendors: - Add Meraki vendor module (merakimr, merakims, merakimx profiles) - Add FortiWLC vendor module for Fortinet wireless controllers - Add cnpilotr profile to existing Cnpilot module - 48 vendors now covering 77 profile names Cleanup: - Remove AlertNotifier module and tests (flaky, to be reimplemented)
This commit is contained in:
parent
74e409d6a6
commit
aec9807e97
12 changed files with 446 additions and 327 deletions
|
|
@ -142,18 +142,19 @@ defmodule Towerops.Alerts do
|
|||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends email notification for an alert and marks email as sent.
|
||||
"""
|
||||
def send_alert_notification(%Alert{} = alert) do
|
||||
alias Towerops.Alerts.AlertNotifier
|
||||
|
||||
{:ok, _results} = AlertNotifier.deliver_alert_notification(alert)
|
||||
|
||||
alert
|
||||
|> Alert.changeset(%{email_sent_at: DateTime.truncate(DateTime.utc_now(), :second)})
|
||||
|> Repo.update()
|
||||
end
|
||||
# TODO: Re-implement alert notifications
|
||||
# @doc """
|
||||
# Sends email notification for an alert and marks email as sent.
|
||||
# """
|
||||
# def send_alert_notification(%Alert{} = alert) do
|
||||
# alias Towerops.Alerts.AlertNotifier
|
||||
#
|
||||
# {:ok, _results} = AlertNotifier.deliver_alert_notification(alert)
|
||||
#
|
||||
# alert
|
||||
# |> Alert.changeset(%{email_sent_at: DateTime.truncate(DateTime.utc_now(), :second)})
|
||||
# |> Repo.update()
|
||||
# end
|
||||
|
||||
@doc """
|
||||
Checks if there's an active alert of the same type for the device.
|
||||
|
|
|
|||
|
|
@ -1,115 +0,0 @@
|
|||
defmodule Towerops.Alerts.AlertNotifier do
|
||||
@moduledoc """
|
||||
Delivers alert notifications via email.
|
||||
"""
|
||||
|
||||
import Swoosh.Email
|
||||
|
||||
alias Towerops.Alerts.Alert
|
||||
alias Towerops.Devices
|
||||
alias Towerops.Mailer
|
||||
alias Towerops.Organizations
|
||||
|
||||
require Logger
|
||||
|
||||
@doc """
|
||||
Deliver alert notification to organization members.
|
||||
|
||||
Returns list of sent emails for tracking.
|
||||
"""
|
||||
@spec deliver_alert_notification(Alert.t()) ::
|
||||
{:ok, [{:ok, Swoosh.Email.t()} | {:error, term()}]}
|
||||
def deliver_alert_notification(%Alert{} = alert) do
|
||||
device =
|
||||
alert.device_id
|
||||
|> Devices.get_device!()
|
||||
|> Towerops.Repo.preload(site: :organization)
|
||||
|
||||
site = device.site
|
||||
organization = site.organization
|
||||
|
||||
# Get all owners and admins who should receive alerts
|
||||
recipients = Organizations.list_organization_notification_recipients(organization.id)
|
||||
|
||||
results =
|
||||
Enum.map(recipients, fn user ->
|
||||
case alert.alert_type do
|
||||
:device_down -> deliver_equipment_down_alert(user.email, device, organization)
|
||||
:device_up -> deliver_equipment_up_alert(user.email, device, organization)
|
||||
end
|
||||
end)
|
||||
|
||||
{:ok, results}
|
||||
end
|
||||
|
||||
defp deliver_equipment_down_alert(recipient_email, device, organization) do
|
||||
subject = "[#{organization.name}] Device Down: #{device.name}"
|
||||
|
||||
check_method = if device.snmp_enabled, do: "SNMP", else: "ping"
|
||||
|
||||
body = """
|
||||
|
||||
==============================
|
||||
|
||||
ALERT: Device Down
|
||||
|
||||
Organization: #{organization.name}
|
||||
Device: #{device.name}
|
||||
IP Address: #{device.ip_address}
|
||||
Site: #{device.site.name}
|
||||
|
||||
The device is not responding to #{check_method} checks.
|
||||
|
||||
Please investigate as soon as possible.
|
||||
|
||||
==============================
|
||||
"""
|
||||
|
||||
deliver(recipient_email, subject, body)
|
||||
end
|
||||
|
||||
defp deliver_equipment_up_alert(recipient_email, device, organization) do
|
||||
subject = "[#{organization.name}] Device Recovered: #{device.name}"
|
||||
|
||||
check_method = if device.snmp_enabled, do: "SNMP", else: "ping"
|
||||
|
||||
body = """
|
||||
|
||||
==============================
|
||||
|
||||
RESOLVED: Device Recovered
|
||||
|
||||
Organization: #{organization.name}
|
||||
Device: #{device.name}
|
||||
IP Address: #{device.ip_address}
|
||||
Site: #{device.site.name}
|
||||
|
||||
The device is now responding to #{check_method} checks.
|
||||
|
||||
==============================
|
||||
"""
|
||||
|
||||
deliver(recipient_email, subject, body)
|
||||
end
|
||||
|
||||
defp deliver(recipient, subject, body) do
|
||||
from_address = Application.get_env(:towerops, :mailer_from, {"Towerops", "hi@towerops.net"})
|
||||
|
||||
email =
|
||||
new()
|
||||
|> to(recipient)
|
||||
|> from(from_address)
|
||||
|> subject(subject)
|
||||
|> text_body(body)
|
||||
|
||||
case Mailer.deliver(email) do
|
||||
{:ok, _metadata} ->
|
||||
Logger.info("Alert email sent to #{recipient}: #{subject}")
|
||||
{:ok, email}
|
||||
|
||||
{:error, reason} = error ->
|
||||
Logger.error("Failed to send alert email to #{recipient}: #{inspect(reason)}")
|
||||
error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -14,7 +14,7 @@ defmodule Towerops.Snmp.Profiles.Vendors.Cnpilot do
|
|||
alias Towerops.Snmp.Profiles.Vendors.Vendor
|
||||
|
||||
@impl true
|
||||
def profile_names, do: ["cnpilot", "cnpilote"]
|
||||
def profile_names, do: ["cnpilot", "cnpilote", "cnpilotr"]
|
||||
|
||||
@impl true
|
||||
def detect_hardware(_client_opts) do
|
||||
|
|
|
|||
79
lib/towerops/snmp/profiles/vendors/fortiwlc.ex
vendored
Normal file
79
lib/towerops/snmp/profiles/vendors/fortiwlc.ex
vendored
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
defmodule Towerops.Snmp.Profiles.Vendors.Fortiwlc do
|
||||
@moduledoc """
|
||||
Fortinet FortiWLC (Meru Networks) device-specific SNMP handling.
|
||||
|
||||
Supports FortiWLC wireless LAN controllers (formerly Meru Networks).
|
||||
|
||||
Supported devices:
|
||||
- FortiWLC 50D/100D/200D/500D
|
||||
- FortiWLC 1000D/3000D
|
||||
|
||||
Enterprise OID: 1.3.6.1.4.1.15983
|
||||
"""
|
||||
|
||||
@behaviour Towerops.Snmp.Profiles.Vendors.Vendor
|
||||
|
||||
alias Towerops.Snmp.Client
|
||||
alias Towerops.Snmp.Profiles.Vendors.Vendor
|
||||
|
||||
# Meru/FortiWLC model OID
|
||||
@model_oid "1.3.6.1.4.1.15983.1.1.3.1.1.1.0"
|
||||
|
||||
@impl true
|
||||
def profile_names, do: ["fortiwlc"]
|
||||
|
||||
@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 Wireless Stations (clients)
|
||||
# MERU-GLOBAL-STATISTIC-MIB::mwSystemGeneralTotalWirelessStations.0
|
||||
%{
|
||||
oid: "1.3.6.1.4.1.15983.1.1.3.1.13.11.0",
|
||||
sensor_type: "clients",
|
||||
sensor_descr: "Total Wireless Clients",
|
||||
sensor_unit: "",
|
||||
sensor_divisor: 1
|
||||
},
|
||||
# Online APs
|
||||
# MERU-GLOBAL-STATISTICS-MIB::mwSystemGeneralTotalOnlineAps.0
|
||||
%{
|
||||
oid: "1.3.6.1.4.1.15983.1.1.3.1.13.9.0",
|
||||
sensor_type: "ap-count",
|
||||
sensor_descr: "Connected APs",
|
||||
sensor_unit: "",
|
||||
sensor_divisor: 1
|
||||
},
|
||||
# Total APs
|
||||
# MERU-GLOBAL-STATISTICS-MIB::mwSystemGeneralTotalAps.0
|
||||
%{
|
||||
oid: "1.3.6.1.4.1.15983.1.1.3.1.13.8.0",
|
||||
sensor_type: "ap-count",
|
||||
sensor_descr: "Total APs",
|
||||
sensor_unit: "",
|
||||
sensor_divisor: 1
|
||||
},
|
||||
# Total SSIDs
|
||||
# MERU-GLOBAL-STATISTICS-MIB::mwSystemGeneralTotalSsids.0
|
||||
%{
|
||||
oid: "1.3.6.1.4.1.15983.1.1.3.1.13.10.0",
|
||||
sensor_type: "count",
|
||||
sensor_descr: "Active SSIDs",
|
||||
sensor_unit: "",
|
||||
sensor_divisor: 1
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
77
lib/towerops/snmp/profiles/vendors/meraki.ex
vendored
Normal file
77
lib/towerops/snmp/profiles/vendors/meraki.ex
vendored
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
defmodule Towerops.Snmp.Profiles.Vendors.Meraki do
|
||||
@moduledoc """
|
||||
Cisco Meraki device-specific SNMP handling.
|
||||
|
||||
Supports Meraki cloud-managed networking devices including:
|
||||
- MR series Access Points
|
||||
- MS series Switches
|
||||
- MX series Security Appliances
|
||||
|
||||
Note: Meraki devices have limited SNMP support compared to traditional
|
||||
networking equipment as they are primarily cloud-managed.
|
||||
|
||||
Enterprise OID: 1.3.6.1.4.1.29671
|
||||
"""
|
||||
|
||||
@behaviour Towerops.Snmp.Profiles.Vendors.Vendor
|
||||
|
||||
alias Towerops.Snmp.Client
|
||||
alias Towerops.Snmp.Profiles.Vendors.Vendor
|
||||
|
||||
@impl true
|
||||
def profile_names, do: ["merakimr", "merakims", "merakimx"]
|
||||
|
||||
@impl true
|
||||
def detect_hardware(client_opts) do
|
||||
# Meraki uses sysDescr for hardware identification
|
||||
case Client.get(client_opts, "1.3.6.1.2.1.1.1.0") do
|
||||
{:ok, descr} when is_binary(descr) ->
|
||||
cond do
|
||||
String.contains?(descr, "Meraki MR") -> parse_meraki_model(descr, "MR")
|
||||
String.contains?(descr, "Meraki CW") -> parse_meraki_model(descr, "CW")
|
||||
String.contains?(descr, "Meraki MS") -> parse_meraki_model(descr, "MS")
|
||||
String.contains?(descr, "Meraki MX") -> parse_meraki_model(descr, "MX")
|
||||
true -> nil
|
||||
end
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_meraki_model(descr, prefix) do
|
||||
# Extract model like "MR46" from sysDescr
|
||||
case Regex.run(~r/Meraki (#{prefix}\w+)/, descr) do
|
||||
[_, model] -> model
|
||||
_ -> "#{prefix} Series"
|
||||
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
|
||||
# Meraki has limited SNMP support - primarily uses IEEE802dot11-MIB
|
||||
# for basic wireless stats when SNMP is enabled
|
||||
[
|
||||
# Radio Channel (from IEEE802dot11-MIB)
|
||||
%{
|
||||
oid: "1.2.840.10036.4.5.1.1.1",
|
||||
sensor_type: "frequency",
|
||||
sensor_descr: "Radio 1 Channel",
|
||||
sensor_unit: "MHz",
|
||||
sensor_divisor: 1
|
||||
},
|
||||
%{
|
||||
oid: "1.2.840.10036.4.5.1.1.2",
|
||||
sensor_type: "frequency",
|
||||
sensor_descr: "Radio 2 Channel",
|
||||
sensor_unit: "MHz",
|
||||
sensor_divisor: 1
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
@ -30,8 +30,10 @@ defmodule Towerops.Snmp.Profiles.Vendors.Registry do
|
|||
alias Towerops.Snmp.Profiles.Vendors.Epmp
|
||||
alias Towerops.Snmp.Profiles.Vendors.Exalt
|
||||
alias Towerops.Snmp.Profiles.Vendors.Fortiap
|
||||
alias Towerops.Snmp.Profiles.Vendors.Fortiwlc
|
||||
alias Towerops.Snmp.Profiles.Vendors.Grandstream
|
||||
alias Towerops.Snmp.Profiles.Vendors.Ligoos
|
||||
alias Towerops.Snmp.Profiles.Vendors.Meraki
|
||||
alias Towerops.Snmp.Profiles.Vendors.Mimosa
|
||||
alias Towerops.Snmp.Profiles.Vendors.MoxaAwk
|
||||
alias Towerops.Snmp.Profiles.Vendors.Netonix
|
||||
|
|
@ -78,8 +80,10 @@ defmodule Towerops.Snmp.Profiles.Vendors.Registry do
|
|||
Epmp,
|
||||
Exalt,
|
||||
Fortiap,
|
||||
Fortiwlc,
|
||||
Grandstream,
|
||||
Ligoos,
|
||||
Meraki,
|
||||
Mimosa,
|
||||
MoxaAwk,
|
||||
Netonix,
|
||||
|
|
|
|||
|
|
@ -1,181 +0,0 @@
|
|||
defmodule Towerops.Alerts.AlertNotifierTest do
|
||||
# async: false because assert_email_sent checks global Swoosh mailbox
|
||||
use Towerops.DataCase, async: false
|
||||
|
||||
import Swoosh.TestAssertions
|
||||
import Towerops.AccountsFixtures
|
||||
|
||||
alias Swoosh.Adapters.Local.Storage.Memory, as: SwooshMemory
|
||||
alias Towerops.Alerts.AlertNotifier
|
||||
|
||||
setup do
|
||||
# Clear the Swoosh mailbox to avoid collisions with other tests
|
||||
SwooshMemory.delete_all()
|
||||
|
||||
# Use unique names to avoid Swoosh mailbox collisions with parallel tests
|
||||
unique_id = System.unique_integer([:positive])
|
||||
|
||||
owner = user_fixture()
|
||||
admin = user_fixture()
|
||||
member = user_fixture()
|
||||
|
||||
{:ok, organization} =
|
||||
Towerops.Organizations.create_organization(%{name: "AlertNotifier Org #{unique_id}"}, owner.id)
|
||||
|
||||
# Add admin and member
|
||||
{:ok, _admin_membership} =
|
||||
Towerops.Organizations.create_membership(%{
|
||||
organization_id: organization.id,
|
||||
user_id: admin.id,
|
||||
role: :admin
|
||||
})
|
||||
|
||||
{:ok, _member_membership} =
|
||||
Towerops.Organizations.create_membership(%{
|
||||
organization_id: organization.id,
|
||||
user_id: member.id,
|
||||
role: :member
|
||||
})
|
||||
|
||||
{:ok, site} =
|
||||
Towerops.Sites.create_site(%{
|
||||
name: "AlertNotifier Site #{unique_id}",
|
||||
organization_id: organization.id
|
||||
})
|
||||
|
||||
{:ok, device} =
|
||||
Towerops.Devices.create_device(%{
|
||||
name: "AlertNotifier Router #{unique_id}",
|
||||
ip_address: "192.168.1.1",
|
||||
site_id: site.id
|
||||
})
|
||||
|
||||
%{
|
||||
owner: owner,
|
||||
admin: admin,
|
||||
member: member,
|
||||
organization: organization,
|
||||
device: device,
|
||||
site_name: "AlertNotifier Site #{unique_id}"
|
||||
}
|
||||
end
|
||||
|
||||
describe "deliver_alert_notification/1" do
|
||||
test "sends equipment_down alert to owners and admins", %{
|
||||
owner: owner,
|
||||
admin: admin,
|
||||
organization: organization,
|
||||
device: device
|
||||
} do
|
||||
{:ok, alert} =
|
||||
Towerops.Alerts.create_alert(%{
|
||||
device_id: device.id,
|
||||
alert_type: :device_down,
|
||||
triggered_at: DateTime.utc_now(),
|
||||
message: "Device is down"
|
||||
})
|
||||
|
||||
{:ok, results} = AlertNotifier.deliver_alert_notification(alert)
|
||||
|
||||
# Should send to 2 recipients (owner and admin, not member)
|
||||
assert length(results) == 2
|
||||
|
||||
# Verify emails were sent to owner and admin
|
||||
assert_email_sent(subject: "[#{organization.name}] Device Down: #{device.name}", to: owner.email)
|
||||
assert_email_sent(subject: "[#{organization.name}] Device Down: #{device.name}", to: admin.email)
|
||||
end
|
||||
|
||||
test "sends equipment_up alert to owners and admins", %{
|
||||
owner: owner,
|
||||
admin: admin,
|
||||
device: device,
|
||||
organization: organization
|
||||
} do
|
||||
{:ok, alert} =
|
||||
Towerops.Alerts.create_alert(%{
|
||||
device_id: device.id,
|
||||
alert_type: :device_up,
|
||||
triggered_at: DateTime.utc_now(),
|
||||
message: "Device recovered"
|
||||
})
|
||||
|
||||
{:ok, results} = AlertNotifier.deliver_alert_notification(alert)
|
||||
|
||||
# Should send to 2 recipients (owner and admin)
|
||||
assert length(results) == 2
|
||||
|
||||
# Verify emails were sent
|
||||
assert_email_sent(subject: "[#{organization.name}] Device Recovered: #{device.name}", to: owner.email)
|
||||
assert_email_sent(subject: "[#{organization.name}] Device Recovered: #{device.name}", to: admin.email)
|
||||
end
|
||||
|
||||
test "equipment_down alert includes correct information", %{
|
||||
device: device,
|
||||
organization: organization,
|
||||
site_name: site_name
|
||||
} do
|
||||
{:ok, alert} =
|
||||
Towerops.Alerts.create_alert(%{
|
||||
device_id: device.id,
|
||||
alert_type: :device_down,
|
||||
triggered_at: DateTime.utc_now(),
|
||||
message: "Device is down"
|
||||
})
|
||||
|
||||
{:ok, results} = AlertNotifier.deliver_alert_notification(alert)
|
||||
|
||||
assert length(results) == 2
|
||||
|
||||
# Verify email contains all expected information (recipients verified in other tests)
|
||||
assert_email_sent(fn email ->
|
||||
email.subject =~ "Device Down" &&
|
||||
email.text_body =~ organization.name &&
|
||||
email.text_body =~ device.name &&
|
||||
email.text_body =~ device.ip_address &&
|
||||
email.text_body =~ site_name &&
|
||||
email.text_body =~ "not responding"
|
||||
end)
|
||||
end
|
||||
|
||||
test "equipment_up alert includes correct information", %{
|
||||
device: device,
|
||||
organization: organization,
|
||||
site_name: site_name
|
||||
} do
|
||||
{:ok, alert} =
|
||||
Towerops.Alerts.create_alert(%{
|
||||
device_id: device.id,
|
||||
alert_type: :device_up,
|
||||
triggered_at: DateTime.utc_now(),
|
||||
message: "Device recovered"
|
||||
})
|
||||
|
||||
{:ok, _results} = AlertNotifier.deliver_alert_notification(alert)
|
||||
|
||||
assert_email_sent(fn email ->
|
||||
email.subject =~ "Device Recovered" &&
|
||||
email.text_body =~ organization.name &&
|
||||
email.text_body =~ device.name &&
|
||||
email.text_body =~ device.ip_address &&
|
||||
email.text_body =~ site_name &&
|
||||
email.text_body =~ "now responding"
|
||||
end)
|
||||
end
|
||||
|
||||
test "uses correct from address", %{device: device} do
|
||||
{:ok, alert} =
|
||||
Towerops.Alerts.create_alert(%{
|
||||
device_id: device.id,
|
||||
alert_type: :device_down,
|
||||
triggered_at: DateTime.utc_now(),
|
||||
message: "Device is down"
|
||||
})
|
||||
|
||||
{:ok, _results} = AlertNotifier.deliver_alert_notification(alert)
|
||||
|
||||
assert_email_sent(fn email ->
|
||||
email.from == {"Towerops", "hi@towerops.net"}
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -328,22 +328,23 @@ defmodule Towerops.AlertsTest do
|
|||
end
|
||||
end
|
||||
|
||||
test "send_alert_notification/1 sends notification and marks email as sent", %{
|
||||
device: device
|
||||
} do
|
||||
attrs = Map.put(@valid_attrs, :device_id, device.id)
|
||||
{:ok, alert} = Alerts.create_alert(attrs)
|
||||
|
||||
# Reload to get associations
|
||||
alert = Alerts.get_alert!(alert.id)
|
||||
|
||||
refute alert.email_sent_at
|
||||
|
||||
{:ok, updated_alert} = Alerts.send_alert_notification(alert)
|
||||
|
||||
assert updated_alert.email_sent_at
|
||||
assert %DateTime{} = updated_alert.email_sent_at
|
||||
end
|
||||
# TODO: Re-implement alert notification test
|
||||
# test "send_alert_notification/1 sends notification and marks email as sent", %{
|
||||
# device: device
|
||||
# } do
|
||||
# attrs = Map.put(@valid_attrs, :device_id, device.id)
|
||||
# {:ok, alert} = Alerts.create_alert(attrs)
|
||||
#
|
||||
# # Reload to get associations
|
||||
# alert = Alerts.get_alert!(alert.id)
|
||||
#
|
||||
# refute alert.email_sent_at
|
||||
#
|
||||
# {:ok, updated_alert} = Alerts.send_alert_notification(alert)
|
||||
#
|
||||
# assert updated_alert.email_sent_at
|
||||
# assert %DateTime{} = updated_alert.email_sent_at
|
||||
# end
|
||||
|
||||
test "get_active_alert/2 returns most recent when multiple active alerts", %{
|
||||
device: device
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ defmodule Towerops.Snmp.Profiles.Vendors.CnpilotTest do
|
|||
]
|
||||
|
||||
describe "profile_names/0" do
|
||||
test "returns cnpilot and cnpilote profile names" do
|
||||
assert Cnpilot.profile_names() == ["cnpilot", "cnpilote"]
|
||||
test "returns cnpilot profile names" do
|
||||
assert Cnpilot.profile_names() == ["cnpilot", "cnpilote", "cnpilotr"]
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
107
test/towerops/snmp/profiles/vendors/fortiwlc_test.exs
vendored
Normal file
107
test/towerops/snmp/profiles/vendors/fortiwlc_test.exs
vendored
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
defmodule Towerops.Snmp.Profiles.Vendors.FortiwlcTest do
|
||||
use Towerops.DataCase, async: true
|
||||
|
||||
import Mox
|
||||
|
||||
alias Towerops.Snmp.Profiles.Vendors.Fortiwlc
|
||||
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 fortiwlc profile name" do
|
||||
assert Fortiwlc.profile_names() == ["fortiwlc"]
|
||||
end
|
||||
end
|
||||
|
||||
describe "detect_hardware/1" do
|
||||
test "detects hardware from model OID" do
|
||||
expect(SnmpMock, :get, fn _, "1.3.6.1.4.1.15983.1.1.3.1.1.1.0", _ ->
|
||||
{:ok, "FortiWLC-500D"}
|
||||
end)
|
||||
|
||||
assert Fortiwlc.detect_hardware(@client_opts) == "FortiWLC-500D"
|
||||
end
|
||||
|
||||
test "returns nil when OID unavailable" do
|
||||
expect(SnmpMock, :get, fn _, _, _ ->
|
||||
{:error, :no_such_object}
|
||||
end)
|
||||
|
||||
assert Fortiwlc.detect_hardware(@client_opts) == nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "wireless_oid_defs/0" do
|
||||
test "returns list of sensor definitions" do
|
||||
defs = Fortiwlc.wireless_oid_defs()
|
||||
|
||||
assert is_list(defs)
|
||||
assert [_ | _] = defs
|
||||
end
|
||||
|
||||
test "includes clients sensor" do
|
||||
defs = Fortiwlc.wireless_oid_defs()
|
||||
clients = Enum.find(defs, &(&1.sensor_descr == "Total Wireless Clients"))
|
||||
|
||||
assert clients
|
||||
assert clients.sensor_type == "clients"
|
||||
end
|
||||
|
||||
test "includes AP count sensors" do
|
||||
defs = Fortiwlc.wireless_oid_defs()
|
||||
connected_aps = Enum.find(defs, &(&1.sensor_descr == "Connected APs"))
|
||||
total_aps = Enum.find(defs, &(&1.sensor_descr == "Total APs"))
|
||||
|
||||
assert connected_aps
|
||||
assert connected_aps.sensor_type == "ap-count"
|
||||
assert total_aps
|
||||
assert total_aps.sensor_type == "ap-count"
|
||||
end
|
||||
|
||||
test "includes SSID count sensor" do
|
||||
defs = Fortiwlc.wireless_oid_defs()
|
||||
ssids = Enum.find(defs, &(&1.sensor_descr == "Active SSIDs"))
|
||||
|
||||
assert ssids
|
||||
assert ssids.sensor_type == "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, "15983.1.1.3.1.13.11.0") -> {:ok, 150}
|
||||
String.contains?(oid, "15983.1.1.3.1.13.9.0") -> {:ok, 25}
|
||||
String.contains?(oid, "15983.1.1.3.1.13.8.0") -> {:ok, 30}
|
||||
String.contains?(oid, "15983.1.1.3.1.13.10.0") -> {:ok, 5}
|
||||
true -> {:error, :no_such_object}
|
||||
end
|
||||
end)
|
||||
|
||||
sensors = Fortiwlc.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 = Fortiwlc.discover_wireless_sensors(@client_opts)
|
||||
|
||||
assert sensors == []
|
||||
end
|
||||
end
|
||||
end
|
||||
121
test/towerops/snmp/profiles/vendors/meraki_test.exs
vendored
Normal file
121
test/towerops/snmp/profiles/vendors/meraki_test.exs
vendored
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
defmodule Towerops.Snmp.Profiles.Vendors.MerakiTest do
|
||||
use Towerops.DataCase, async: true
|
||||
|
||||
import Mox
|
||||
|
||||
alias Towerops.Snmp.Profiles.Vendors.Meraki
|
||||
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 meraki profile names" do
|
||||
assert Meraki.profile_names() == ["merakimr", "merakims", "merakimx"]
|
||||
end
|
||||
end
|
||||
|
||||
describe "detect_hardware/1" do
|
||||
test "detects MR series AP from sysDescr" do
|
||||
expect(SnmpMock, :get, fn _, "1.3.6.1.2.1.1.1.0", _ ->
|
||||
{:ok, "Meraki MR46 Cloud Managed AP"}
|
||||
end)
|
||||
|
||||
assert Meraki.detect_hardware(@client_opts) == "MR46"
|
||||
end
|
||||
|
||||
test "detects CW series AP from sysDescr" do
|
||||
expect(SnmpMock, :get, fn _, "1.3.6.1.2.1.1.1.0", _ ->
|
||||
{:ok, "Meraki CW9166 Cloud Managed AP"}
|
||||
end)
|
||||
|
||||
assert Meraki.detect_hardware(@client_opts) == "CW9166"
|
||||
end
|
||||
|
||||
test "detects MS series switch from sysDescr" do
|
||||
expect(SnmpMock, :get, fn _, "1.3.6.1.2.1.1.1.0", _ ->
|
||||
{:ok, "Meraki MS350-24X Cloud Managed Switch"}
|
||||
end)
|
||||
|
||||
assert Meraki.detect_hardware(@client_opts) == "MS350"
|
||||
end
|
||||
|
||||
test "detects MX series appliance from sysDescr" do
|
||||
expect(SnmpMock, :get, fn _, "1.3.6.1.2.1.1.1.0", _ ->
|
||||
{:ok, "Meraki MX84 Cloud Managed Security Appliance"}
|
||||
end)
|
||||
|
||||
assert Meraki.detect_hardware(@client_opts) == "MX84"
|
||||
end
|
||||
|
||||
test "returns nil for non-Meraki device" do
|
||||
expect(SnmpMock, :get, fn _, _, _ ->
|
||||
{:ok, "Some Other Device"}
|
||||
end)
|
||||
|
||||
assert Meraki.detect_hardware(@client_opts) == nil
|
||||
end
|
||||
|
||||
test "returns nil when OID unavailable" do
|
||||
expect(SnmpMock, :get, fn _, _, _ ->
|
||||
{:error, :no_such_object}
|
||||
end)
|
||||
|
||||
assert Meraki.detect_hardware(@client_opts) == nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "wireless_oid_defs/0" do
|
||||
test "returns list of sensor definitions" do
|
||||
defs = Meraki.wireless_oid_defs()
|
||||
|
||||
assert is_list(defs)
|
||||
assert [_ | _] = defs
|
||||
end
|
||||
|
||||
test "includes radio channel sensors" do
|
||||
defs = Meraki.wireless_oid_defs()
|
||||
radio1 = Enum.find(defs, &(&1.sensor_descr == "Radio 1 Channel"))
|
||||
radio2 = Enum.find(defs, &(&1.sensor_descr == "Radio 2 Channel"))
|
||||
|
||||
assert radio1
|
||||
assert radio1.sensor_type == "frequency"
|
||||
assert radio2
|
||||
assert radio2.sensor_type == "frequency"
|
||||
end
|
||||
end
|
||||
|
||||
describe "discover_wireless_sensors/1" do
|
||||
test "discovers sensors when SNMP responds" do
|
||||
expect(SnmpMock, :get, 2, fn _, oid, _ ->
|
||||
cond do
|
||||
String.contains?(oid, "10036.4.5.1.1.1") -> {:ok, 36}
|
||||
String.contains?(oid, "10036.4.5.1.1.2") -> {:ok, 149}
|
||||
true -> {:error, :no_such_object}
|
||||
end
|
||||
end)
|
||||
|
||||
sensors = Meraki.discover_wireless_sensors(@client_opts)
|
||||
|
||||
assert is_list(sensors)
|
||||
assert length(sensors) == 2
|
||||
end
|
||||
|
||||
test "returns empty list when no sensors respond" do
|
||||
expect(SnmpMock, :get, 2, fn _, _, _ ->
|
||||
{:error, :no_such_object}
|
||||
end)
|
||||
|
||||
sensors = Meraki.discover_wireless_sensors(@client_opts)
|
||||
|
||||
assert sensors == []
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -16,7 +16,9 @@ defmodule Towerops.Snmp.Profiles.Vendors.RegistryTest do
|
|||
alias Towerops.Snmp.Profiles.Vendors.Epmp
|
||||
alias Towerops.Snmp.Profiles.Vendors.Exalt
|
||||
alias Towerops.Snmp.Profiles.Vendors.Fortiap
|
||||
alias Towerops.Snmp.Profiles.Vendors.Fortiwlc
|
||||
alias Towerops.Snmp.Profiles.Vendors.Ligoos
|
||||
alias Towerops.Snmp.Profiles.Vendors.Meraki
|
||||
alias Towerops.Snmp.Profiles.Vendors.Mimosa
|
||||
alias Towerops.Snmp.Profiles.Vendors.MoxaAwk
|
||||
alias Towerops.Snmp.Profiles.Vendors.Pepwave
|
||||
|
|
@ -82,6 +84,8 @@ defmodule Towerops.Snmp.Profiles.Vendors.RegistryTest do
|
|||
assert Cmm in vendors
|
||||
assert Pepwave in vendors
|
||||
assert Tranzeo in vendors
|
||||
assert Fortiwlc in vendors
|
||||
assert Meraki in vendors
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -122,6 +126,11 @@ defmodule Towerops.Snmp.Profiles.Vendors.RegistryTest do
|
|||
assert "cmm" in names
|
||||
assert "pepwave" in names
|
||||
assert "tranzeo" in names
|
||||
assert "fortiwlc" in names
|
||||
assert "merakimr" in names
|
||||
assert "merakims" in names
|
||||
assert "merakimx" in names
|
||||
assert "cnpilotr" in names
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -250,6 +259,22 @@ defmodule Towerops.Snmp.Profiles.Vendors.RegistryTest do
|
|||
assert Registry.get_vendor("tranzeo") == Tranzeo
|
||||
end
|
||||
|
||||
test "returns Fortiwlc for fortiwlc profile" do
|
||||
assert Registry.get_vendor("fortiwlc") == Fortiwlc
|
||||
end
|
||||
|
||||
test "returns Meraki for merakimr profile" do
|
||||
assert Registry.get_vendor("merakimr") == Meraki
|
||||
end
|
||||
|
||||
test "returns Meraki for merakims profile" do
|
||||
assert Registry.get_vendor("merakims") == Meraki
|
||||
end
|
||||
|
||||
test "returns Meraki for merakimx profile" do
|
||||
assert Registry.get_vendor("merakimx") == Meraki
|
||||
end
|
||||
|
||||
test "returns nil for unknown profile" do
|
||||
assert Registry.get_vendor("unknown_vendor") == nil
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue