Trying to save some events

This commit is contained in:
lda
2026-04-30 13:59:18 +07:00 Verified
parent 2b98662bcc
commit b20f53c6ba
11 changed files with 377 additions and 192 deletions
@@ -0,0 +1,86 @@
{
"db_name": "SQLite",
"query": "SELECT events.event_id as \"event_id!\",\n ('agent:' || events.agent_id || ':' || events.kind || ':' ||\n CASE\n WHEN events.kind = 'neigh' AND events.mac IS NOT NULL AND events.ip IS NOT NULL\n THEN 'mac:' || events.mac || ':ip:' || events.ip\n WHEN events.mac IS NOT NULL THEN 'mac:' || events.mac\n WHEN events.ip IS NOT NULL THEN 'ip:' || events.ip\n ELSE ''\n END) as \"observation_key!\",\n events.agent_id as \"agent_id!\",\n events.kind as \"kind!\",\n events.action as \"action!\",\n events.mac,\n events.ip,\n events.hostname,\n events.ts_unix,\n known_devices.device_id,\n known_devices.display_name,\n known_devices.pinned\n FROM agent_device_observation_events events\n LEFT JOIN device_identifiers identifiers\n ON identifiers.identifier_key =\n CASE\n WHEN events.mac IS NOT NULL THEN 'mac:' || events.mac\n WHEN events.ip IS NOT NULL THEN 'ip:' || events.ip\n END\n LEFT JOIN known_devices ON known_devices.device_id = identifiers.device_id\n WHERE (?1 IS NULL OR events.agent_id = ?1)\n AND (?2 IS NULL OR events.kind = ?2)\n AND (?3 IS NULL OR events.mac = ?3)\n AND (?4 IS NULL OR events.ip = ?4)\n AND (?5 IS NULL OR ('agent:' || events.agent_id || ':' || events.kind || ':' ||\n CASE\n WHEN events.kind = 'neigh' AND events.mac IS NOT NULL AND events.ip IS NOT NULL\n THEN 'mac:' || events.mac || ':ip:' || events.ip\n WHEN events.mac IS NOT NULL THEN 'mac:' || events.mac\n WHEN events.ip IS NOT NULL THEN 'ip:' || events.ip\n ELSE ''\n END) = ?5)\n ORDER BY events.ts_unix DESC\n LIMIT ?6",
"describe": {
"columns": [
{
"name": "event_id!",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "observation_key!",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "agent_id!",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "kind!",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "action!",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "mac",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "ip",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "hostname",
"ordinal": 7,
"type_info": "Text"
},
{
"name": "ts_unix",
"ordinal": 8,
"type_info": "Integer"
},
{
"name": "device_id",
"ordinal": 9,
"type_info": "Text"
},
{
"name": "display_name",
"ordinal": 10,
"type_info": "Text"
},
{
"name": "pinned",
"ordinal": 11,
"type_info": "Integer"
}
],
"parameters": {
"Right": 6
},
"nullable": [
true,
true,
false,
false,
false,
true,
true,
true,
false,
true,
true,
true
]
},
"hash": "52a39dd83e65374a5c9e5f55c8492d9927273712ec75814d08674784771fbad2"
}
@@ -1,86 +0,0 @@
{
"db_name": "SQLite",
"query": "SELECT events.event_id as \"event_id!\",\n ('agent:' || events.agent_id || ':' || events.kind || ':' ||\n CASE\n WHEN events.mac IS NOT NULL THEN 'mac:' || events.mac\n WHEN events.ip IS NOT NULL THEN 'ip:' || events.ip\n ELSE ''\n END) as \"observation_key!\",\n events.agent_id as \"agent_id!\",\n events.kind as \"kind!\",\n events.action as \"action!\",\n events.mac,\n events.ip,\n events.hostname,\n events.ts_unix,\n known_devices.device_id,\n known_devices.display_name,\n known_devices.pinned\n FROM agent_device_observation_events events\n LEFT JOIN device_identifiers identifiers\n ON identifiers.identifier_key =\n CASE\n WHEN events.mac IS NOT NULL THEN 'mac:' || events.mac\n WHEN events.ip IS NOT NULL THEN 'ip:' || events.ip\n END\n LEFT JOIN known_devices ON known_devices.device_id = identifiers.device_id\n WHERE (?1 IS NULL OR events.agent_id = ?1)\n AND (?2 IS NULL OR events.kind = ?2)\n AND (?3 IS NULL OR events.mac = ?3)\n AND (?4 IS NULL OR events.ip = ?4)\n AND (?5 IS NULL OR ('agent:' || events.agent_id || ':' || events.kind || ':' ||\n CASE\n WHEN events.mac IS NOT NULL THEN 'mac:' || events.mac\n WHEN events.ip IS NOT NULL THEN 'ip:' || events.ip\n ELSE ''\n END) = ?5)\n ORDER BY events.ts_unix DESC\n LIMIT ?6",
"describe": {
"columns": [
{
"name": "event_id!",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "observation_key!",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "agent_id!",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "kind!",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "action!",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "mac",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "ip",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "hostname",
"ordinal": 7,
"type_info": "Text"
},
{
"name": "ts_unix",
"ordinal": 8,
"type_info": "Integer"
},
{
"name": "device_id",
"ordinal": 9,
"type_info": "Text"
},
{
"name": "display_name",
"ordinal": 10,
"type_info": "Text"
},
{
"name": "pinned",
"ordinal": 11,
"type_info": "Integer"
}
],
"parameters": {
"Right": 6
},
"nullable": [
true,
true,
false,
false,
false,
true,
true,
true,
false,
true,
true,
true
]
},
"hash": "908d763909c76fb6170b818784e360372c02295523ef9970c91199db7d4f535a"
}
+38 -5
View File
@@ -323,6 +323,13 @@ pub async fn wake_fleet_device(
"selected route agent is not connected",
));
}
if !route.wakeable {
return Err(json_error(
StatusCode::BAD_REQUEST,
"wake_route_unavailable",
"selected route is not wakeable",
));
}
let mac = mac.parse().map_err(|err| {
json_error(
@@ -530,6 +537,7 @@ fn add_observation_to_entry(
observation: AgentDeviceObservation,
context: &FleetBuildContext,
) {
let observation_offline = observation_is_offline(&observation);
if let Some(summary) = observation_known_device(&observation, context)
&& entry.known_device.is_none()
{
@@ -540,7 +548,7 @@ fn add_observation_to_entry(
if let Some(mac) = observation.mac.as_deref() {
entry.macs.insert(mac.to_string());
}
if let Some(ip) = observation.ip.as_deref() {
if !observation_offline && let Some(ip) = observation.ip.as_deref() {
entry.ips.insert(ip.to_string());
}
if let Some(hostname) = observation.hostname.as_deref() {
@@ -595,7 +603,7 @@ fn add_observation_to_entry(
observation.ip.as_deref(),
&observation.kind,
);
let wakeable = status.connected && observation.mac.is_some();
let wakeable = status.connected && observation.mac.is_some() && !observation_offline;
entry.routes.insert(
route_id.clone(),
FleetWakeRoute {
@@ -612,7 +620,7 @@ fn add_observation_to_entry(
},
);
if let Some(route) = entry.routes.get_mut(&route_id) {
route.wakeable = route.connected && route.mac.is_some();
route.wakeable = route.connected && route.mac.is_some() && !observation_offline;
}
}
@@ -744,12 +752,17 @@ fn inventory_result_to_observations(
fn observation_presence_rank(observation: &AgentDeviceObservation) -> u8 {
match observation.last_action.as_str() {
"remove" => 0,
"add" | "old" | "update" => 2,
"remove" | "failed" => 0,
"permanent" | "reachable" => 3,
"stale" | "add" | "old" | "update" => 2,
_ => 1,
}
}
fn observation_is_offline(observation: &AgentDeviceObservation) -> bool {
matches!(observation.last_action.as_str(), "remove" | "failed")
}
fn rank_presence(rank: u8) -> &'static str {
match rank {
3 => "online",
@@ -932,6 +945,26 @@ mod tests {
assert!(!devices[0].route_candidates[0].wakeable);
}
#[test]
fn offline_observation_does_not_advertise_current_ip_or_wake_route() {
let mut offline = observation(
"agent-a",
Some("aa:bb:cc:dd:ee:ff"),
Some("192.168.1.2"),
20,
);
offline.kind = "neigh".into();
offline.last_action = "remove".into();
let devices = build_fleet_devices(Vec::new(), vec![offline], &context(&["agent-a"]));
assert_eq!(devices.len(), 1);
assert!(devices[0].ips.is_empty());
assert_eq!(devices[0].presence, "offline");
assert!(devices[0].recommended_route.is_none());
assert!(!devices[0].route_candidates[0].wakeable);
}
#[test]
fn inventory_result_maps_to_stored_observations() {
let observations = inventory_result_to_observations(serde_json::json!({
@@ -259,10 +259,7 @@ pub(in crate::state::store) fn normalize_agent_observation(
let mac = normalize_optional_text(input.mac.as_deref()).map(|value| value.to_ascii_lowercase());
let ip = normalize_optional_text(input.ip.as_deref());
let hostname = normalize_optional_text(input.hostname.as_deref());
let identifier = mac
.as_ref()
.map(|value| format!("mac:{value}"))
.or_else(|| ip.as_ref().map(|value| format!("ip:{value}")))
let identifier = observation_identifier(&kind, mac.as_deref(), ip.as_deref())
.ok_or_else(|| anyhow::anyhow!("observation requires mac or ip"))?;
let observation_key = format!("agent:{agent_id}:{kind}:{identifier}");
Ok(AgentDeviceObservation {
@@ -278,6 +275,15 @@ pub(in crate::state::store) fn normalize_agent_observation(
})
}
fn observation_identifier(kind: &str, mac: Option<&str>, ip: Option<&str>) -> Option<String> {
match (kind, mac, ip) {
("neigh", Some(mac), Some(ip)) => Some(format!("mac:{mac}:ip:{ip}")),
(_, Some(mac), _) => Some(format!("mac:{mac}")),
(_, None, Some(ip)) => Some(format!("ip:{ip}")),
(_, None, None) => None,
}
}
pub(in crate::state::store) async fn insert_device_identifier_tx(
tx: &mut Transaction<'_, Sqlite>,
device_id: &str,
@@ -211,6 +211,8 @@ impl Store {
r#"SELECT events.event_id as "event_id!",
('agent:' || events.agent_id || ':' || events.kind || ':' ||
CASE
WHEN events.kind = 'neigh' AND events.mac IS NOT NULL AND events.ip IS NOT NULL
THEN 'mac:' || events.mac || ':ip:' || events.ip
WHEN events.mac IS NOT NULL THEN 'mac:' || events.mac
WHEN events.ip IS NOT NULL THEN 'ip:' || events.ip
ELSE ''
@@ -239,6 +241,8 @@ impl Store {
AND (?4 IS NULL OR events.ip = ?4)
AND (?5 IS NULL OR ('agent:' || events.agent_id || ':' || events.kind || ':' ||
CASE
WHEN events.kind = 'neigh' AND events.mac IS NOT NULL AND events.ip IS NOT NULL
THEN 'mac:' || events.mac || ':ip:' || events.ip
WHEN events.mac IS NOT NULL THEN 'mac:' || events.mac
WHEN events.ip IS NOT NULL THEN 'ip:' || events.ip
ELSE ''