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 UpgradeScheduled {
50 node_id: u32,
51 pending_version: String,
52 },
53 NodeUpgraded {
56 node_id: u32,
57 old_version: String,
58 new_version: String,
59 },
60 NodeEvicted {
63 node_id: u32,
64 reason: String,
66 reclaimed_bytes: u64,
68 },
69 FleetHealthChanged {
72 overall: String,
74 },
75}
76
77impl NodeEvent {
78 pub fn event_type(&self) -> &'static str {
80 match self {
81 NodeEvent::NodeStarting { .. } => "node_starting",
82 NodeEvent::NodeStarted { .. } => "node_started",
83 NodeEvent::NodeStopping { .. } => "node_stopping",
84 NodeEvent::NodeStopped { .. } => "node_stopped",
85 NodeEvent::NodeCrashed { .. } => "node_crashed",
86 NodeEvent::NodeRestarting { .. } => "node_restarting",
87 NodeEvent::NodeErrored { .. } => "node_errored",
88 NodeEvent::DownloadStarted { .. } => "download_started",
89 NodeEvent::DownloadProgress { .. } => "download_progress",
90 NodeEvent::DownloadComplete { .. } => "download_complete",
91 NodeEvent::UpgradeScheduled { .. } => "upgrade_scheduled",
92 NodeEvent::NodeUpgraded { .. } => "node_upgraded",
93 NodeEvent::NodeEvicted { .. } => "node_evicted",
94 NodeEvent::FleetHealthChanged { .. } => "fleet_health_changed",
95 }
96 }
97}
98
99pub trait EventListener: Send + Sync {
101 fn on_event(&self, event: NodeEvent);
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn event_serializes_with_type_tag() {
110 let event = NodeEvent::NodeStarted {
111 node_id: 1,
112 pid: 1234,
113 };
114 let json = serde_json::to_string(&event).unwrap();
115 assert!(json.contains("\"type\":\"node_started\""));
116 assert!(json.contains("\"node_id\":1"));
117 assert!(json.contains("\"pid\":1234"));
118 }
119
120 #[test]
121 fn event_type_matches_serde_tag() {
122 let event = NodeEvent::NodeCrashed {
123 node_id: 2,
124 exit_code: Some(1),
125 };
126 assert_eq!(event.event_type(), "node_crashed");
127
128 let json = serde_json::to_string(&event).unwrap();
130 assert!(json.contains(&format!("\"type\":\"{}\"", event.event_type())));
131 }
132
133 #[test]
134 fn event_roundtrips() {
135 let event = NodeEvent::DownloadProgress {
136 bytes: 1024,
137 total: 4096,
138 };
139 let json = serde_json::to_string(&event).unwrap();
140 let deserialized: NodeEvent = serde_json::from_str(&json).unwrap();
141 assert_eq!(deserialized.event_type(), "download_progress");
142 }
143
144 #[test]
145 fn upgrade_scheduled_event_serializes() {
146 let event = NodeEvent::UpgradeScheduled {
147 node_id: 2,
148 pending_version: "0.10.11-rc.1".to_string(),
149 };
150 let json = serde_json::to_string(&event).unwrap();
151 assert!(json.contains("\"type\":\"upgrade_scheduled\""));
152 assert!(json.contains("\"node_id\":2"));
153 assert!(json.contains("\"pending_version\":\"0.10.11-rc.1\""));
154 assert_eq!(event.event_type(), "upgrade_scheduled");
155 }
156
157 #[test]
158 fn node_upgraded_event_serializes() {
159 let event = NodeEvent::NodeUpgraded {
160 node_id: 3,
161 old_version: "0.10.1".to_string(),
162 new_version: "0.10.11-rc.1".to_string(),
163 };
164 let json = serde_json::to_string(&event).unwrap();
165 assert!(json.contains("\"type\":\"node_upgraded\""));
166 assert!(json.contains("\"old_version\":\"0.10.1\""));
167 assert!(json.contains("\"new_version\":\"0.10.11-rc.1\""));
168 assert_eq!(event.event_type(), "node_upgraded");
169 }
170}