1use std::collections::HashMap;
2use std::sync::Arc;
3use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
4
5use async_trait::async_trait;
6use thiserror::Error;
7use tokio::sync::watch;
8use tokio_util::sync::CancellationToken;
9
10use crate::CamelError;
11
12#[derive(Debug, Clone)]
16pub struct PlatformIdentity {
17 pub node_id: String,
18 pub namespace: Option<String>,
19 pub labels: HashMap<String, String>,
20}
21
22impl PlatformIdentity {
23 pub fn local(node_id: impl Into<String>) -> Self {
24 Self {
25 node_id: node_id.into(),
26 namespace: None,
27 labels: HashMap::new(),
28 }
29 }
30}
31
32#[derive(Debug, Clone, PartialEq)]
34pub enum LeadershipEvent {
35 StartedLeading,
36 StoppedLeading,
37}
38
39#[derive(Debug, Error)]
41pub enum PlatformError {
42 #[error("leadership lock already active: {lock_name}")]
43 LockAlreadyActive { lock_name: String },
44 #[error("step_down failed: elector loop terminated unexpectedly")]
45 StepDownFailed,
46 #[error("platform not available: {0}")]
47 NotAvailable(String),
48 #[error("configuration error: {0}")]
49 Config(String),
50}
51
52pub struct LeadershipHandle {
54 pub events: watch::Receiver<Option<LeadershipEvent>>,
56 is_leader: Arc<AtomicBool>,
58 leader_epoch: Arc<AtomicU64>,
63 cancel: CancellationToken,
65 terminated: Option<tokio::sync::oneshot::Receiver<()>>,
67}
68
69impl LeadershipHandle {
70 pub fn new(
73 events: watch::Receiver<Option<LeadershipEvent>>,
74 is_leader: Arc<AtomicBool>,
75 leader_epoch: Arc<AtomicU64>,
76 cancel: CancellationToken,
77 terminated: tokio::sync::oneshot::Receiver<()>,
78 ) -> Self {
79 Self {
80 events,
81 is_leader,
82 leader_epoch,
83 cancel,
84 terminated: Some(terminated),
85 }
86 }
87
88 pub fn is_leader(&self) -> bool {
89 self.is_leader.load(Ordering::Acquire)
90 }
91
92 pub fn leader_epoch(&self) -> u64 {
96 self.leader_epoch.load(Ordering::Acquire)
97 }
98
99 pub fn leader_epoch_arc(&self) -> Arc<AtomicU64> {
101 Arc::clone(&self.leader_epoch)
102 }
103
104 pub async fn step_down(mut self) -> Result<(), PlatformError> {
108 self.cancel.cancel();
109 let rx = self
110 .terminated
111 .take()
112 .ok_or(PlatformError::StepDownFailed)?;
113 tokio::time::timeout(std::time::Duration::from_secs(10), rx)
114 .await
115 .map_err(|_| PlatformError::StepDownFailed)?
116 .map_err(|_| PlatformError::StepDownFailed)
117 }
118}
119
120impl Drop for LeadershipHandle {
121 fn drop(&mut self) {
122 self.cancel.cancel();
123 }
124}
125
126#[async_trait]
128pub trait LeadershipService: Send + Sync {
129 async fn start(&self, lock_name: &str) -> Result<LeadershipHandle, PlatformError>;
130}
131
132pub trait PlatformService: Send + Sync {
134 fn identity(&self) -> PlatformIdentity;
135 fn readiness_gate(&self) -> Arc<dyn ReadinessGate>;
136 fn leadership(&self) -> Arc<dyn LeadershipService>;
137}
138
139#[async_trait]
147pub trait ReadinessGate: Send + Sync {
148 async fn notify_ready(&self) -> Result<(), CamelError>;
149 async fn notify_not_ready(&self, reason: &str) -> Result<(), CamelError>;
150 async fn notify_starting(&self) -> Result<(), CamelError>;
151}
152
153pub struct NoopLeadershipService;
160
161impl NoopLeadershipService {
162 pub fn new() -> Self {
163 Self
164 }
165}
166
167impl Default for NoopLeadershipService {
168 fn default() -> Self {
169 Self::new()
170 }
171}
172
173#[async_trait]
174impl LeadershipService for NoopLeadershipService {
175 async fn start(&self, lock_name: &str) -> Result<LeadershipHandle, PlatformError> {
176 let (tx, rx) = watch::channel(Some(LeadershipEvent::StartedLeading));
177 let (term_tx, term_rx) = tokio::sync::oneshot::channel::<()>();
178 let cancel = CancellationToken::new();
179 let cancel_for_task = cancel.clone();
180 let is_leader = Arc::new(AtomicBool::new(true));
181 let is_leader_for_task = Arc::clone(&is_leader);
182 let leader_epoch = Arc::new(AtomicU64::new(1));
186 let lock_name = lock_name.to_string();
187
188 tokio::spawn(async move {
189 cancel_for_task.cancelled().await;
190 is_leader_for_task.store(false, Ordering::Release);
191 let _ = tx.send(Some(LeadershipEvent::StoppedLeading));
192 drop(lock_name);
193 let _ = term_tx.send(());
194 });
195
196 Ok(LeadershipHandle::new(
197 rx,
198 is_leader,
199 leader_epoch,
200 cancel,
201 term_rx,
202 ))
203 }
204}
205
206pub struct NoopPlatformService {
208 identity: PlatformIdentity,
209 readiness_gate: Arc<dyn ReadinessGate>,
210 leadership: Arc<dyn LeadershipService>,
211}
212
213impl NoopPlatformService {
214 pub fn new(identity: PlatformIdentity) -> Self {
215 Self {
216 identity,
217 readiness_gate: Arc::new(NoopReadinessGate),
218 leadership: Arc::new(NoopLeadershipService::new()),
219 }
220 }
221}
222
223impl Default for NoopPlatformService {
224 fn default() -> Self {
225 Self::new(PlatformIdentity::local("noop"))
226 }
227}
228
229impl PlatformService for NoopPlatformService {
230 fn identity(&self) -> PlatformIdentity {
231 self.identity.clone()
232 }
233
234 fn readiness_gate(&self) -> Arc<dyn ReadinessGate> {
235 Arc::clone(&self.readiness_gate)
236 }
237
238 fn leadership(&self) -> Arc<dyn LeadershipService> {
239 Arc::clone(&self.leadership)
240 }
241}
242
243pub struct NoopReadinessGate;
245
246#[async_trait]
247impl ReadinessGate for NoopReadinessGate {
248 async fn notify_ready(&self) -> Result<(), CamelError> {
249 Ok(())
250 }
251 async fn notify_not_ready(&self, _reason: &str) -> Result<(), CamelError> {
252 Ok(())
253 }
254 async fn notify_starting(&self) -> Result<(), CamelError> {
255 Ok(())
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use super::*;
262
263 #[test]
264 fn test_platform_identity_local() {
265 let id = PlatformIdentity::local("my-node");
266 assert_eq!(id.node_id, "my-node");
267 assert!(id.namespace.is_none());
268 assert!(id.labels.is_empty());
269 }
270
271 #[tokio::test]
272 async fn test_noop_leadership_service_is_leader() {
273 let leadership = NoopLeadershipService::new();
274 let handle = leadership.start("lock-a").await.unwrap();
275 assert!(handle.is_leader());
276 }
277
278 #[tokio::test]
279 async fn test_noop_leadership_service_allows_multiple_distinct_locks() {
280 let leadership = NoopLeadershipService::new();
281 let lock_a = leadership.start("lock-a").await.unwrap();
282 let lock_b = leadership.start("lock-b").await.unwrap();
283
284 assert!(lock_a.is_leader());
285 assert!(lock_b.is_leader());
286
287 lock_a.step_down().await.unwrap();
288 lock_b.step_down().await.unwrap();
289 }
290
291 #[tokio::test]
292 async fn test_noop_leadership_service_same_lock_allows_multiple() {
293 let leadership = NoopLeadershipService::new();
294 let first = leadership.start("lock-a").await.unwrap();
295 let second = leadership.start("lock-a").await.unwrap();
296
297 assert!(first.is_leader());
298 assert!(second.is_leader());
299
300 first.step_down().await.unwrap();
301 second.step_down().await.unwrap();
302 }
303
304 #[tokio::test]
305 async fn test_noop_leadership_handle_semantics_and_reacquire() {
306 let leadership = NoopLeadershipService::new();
307 let handle = leadership.start("lock-a").await.unwrap();
308 let mut events = handle.events.clone();
309 let is_leader = Arc::clone(&handle.is_leader);
310
311 let event = handle.events.borrow().clone();
312 assert_eq!(event, Some(LeadershipEvent::StartedLeading));
313
314 handle.step_down().await.unwrap();
315 events.changed().await.unwrap();
316 assert_eq!(*events.borrow(), Some(LeadershipEvent::StoppedLeading));
317 assert!(!is_leader.load(Ordering::Acquire));
318
319 let reacquired = leadership.start("lock-a").await;
320 assert!(reacquired.is_ok());
321 }
322
323 #[tokio::test]
324 async fn test_noop_leadership_drop_cleans_up() {
325 let leadership = NoopLeadershipService::new();
326 let handle = leadership.start("lock-drop").await.unwrap();
327 assert!(handle.is_leader());
328 drop(handle);
329
330 let handle2 = leadership.start("lock-drop").await.unwrap();
331 assert!(handle2.is_leader());
332 handle2.step_down().await.unwrap();
333 }
334
335 #[tokio::test]
336 async fn test_noop_readiness_gate_all_methods() {
337 let gate = NoopReadinessGate;
338 gate.notify_starting().await.unwrap();
339 gate.notify_not_ready("test").await.unwrap();
340 gate.notify_ready().await.unwrap();
341 }
342
343 #[test]
344 fn test_leadership_event_equality() {
345 assert_eq!(
346 LeadershipEvent::StartedLeading,
347 LeadershipEvent::StartedLeading
348 );
349 assert_ne!(
350 LeadershipEvent::StartedLeading,
351 LeadershipEvent::StoppedLeading
352 );
353 }
354
355 #[test]
356 fn test_platform_error_display() {
357 let e = PlatformError::LockAlreadyActive {
358 lock_name: "alpha".into(),
359 };
360 assert!(e.to_string().contains("alpha"));
361 let e2 = PlatformError::NotAvailable("no k8s".into());
362 assert!(e2.to_string().contains("no k8s"));
363 }
364}