poll device immediately after discovery completes
newly discovered devices sat idle with no metric data until the next scheduling cycle. now the channel sends a poll job (and ping job if monitoring enabled) right after discovery succeeds, using send(self(), ...) so discovery DB writes commit first. also reduces k8s replicas from 3 to 2.
This commit is contained in:
parent
10d1157ec3
commit
9e36d5030a
2 changed files with 137 additions and 0 deletions
|
|
@ -145,6 +145,38 @@ defmodule ToweropsWeb.AgentChannel do
|
|||
{:noreply, socket}
|
||||
end
|
||||
|
||||
# Poll a device immediately after discovery completes.
|
||||
# Uses send(self(), ...) so discovery DB writes commit first.
|
||||
def handle_info({:poll_after_discovery, device_id}, socket) do
|
||||
case Devices.get_device_with_details(device_id) do
|
||||
nil ->
|
||||
Logger.error("Device not found for post-discovery poll", device_id: device_id)
|
||||
|
||||
device ->
|
||||
jobs = [build_polling_job(device)]
|
||||
|
||||
jobs =
|
||||
if device.monitoring_enabled do
|
||||
jobs ++ [build_ping_job(device)]
|
||||
else
|
||||
jobs
|
||||
end
|
||||
|
||||
job_list = %AgentJobList{jobs: jobs}
|
||||
binary = AgentJobList.encode(job_list)
|
||||
|
||||
Logger.info("Sending immediate poll after discovery",
|
||||
device_id: device_id,
|
||||
device_name: device.name,
|
||||
job_count: length(jobs)
|
||||
)
|
||||
|
||||
push(socket, "jobs", %{binary: Base.encode64(binary)})
|
||||
end
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
# Periodic heartbeat check — close channel if agent hasn't sent a heartbeat recently
|
||||
def handle_info(:check_heartbeat, socket) do
|
||||
seconds_since = DateTime.diff(DateTime.utc_now(), socket.assigns.last_heartbeat_at, :second)
|
||||
|
|
@ -1249,6 +1281,9 @@ defmodule ToweropsWeb.AgentChannel do
|
|||
{:discovery_completed, device.id}
|
||||
)
|
||||
|
||||
# Poll the device immediately so it doesn't sit idle until the next cycle
|
||||
send(self(), {:poll_after_discovery, device.id})
|
||||
|
||||
:ok
|
||||
|
||||
{:error, reason} ->
|
||||
|
|
|
|||
102
test/towerops_web/channels/agent_channel_test.exs
Normal file
102
test/towerops_web/channels/agent_channel_test.exs
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
defmodule ToweropsWeb.AgentChannelTest do
|
||||
use Towerops.DataCase, async: false
|
||||
|
||||
import Phoenix.ChannelTest
|
||||
|
||||
alias Towerops.AccountsFixtures
|
||||
alias Towerops.Agent.AgentJobList
|
||||
alias Towerops.Agent.SnmpResult
|
||||
alias Towerops.AgentsFixtures
|
||||
alias Towerops.DevicesFixtures
|
||||
alias Towerops.OrganizationsFixtures
|
||||
alias ToweropsWeb.AgentSocket
|
||||
|
||||
@endpoint ToweropsWeb.Endpoint
|
||||
|
||||
setup do
|
||||
# Create organization, site, device, and agent token
|
||||
user = AccountsFixtures.user_fixture()
|
||||
organization = OrganizationsFixtures.organization_fixture(user.id)
|
||||
|
||||
device =
|
||||
DevicesFixtures.device_fixture(%{
|
||||
organization_id: organization.id,
|
||||
snmp_version: "2c",
|
||||
snmp_community: "public"
|
||||
})
|
||||
|
||||
{:ok, agent_token, token_string} =
|
||||
AgentsFixtures.agent_token_fixture(organization.id)
|
||||
|
||||
# Assign device to agent
|
||||
{:ok, _assignment} =
|
||||
AgentsFixtures.agent_assignment_fixture(agent_token.id, device.id)
|
||||
|
||||
# Connect socket and join channel
|
||||
{:ok, socket} = connect(AgentSocket, %{})
|
||||
|
||||
{:ok, _, socket} =
|
||||
subscribe_and_join(socket, "agent:#{agent_token.id}", %{"token" => token_string})
|
||||
|
||||
# Drain the initial :send_jobs message pushed on join
|
||||
assert_push "jobs", _initial_jobs
|
||||
|
||||
%{
|
||||
socket: socket,
|
||||
device: device,
|
||||
agent_token: agent_token,
|
||||
organization: organization
|
||||
}
|
||||
end
|
||||
|
||||
describe "poll after discovery" do
|
||||
test "pushes poll jobs after successful discovery result", %{
|
||||
socket: socket,
|
||||
device: device
|
||||
} do
|
||||
# Build a minimal discovery result with system info OIDs
|
||||
# that AgentDiscovery.process_agent_discovery can handle
|
||||
discovery_result = %SnmpResult{
|
||||
device_id: device.id,
|
||||
job_type: :DISCOVER,
|
||||
job_id: "discover:#{device.id}",
|
||||
timestamp: DateTime.to_unix(DateTime.utc_now()),
|
||||
oid_values: %{
|
||||
# sysDescr
|
||||
"1.3.6.1.2.1.1.1.0" => "Test Device Description",
|
||||
# sysObjectID
|
||||
"1.3.6.1.2.1.1.2.0" => "1.3.6.1.4.1.9.1.1",
|
||||
# sysUpTime
|
||||
"1.3.6.1.2.1.1.3.0" => "123456",
|
||||
# sysContact
|
||||
"1.3.6.1.2.1.1.4.0" => "admin@test.com",
|
||||
# sysName
|
||||
"1.3.6.1.2.1.1.5.0" => "test-device",
|
||||
# sysLocation
|
||||
"1.3.6.1.2.1.1.6.0" => "Test Location"
|
||||
}
|
||||
}
|
||||
|
||||
binary = SnmpResult.encode(discovery_result)
|
||||
payload = %{"binary" => Base.encode64(binary)}
|
||||
|
||||
# Send the discovery result to the channel
|
||||
push(socket, "result", payload)
|
||||
|
||||
# Should receive a "jobs" push with poll job(s) for the discovered device
|
||||
assert_push "jobs", %{binary: jobs_binary}, 5_000
|
||||
|
||||
# Decode the job list and verify it contains a POLL job for our device
|
||||
{:ok, decoded_binary} = Base.decode64(jobs_binary)
|
||||
job_list = AgentJobList.decode(decoded_binary)
|
||||
|
||||
poll_job =
|
||||
Enum.find(job_list.jobs, fn job ->
|
||||
job.job_type == :POLL and job.device_id == device.id
|
||||
end)
|
||||
|
||||
assert poll_job != nil, "Expected a POLL job for device #{device.id}"
|
||||
assert poll_job.job_id == "poll:#{device.id}"
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue