Replace sequential multi-GET with true batched SNMP GET support.
Previously, Client.get_multiple/2 sent N individual SNMP GET PDUs
for N OIDs. Now sends a single PDU with multiple varbinds, reducing
network round-trips by ~80% for multi-OID operations.
Implementation:
- Add get_multiple/3 callback to SnmpBehaviour
- Implement batched GET in Manager using PDU.build_get_request_multi/2
- Update Client.get_multiple/2 to use batched GET with sequential fallback
- Return map format %{oid => {type, value}} for batched results
- Convert to ordered list for backward compatibility
- Handle SNMP exceptions (noSuchObject, noSuchInstance) in result map
Testing:
- Update all test mocks to use get_multiple expectations
- Add individual get stubs for categorize_device_speed and optional fields
- 2249/2251 tests passing (99.87%)
This addresses F1 from LibreNMS feature parity analysis:
"Replace sequential multi-get with batched GET support."
Impact:
- Discovery system info (6 OIDs): 1 SNMP request instead of 6
- Storage polling (3 OIDs/entry): 1 request per entry instead of 3
- Network round-trips reduced by ~80% for all get_multiple operations
Reviewed-on: graham/towerops-web#183
79 lines
2.4 KiB
Elixir
79 lines
2.4 KiB
Elixir
# Configure MIB directories BEFORE any NIF loading
|
|
# This must happen before ExUnit.start() to ensure ToweropsNative NIF
|
|
# can find vendor MIBs when it initializes
|
|
mib_dir = Application.app_dir(:towerops, "priv/mibs")
|
|
|
|
if File.dir?(mib_dir) do
|
|
# Get main directory plus all subdirectories for vendor MIBs
|
|
subdirs =
|
|
mib_dir
|
|
|> File.ls!()
|
|
|> Enum.map(&Path.join(mib_dir, &1))
|
|
|> Enum.filter(&File.dir?/1)
|
|
|
|
mib_dirs = [mib_dir | subdirs]
|
|
mibdirs_env = Enum.join(mib_dirs, ":")
|
|
|
|
# Set environment variables BEFORE initializing the NIF
|
|
System.put_env("MIBDIRS", mibdirs_env)
|
|
System.put_env("MIBS", "ALL")
|
|
|
|
# Add each directory to net-snmp's search path
|
|
Enum.each(mib_dirs, fn dir ->
|
|
case ToweropsNative.load_mib_directory(dir) do
|
|
:ok -> :ok
|
|
# Ignore errors in test environment
|
|
{:error, _reason} -> :ok
|
|
end
|
|
end)
|
|
|
|
# Explicitly initialize the MIB library with correct environment variables
|
|
case ToweropsNative.init_mib_library() do
|
|
result when result in ["initialized", "already_initialized"] ->
|
|
IO.puts("Test MIB library initialized: #{result}")
|
|
|
|
other ->
|
|
IO.puts("Warning: Unexpected MIB library init result: #{inspect(other)}")
|
|
end
|
|
end
|
|
|
|
ExUnit.start()
|
|
|
|
# Exclude tests by default
|
|
# Run specific tests with: mix test --only <tag>
|
|
ExUnit.configure(
|
|
exclude: [
|
|
:integration,
|
|
# Tests making real network calls (ping, DNS, SSL)
|
|
:network,
|
|
# SnmpKit-specific exclusions
|
|
:performance,
|
|
:snmpv3,
|
|
:memory,
|
|
:shell_integration,
|
|
:optional,
|
|
:needs_simulator,
|
|
:manual,
|
|
:real_device,
|
|
:yecc_required
|
|
]
|
|
)
|
|
|
|
# Define mocks for testing
|
|
Mox.defmock(Towerops.Monitoring.PingMock, for: Towerops.Monitoring.PingBehaviour)
|
|
Mox.defmock(Towerops.Snmp.PollerMock, for: Towerops.Snmp.PollerBehaviour)
|
|
Mox.defmock(Towerops.Snmp.SnmpMock, for: Towerops.Snmp.SnmpBehaviour)
|
|
|
|
# Define stub module for SNMP mock (returns errors for all calls)
|
|
defmodule Towerops.Snmp.SnmpMockStub do
|
|
@moduledoc false
|
|
@behaviour Towerops.Snmp.SnmpBehaviour
|
|
|
|
def get(_target, _oid, _opts), do: {:error, :timeout}
|
|
def get_next(_target, _oid, _opts), do: {:error, :timeout}
|
|
def walk(_target, _oid, _opts), do: {:error, :timeout}
|
|
def get_bulk(_target, _oid, _opts), do: {:error, :timeout}
|
|
def get_multiple(_target, _oids, _opts), do: {:error, :timeout}
|
|
end
|
|
|
|
Ecto.Adapters.SQL.Sandbox.mode(Towerops.Repo, :manual)
|