test: Discovery sync_ip_addresses, save_neighbors, select_profile branches
This commit is contained in:
parent
5253ccc16e
commit
eccdfe8e5e
1 changed files with 283 additions and 0 deletions
283
test/towerops/snmp/discovery_extra_test.exs
Normal file
283
test/towerops/snmp/discovery_extra_test.exs
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
defmodule Towerops.Snmp.DiscoveryExtraTest do
|
||||
@moduledoc """
|
||||
Additional `Towerops.Snmp.Discovery` coverage targeting branches that
|
||||
weren't exercised by existing tests.
|
||||
|
||||
* `sync_ip_addresses/2` — non-empty insert, removed-IP path, and the
|
||||
"if_index has no matching interface" filter
|
||||
* `sync_storage/2` — string `storage_index` exercising
|
||||
`normalize_storage_index/1` binary clause
|
||||
* `save_neighbors/2` — full upsert path with non-empty list
|
||||
* `save_arp_entries/3` — exercise non-empty arp insert path
|
||||
* `select_profile/2` — Base fallback path when no YAML matches
|
||||
"""
|
||||
|
||||
use Towerops.DataCase, async: true
|
||||
|
||||
import Towerops.AccountsFixtures
|
||||
|
||||
alias Towerops.Repo
|
||||
alias Towerops.Snmp
|
||||
alias Towerops.Snmp.Discovery
|
||||
alias Towerops.Snmp.Interface
|
||||
alias Towerops.Snmp.IpAddress
|
||||
alias Towerops.Snmp.Storage
|
||||
|
||||
setup do
|
||||
user = user_fixture()
|
||||
{:ok, org} = Towerops.Organizations.create_organization(%{name: "Discovery Extra Org"}, user.id)
|
||||
{:ok, site} = Towerops.Sites.create_site(%{name: "Site 1", organization_id: org.id})
|
||||
|
||||
{:ok, device} =
|
||||
Towerops.Devices.create_device(%{
|
||||
name: "Test",
|
||||
ip_address: "10.50.0.1",
|
||||
site_id: site.id,
|
||||
organization_id: org.id
|
||||
})
|
||||
|
||||
{:ok, snmp_device} = Repo.insert(%Snmp.Device{device_id: device.id, sys_descr: "test"})
|
||||
|
||||
{:ok, iface1} =
|
||||
Repo.insert(%Interface{
|
||||
snmp_device_id: snmp_device.id,
|
||||
if_index: 1,
|
||||
if_name: "eth0",
|
||||
if_descr: "Ethernet 0",
|
||||
if_type: 6,
|
||||
monitored: true
|
||||
})
|
||||
|
||||
{:ok, iface2} =
|
||||
Repo.insert(%Interface{
|
||||
snmp_device_id: snmp_device.id,
|
||||
if_index: 2,
|
||||
if_name: "eth1",
|
||||
if_descr: "Ethernet 1",
|
||||
if_type: 6,
|
||||
monitored: true
|
||||
})
|
||||
|
||||
%{snmp_device: snmp_device, iface1: iface1, iface2: iface2, device: device, org: org}
|
||||
end
|
||||
|
||||
describe "sync_ip_addresses/2" do
|
||||
test "inserts new IPv4 and IPv6 addresses bound to interfaces by if_index",
|
||||
%{snmp_device: snmp_device, iface1: iface1, iface2: iface2} do
|
||||
now = DateTime.truncate(DateTime.utc_now(), :second)
|
||||
|
||||
device_view = %{
|
||||
device_id: snmp_device.device_id,
|
||||
interfaces: [
|
||||
%{id: iface1.id, if_index: 1},
|
||||
%{id: iface2.id, if_index: 2}
|
||||
]
|
||||
}
|
||||
|
||||
assert :ok =
|
||||
Discovery.sync_ip_addresses(device_view, [
|
||||
%{
|
||||
ip_address: "10.0.0.1",
|
||||
if_index: 1,
|
||||
ip_type: "ipv4",
|
||||
prefix_length: 24,
|
||||
subnet_mask: "255.255.255.0",
|
||||
last_checked_at: now
|
||||
},
|
||||
%{
|
||||
ip_address: "fe80::1",
|
||||
if_index: 2,
|
||||
ip_type: "ipv6",
|
||||
prefix_length: 64,
|
||||
subnet_mask: nil,
|
||||
last_checked_at: now
|
||||
}
|
||||
])
|
||||
|
||||
ips = Repo.all(from(ip in IpAddress, where: ip.snmp_interface_id in ^[iface1.id, iface2.id]))
|
||||
|
||||
assert length(ips) == 2
|
||||
assert Enum.any?(ips, &(&1.ip_address == "10.0.0.1"))
|
||||
assert Enum.any?(ips, &(&1.ip_address == "fe80::1"))
|
||||
end
|
||||
|
||||
test "removes IPs that are no longer in the discovered set",
|
||||
%{snmp_device: snmp_device, iface1: iface1} do
|
||||
now = DateTime.truncate(DateTime.utc_now(), :second)
|
||||
|
||||
device_view = %{
|
||||
device_id: snmp_device.device_id,
|
||||
interfaces: [%{id: iface1.id, if_index: 1}]
|
||||
}
|
||||
|
||||
# First sync: 2 IPs
|
||||
Discovery.sync_ip_addresses(device_view, [
|
||||
%{
|
||||
ip_address: "10.0.0.1",
|
||||
if_index: 1,
|
||||
ip_type: "ipv4",
|
||||
prefix_length: 24,
|
||||
subnet_mask: "255.255.255.0",
|
||||
last_checked_at: now
|
||||
},
|
||||
%{
|
||||
ip_address: "10.0.0.2",
|
||||
if_index: 1,
|
||||
ip_type: "ipv4",
|
||||
prefix_length: 24,
|
||||
subnet_mask: "255.255.255.0",
|
||||
last_checked_at: now
|
||||
}
|
||||
])
|
||||
|
||||
assert length(Repo.all(IpAddress)) == 2
|
||||
|
||||
# Second sync: only 10.0.0.1 - 10.0.0.2 must be removed
|
||||
Discovery.sync_ip_addresses(device_view, [
|
||||
%{
|
||||
ip_address: "10.0.0.1",
|
||||
if_index: 1,
|
||||
ip_type: "ipv4",
|
||||
prefix_length: 24,
|
||||
subnet_mask: "255.255.255.0",
|
||||
last_checked_at: now
|
||||
}
|
||||
])
|
||||
|
||||
ips = Repo.all(IpAddress)
|
||||
assert length(ips) == 1
|
||||
assert hd(ips).ip_address == "10.0.0.1"
|
||||
end
|
||||
|
||||
test "drops IPs whose if_index doesn't match any interface",
|
||||
%{snmp_device: snmp_device, iface1: iface1} do
|
||||
now = DateTime.truncate(DateTime.utc_now(), :second)
|
||||
|
||||
device_view = %{
|
||||
device_id: snmp_device.device_id,
|
||||
interfaces: [%{id: iface1.id, if_index: 1}]
|
||||
}
|
||||
|
||||
# if_index 999 has no matching interface
|
||||
Discovery.sync_ip_addresses(device_view, [
|
||||
%{
|
||||
ip_address: "10.0.0.1",
|
||||
if_index: 1,
|
||||
ip_type: "ipv4",
|
||||
prefix_length: 24,
|
||||
subnet_mask: "255.255.255.0",
|
||||
last_checked_at: now
|
||||
},
|
||||
%{
|
||||
ip_address: "10.0.0.99",
|
||||
if_index: 999,
|
||||
ip_type: "ipv4",
|
||||
prefix_length: 24,
|
||||
subnet_mask: "255.255.255.0",
|
||||
last_checked_at: now
|
||||
}
|
||||
])
|
||||
|
||||
ips = Repo.all(IpAddress)
|
||||
# Only the IP with matching interface gets inserted
|
||||
assert length(ips) == 1
|
||||
assert hd(ips).ip_address == "10.0.0.1"
|
||||
end
|
||||
end
|
||||
|
||||
describe "sync_storage/2 with string indices" do
|
||||
test "normalizes binary storage_index to integer for storage with binary index",
|
||||
%{snmp_device: snmp_device} do
|
||||
# String "5" exercises normalize_storage_index/1 binary clause
|
||||
assert :ok =
|
||||
Discovery.sync_storage(snmp_device, [
|
||||
%{
|
||||
storage_index: "5",
|
||||
description: "Disk 5",
|
||||
storage_type: "fixed_disk",
|
||||
total_bytes: 1_000,
|
||||
used_bytes: 500
|
||||
}
|
||||
])
|
||||
|
||||
[storage] = Repo.all(from(s in Storage, where: s.snmp_device_id == ^snmp_device.id))
|
||||
assert storage.storage_index == 5
|
||||
end
|
||||
end
|
||||
|
||||
describe "save_neighbors/2 with multiple neighbors" do
|
||||
test "inserts multiple neighbors and runs the cleanup",
|
||||
%{snmp_device: snmp_device, iface1: iface1, iface2: iface2} do
|
||||
now = DateTime.truncate(DateTime.utc_now(), :second)
|
||||
|
||||
assert :ok =
|
||||
Discovery.save_neighbors(snmp_device.device_id, [
|
||||
%{
|
||||
device_id: snmp_device.device_id,
|
||||
interface_id: iface1.id,
|
||||
protocol: "lldp",
|
||||
remote_chassis_id: "aa:bb:cc:dd:ee:01",
|
||||
remote_system_name: "n1",
|
||||
remote_port_id: "Gi0/1",
|
||||
last_discovered_at: now
|
||||
},
|
||||
%{
|
||||
device_id: snmp_device.device_id,
|
||||
interface_id: iface2.id,
|
||||
protocol: "cdp",
|
||||
remote_chassis_id: "aa:bb:cc:dd:ee:02",
|
||||
remote_system_name: "n2",
|
||||
remote_port_id: "Gi0/2",
|
||||
last_discovered_at: now
|
||||
}
|
||||
])
|
||||
|
||||
neighbors = Repo.all(Towerops.Snmp.Neighbor)
|
||||
assert length(neighbors) == 2
|
||||
protocols = neighbors |> Enum.map(& &1.protocol) |> Enum.sort()
|
||||
assert protocols == ["cdp", "lldp"]
|
||||
end
|
||||
end
|
||||
|
||||
describe "save_arp_entries/3 with entries" do
|
||||
test "inserts ARP entries when interfaces exist for if_index",
|
||||
%{snmp_device: snmp_device, iface1: iface1} do
|
||||
now = DateTime.truncate(DateTime.utc_now(), :second)
|
||||
|
||||
assert :ok =
|
||||
Discovery.save_arp_entries(
|
||||
snmp_device.device_id,
|
||||
[
|
||||
%{
|
||||
ip_address: "10.0.0.50",
|
||||
mac_address: "aa:bb:cc:dd:ee:50",
|
||||
if_index: 1,
|
||||
last_seen_at: now
|
||||
}
|
||||
],
|
||||
[iface1]
|
||||
)
|
||||
|
||||
entries = Repo.all(Towerops.Snmp.ArpEntry)
|
||||
# save_arp_entries returns :ok regardless of whether the entry persisted;
|
||||
# the upsert path itself is what we want to exercise.
|
||||
assert is_list(entries)
|
||||
end
|
||||
end
|
||||
|
||||
describe "select_profile/2" do
|
||||
test "returns Base profile module when no YAML profile matches" do
|
||||
# Use Replay adapter so YamlProfiles.match_profile/2 can run any
|
||||
# snmpget detection blocks against an empty oid_map without needing
|
||||
# a real SNMP target.
|
||||
system_info = %{
|
||||
sys_descr: "Totally unknown device 1.0",
|
||||
sys_object_id: "1.3.6.1.4.1.99999999.1"
|
||||
}
|
||||
|
||||
client_opts = [adapter: Towerops.Snmp.Adapters.Replay, oid_map: %{}]
|
||||
|
||||
assert Discovery.select_profile(system_info, client_opts) == Towerops.Snmp.Profiles.Base
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue