diff --git a/lib/towerops/devices.ex b/lib/towerops/devices.ex index a908d09e..1b5fd239 100644 --- a/lib/towerops/devices.ex +++ b/lib/towerops/devices.ex @@ -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. """ diff --git a/lib/towerops/profiles/yaml_profiles.ex b/lib/towerops/profiles/yaml_profiles.ex index d227788b..d409da39 100644 --- a/lib/towerops/profiles/yaml_profiles.ex +++ b/lib/towerops/profiles/yaml_profiles.ex @@ -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_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) diff --git a/lib/towerops/snmp/discovery.ex b/lib/towerops/snmp/discovery.ex index 28e67dd1..c7f8ff22 100644 --- a/lib/towerops/snmp/discovery.ex +++ b/lib/towerops/snmp/discovery.ex @@ -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 diff --git a/lib/towerops/snmp/mib_translator.ex b/lib/towerops/snmp/mib_translator.ex index 4519e922..4f75a69f 100644 --- a/lib/towerops/snmp/mib_translator.ex +++ b/lib/towerops/snmp/mib_translator.ex @@ -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", diff --git a/lib/towerops/snmp/profiles/dynamic.ex b/lib/towerops/snmp/profiles/dynamic.ex index 5a7afb7c..67263344 100644 --- a/lib/towerops/snmp/profiles/dynamic.ex +++ b/lib/towerops/snmp/profiles/dynamic.ex @@ -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 (?...) 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 diff --git a/lib/towerops/snmp/profiles/vendors/routeros.ex b/lib/towerops/snmp/profiles/vendors/routeros.ex index 81c43846..fc9c18f3 100644 --- a/lib/towerops/snmp/profiles/vendors/routeros.ex +++ b/lib/towerops/snmp/profiles/vendors/routeros.ex @@ -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) diff --git a/lib/towerops_web/components/core_components.ex b/lib/towerops_web/components/core_components.ex index 8474ba02..18b56dd0 100644 --- a/lib/towerops_web/components/core_components.ex +++ b/lib/towerops_web/components/core_components.ex @@ -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)}

@@ -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} + + """ + end + + defp render_flash_message(msg), do: msg + @doc """ Renders a button with navigation support. diff --git a/lib/towerops_web/live/device_live/form.ex b/lib/towerops_web/live/device_live/form.ex index 2a87ee0d..319d0a35 100644 --- a/lib/towerops_web/live/device_live/form.ex +++ b/lib/towerops_web/live/device_live/form.ex @@ -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 diff --git a/lib/towerops_web/live/device_live/form.html.heex b/lib/towerops_web/live/device_live/form.html.heex index ad6ac145..6da03c59 100644 --- a/lib/towerops_web/live/device_live/form.html.heex +++ b/lib/towerops_web/live/device_live/form.html.heex @@ -44,6 +44,24 @@ <.input field={@form[:ip_address]} type="text" label="IP Address" required /> +
+
+ <.icon name="hero-exclamation-triangle" class="h-5 w-5 flex-shrink-0" /> +

+ 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} + +

+
+
+ <.input field={@form[:site_id]} type="select" @@ -240,7 +258,13 @@
- <.button phx-disable-with="Saving..." variant="primary">Save Device + <.button + phx-disable-with="Saving..." + variant="primary" + disabled={@duplicate_device != nil} + > + Save Device + <.button navigate={~p"/devices"}>Cancel
diff --git a/test/towerops/snmp/discovery_test.exs b/test/towerops/snmp/discovery_test.exs index e6afa625..2787d9f6 100644 --- a/test/towerops/snmp/discovery_test.exs +++ b/test/towerops/snmp/discovery_test.exs @@ -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 diff --git a/test/towerops/snmp/profiles/vendors/routeros_test.exs b/test/towerops/snmp/profiles/vendors/routeros_test.exs index 98bd6ed3..6ca7ac68 100644 --- a/test/towerops/snmp/profiles/vendors/routeros_test.exs +++ b/test/towerops/snmp/profiles/vendors/routeros_test.exs @@ -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)