mikrotik improvements

This commit is contained in:
Graham McIntire 2026-01-22 13:05:28 -06:00
parent 218b99972e
commit d401e0b036
No known key found for this signature in database
11 changed files with 437 additions and 33 deletions

View file

@ -142,6 +142,31 @@ defmodule Towerops.Devices do
end
end
@doc """
Finds a device by IP address within a site, optionally excluding a specific device ID.
Returns nil if no device found.
"""
def get_device_by_ip(ip_address, site_id, exclude_id \\ nil)
def get_device_by_ip(_ip_address, nil, _exclude_id), do: nil
def get_device_by_ip(ip_address, site_id, exclude_id) do
query =
from(d in DeviceSchema,
where: d.ip_address == ^ip_address and d.site_id == ^site_id,
preload: [:site]
)
query =
if exclude_id do
from(d in query, where: d.id != ^exclude_id)
else
query
end
Repo.one(query)
end
@doc """
Gets a single device belonging to a specific site.
"""

View file

@ -148,6 +148,8 @@ defmodule Towerops.Profiles.YamlProfiles do
# Extract all detection blocks with their conditions for per-block matching
detection_blocks: extract_detection_blocks(detection),
device_oids: extract_device_oids(discovery),
# Regex to extract hardware model from sysDescr (e.g., "/RouterOS (?<hardware>.*)/")
hardware_regex: extract_hardware_regex(discovery),
# Scalar sensors (direct OID polling)
sensor_oids: extract_sensor_oids(discovery),
# Table-based sensors (OID walks) - all standard sensor types
@ -321,6 +323,7 @@ defmodule Towerops.Profiles.YamlProfiles do
field_mappings = [
{"serial", :serial_number},
{"version", :firmware_version},
{"features", :license_version},
{"hardware", :hardware},
{"lat", :latitude},
{"long", :longitude}
@ -338,6 +341,18 @@ defmodule Towerops.Profiles.YamlProfiles do
|> Map.new()
end
# Extract sysDescr_regex from os module for hardware extraction
# Returns a compiled regex if present, nil otherwise
defp extract_hardware_regex(yaml) do
case get_in(yaml, ["modules", "os", "sysDescr_regex"]) do
pattern when is_binary(pattern) ->
parse_regex_pattern(pattern)
_ ->
nil
end
end
# All sensor types supported by LibreNMS
@sensor_types ~w(temperature voltage current power fanspeed humidity frequency
dbm signal snr airflow load runtime waterflow pressure cooling charge)

View file

@ -519,7 +519,12 @@ defmodule Towerops.Snmp.Discovery do
@spec sync_sensors(Device.t(), [sensor_data()]) :: [Sensor.t()]
defp sync_sensors(device, discovered_sensors) do
# Deduplicate sensors by sensor_index (keep first occurrence)
# Deduplicate sensors by OID first - when multiple sensors share the same OID,
# prefer the one with a proper description (not containing "MIB::")
# This handles cases where YAML profiles and vendor profiles discover the same sensor
discovered_sensors = deduplicate_sensors_by_oid(discovered_sensors)
# Then deduplicate by sensor_index (keep first occurrence)
# This prevents duplicate key errors when profiles generate duplicate indices
discovered_sensors = Enum.uniq_by(discovered_sensors, & &1.sensor_index)
@ -744,4 +749,17 @@ defmodule Towerops.Snmp.Discovery do
end
defp sanitize_for_json(other), do: other
# Deduplicate sensors by OID - when multiple sensors share the same OID,
# prefer the one with a proper description (not containing "MIB::")
# This handles cases where YAML profiles and vendor profiles discover the same sensor
defp deduplicate_sensors_by_oid(sensors) do
sensors
|> Enum.group_by(& &1.sensor_oid)
|> Enum.map(fn {_oid, group} ->
# Prefer sensor with proper description (no MIB name)
Enum.find(group, fn s -> not String.contains?(s.sensor_descr, "MIB::") end) ||
List.last(group)
end)
end
end

View file

@ -31,6 +31,9 @@ defmodule Towerops.Snmp.MibTranslator do
"MIKROTIK-MIB::mtxrSystemDisplayName.0" => "1.3.6.1.4.1.14988.1.1.7.8.0",
"MIKROTIK-MIB::mtxrBoardName.0" => "1.3.6.1.4.1.14988.1.1.7.9.0",
"MIKROTIK-MIB::mtxrLicenseVersion.0" => "1.3.6.1.4.1.14988.1.1.7.5.0",
# MikroTik License info (short names used in YAML profiles)
"MIKROTIK-MIB::mtxrLicVersion.0" => "1.3.6.1.4.1.14988.1.1.4.3.0",
"MIKROTIK-MIB::mtxrLicLevel.0" => "1.3.6.1.4.1.14988.1.1.4.4.0",
# MikroTik Health sensors
"MIKROTIK-MIB::mtxrHlCoreVoltage.0" => "1.3.6.1.4.1.14988.1.1.3.1.0",
"MIKROTIK-MIB::mtxrHlThreeDotThreeVoltage.0" => "1.3.6.1.4.1.14988.1.1.3.2.0",

View file

@ -29,10 +29,11 @@ defmodule Towerops.Snmp.Profiles.Dynamic do
# Format firmware version (MikroTik has special formatting with license level)
firmware_version = format_firmware_version(profile, device_data)
# Get hardware model - use vendor-specific detection, then YAML hardware OID, then sysDescr
# Get hardware model - use YAML hardware OID, then sysDescr_regex capture, then vendor-specific, then sysDescr
model =
detect_vendor_hardware(profile, client_opts) ||
Map.get(device_data, :hardware) ||
Map.get(device_data, :hardware) ||
extract_hardware_from_regex(profile, system_info[:sys_descr]) ||
detect_vendor_hardware(profile, client_opts) ||
system_info[:sys_descr]
# Merge with system_info
@ -93,7 +94,11 @@ defmodule Towerops.Snmp.Profiles.Dynamic do
count_results ++
state_results ++ wireless_results ++ base_sensors
{:ok, all_sensors}
# Deduplicate by OID - when multiple sensors have the same OID,
# prefer the one with a proper description (not a MIB name)
deduped_sensors = deduplicate_by_oid(all_sensors)
{:ok, deduped_sensors}
end
@doc """
@ -128,6 +133,18 @@ defmodule Towerops.Snmp.Profiles.Dynamic do
defp detect_vendor_hardware(_profile, _client_opts), do: nil
# Extract hardware model from sysDescr using profile's sysDescr_regex
# LibreNMS uses named capture groups like (?<hardware>...) in the regex
defp extract_hardware_from_regex(%{hardware_regex: regex}, sys_descr)
when is_struct(regex, Regex) and is_binary(sys_descr) do
case Regex.named_captures(regex, sys_descr) do
%{"hardware" => hardware} when hardware != "" -> hardware
_ -> nil
end
end
defp extract_hardware_from_regex(_profile, _sys_descr), do: nil
defp format_firmware_version(%{vendor: vendor}, device_data) when vendor in ["MikroTik", "Mikrotik RouterOS"] do
case {Map.get(device_data, :firmware_version), Map.get(device_data, :license_version)} do
{fw, license} when not is_nil(fw) and not is_nil(license) ->
@ -344,4 +361,16 @@ defmodule Towerops.Snmp.Profiles.Dynamic do
end
defp discover_vendor_wireless_sensors(_profile, _client_opts), do: []
# Deduplicate sensors by OID - when multiple sensors share the same OID,
# prefer the one with a proper description (not containing "MIB::")
defp deduplicate_by_oid(sensors) do
sensors
|> Enum.group_by(& &1.sensor_oid)
|> Enum.map(fn {_oid, group} ->
# Prefer sensor with proper description (no MIB name)
Enum.find(group, fn s -> not String.contains?(s.sensor_descr, "MIB::") end) ||
List.last(group)
end)
end
end

View file

@ -27,6 +27,23 @@ defmodule Towerops.Snmp.Profiles.Vendors.Routeros do
@wl_60g_table "1.3.6.1.4.1.14988.1.1.1.8.1"
@lte_modem_table "1.3.6.1.4.1.14988.1.1.16.1.1"
# mtxrGaugeTable - System health sensors (temperature, voltage, current, power, fan speed)
# .2.{idx} = mtxrGaugeName, .3.{idx} = mtxrGaugeValue, .4.{idx} = mtxrGaugeUnit
@gauge_table "1.3.6.1.4.1.14988.1.1.3.100.1"
# mtxrOpticalTable - SFP/optical transceiver sensors
# .2.{idx} = mtxrOpticalName, .6.{idx} = Temperature, .7.{idx} = Supply Voltage
# .8.{idx} = Tx Bias Current, .9.{idx} = Tx Power, .10.{idx} = Rx Power
@optical_table "1.3.6.1.4.1.14988.1.1.19.1.1"
# mtxrGaugeUnit values
@gauge_unit_celsius 1
@gauge_unit_rpm 2
@gauge_unit_dv 3
@gauge_unit_da 4
@gauge_unit_dw 5
@gauge_unit_status 6
@impl true
def profile_names, do: ["routeros"]
@ -51,6 +68,8 @@ defmodule Towerops.Snmp.Profiles.Vendors.Routeros do
discover_station_sensors(client_opts) ++
discover_60g_sensors(client_opts) ++
discover_lte_sensors(client_opts) ++
discover_gauge_sensors(client_opts) ++
discover_optical_sensors(client_opts) ++
discover_system_sensors(client_opts)
Logger.debug("RouterOS: Discovered #{length(sensors)} wireless sensors")
@ -537,6 +556,187 @@ defmodule Towerops.Snmp.Profiles.Vendors.Routeros do
sensors
end
# Discover sensors from mtxrGaugeTable (system health: temperature, voltage, current, power, fan speed)
defp discover_gauge_sensors(client_opts) do
case Client.walk(client_opts, @gauge_table) do
{:ok, results} when is_map(results) and map_size(results) > 0 ->
parse_gauge_table(results)
_ ->
[]
end
end
defp parse_gauge_table(results) do
# Group by index (last OID component)
grouped = group_by_index(results)
grouped
|> Enum.flat_map(fn {idx, values} ->
build_gauge_sensor(idx, values)
end)
|> Enum.reject(&is_nil/1)
end
defp build_gauge_sensor(idx, values) do
# .2 = mtxrGaugeName, .3 = mtxrGaugeValue, .4 = mtxrGaugeUnit
name = get_string_value(values, 2)
value = get_int_value(values, 3)
unit_type = get_int_value(values, 4)
if name && value do
{sensor_type, sensor_unit, divisor} = gauge_unit_to_sensor_type(unit_type)
[
%{
sensor_type: sensor_type,
sensor_index: "mikrotik_gauge_#{idx}",
sensor_oid: "#{@gauge_table}.3.#{idx}",
sensor_descr: name,
sensor_unit: sensor_unit,
sensor_divisor: divisor,
last_value: value / divisor
}
]
else
[]
end
end
# Map mtxrGaugeUnit value to sensor type, unit string, and divisor
defp gauge_unit_to_sensor_type(@gauge_unit_celsius), do: {"temperature", "°C", 10}
defp gauge_unit_to_sensor_type(@gauge_unit_rpm), do: {"fanspeed", "RPM", 1}
defp gauge_unit_to_sensor_type(@gauge_unit_dv), do: {"voltage", "V", 10}
defp gauge_unit_to_sensor_type(@gauge_unit_da), do: {"current", "A", 1000}
defp gauge_unit_to_sensor_type(@gauge_unit_dw), do: {"power", "W", 10}
defp gauge_unit_to_sensor_type(@gauge_unit_status), do: {"state", "", 1}
defp gauge_unit_to_sensor_type(_), do: {"unknown", "", 1}
# Discover sensors from mtxrOpticalTable (SFP/optical transceiver sensors)
defp discover_optical_sensors(client_opts) do
case Client.walk(client_opts, @optical_table) do
{:ok, results} when is_map(results) and map_size(results) > 0 ->
parse_optical_table(results)
_ ->
[]
end
end
defp parse_optical_table(results) do
# Group by index (last OID component)
grouped = group_by_index(results)
Enum.flat_map(grouped, fn {idx, values} ->
build_optical_sensors(idx, values)
end)
end
defp build_optical_sensors(idx, values) do
# .2 = mtxrOpticalName, .6 = Temperature, .7 = Supply Voltage
# .8 = Tx Bias Current, .9 = Tx Power, .10 = Rx Power
name = get_string_value(values, 2) || "SFP #{idx}"
sensors = []
# Temperature (.6)
sensors =
if temp = get_int_value(values, 6) do
[
%{
sensor_type: "temperature",
sensor_index: "mikrotik_optical_temp_#{idx}",
sensor_oid: "#{@optical_table}.6.#{idx}",
sensor_descr: "#{name} Temperature",
sensor_unit: "°C",
sensor_divisor: 1,
last_value: temp / 1
}
| sensors
]
else
sensors
end
# Supply Voltage (.7) - in mV, convert to V
sensors =
if voltage = get_int_value(values, 7) do
[
%{
sensor_type: "voltage",
sensor_index: "mikrotik_optical_voltage_#{idx}",
sensor_oid: "#{@optical_table}.7.#{idx}",
sensor_descr: "#{name} Supply Voltage",
sensor_unit: "V",
sensor_divisor: 1000,
last_value: voltage / 1000
}
| sensors
]
else
sensors
end
# Tx Bias Current (.8) - in uA, convert to mA
sensors =
if bias = get_int_value(values, 8) do
[
%{
sensor_type: "current",
sensor_index: "mikrotik_optical_tx_bias_#{idx}",
sensor_oid: "#{@optical_table}.8.#{idx}",
sensor_descr: "#{name} Tx Bias",
sensor_unit: "mA",
sensor_divisor: 1000,
last_value: bias / 1000
}
| sensors
]
else
sensors
end
# Tx Power (.9) - in thousandths of dBm
sensors =
if tx_power = get_int_value(values, 9) do
[
%{
sensor_type: "dbm",
sensor_index: "mikrotik_optical_tx_power_#{idx}",
sensor_oid: "#{@optical_table}.9.#{idx}",
sensor_descr: "#{name} Tx Power",
sensor_unit: "dBm",
sensor_divisor: 1000,
last_value: tx_power / 1000
}
| sensors
]
else
sensors
end
# Rx Power (.10) - in thousandths of dBm
sensors =
if rx_power = get_int_value(values, 10) do
[
%{
sensor_type: "dbm",
sensor_index: "mikrotik_optical_rx_power_#{idx}",
sensor_oid: "#{@optical_table}.10.#{idx}",
sensor_descr: "#{name} Rx Power",
sensor_unit: "dBm",
sensor_divisor: 1000,
last_value: rx_power / 1000
}
| sensors
]
else
sensors
end
sensors
end
# Discover system-level sensors (DHCP leases, PPPoE sessions)
defp discover_system_sensors(client_opts) do
Vendor.fetch_sensors(wireless_oid_defs(), client_opts)

View file

@ -84,7 +84,7 @@ defmodule ToweropsWeb.CoreComponents do
"text-sm text-gray-500 dark:text-gray-400",
@title && "mt-1"
]}>
{msg}
{render_flash_message(msg)}
</p>
</div>
<div class="ml-4 flex shrink-0">
@ -105,6 +105,24 @@ defmodule ToweropsWeb.CoreComponents do
"""
end
# Renders flash message content, supporting both plain strings and maps with links
# Map format: %{text: "Message", link_text: "Link", link_url: "/path"}
defp render_flash_message(%{text: text, link_text: link_text, link_url: link_url}) do
assigns = %{text: text, link_text: link_text, link_url: link_url}
~H"""
{@text}
<.link
navigate={@link_url}
class="font-medium text-indigo-600 hover:text-indigo-500 dark:text-indigo-400 dark:hover:text-indigo-300"
>
{@link_text}
</.link>
"""
end
defp render_flash_message(msg), do: msg
@doc """
Renders a button with navigation support.

View file

@ -28,7 +28,8 @@ defmodule ToweropsWeb.DeviceLive.Form do
|> assign(:available_sites, sites)
|> assign(:available_agents, agents)
|> assign(:preselected_site_id, params["site_id"])
|> assign(:snmp_test_result, nil)}
|> assign(:snmp_test_result, nil)
|> assign(:duplicate_device, nil)}
end
end
@ -143,16 +144,32 @@ defmodule ToweropsWeb.DeviceLive.Form do
@impl true
def handle_event("validate", %{"device" => device_params}, socket) do
device_params = sanitize_device_params(device_params)
changeset =
socket.assigns.device
|> Devices.change_device(device_params)
|> Map.put(:action, :validate)
{:noreply, assign(socket, :form, to_form(changeset))}
# Check for duplicate IP address within the same site
ip_address = device_params["ip_address"]
site_id = device_params["site_id"]
exclude_id = if socket.assigns.live_action == :edit, do: socket.assigns.device.id
duplicate_device =
if ip_address && String.trim(ip_address) != "" && site_id do
Devices.get_device_by_ip(String.trim(ip_address), site_id, exclude_id)
end
{:noreply,
socket
|> assign(:form, to_form(changeset))
|> assign(:duplicate_device, duplicate_device)}
end
@impl true
def handle_event("save", %{"device" => device_params}, socket) do
device_params = sanitize_device_params(device_params)
save_device(socket, socket.assigns.live_action, device_params)
end
@ -279,12 +296,24 @@ defmodule ToweropsWeb.DeviceLive.Form do
end
defp handle_device_creation(device) do
device_label = device.name || device.ip_address
base_message =
if device.snmp_enabled do
"Device created. SNMP discovery started. "
else
"Device created. "
end
if device.snmp_enabled do
_ = enqueue_discovery(device.id)
"Device created successfully. SNMP discovery started in background."
else
"Device created successfully"
end
%{
text: base_message,
link_text: device_label,
link_url: "/devices/#{device.id}"
}
end
defp handle_device_update(old_device, device) do
@ -335,7 +364,7 @@ defmodule ToweropsWeb.DeviceLive.Form do
# If device community is nil or empty, resolve from site/org hierarchy
effective_community =
if is_nil(device_community) or device_community == "" do
resolve_inherited_community(assigns)
resolve_inherited_community(params, assigns)
else
device_community
end
@ -348,27 +377,45 @@ defmodule ToweropsWeb.DeviceLive.Form do
}
end
defp resolve_inherited_community(assigns) do
cond do
# For new devices, check preselected site
assigns[:preselected_site_id] ->
site = Enum.find(assigns.available_sites, &(&1.id == assigns.preselected_site_id))
site && site.snmp_community
# For edit mode, use effective SNMP community
assigns[:effective_snmp_community] && assigns.effective_snmp_community != "(not set)" ->
assigns.effective_snmp_community
# Fallback: check if there's only one site
length(assigns[:available_sites] || []) == 1 ->
site = List.first(assigns.available_sites)
site && site.snmp_community
true ->
nil
defp resolve_inherited_community(params, assigns) do
# For edit mode, use effective SNMP community (already resolved)
if has_effective_community?(assigns) do
assigns.effective_snmp_community
else
resolve_community_from_hierarchy(params, assigns)
end
end
defp has_effective_community?(assigns) do
assigns[:effective_snmp_community] && assigns.effective_snmp_community != "(not set)"
end
defp resolve_community_from_hierarchy(params, assigns) do
selected_site_id = get_selected_site_id(params, assigns)
site_community = get_site_community(selected_site_id, assigns)
site_community || get_org_community(assigns)
end
defp get_selected_site_id(params, assigns) do
sites = assigns[:available_sites] || []
params["site_id"] ||
assigns[:preselected_site_id] ||
(length(sites) == 1 && List.first(sites).id)
end
defp get_site_community(nil, _assigns), do: nil
defp get_site_community(site_id, assigns) do
site = Enum.find(assigns.available_sites, &(&1.id == site_id))
if site && site.snmp_community && site.snmp_community != "", do: site.snmp_community
end
defp get_org_community(assigns) do
org = assigns[:organization]
org && org.snmp_community
end
@spec normalize_port(integer() | String.t() | any()) :: integer()
defp normalize_port(port) when is_integer(port) and port >= 1 and port <= 65_535, do: port
defp normalize_port(port) when is_integer(port), do: 161
@ -451,4 +498,15 @@ defmodule ToweropsWeb.DeviceLive.Form do
end
defp handle_agent_assignment(_device_id, _), do: :ok
# Sanitize device params - trim whitespace from IP address
defp sanitize_device_params(params) do
case Map.get(params, "ip_address") do
ip when is_binary(ip) ->
Map.put(params, "ip_address", String.trim(ip))
_ ->
params
end
end
end

View file

@ -44,6 +44,24 @@
<.input field={@form[:ip_address]} type="text" label="IP Address" required />
<div
:if={@duplicate_device}
class="rounded-md bg-amber-50 dark:bg-amber-900/20 p-3 -mt-2 mb-4"
>
<div class="flex items-center gap-2 text-amber-800 dark:text-amber-200">
<.icon name="hero-exclamation-triangle" class="h-5 w-5 flex-shrink-0" />
<p class="text-sm">
A device with this IP address already exists:
<.link
navigate={~p"/devices/#{@duplicate_device.id}"}
class="font-medium underline hover:text-amber-900 dark:hover:text-amber-100"
>
{@duplicate_device.name || @duplicate_device.ip_address}
</.link>
</p>
</div>
</div>
<.input
field={@form[:site_id]}
type="select"
@ -240,7 +258,13 @@
</div>
<div class="flex gap-3 mt-6">
<.button phx-disable-with="Saving..." variant="primary">Save Device</.button>
<.button
phx-disable-with="Saving..."
variant="primary"
disabled={@duplicate_device != nil}
>
Save Device
</.button>
<.button navigate={~p"/devices"}>Cancel</.button>
</div>
</.form>

View file

@ -190,9 +190,9 @@ defmodule Towerops.Snmp.DiscoveryTest do
assert {:ok, device} = Discovery.discover_device(device)
# YAML profile "routeros" returns "Mikrotik RouterOS" as vendor
# Model comes from sysDescr when using YAML profiles
# Model comes from sysDescr_regex capture (hardware group extracts model from sysDescr)
assert device.manufacturer == "Mikrotik RouterOS"
assert device.model == "RouterOS RB750"
assert device.model == "RB750"
assert device.sys_name == "router1"
# Verify interfaces were created

View file

@ -22,6 +22,8 @@ defmodule Towerops.Snmp.Profiles.Vendors.RouterosTest do
@wl_stat_table "1.3.6.1.4.1.14988.1.1.1.1.1"
@wl_60g_table "1.3.6.1.4.1.14988.1.1.1.8.1"
@lte_modem_table "1.3.6.1.4.1.14988.1.1.16.1.1"
@gauge_table "1.3.6.1.4.1.14988.1.1.3.100.1"
@optical_table "1.3.6.1.4.1.14988.1.1.19.1.1"
describe "profile_names/0" do
test "returns expected profile names" do
@ -113,6 +115,8 @@ defmodule Towerops.Snmp.Profiles.Vendors.RouterosTest do
expect(SnmpMock, :walk, fn _, @wl_stat_table, _ -> {:ok, []} end)
expect(SnmpMock, :walk, fn _, @wl_60g_table, _ -> {:ok, []} end)
expect(SnmpMock, :walk, fn _, @lte_modem_table, _ -> {:ok, []} end)
expect(SnmpMock, :walk, fn _, @gauge_table, _ -> {:ok, []} end)
expect(SnmpMock, :walk, fn _, @optical_table, _ -> {:ok, []} end)
# System sensors (DHCP leases and PPPoE)
expect(SnmpMock, :get, 2, fn _, _oid, _ -> {:ok, 100} end)
@ -150,6 +154,8 @@ defmodule Towerops.Snmp.Profiles.Vendors.RouterosTest do
expect(SnmpMock, :walk, fn _, @wl_60g_table, _ -> {:ok, []} end)
expect(SnmpMock, :walk, fn _, @lte_modem_table, _ -> {:ok, []} end)
expect(SnmpMock, :walk, fn _, @gauge_table, _ -> {:ok, []} end)
expect(SnmpMock, :walk, fn _, @optical_table, _ -> {:ok, []} end)
expect(SnmpMock, :get, 2, fn _, _oid, _ -> {:ok, 50} end)
sensors = Routeros.discover_wireless_sensors(@client_opts)
@ -180,6 +186,8 @@ defmodule Towerops.Snmp.Profiles.Vendors.RouterosTest do
end)
expect(SnmpMock, :walk, fn _, @lte_modem_table, _ -> {:ok, []} end)
expect(SnmpMock, :walk, fn _, @gauge_table, _ -> {:ok, []} end)
expect(SnmpMock, :walk, fn _, @optical_table, _ -> {:ok, []} end)
expect(SnmpMock, :get, 2, fn _, _oid, _ -> {:ok, 25} end)
sensors = Routeros.discover_wireless_sensors(@client_opts)
@ -209,6 +217,8 @@ defmodule Towerops.Snmp.Profiles.Vendors.RouterosTest do
]}
end)
expect(SnmpMock, :walk, fn _, @gauge_table, _ -> {:ok, []} end)
expect(SnmpMock, :walk, fn _, @optical_table, _ -> {:ok, []} end)
expect(SnmpMock, :get, 2, fn _, _oid, _ -> {:ok, 10} end)
sensors = Routeros.discover_wireless_sensors(@client_opts)
@ -228,6 +238,8 @@ defmodule Towerops.Snmp.Profiles.Vendors.RouterosTest do
expect(SnmpMock, :walk, fn _, @wl_stat_table, _ -> {:ok, []} end)
expect(SnmpMock, :walk, fn _, @wl_60g_table, _ -> {:ok, []} end)
expect(SnmpMock, :walk, fn _, @lte_modem_table, _ -> {:ok, []} end)
expect(SnmpMock, :walk, fn _, @gauge_table, _ -> {:ok, []} end)
expect(SnmpMock, :walk, fn _, @optical_table, _ -> {:ok, []} end)
# System sensors (DHCP leases and PPPoE)
expect(SnmpMock, :get, 2, fn _, _oid, _ -> {:ok, 100} end)
@ -247,6 +259,8 @@ defmodule Towerops.Snmp.Profiles.Vendors.RouterosTest do
expect(SnmpMock, :walk, fn _, @wl_stat_table, _ -> {:error, :timeout} end)
expect(SnmpMock, :walk, fn _, @wl_60g_table, _ -> {:error, :timeout} end)
expect(SnmpMock, :walk, fn _, @lte_modem_table, _ -> {:error, :timeout} end)
expect(SnmpMock, :walk, fn _, @gauge_table, _ -> {:error, :timeout} end)
expect(SnmpMock, :walk, fn _, @optical_table, _ -> {:error, :timeout} end)
expect(SnmpMock, :get, 2, fn _, _, _ -> {:error, :timeout} end)
sensors = Routeros.discover_wireless_sensors(@client_opts)