rust_logic_graph/fault_tolerance/
failover.rs1use crate::distributed::InMemoryStore;
2use crate::fault_tolerance::circuit_breaker::CircuitBreaker;
3use std::sync::Arc;
4use tokio::sync::RwLock;
5
6#[derive(Debug, Clone)]
7pub struct ServiceEndpoint {
8 pub name: String,
9 pub url: String,
10}
11
12pub struct FailoverManager {
13 endpoints: Arc<RwLock<Vec<ServiceEndpoint>>>,
14 cb: Arc<CircuitBreaker>,
15}
16
17impl FailoverManager {
18 pub fn new(endpoints: Vec<ServiceEndpoint>, cb: Arc<CircuitBreaker>) -> Self {
19 Self {
20 endpoints: Arc::new(RwLock::new(endpoints)),
21 cb,
22 }
23 }
24
25 pub async fn select(&self) -> Option<ServiceEndpoint> {
26 let eps = self.endpoints.read().await;
27 for ep in eps.iter() {
28 if self.cb.is_allowed().await {
30 return Some(ep.clone());
31 }
32 }
33 None
34 }
35
36 pub async fn add_endpoint(&self, ep: ServiceEndpoint) {
37 let mut eps = self.endpoints.write().await;
38 eps.push(ep);
39 }
40}
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45 use crate::fault_tolerance::circuit_breaker::CircuitConfig;
46 use std::sync::Arc;
47
48 #[tokio::test]
49 async fn test_failover_selection() {
50 let cb = Arc::new(CircuitBreaker::new(
51 "svc-a",
52 Some(Arc::new(InMemoryStore::new())),
53 Some(CircuitConfig {
54 failure_threshold: 10,
55 recovery_timeout: std::time::Duration::from_secs(60),
56 probe_interval: std::time::Duration::from_secs(5),
57 }),
58 ));
59 let eps = vec![
60 ServiceEndpoint {
61 name: "primary".into(),
62 url: "https://primary.local".into(),
63 },
64 ServiceEndpoint {
65 name: "backup".into(),
66 url: "https://backup.local".into(),
67 },
68 ];
69 let fm = FailoverManager::new(eps, cb);
70 let selected = fm.select().await;
71 assert!(selected.is_some());
72 }
73}