use super::*;
#[allow(clippy::too_many_arguments)]
pub(in crate::commands::ops) fn build_ops_diff_payload(
current: &StatusSnapshot,
current_source: &OpsSource,
baseline: &StatusSnapshot,
baseline_source: &OpsSource,
current_alerts: &[OpsAlert],
baseline_alerts: &[OpsAlert],
current_health: &str,
baseline_health: &str,
) -> serde_json::Value {
let (added_edges, removed_edges) = diff_topology_edges(current, baseline);
let mission_changes = diff_mission_states(current, baseline);
let plugin_changes = diff_plugin_states(current, baseline);
let health_changes = diff_health_states(current, baseline);
serde_json::json!({
"api_version": OPS_DIFF_API_VERSION,
"kind": "ops_diff",
"captured_at_unix_nanos": current.captured_at_unix_nanos,
"source": {
"current": current_source.json,
"baseline": baseline_source.json,
},
"result": {
"summary": {
"overall_health": {
"current": current_health,
"baseline": baseline_health,
},
"alerts": {
"current": current_alerts.len(),
"baseline": baseline_alerts.len(),
},
},
"delta": {
"nodes": count_delta(current.nodes.len(), baseline.nodes.len()),
"topics": count_delta(current.topics.len(), baseline.topics.len()),
"services": count_delta(current.services.len(), baseline.services.len()),
"actions": count_delta(current.actions.len(), baseline.actions.len()),
"missions": count_delta(current.missions.len(), baseline.missions.len()),
"plugins_loaded": count_delta(
current.plugins.iter().filter(|plugin| plugin.loaded).count(),
baseline.plugins.iter().filter(|plugin| plugin.loaded).count(),
),
},
"topology": {
"added_edges": added_edges,
"removed_edges": removed_edges,
},
"missions": {
"changed": mission_changes,
},
"plugins": {
"changed": plugin_changes,
},
"health": {
"changed": health_changes,
}
}
})
}
pub(in crate::commands::ops) fn count_delta(current: usize, baseline: usize) -> serde_json::Value {
let current_i64 = current as i64;
let baseline_i64 = baseline as i64;
serde_json::json!({
"current": current,
"baseline": baseline,
"delta": current_i64 - baseline_i64,
})
}
pub(in crate::commands::ops) fn diff_topology_edges(
current: &StatusSnapshot,
baseline: &StatusSnapshot,
) -> (Vec<String>, Vec<String>) {
let current_edges = current
.edges
.iter()
.map(|edge| format!("{} --{}--> {}", edge.from, edge.relation, edge.to))
.collect::<HashSet<_>>();
let baseline_edges = baseline
.edges
.iter()
.map(|edge| format!("{} --{}--> {}", edge.from, edge.relation, edge.to))
.collect::<HashSet<_>>();
let mut added = current_edges
.difference(&baseline_edges)
.cloned()
.collect::<Vec<_>>();
let mut removed = baseline_edges
.difference(¤t_edges)
.cloned()
.collect::<Vec<_>>();
added.sort();
removed.sort();
(added, removed)
}
pub(in crate::commands::ops) fn diff_mission_states(
current: &StatusSnapshot,
baseline: &StatusSnapshot,
) -> Vec<serde_json::Value> {
let mut current_map = BTreeMap::new();
for mission in ¤t.missions {
current_map.insert(mission.name.clone(), mission.state.clone());
}
let mut baseline_map = BTreeMap::new();
for mission in &baseline.missions {
baseline_map.insert(mission.name.clone(), mission.state.clone());
}
let mut names = current_map.keys().cloned().collect::<HashSet<_>>();
names.extend(baseline_map.keys().cloned());
let mut keys = names.into_iter().collect::<Vec<_>>();
keys.sort();
keys.into_iter()
.filter_map(|name| {
let current_state = current_map
.get(&name)
.cloned()
.unwrap_or_else(|| "<absent>".to_string());
let baseline_state = baseline_map
.get(&name)
.cloned()
.unwrap_or_else(|| "<absent>".to_string());
if current_state == baseline_state {
return None;
}
Some(serde_json::json!({
"name": name,
"current": current_state,
"baseline": baseline_state,
}))
})
.collect()
}
pub(in crate::commands::ops) fn diff_plugin_states(
current: &StatusSnapshot,
baseline: &StatusSnapshot,
) -> Vec<serde_json::Value> {
let mut current_map = BTreeMap::new();
for plugin in ¤t.plugins {
current_map.insert(plugin.name.clone(), plugin.loaded);
}
let mut baseline_map = BTreeMap::new();
for plugin in &baseline.plugins {
baseline_map.insert(plugin.name.clone(), plugin.loaded);
}
let mut names = current_map.keys().cloned().collect::<HashSet<_>>();
names.extend(baseline_map.keys().cloned());
let mut keys = names.into_iter().collect::<Vec<_>>();
keys.sort();
keys.into_iter()
.filter_map(|name| {
let current_loaded = current_map.get(&name).copied().unwrap_or(false);
let baseline_loaded = baseline_map.get(&name).copied().unwrap_or(false);
if current_loaded == baseline_loaded {
return None;
}
Some(serde_json::json!({
"name": name,
"current": current_loaded,
"baseline": baseline_loaded,
}))
})
.collect()
}
pub(in crate::commands::ops) fn diff_health_states(
current: &StatusSnapshot,
baseline: &StatusSnapshot,
) -> Vec<serde_json::Value> {
let mut current_map = BTreeMap::new();
for item in ¤t.health {
let reason = item.reason.clone().unwrap_or_default();
current_map.insert(
item.component.clone(),
format!("{}|{}", item.status, reason),
);
}
let mut baseline_map = BTreeMap::new();
for item in &baseline.health {
let reason = item.reason.clone().unwrap_or_default();
baseline_map.insert(
item.component.clone(),
format!("{}|{}", item.status, reason),
);
}
let mut names = current_map.keys().cloned().collect::<HashSet<_>>();
names.extend(baseline_map.keys().cloned());
let mut keys = names.into_iter().collect::<Vec<_>>();
keys.sort();
keys.into_iter()
.filter_map(|name| {
let current_state = current_map
.get(&name)
.cloned()
.unwrap_or_else(|| "<absent>|".to_string());
let baseline_state = baseline_map
.get(&name)
.cloned()
.unwrap_or_else(|| "<absent>|".to_string());
if current_state == baseline_state {
return None;
}
Some(serde_json::json!({
"component": name,
"current": current_state,
"baseline": baseline_state,
}))
})
.collect()
}