feat: add agent-side ICMP ping health monitoring
Implements health monitoring in the agent using ICMP ping, allowing devices assigned to local (non-cloud) agents to be monitored. Changes: - Add surge-ping library for async ICMP ping operations - Create ping.rs module with ping_device() function (5s timeout) - Rename JobType::MONITOR to JobType::PING in protobuf - Add monitoring_check channels for result communication - Implement execute_ping_job() to handle PING jobs - Update TUI to display ping result events The agent now receives PING jobs from Phoenix, executes ICMP pings, and sends MonitoringCheck results back for storage in the database. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
b29a38edb5
commit
cd45e9ff51
8 changed files with 300 additions and 1 deletions
72
Cargo.lock
generated
72
Cargo.lock
generated
|
|
@ -1238,6 +1238,12 @@ dependencies = [
|
||||||
"polyval",
|
"polyval",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "glob"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "group"
|
name = "group"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
|
|
@ -1779,6 +1785,12 @@ dependencies = [
|
||||||
"memoffset",
|
"memoffset",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "no-std-net"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "7.1.3"
|
version = "7.1.3"
|
||||||
|
|
@ -2241,6 +2253,48 @@ version = "0.3.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pnet_base"
|
||||||
|
version = "0.34.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fe4cf6fb3ab38b68d01ab2aea03ed3d1132b4868fa4e06285f29f16da01c5f4c"
|
||||||
|
dependencies = [
|
||||||
|
"no-std-net",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pnet_macros"
|
||||||
|
version = "0.34.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "688b17499eee04a0408aca0aa5cba5fc86401d7216de8a63fdf7a4c227871804"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"regex",
|
||||||
|
"syn 2.0.114",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pnet_macros_support"
|
||||||
|
version = "0.34.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eea925b72f4bd37f8eab0f221bbe4c78b63498350c983ffa9dd4bcde7e030f56"
|
||||||
|
dependencies = [
|
||||||
|
"pnet_base",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pnet_packet"
|
||||||
|
version = "0.34.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a9a005825396b7fe7a38a8e288dbc342d5034dac80c15212436424fef8ea90ba"
|
||||||
|
dependencies = [
|
||||||
|
"glob",
|
||||||
|
"pnet_base",
|
||||||
|
"pnet_macros",
|
||||||
|
"pnet_macros_support",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "poly1305"
|
name = "poly1305"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
|
@ -3159,6 +3213,22 @@ version = "2.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "surge-ping"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "30498e9c9feba213c3df6ed675bdf75519ccbee493517e7225305898c86cac05"
|
||||||
|
dependencies = [
|
||||||
|
"hex",
|
||||||
|
"parking_lot",
|
||||||
|
"pnet_packet",
|
||||||
|
"rand 0.9.2",
|
||||||
|
"socket2",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"tokio",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.109"
|
version = "1.0.109"
|
||||||
|
|
@ -3426,11 +3496,13 @@ dependencies = [
|
||||||
"prost",
|
"prost",
|
||||||
"prost-build",
|
"prost-build",
|
||||||
"prost-types",
|
"prost-types",
|
||||||
|
"rand 0.8.5",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"russh",
|
"russh",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"snmp2",
|
"snmp2",
|
||||||
|
"surge-ping",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ home = "=0.5.12"
|
||||||
zeroize = { version = "1", features = ["derive"] }
|
zeroize = { version = "1", features = ["derive"] }
|
||||||
hostname = "0.4"
|
hostname = "0.4"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
|
surge-ping = "0.8"
|
||||||
|
rand = "0.8"
|
||||||
|
|
||||||
# TUI dependencies (optional feature)
|
# TUI dependencies (optional feature)
|
||||||
ratatui = { version = "0.30", optional = true }
|
ratatui = { version = "0.30", optional = true }
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,7 @@ enum JobType {
|
||||||
POLL = 1;
|
POLL = 1;
|
||||||
MIKROTIK = 2;
|
MIKROTIK = 2;
|
||||||
TEST_CREDENTIALS = 3;
|
TEST_CREDENTIALS = 3;
|
||||||
|
PING = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum QueryType {
|
enum QueryType {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
mod mikrotik;
|
mod mikrotik;
|
||||||
|
mod ping;
|
||||||
mod proto;
|
mod proto;
|
||||||
pub mod secret;
|
pub mod secret;
|
||||||
mod snmp;
|
mod snmp;
|
||||||
|
|
|
||||||
68
src/ping.rs
Normal file
68
src/ping.rs
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use std::net::IpAddr;
|
||||||
|
use std::time::Duration;
|
||||||
|
use surge_ping::{Client, Config, PingIdentifier, PingSequence};
|
||||||
|
use tokio::time::timeout;
|
||||||
|
|
||||||
|
/// Ping a device and return response time in milliseconds.
|
||||||
|
///
|
||||||
|
/// Returns Ok(response_time_ms) on success, Err on failure.
|
||||||
|
pub async fn ping_device(ip_address: &str, timeout_ms: u64) -> Result<f64> {
|
||||||
|
let ip: IpAddr = ip_address
|
||||||
|
.parse()
|
||||||
|
.context(format!("Invalid IP address: {}", ip_address))?;
|
||||||
|
|
||||||
|
// Create ICMP client
|
||||||
|
let client = Client::new(&Config::default())
|
||||||
|
.context("Failed to create ping client - may require root/admin privileges")?;
|
||||||
|
|
||||||
|
// Send ping with timeout
|
||||||
|
let payload = [0; 56]; // Standard ping payload size
|
||||||
|
let identifier = PingIdentifier(rand::random());
|
||||||
|
let sequence = PingSequence(1);
|
||||||
|
|
||||||
|
let ping_future = async {
|
||||||
|
match ip {
|
||||||
|
IpAddr::V4(addr) => {
|
||||||
|
client
|
||||||
|
.pinger(addr.into(), identifier)
|
||||||
|
.await
|
||||||
|
.ping(sequence, &payload)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
IpAddr::V6(addr) => {
|
||||||
|
client
|
||||||
|
.pinger(addr.into(), identifier)
|
||||||
|
.await
|
||||||
|
.ping(sequence, &payload)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Apply timeout
|
||||||
|
let (_, duration) = timeout(Duration::from_millis(timeout_ms), ping_future)
|
||||||
|
.await
|
||||||
|
.context("Ping timeout")?
|
||||||
|
.context("Ping failed")?;
|
||||||
|
|
||||||
|
// Convert Duration to milliseconds (f64 for sub-millisecond precision)
|
||||||
|
let ms = duration.as_secs_f64() * 1000.0;
|
||||||
|
|
||||||
|
Ok(ms)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[ignore] // Requires network access and elevated privileges
|
||||||
|
async fn test_ping_localhost() {
|
||||||
|
let result = ping_device("127.0.0.1", 5000).await;
|
||||||
|
assert!(result.is_ok());
|
||||||
|
let response_time = result.unwrap();
|
||||||
|
assert!(response_time > 0.0);
|
||||||
|
assert!(response_time < 100.0); // Localhost should be fast
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -29,6 +29,10 @@ pub enum AgentEvent {
|
||||||
device_id: String,
|
device_id: String,
|
||||||
sentence_count: usize,
|
sentence_count: usize,
|
||||||
},
|
},
|
||||||
|
MonitoringCheckSent {
|
||||||
|
device_id: String,
|
||||||
|
status: String,
|
||||||
|
},
|
||||||
PollerCreated {
|
PollerCreated {
|
||||||
device_ip: String,
|
device_ip: String,
|
||||||
total_count: usize,
|
total_count: usize,
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,9 @@ impl AgentState {
|
||||||
device_id, sentence_count
|
device_id, sentence_count
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
AgentEvent::MonitoringCheckSent { device_id, status } => {
|
||||||
|
format!("Monitoring check sent for {} (status: {})", device_id, status)
|
||||||
|
}
|
||||||
AgentEvent::PollerCreated {
|
AgentEvent::PollerCreated {
|
||||||
device_ip,
|
device_ip,
|
||||||
total_count,
|
total_count,
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>
|
||||||
|
|
||||||
use crate::proto::agent::{
|
use crate::proto::agent::{
|
||||||
AgentHeartbeat, AgentJob, AgentJobList, CredentialTestResult, JobType, MikrotikResult,
|
AgentHeartbeat, AgentJob, AgentJobList, CredentialTestResult, JobType, MikrotikResult,
|
||||||
MikrotikSentence, QueryType, SnmpResult,
|
MikrotikSentence, MonitoringCheck, QueryType, SnmpResult,
|
||||||
};
|
};
|
||||||
use crate::snmp::{DeviceConfig, PollerRegistry, SnmpValue};
|
use crate::snmp::{DeviceConfig, PollerRegistry, SnmpValue};
|
||||||
|
|
||||||
|
|
@ -62,6 +62,8 @@ pub struct AgentClient {
|
||||||
mikrotik_result_rx: mpsc::Receiver<MikrotikResult>,
|
mikrotik_result_rx: mpsc::Receiver<MikrotikResult>,
|
||||||
credential_test_tx: mpsc::Sender<CredentialTestResult>,
|
credential_test_tx: mpsc::Sender<CredentialTestResult>,
|
||||||
credential_test_rx: mpsc::Receiver<CredentialTestResult>,
|
credential_test_rx: mpsc::Receiver<CredentialTestResult>,
|
||||||
|
monitoring_check_tx: mpsc::Sender<MonitoringCheck>,
|
||||||
|
monitoring_check_rx: mpsc::Receiver<MonitoringCheck>,
|
||||||
poller_registry: PollerRegistry,
|
poller_registry: PollerRegistry,
|
||||||
/// Counter for Phoenix transport heartbeat refs
|
/// Counter for Phoenix transport heartbeat refs
|
||||||
phx_heartbeat_ref: u64,
|
phx_heartbeat_ref: u64,
|
||||||
|
|
@ -139,6 +141,7 @@ impl AgentClient {
|
||||||
let (result_tx, result_rx) = mpsc::channel(RESULT_CHANNEL_CAPACITY);
|
let (result_tx, result_rx) = mpsc::channel(RESULT_CHANNEL_CAPACITY);
|
||||||
let (mikrotik_result_tx, mikrotik_result_rx) = mpsc::channel(RESULT_CHANNEL_CAPACITY);
|
let (mikrotik_result_tx, mikrotik_result_rx) = mpsc::channel(RESULT_CHANNEL_CAPACITY);
|
||||||
let (credential_test_tx, credential_test_rx) = mpsc::channel(RESULT_CHANNEL_CAPACITY);
|
let (credential_test_tx, credential_test_rx) = mpsc::channel(RESULT_CHANNEL_CAPACITY);
|
||||||
|
let (monitoring_check_tx, monitoring_check_rx) = mpsc::channel(RESULT_CHANNEL_CAPACITY);
|
||||||
|
|
||||||
// Split the WebSocket stream so reads and writes can proceed concurrently.
|
// Split the WebSocket stream so reads and writes can proceed concurrently.
|
||||||
// The write half is owned by a dedicated writer task.
|
// The write half is owned by a dedicated writer task.
|
||||||
|
|
@ -175,6 +178,8 @@ impl AgentClient {
|
||||||
mikrotik_result_rx,
|
mikrotik_result_rx,
|
||||||
credential_test_tx,
|
credential_test_tx,
|
||||||
credential_test_rx,
|
credential_test_rx,
|
||||||
|
monitoring_check_tx,
|
||||||
|
monitoring_check_rx,
|
||||||
poller_registry: PollerRegistry::new(),
|
poller_registry: PollerRegistry::new(),
|
||||||
phx_heartbeat_ref: 0,
|
phx_heartbeat_ref: 0,
|
||||||
cached_hostname: get_hostname(),
|
cached_hostname: get_hostname(),
|
||||||
|
|
@ -228,6 +233,7 @@ impl AgentClient {
|
||||||
let (result_tx, result_rx) = mpsc::channel(RESULT_CHANNEL_CAPACITY);
|
let (result_tx, result_rx) = mpsc::channel(RESULT_CHANNEL_CAPACITY);
|
||||||
let (mikrotik_result_tx, mikrotik_result_rx) = mpsc::channel(RESULT_CHANNEL_CAPACITY);
|
let (mikrotik_result_tx, mikrotik_result_rx) = mpsc::channel(RESULT_CHANNEL_CAPACITY);
|
||||||
let (credential_test_tx, credential_test_rx) = mpsc::channel(RESULT_CHANNEL_CAPACITY);
|
let (credential_test_tx, credential_test_rx) = mpsc::channel(RESULT_CHANNEL_CAPACITY);
|
||||||
|
let (monitoring_check_tx, monitoring_check_rx) = mpsc::channel(RESULT_CHANNEL_CAPACITY);
|
||||||
|
|
||||||
// Split the WebSocket stream so reads and writes can proceed concurrently.
|
// Split the WebSocket stream so reads and writes can proceed concurrently.
|
||||||
// The write half is owned by a dedicated writer task.
|
// The write half is owned by a dedicated writer task.
|
||||||
|
|
@ -264,6 +270,8 @@ impl AgentClient {
|
||||||
mikrotik_result_rx,
|
mikrotik_result_rx,
|
||||||
credential_test_tx,
|
credential_test_tx,
|
||||||
credential_test_rx,
|
credential_test_rx,
|
||||||
|
monitoring_check_tx,
|
||||||
|
monitoring_check_rx,
|
||||||
poller_registry: PollerRegistry::new(),
|
poller_registry: PollerRegistry::new(),
|
||||||
phx_heartbeat_ref: 0,
|
phx_heartbeat_ref: 0,
|
||||||
cached_hostname: get_hostname(),
|
cached_hostname: get_hostname(),
|
||||||
|
|
@ -380,6 +388,20 @@ impl AgentClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Receive monitoring check results from job tasks
|
||||||
|
Some(monitoring_check) = self.monitoring_check_rx.recv() => {
|
||||||
|
#[cfg(feature = "tui")]
|
||||||
|
let device_id = monitoring_check.device_id.clone();
|
||||||
|
|
||||||
|
if let Err(e) = self.send_monitoring_check(monitoring_check).await {
|
||||||
|
tracing::error!("Error sending monitoring check: {}", e);
|
||||||
|
#[cfg(feature = "tui")]
|
||||||
|
self.publish_event(crate::tui::AgentEvent::Error {
|
||||||
|
message: format!("Monitoring check send failed for {}: {}", device_id, e),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Send periodic heartbeats
|
// Send periodic heartbeats
|
||||||
_ = heartbeat_interval.tick() => {
|
_ = heartbeat_interval.tick() => {
|
||||||
if let Err(e) = self.send_heartbeat().await {
|
if let Err(e) = self.send_heartbeat().await {
|
||||||
|
|
@ -573,6 +595,42 @@ impl AgentClient {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
JobType::Ping => {
|
||||||
|
// Execute ICMP ping health check
|
||||||
|
let monitoring_check_tx = self.monitoring_check_tx.clone();
|
||||||
|
#[cfg(feature = "tui")]
|
||||||
|
let event_bus = self.event_bus.clone();
|
||||||
|
let job_id = job.job_id.clone();
|
||||||
|
let device_id = job.device_id.clone();
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let start_time = std::time::Instant::now();
|
||||||
|
match execute_ping_job(job, monitoring_check_tx).await {
|
||||||
|
Ok(_) =>
|
||||||
|
{
|
||||||
|
#[cfg(feature = "tui")]
|
||||||
|
if let Some(ref bus) = event_bus {
|
||||||
|
let _ = bus.send(crate::tui::AgentEvent::JobCompleted {
|
||||||
|
job_id,
|
||||||
|
device_id,
|
||||||
|
duration_ms: start_time.elapsed().as_millis() as u64,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Ping job execution failed: {}", e);
|
||||||
|
#[cfg(feature = "tui")]
|
||||||
|
if let Some(ref bus) = event_bus {
|
||||||
|
let _ = bus.send(crate::tui::AgentEvent::JobFailed {
|
||||||
|
job_id,
|
||||||
|
device_id,
|
||||||
|
error: e.to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Execute SNMP job (discovery or polling)
|
// Execute SNMP job (discovery or polling)
|
||||||
let result_tx = self.result_tx.clone();
|
let result_tx = self.result_tx.clone();
|
||||||
|
|
@ -765,6 +823,38 @@ impl AgentClient {
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Send monitoring check result to server.
|
||||||
|
async fn send_monitoring_check(&mut self, result: MonitoringCheck) -> Result<()> {
|
||||||
|
let binary = result.encode_to_vec();
|
||||||
|
|
||||||
|
let msg = PhoenixMessage {
|
||||||
|
topic: format!("agent:{}", self.agent_id),
|
||||||
|
event: "monitoring_check".to_string(),
|
||||||
|
payload: serde_json::json!({"binary": base64_encode(&binary)}),
|
||||||
|
reference: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let text = serde_json::to_string(&msg)?;
|
||||||
|
self.ws_write_tx
|
||||||
|
.send(WsMessage::Text(text.into()))
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Writer task closed: {}", e))?;
|
||||||
|
|
||||||
|
tracing::debug!(
|
||||||
|
"Sent monitoring check for device {} (status: {})",
|
||||||
|
result.device_id,
|
||||||
|
result.status
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "tui")]
|
||||||
|
self.publish_event(crate::tui::AgentEvent::MonitoringCheckSent {
|
||||||
|
device_id: result.device_id.clone(),
|
||||||
|
status: result.status.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Dedicated writer task that owns the WebSocket write half.
|
/// Dedicated writer task that owns the WebSocket write half.
|
||||||
|
|
@ -1082,6 +1172,64 @@ async fn execute_credential_test(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Execute a ping job using ICMP ping to check device health.
|
||||||
|
async fn execute_ping_job(
|
||||||
|
job: AgentJob,
|
||||||
|
result_tx: mpsc::Sender<MonitoringCheck>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let device_id = job.device_id.clone();
|
||||||
|
let snmp_device = job.snmp_device.ok_or("Job missing SNMP device info")?;
|
||||||
|
let ip_address = &snmp_device.ip;
|
||||||
|
|
||||||
|
// Use 5-second timeout for pings (same as Phoenix DeviceMonitorWorker)
|
||||||
|
let timeout_ms = 5000;
|
||||||
|
|
||||||
|
tracing::debug!("Executing health check for device {} at {}", device_id, ip_address);
|
||||||
|
|
||||||
|
let timestamp = std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)?
|
||||||
|
.as_secs() as i64;
|
||||||
|
|
||||||
|
// Execute ping
|
||||||
|
let result = match crate::ping::ping_device(ip_address, timeout_ms).await {
|
||||||
|
Ok(response_time_ms) => {
|
||||||
|
tracing::info!(
|
||||||
|
"✓ Device {} is up (response time: {:.1}ms)",
|
||||||
|
device_id,
|
||||||
|
response_time_ms
|
||||||
|
);
|
||||||
|
|
||||||
|
MonitoringCheck {
|
||||||
|
device_id: device_id.clone(),
|
||||||
|
status: "success".to_string(),
|
||||||
|
response_time_ms,
|
||||||
|
timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!("✗ Device {} is down: {}", device_id, e);
|
||||||
|
|
||||||
|
MonitoringCheck {
|
||||||
|
device_id: device_id.clone(),
|
||||||
|
status: "failure".to_string(),
|
||||||
|
response_time_ms: 0.0,
|
||||||
|
timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send result back to main client task
|
||||||
|
if let Err(e) = result_tx.send(result).await {
|
||||||
|
tracing::warn!(
|
||||||
|
"Failed to send monitoring check for device {}: channel closed",
|
||||||
|
device_id
|
||||||
|
);
|
||||||
|
return Err(format!("Result channel closed: {}", e).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Execute a MikroTik API job and collect results.
|
/// Execute a MikroTik API job and collect results.
|
||||||
async fn execute_mikrotik_job(
|
async fn execute_mikrotik_job(
|
||||||
job: AgentJob,
|
job: AgentJob,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue