fix: use correct device_id for neighbor discovery

The neighbor discovery was using snmp_device.id instead of the actual
Equipment device.id when creating neighbor records. This caused all
neighbor inserts to fail because the foreign key constraint expected
the Equipment ID, not the SNMP Device ID.

Fixed in two places:
- Discovery: Use snmp_device.device_id (FK to Equipment)
- PollerWorker: Use device.id (Equipment ID) instead of snmp_device.id

Neighbors should now be properly discovered and saved during both
initial discovery and ongoing polling.
This commit is contained in:
Graham McIntire 2026-01-18 13:00:31 -06:00
parent dc9400e6ae
commit 592b99d739
No known key found for this signature in database
4 changed files with 295 additions and 6 deletions

View file

@ -0,0 +1,2 @@
13:00:24.245 [info] Loading 161 CA(s) from :otp store

286
.expert/expert.log Normal file
View file

@ -0,0 +1,286 @@
13:00:30.826 [debug] Child {XPLoggerFileBackend, :general_log} of Supervisor Logger.Backends.Supervisor started
Pid: #PID<0.102.0>
Start Call: Logger.Backends.Watcher.start_link({{XPLoggerFileBackend, :general_log}, {XPLoggerFileBackend, :general_log}})
Restart: :transient
Shutdown: 5000
Type: :worker
13:00:30.826 [info] Child Logger.Backends.Supervisor of Supervisor Logger.Backends.Internal started
Pid: #PID<0.101.0>
Start Call: Logger.Backends.Supervisor.start_link([{XPLoggerFileBackend, :general_log}])
Restart: :permanent
Shutdown: :infinity
Type: :supervisor
13:00:30.826 [info] Child Logger.Backends.Internal of Supervisor Logger.Supervisor started
Pid: #PID<0.97.0>
Start Call: Logger.Backends.Internal.start_link([])
Restart: :permanent
Shutdown: :infinity
Type: :supervisor
13:00:30.826 [info] Application logger started at :nonode@nohost
13:00:30.826 [info] Application xp_mime started at :nonode@nohost
13:00:30.826 [info] Application xp_hpax started at :nonode@nohost
13:00:30.826 [info] Application crypto started at :nonode@nohost
13:00:30.826 [info] Application asn1 started at :nonode@nohost
13:00:30.826 [info] Application public_key started at :nonode@nohost
13:00:30.827 [debug] Child :ssl_handler of Supervisor :logger_sup started
Pid: #PID<0.107.0>
Start Call: :logger_olp.start_link(:logger_std_h_ssl_handler, :logger_h_common, %{id: :ssl_handler, module: :logger_std_h, config: %{type: :standard_io, filesync_repeat_interval: :no_repeat}, level: :debug, filter_default: :stop, filters: [], formatter: {:ssl_logger, %{}}}, %{})
Restart: :temporary
Shutdown: 2000
Type: :worker
13:00:30.829 [info] Child :ssl_pem_cache of Supervisor :ssl_admin_sup started
Pid: #PID<0.111.0>
Start Call: :ssl_pem_cache.start_link([])
Restart: :permanent
Shutdown: 4000
Type: :worker
13:00:30.831 [info] Child :ssl_manager of Supervisor :ssl_admin_sup started
Pid: #PID<0.112.0>
Start Call: :ssl_manager.start_link([])
Restart: :permanent
Shutdown: 4000
Type: :worker
13:00:30.832 [info] Child :tls_client_ticket_store of Supervisor :ssl_admin_sup started
Pid: #PID<0.113.0>
Start Call: :tls_client_ticket_store.start_link(1000, 7200)
Restart: :permanent
Shutdown: 4000
Type: :worker
13:00:30.832 [info] Child :ssl_admin_sup of Supervisor :ssl_sup started
Pid: #PID<0.110.0>
Start Call: :ssl_admin_sup.start_link()
Restart: :permanent
Shutdown: 4000
Type: :supervisor
13:00:30.833 [info] Child :tls_connection_sup of Supervisor :tls_sup started
Pid: #PID<0.116.0>
Start Call: :tls_connection_sup.start_link()
Restart: :permanent
Shutdown: 4000
Type: :supervisor
13:00:30.834 [info] Child :ssl_listen_tracker_sup of Supervisor :tls_server_sup started
Pid: #PID<0.118.0>
Start Call: :ssl_listen_tracker_sup.start_link()
Restart: :permanent
Shutdown: 4000
Type: :supervisor
13:00:30.835 [info] Child :tls_server_session_ticket of Supervisor :tls_server_sup started
Pid: #PID<0.119.0>
Start Call: :tls_server_session_ticket_sup.start_link()
Restart: :permanent
Shutdown: 4000
Type: :supervisor
13:00:30.835 [info] Child :ssl_server_session_cache_sup of Supervisor :tls_server_sup started
Pid: #PID<0.120.0>
Start Call: :ssl_server_session_cache_sup.start_link()
Restart: :permanent
Shutdown: 4000
Type: :supervisor
13:00:30.836 [info] Child :ssl_upgrade_server_session_cache_sup of Supervisor :tls_server_sup started
Pid: #PID<0.121.0>
Start Call: :ssl_upgrade_server_session_cache_sup.start_link()
Restart: :permanent
Shutdown: 4000
Type: :supervisor
13:00:30.836 [info] Child :tls_server_sup of Supervisor :tls_sup started
Pid: #PID<0.117.0>
Start Call: :tls_server_sup.start_link()
Restart: :permanent
Shutdown: 4000
Type: :supervisor
13:00:30.836 [info] Child :tls_sup of Supervisor :ssl_connection_sup started
Pid: #PID<0.115.0>
Start Call: :tls_sup.start_link()
Restart: :permanent
Shutdown: 4000
Type: :supervisor
13:00:30.837 [info] Child :dtls_connection_sup of Supervisor :dtls_sup started
Pid: #PID<0.123.0>
Start Call: :dtls_connection_sup.start_link()
Restart: :permanent
Shutdown: 4000
Type: :supervisor
13:00:30.837 [info] Child :dtls_listener_sup of Supervisor :dtls_server_sup started
Pid: #PID<0.125.0>
Start Call: :dtls_listener_sup.start_link()
Restart: :permanent
Shutdown: 4000
Type: :supervisor
13:00:30.838 [info] Child :dtls_server_session_cache_sup of Supervisor :dtls_server_sup started
Pid: #PID<0.126.0>
Start Call: :dtls_server_session_cache_sup.start_link()
Restart: :permanent
Shutdown: 4000
Type: :supervisor
13:00:30.838 [info] Child :dtls_server_sup of Supervisor :dtls_sup started
Pid: #PID<0.124.0>
Start Call: :dtls_server_sup.start_link()
Restart: :permanent
Shutdown: 4000
Type: :supervisor
13:00:30.838 [info] Child :dtls_sup of Supervisor :ssl_connection_sup started
Pid: #PID<0.122.0>
Start Call: :dtls_sup.start_link()
Restart: :permanent
Shutdown: 4000
Type: :supervisor
13:00:30.838 [info] Child :ssl_connection_sup of Supervisor :ssl_sup started
Pid: #PID<0.114.0>
Start Call: :ssl_connection_sup.start_link()
Restart: :permanent
Shutdown: 4000
Type: :supervisor
13:00:30.838 [info] Application ssl started at :nonode@nohost
13:00:30.839 [info] Application xp_mint started at :nonode@nohost
13:00:30.839 [info] Application xp_nimble_options started at :nonode@nohost
13:00:30.841 [info] Child XPNimblePool.TaskSupervisor of Supervisor #PID<0.132.0> (Supervisor.Default) started
Pid: #PID<0.133.0>
Start Call: Task.Supervisor.start_link([name: XPNimblePool.TaskSupervisor])
Restart: :permanent
Shutdown: :infinity
Type: :supervisor
13:00:30.841 [info] Application xp_nimble_pool started at :nonode@nohost
13:00:30.843 [info] Child :telemetry_handler_table of Supervisor :telemetry_sup started
Pid: #PID<0.137.0>
Start Call: :telemetry_handler_table.start_link()
Restart: :permanent
Shutdown: 5000
Type: :worker
13:00:30.843 [info] Application xp_telemetry started at :nonode@nohost
13:00:30.843 [info] Application xp_finch started at :nonode@nohost
13:00:30.858 [info] Child XPReq.Finch.PIDPartition0 of Supervisor XPReq.Finch started
Pid: #PID<0.143.0>
Start Call: Registry.Partition.start_link(XPReq.Finch.PIDPartition0, {:duplicate, XPReq.Finch, 0, 1, XPReq.Finch.KeyPartition0, XPReq.Finch.PIDPartition0, [], false})
Restart: :permanent
Shutdown: 5000
Type: :worker
13:00:30.858 [info] Child XPReq.Finch of Supervisor XPReq.Finch.Supervisor started
Pid: #PID<0.142.0>
Start Call: Registry.start_link([keys: :duplicate, name: XPReq.Finch, meta: [config: %{pools: %{}, registry_name: XPReq.Finch, supervisor_name: XPReq.Finch.PoolSupervisor, manager_name: XPReq.Finch.PoolManager, default_pool_config: %{count: 1, size: 50, mod: XPFinch.HTTP1.Pool, conn_opts: [protocols: [:http1], transport_opts: [keepalive: true, nodelay: true, timeout: 5000], ssl_key_log_file_device: nil], pool_max_idle_time: :infinity, conn_max_idle_time: :infinity, start_pool_metrics?: false}}]])
Restart: :permanent
Shutdown: :infinity
Type: :supervisor
13:00:30.858 [info] Child XPReq.Finch.PoolSupervisor of Supervisor XPReq.Finch.Supervisor started
Pid: #PID<0.144.0>
Start Call: DynamicSupervisor.start_link([name: XPReq.Finch.PoolSupervisor, strategy: :one_for_one])
Restart: :permanent
Shutdown: :infinity
Type: :supervisor
13:00:30.859 [info] Child XPFinch.PoolManager of Supervisor XPReq.Finch.Supervisor started
Pid: #PID<0.145.0>
Start Call: XPFinch.PoolManager.start_link(%{pools: %{}, registry_name: XPReq.Finch, supervisor_name: XPReq.Finch.PoolSupervisor, manager_name: XPReq.Finch.PoolManager, default_pool_config: %{count: 1, size: 50, mod: XPFinch.HTTP1.Pool, conn_opts: [protocols: [:http1], transport_opts: [keepalive: true, nodelay: true, timeout: 5000], ssl_key_log_file_device: nil], pool_max_idle_time: :infinity, conn_max_idle_time: :infinity, start_pool_metrics?: false}})
Restart: :permanent
Shutdown: 5000
Type: :worker
13:00:30.859 [info] Child XPReq.Finch of Supervisor #PID<0.140.0> (Supervisor.Default) started
Pid: #PID<0.141.0>
Start Call: XPFinch.start_link([name: XPReq.Finch, pools: %{default: [protocols: [:http1]]}])
Restart: :permanent
Shutdown: 5000
Type: :worker
13:00:30.859 [info] Child XPReq.FinchSupervisor of Supervisor #PID<0.140.0> (Supervisor.Default) started
Pid: #PID<0.146.0>
Start Call: DynamicSupervisor.start_link([strategy: :one_for_one, name: XPReq.FinchSupervisor])
Restart: :permanent
Shutdown: :infinity
Type: :supervisor
13:00:30.859 [info] Child XPReq.Test.Ownership of Supervisor #PID<0.140.0> (Supervisor.Default) started
Pid: #PID<0.147.0>
Start Call: XPReq.Test.Ownership.start_link([name: XPReq.Test.Ownership])
Restart: :permanent
Shutdown: 5000
Type: :worker
13:00:30.859 [info] Application xp_req started at :nonode@nohost
13:00:30.859 [info] Application burrito started at :nonode@nohost
13:00:30.861 [info] Child XPBriefly.Entry of Supervisor XPBriefly.Supervisor started
Pid: #PID<0.151.0>
Start Call: XPBriefly.Entry.start_link([])
Restart: :permanent
Shutdown: 5000
Type: :worker
13:00:30.861 [info] Application xp_briefly started at :nonode@nohost
13:00:30.861 [info] Application xp_schematic started at :nonode@nohost
13:00:30.861 [info] Application xp_typed_struct started at :nonode@nohost
13:00:30.861 [info] Application xp_gen_lsp started at :nonode@nohost
13:00:30.866 [info] Child XPSnowflake.Generator of Supervisor #PID<0.154.0> (Supervisor.Default) started
Pid: #PID<0.155.0>
Start Call: XPSnowflake.Generator.start_link(0, 1023)
Restart: :permanent
Shutdown: 5000
Type: :worker
13:00:30.866 [info] Application xp_snowflake started at :nonode@nohost
13:00:30.866 [info] Application xp_sourceror started at :nonode@nohost
13:00:30.866 [info] Application xp_forge started at :nonode@nohost
13:00:30.866 [info] Application xp_logger_file_backend started at :nonode@nohost
13:00:30.870 [info] Child :ttb_autostart of Supervisor :runtime_tools_sup started
Pid: #PID<0.159.0>
Start Call: :ttb_autostart.start_link()
Restart: :temporary
Shutdown: 3000
Type: :worker
13:00:30.870 [info] Application runtime_tools started at :nonode@nohost
13:00:30.870 [info] Application et started at :nonode@nohost
13:00:30.870 [info] Application observer started at :nonode@nohost
13:00:30.870 [info] Application xp_path_glob started at :nonode@nohost
13:00:30.883 [info] Starting NodePortMapper on nonode@nohost
13:00:30.883 [info] Child XPForge.NodePortMapper of Supervisor XPExpert.Supervisor started
Pid: #PID<0.163.0>
Start Call: XPForge.NodePortMapper.start_link([])
Restart: :permanent
Shutdown: 5000
Type: :worker
13:00:30.884 [info] Child XPForge.Document.Store of Supervisor XPExpert.Supervisor started
Pid: #PID<0.164.0>
Start Call: XPForge.Document.Store.start_link([derive: [analysis: &XPForge.Ast.analyze/1]])
Restart: :permanent
Shutdown: 5000
Type: :worker
13:00:30.885 [info] Child XPExpert.ProjectSupervisor of Supervisor XPExpert.Supervisor started
Pid: #PID<0.165.0>
Start Call: DynamicSupervisor.start_link([name: XPExpert.ProjectSupervisor, strategy: :one_for_one])
Restart: :permanent
Shutdown: :infinity
Type: :supervisor
13:00:30.885 [info] Child XPExpert.DynamicSupervisor of Supervisor XPExpert.Supervisor started
Pid: #PID<0.166.0>
Start Call: DynamicSupervisor.start_link([name: XPExpert.DynamicSupervisor])
Restart: :permanent
Shutdown: :infinity
Type: :supervisor
13:00:30.886 [info] Child XPGenLSP.Assigns of Supervisor XPExpert.Supervisor started
Pid: #PID<0.167.0>
Start Call: XPGenLSP.Assigns.start_link([name: XPExpert.Assigns])
Restart: :permanent
Shutdown: 5000
Type: :worker
13:00:30.886 [info] Child :expert_task_queue of Supervisor XPExpert.Supervisor started
Pid: #PID<0.168.0>
Start Call: Task.Supervisor.start_link([name: :expert_task_queue])
Restart: :permanent
Shutdown: :infinity
Type: :supervisor
13:00:30.889 [info] Child XPGenLSP.Buffer of Supervisor XPExpert.Supervisor started
Pid: #PID<0.169.0>
Start Call: XPGenLSP.Buffer.start_link([name: XPExpert.Buffer])
Restart: :permanent
Shutdown: 5000
Type: :worker
13:00:30.889 [info] Child XPExpert.ActiveProjects of Supervisor XPExpert.Supervisor started
Pid: #PID<0.170.0>
Start Call: XPExpert.ActiveProjects.start_link()
Restart: :permanent
Shutdown: 5000
Type: :worker
13:00:30.890 [debug] [name: XPExpert, buffer: XPExpert.Buffer, task_supervisor: :expert_task_queue, dynamic_supervisor: XPExpert.DynamicSupervisor, assigns: XPExpert.Assigns]
13:00:30.894 [info] Child XPExpert of Supervisor XPExpert.Supervisor started
Pid: #PID<0.171.0>
Start Call: XPExpert.start_link([name: XPExpert, buffer: XPExpert.Buffer, task_supervisor: :expert_task_queue, dynamic_supervisor: XPExpert.DynamicSupervisor, assigns: XPExpert.Assigns])
Restart: :permanent
Shutdown: 500
Type: :worker
13:00:30.895 [info] Application xp_expert started at :nonode@nohost
13:00:30.921 [debug] handled notification client -> server $/cancelRequest in 18ms
13:00:30.942 [debug] sent notification server -> client window/logMessage
13:00:30.945 [error] Received shutdown before server was initialized
13:00:30.946 [error] Failed to handle Elixir.XPGenLSP.Requests.Shutdown, {{:error, :not_initialized}, %XPExpert.State{initialized?: false, shutdown_received?: false, in_flight_requests: %{}}}
13:00:30.947 [debug] handled request client -> server shutdown in 44ms

View file

@ -327,15 +327,16 @@ defmodule Towerops.Snmp.Discovery do
defp save_discovery_results(device, device_info, interfaces, sensors) do
Repo.transaction(fn ->
# Upsert Device
device = upsert_device(device, device_info)
snmp_device = upsert_device(device, device_info)
# Delete old interfaces/sensors and insert new ones
_ = delete_old_data(device)
new_interfaces = insert_interfaces(device, interfaces)
_ = insert_sensors(device, sensors)
_ = delete_old_data(snmp_device)
new_interfaces = insert_interfaces(snmp_device, interfaces)
_ = insert_sensors(snmp_device, sensors)
# Return device with interfaces loaded and device_id added
%{device | interfaces: Enum.map(new_interfaces, &Map.put(&1, :device_id, device.id))}
# Use snmp_device.device_id which references the Equipment table
%{snmp_device | interfaces: Enum.map(new_interfaces, &Map.put(&1, :device_id, snmp_device.device_id))}
end)
end

View file

@ -198,7 +198,7 @@ defmodule Towerops.Snmp.PollerWorker do
defp poll_device_neighbors(device, snmp_device, client_opts) do
# Add device_id to interfaces for neighbor discovery
interfaces_with_device = Enum.map(snmp_device.interfaces, &Map.put(&1, :device_id, snmp_device.id))
interfaces_with_device = Enum.map(snmp_device.interfaces, &Map.put(&1, :device_id, device.id))
{:ok, neighbors} = NeighborDiscovery.discover_neighbors(client_opts, interfaces_with_device)