502 lines
13 KiB
Elixir
502 lines
13 KiB
Elixir
defmodule SnmpKit.SnmpMgrIntegrationTest do
|
|
use ExUnit.Case, async: false
|
|
|
|
alias SnmpKit.SnmpMgr
|
|
|
|
@moduletag :integration
|
|
@moduletag :snmpkit
|
|
|
|
describe "SnmpMgr get/3" do
|
|
test "successfully retrieves sysUpTime from localhost simulator" do
|
|
target = "127.0.0.1"
|
|
oid = "1.3.6.1.2.1.1.3.0"
|
|
opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]
|
|
|
|
case SnmpMgr.get(target, oid, opts) do
|
|
{:ok, value} ->
|
|
assert tuple_size(value) == 2
|
|
{type, _data} = value
|
|
assert type == :timeticks
|
|
|
|
{:error, :timeout} ->
|
|
# Simulator not running, skip test
|
|
:ok
|
|
|
|
{:error, reason} ->
|
|
flunk("Unexpected error: #{inspect(reason)}")
|
|
end
|
|
end
|
|
|
|
test "handles string OID format" do
|
|
target = "127.0.0.1"
|
|
oid = "1.3.6.1.2.1.1.1.0"
|
|
opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]
|
|
|
|
result = SnmpMgr.get(target, oid, opts)
|
|
assert elem(result, 0) in [:ok, :error]
|
|
end
|
|
|
|
test "handles list OID format" do
|
|
target = "127.0.0.1"
|
|
oid = [1, 3, 6, 1, 2, 1, 1, 1, 0]
|
|
opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]
|
|
|
|
result = SnmpMgr.get(target, oid, opts)
|
|
assert elem(result, 0) in [:ok, :error]
|
|
end
|
|
|
|
test "returns error for invalid OID" do
|
|
target = "127.0.0.1"
|
|
oid = "1.3.6.1.99.99.99.99"
|
|
opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]
|
|
|
|
case SnmpMgr.get(target, oid, opts) do
|
|
{:error, _reason} -> :ok
|
|
{:ok, _} -> flunk("Expected error for invalid OID")
|
|
end
|
|
end
|
|
|
|
test "handles timeout gracefully" do
|
|
target = "192.0.2.1"
|
|
# RFC 5737 TEST-NET-1 address that should not route
|
|
oid = "1.3.6.1.2.1.1.1.0"
|
|
opts = [community: "public", version: :v2c, port: 161, timeout: 1000]
|
|
|
|
assert {:error, :timeout} = SnmpMgr.get(target, oid, opts)
|
|
end
|
|
|
|
test "returns error for wrong community string" do
|
|
target = "127.0.0.1"
|
|
oid = "1.3.6.1.2.1.1.1.0"
|
|
opts = [community: "wrong_community", version: :v2c, port: 1161, timeout: 5000]
|
|
|
|
case SnmpMgr.get(target, oid, opts) do
|
|
{:error, _reason} ->
|
|
:ok
|
|
|
|
{:ok, _} ->
|
|
# Simulator may allow any community
|
|
:ok
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "SnmpMgr walk/3" do
|
|
test "successfully walks interface table" do
|
|
target = "127.0.0.1"
|
|
oid = "1.3.6.1.2.1.2.2.1.2"
|
|
opts = [community: "public", version: :v2c, port: 1161, timeout: 10_000]
|
|
|
|
case SnmpMgr.walk(target, oid, opts) do
|
|
{:ok, results} ->
|
|
case results do
|
|
[first | _rest] ->
|
|
# Verify structure of results
|
|
assert Map.has_key?(first, :oid)
|
|
assert Map.has_key?(first, :value)
|
|
|
|
[] ->
|
|
:ok
|
|
end
|
|
|
|
{:error, :timeout} ->
|
|
# Simulator not running
|
|
:ok
|
|
|
|
{:error, reason} ->
|
|
# Other errors are acceptable (no such object, etc.)
|
|
assert reason in [:noSuchName, :noSuchObject, :genErr, :tooBig, :badValue, :readOnly]
|
|
end
|
|
end
|
|
|
|
test "returns empty list for non-existent subtree" do
|
|
target = "127.0.0.1"
|
|
oid = "1.3.6.1.99.99.99"
|
|
opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]
|
|
|
|
case SnmpMgr.walk(target, oid, opts) do
|
|
{:ok, results} ->
|
|
assert results == []
|
|
|
|
{:error, _reason} ->
|
|
# Error is also acceptable
|
|
:ok
|
|
end
|
|
end
|
|
|
|
test "handles walk timeout" do
|
|
target = "192.0.2.1"
|
|
oid = "1.3.6.1.2.1.2.2.1"
|
|
opts = [community: "public", version: :v2c, port: 161, timeout: 1000]
|
|
|
|
assert {:error, :timeout} = SnmpMgr.walk(target, oid, opts)
|
|
end
|
|
|
|
test "walk result format is consistent" do
|
|
target = "127.0.0.1"
|
|
oid = "1.3.6.1.2.1.1"
|
|
opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]
|
|
|
|
case SnmpMgr.walk(target, oid, opts) do
|
|
{:ok, results} when is_list(results) ->
|
|
# All results should be maps with :oid and :value keys
|
|
for result <- results do
|
|
assert Map.has_key?(result, :oid)
|
|
assert Map.has_key?(result, :value)
|
|
assert byte_size(result.oid) > 0
|
|
end
|
|
|
|
{:error, :timeout} ->
|
|
:ok
|
|
|
|
{:error, _} ->
|
|
:ok
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "SnmpMgr get_bulk/3" do
|
|
test "successfully performs get_bulk operation" do
|
|
target = "127.0.0.1"
|
|
oid = "1.3.6.1.2.1.2.2.1.10"
|
|
|
|
opts = [
|
|
community: "public",
|
|
version: :v2c,
|
|
port: 1161,
|
|
timeout: 5000,
|
|
max_repetitions: 10
|
|
]
|
|
|
|
case SnmpMgr.get_bulk(target, oid, opts) do
|
|
{:ok, results} ->
|
|
case results do
|
|
[first | _rest] ->
|
|
assert Map.has_key?(first, :oid)
|
|
assert Map.has_key?(first, :value)
|
|
|
|
[] ->
|
|
:ok
|
|
end
|
|
|
|
{:error, :timeout} ->
|
|
:ok
|
|
|
|
{:error, reason} ->
|
|
assert match?(r when is_atom(r), reason)
|
|
end
|
|
end
|
|
|
|
test "respects max_repetitions parameter" do
|
|
target = "127.0.0.1"
|
|
oid = "1.3.6.1.2.1.2.2.1"
|
|
|
|
opts = [
|
|
community: "public",
|
|
version: :v2c,
|
|
port: 1161,
|
|
timeout: 5000,
|
|
max_repetitions: 5
|
|
]
|
|
|
|
case SnmpMgr.get_bulk(target, oid, opts) do
|
|
{:ok, results} ->
|
|
# Should get at most max_repetitions results per column
|
|
assert match?(l when is_list(l), results)
|
|
|
|
{:error, :timeout} ->
|
|
:ok
|
|
|
|
{:error, _} ->
|
|
:ok
|
|
end
|
|
end
|
|
|
|
test "get_bulk only works with SNMPv2c" do
|
|
target = "127.0.0.1"
|
|
oid = "1.3.6.1.2.1.2.2.1.10"
|
|
|
|
# Try with v1 - should not support get_bulk
|
|
opts_v1 = [community: "public", version: :v1, port: 1161, timeout: 5000]
|
|
|
|
case SnmpMgr.get_bulk(target, oid, opts_v1) do
|
|
{:error, _reason} ->
|
|
# Expected - v1 doesn't support get_bulk
|
|
:ok
|
|
|
|
{:ok, _} ->
|
|
# Some implementations may allow it or fall back
|
|
:ok
|
|
end
|
|
|
|
# Try with v2c - should work
|
|
opts_v2c = [community: "public", version: :v2c, port: 1161, timeout: 5000]
|
|
|
|
case SnmpMgr.get_bulk(target, oid, opts_v2c) do
|
|
{:ok, results} ->
|
|
assert match?(l when is_list(l), results)
|
|
|
|
{:error, :timeout} ->
|
|
:ok
|
|
|
|
{:error, _} ->
|
|
:ok
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "SnmpMgr with different versions" do
|
|
test "SNMPv1 get operation" do
|
|
target = "127.0.0.1"
|
|
oid = "1.3.6.1.2.1.1.3.0"
|
|
opts = [community: "public", version: :v1, port: 1161, timeout: 5000]
|
|
|
|
result = SnmpMgr.get(target, oid, opts)
|
|
assert elem(result, 0) in [:ok, :error]
|
|
end
|
|
|
|
test "SNMPv2c get operation" do
|
|
target = "127.0.0.1"
|
|
oid = "1.3.6.1.2.1.1.3.0"
|
|
opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]
|
|
|
|
result = SnmpMgr.get(target, oid, opts)
|
|
assert elem(result, 0) in [:ok, :error]
|
|
end
|
|
end
|
|
|
|
describe "SnmpMgr error handling" do
|
|
test "handles network errors gracefully" do
|
|
target = "192.0.2.1"
|
|
oid = "1.3.6.1.2.1.1.1.0"
|
|
opts = [community: "public", version: :v2c, port: 161, timeout: 1000]
|
|
|
|
assert {:error, :timeout} = SnmpMgr.get(target, oid, opts)
|
|
end
|
|
|
|
test "handles invalid target format" do
|
|
target = "not-a-valid-ip"
|
|
oid = "1.3.6.1.2.1.1.1.0"
|
|
opts = [community: "public", version: :v2c, port: 161, timeout: 1000]
|
|
|
|
case SnmpMgr.get(target, oid, opts) do
|
|
{:error, _reason} ->
|
|
:ok
|
|
|
|
{:ok, _} ->
|
|
:ok
|
|
# Some systems may resolve hostnames
|
|
end
|
|
end
|
|
|
|
test "handles malformed OID" do
|
|
target = "127.0.0.1"
|
|
# Invalid OID format
|
|
oid = "not.an.oid"
|
|
opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]
|
|
|
|
case SnmpMgr.get(target, oid, opts) do
|
|
{:error, _reason} ->
|
|
# Expected
|
|
:ok
|
|
|
|
{:ok, _} ->
|
|
# Some parsers may handle this
|
|
:ok
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "SnmpMgr real-world scenarios" do
|
|
test "retrieve system information bundle" do
|
|
target = "127.0.0.1"
|
|
opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]
|
|
|
|
system_oids = [
|
|
"1.3.6.1.2.1.1.1.0",
|
|
# sysDescr
|
|
"1.3.6.1.2.1.1.2.0",
|
|
# sysObjectID
|
|
"1.3.6.1.2.1.1.3.0",
|
|
# sysUpTime
|
|
"1.3.6.1.2.1.1.4.0",
|
|
# sysContact
|
|
"1.3.6.1.2.1.1.5.0",
|
|
# sysName
|
|
"1.3.6.1.2.1.1.6.0"
|
|
# sysLocation
|
|
]
|
|
|
|
# Try to get all system info
|
|
results =
|
|
Enum.map(system_oids, fn oid ->
|
|
SnmpMgr.get(target, oid, opts)
|
|
end)
|
|
|
|
# At least some should succeed (if simulator running) or all timeout
|
|
assert Enum.all?(results, fn result ->
|
|
match?({:ok, _}, result) or match?({:error, _}, result)
|
|
end)
|
|
end
|
|
|
|
test "walk and count interfaces" do
|
|
target = "127.0.0.1"
|
|
oid = "1.3.6.1.2.1.2.2.1.1"
|
|
# ifIndex
|
|
opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]
|
|
|
|
case SnmpMgr.walk(target, oid, opts) do
|
|
{:ok, results} ->
|
|
interface_count = length(results)
|
|
assert interface_count >= 0
|
|
|
|
{:error, :timeout} ->
|
|
:ok
|
|
|
|
{:error, _} ->
|
|
:ok
|
|
end
|
|
end
|
|
|
|
test "retrieve interface statistics" do
|
|
target = "127.0.0.1"
|
|
opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]
|
|
|
|
# Common interface stats OIDs
|
|
stats_oids = [
|
|
"1.3.6.1.2.1.2.2.1.10.1",
|
|
# ifInOctets for interface 1
|
|
"1.3.6.1.2.1.2.2.1.16.1",
|
|
# ifOutOctets for interface 1
|
|
"1.3.6.1.2.1.2.2.1.8.1"
|
|
# ifOperStatus for interface 1
|
|
]
|
|
|
|
results =
|
|
Enum.map(stats_oids, fn oid ->
|
|
SnmpMgr.get(target, oid, opts)
|
|
end)
|
|
|
|
assert Enum.all?(results, fn result ->
|
|
match?({:ok, _}, result) or match?({:error, _}, result)
|
|
end)
|
|
end
|
|
end
|
|
|
|
describe "SnmpMgr performance and reliability" do
|
|
test "handles multiple concurrent requests" do
|
|
target = "127.0.0.1"
|
|
oid = "1.3.6.1.2.1.1.3.0"
|
|
opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]
|
|
|
|
tasks =
|
|
for _ <- 1..5 do
|
|
Task.async(fn -> SnmpMgr.get(target, oid, opts) end)
|
|
end
|
|
|
|
results = Task.await_many(tasks, 10_000)
|
|
|
|
# All requests should complete
|
|
assert length(results) == 5
|
|
|
|
# All should either succeed or have consistent errors
|
|
assert Enum.all?(results, fn result ->
|
|
match?({:ok, _}, result) or match?({:error, _}, result)
|
|
end)
|
|
end
|
|
|
|
test "respects timeout parameter" do
|
|
target = "192.0.2.1"
|
|
oid = "1.3.6.1.2.1.1.1.0"
|
|
opts = [community: "public", version: :v2c, port: 161, timeout: 500]
|
|
|
|
start_time = System.monotonic_time(:millisecond)
|
|
result = SnmpMgr.get(target, oid, opts)
|
|
end_time = System.monotonic_time(:millisecond)
|
|
|
|
assert {:error, :timeout} = result
|
|
|
|
# Should timeout around the specified timeout value
|
|
duration = end_time - start_time
|
|
# Allow some overhead
|
|
assert duration < 2000
|
|
end
|
|
|
|
test "handles rapid sequential requests" do
|
|
target = "127.0.0.1"
|
|
oid = "1.3.6.1.2.1.1.3.0"
|
|
opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]
|
|
|
|
results =
|
|
for _ <- 1..10 do
|
|
SnmpMgr.get(target, oid, opts)
|
|
end
|
|
|
|
# All should complete
|
|
assert length(results) == 10
|
|
|
|
# All should have consistent results
|
|
assert Enum.all?(results, fn result ->
|
|
match?({:ok, _}, result) or match?({:error, _}, result)
|
|
end)
|
|
end
|
|
end
|
|
|
|
describe "SnmpMgr data type handling" do
|
|
test "correctly handles timeticks values" do
|
|
target = "127.0.0.1"
|
|
oid = "1.3.6.1.2.1.1.3.0"
|
|
# sysUpTime
|
|
opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]
|
|
|
|
case SnmpMgr.get(target, oid, opts) do
|
|
{:ok, value} ->
|
|
assert elem(value, 0) == :timeticks
|
|
|
|
{:error, :timeout} ->
|
|
:ok
|
|
|
|
{:error, _} ->
|
|
:ok
|
|
end
|
|
end
|
|
|
|
test "correctly handles octet string values" do
|
|
target = "127.0.0.1"
|
|
oid = "1.3.6.1.2.1.1.1.0"
|
|
# sysDescr
|
|
opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]
|
|
|
|
case SnmpMgr.get(target, oid, opts) do
|
|
{:ok, value} ->
|
|
# Could be :octet_string
|
|
type = elem(value, 0)
|
|
assert type == :octet_string
|
|
|
|
{:error, :timeout} ->
|
|
:ok
|
|
|
|
{:error, _} ->
|
|
:ok
|
|
end
|
|
end
|
|
|
|
test "correctly handles integer values" do
|
|
target = "127.0.0.1"
|
|
oid = "1.3.6.1.2.1.2.2.1.8.1"
|
|
# ifOperStatus
|
|
opts = [community: "public", version: :v2c, port: 1161, timeout: 5000]
|
|
|
|
case SnmpMgr.get(target, oid, opts) do
|
|
{:ok, value} ->
|
|
assert is_integer(value)
|
|
|
|
{:error, :timeout} ->
|
|
:ok
|
|
|
|
{:error, _} ->
|
|
:ok
|
|
end
|
|
end
|
|
end
|
|
end
|