1use std::path::PathBuf;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
9#[serde(tag = "type", rename_all = "snake_case")]
10pub enum NodeEvent {
11 NodeStarting {
12 node_id: u32,
13 },
14 NodeStarted {
15 node_id: u32,
16 pid: u32,
17 },
18 NodeStopping {
19 node_id: u32,
20 },
21 NodeStopped {
22 node_id: u32,
23 },
24 NodeCrashed {
25 node_id: u32,
26 exit_code: Option<i32>,
27 },
28 NodeRestarting {
29 node_id: u32,
30 attempt: u32,
31 },
32 NodeErrored {
33 node_id: u32,
34 message: String,
35 },
36 DownloadStarted {
37 version: String,
38 },
39 DownloadProgress {
40 bytes: u64,
41 total: u64,
42 },
43 DownloadComplete {
44 version: String,
45 path: PathBuf,
46 },
47 NodeUpgraded {
50 node_id: u32,
51 old_version: String,
52 new_version: String,
53 },
54 NodeEvicted {
57 node_id: u32,
58 reason: String,
60 reclaimed_bytes: u64,
62 },
63 FleetHealthChanged {
66 overall: String,
68 },
69}
70
71impl NodeEvent {
72 pub fn event_type(&self) -> &'static str {
74 match self {
75 NodeEvent::NodeStarting { .. } => "node_starting",
76 NodeEvent::NodeStarted { .. } => "node_started",
77 NodeEvent::NodeStopping { .. } => "node_stopping",
78 NodeEvent::NodeStopped { .. } => "node_stopped",
79 NodeEvent::NodeCrashed { .. } => "node_crashed",
80 NodeEvent::NodeRestarting { .. } => "node_restarting",
81 NodeEvent::NodeErrored { .. } => "node_errored",
82 NodeEvent::DownloadStarted { .. } => "download_started",
83 NodeEvent::DownloadProgress { .. } => "download_progress",
84 NodeEvent::DownloadComplete { .. } => "download_complete",
85 NodeEvent::NodeUpgraded { .. } => "node_upgraded",
86 NodeEvent::NodeEvicted { .. } => "node_evicted",
87 NodeEvent::FleetHealthChanged { .. } => "fleet_health_changed",
88 }
89 }
90}
91
92pub trait EventListener: Send + Sync {
94 fn on_event(&self, event: NodeEvent);
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn event_serializes_with_type_tag() {
103 let event = NodeEvent::NodeStarted {
104 node_id: 1,
105 pid: 1234,
106 };
107 let json = serde_json::to_string(&event).unwrap();
108 assert!(json.contains("\"type\":\"node_started\""));
109 assert!(json.contains("\"node_id\":1"));
110 assert!(json.contains("\"pid\":1234"));
111 }
112
113 #[test]
114 fn event_type_matches_serde_tag() {
115 let event = NodeEvent::NodeCrashed {
116 node_id: 2,
117 exit_code: Some(1),
118 };
119 assert_eq!(event.event_type(), "node_crashed");
120
121 let json = serde_json::to_string(&event).unwrap();
123 assert!(json.contains(&format!("\"type\":\"{}\"", event.event_type())));
124 }
125
126 #[test]
127 fn event_roundtrips() {
128 let event = NodeEvent::DownloadProgress {
129 bytes: 1024,
130 total: 4096,
131 };
132 let json = serde_json::to_string(&event).unwrap();
133 let deserialized: NodeEvent = serde_json::from_str(&json).unwrap();
134 assert_eq!(deserialized.event_type(), "download_progress");
135 }
136
137 #[test]
138 fn node_upgraded_event_serializes() {
139 let event = NodeEvent::NodeUpgraded {
140 node_id: 3,
141 old_version: "0.10.1".to_string(),
142 new_version: "0.10.11-rc.1".to_string(),
143 };
144 let json = serde_json::to_string(&event).unwrap();
145 assert!(json.contains("\"type\":\"node_upgraded\""));
146 assert!(json.contains("\"old_version\":\"0.10.1\""));
147 assert!(json.contains("\"new_version\":\"0.10.11-rc.1\""));
148 assert_eq!(event.event_type(), "node_upgraded");
149 }
150}