1use crate::infrastructure::high_availability::config::HighAvailabilityConfig;
2use crate::infrastructure::high_availability::{ClusterStatus, FailoverPhase, HaError};
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::sync::Arc;
6use tokio::sync::RwLock;
7use tracing::{debug, error, info, warn};
8use uuid::Uuid;
9
10pub struct ClusterManager {
13 config: Arc<HighAvailabilityConfig>,
14 nodes: Arc<RwLock<HashMap<String, NodeInfo>>>,
15 node_id: String,
16 current_leader: Arc<RwLock<Option<String>>>,
17 status: Arc<RwLock<ClusterStatus>>,
18 discovery_service: Box<dyn NodeDiscovery + Send + Sync>,
19 membership_service: Box<dyn ClusterMembership + Send + Sync>,
20}
21
22impl ClusterManager {
23 pub fn new(config: &HighAvailabilityConfig) -> Self {
25 let node_id = Uuid::new_v4().to_string();
26 let discovery_service = create_discovery_service(config);
27 let membership_service = create_membership_service(config);
28
29 Self {
30 config: Arc::new(config.clone()),
31 nodes: Arc::new(RwLock::new(HashMap::new())),
32 node_id,
33 current_leader: Arc::new(RwLock::new(None)),
34 status: Arc::new(RwLock::new(ClusterStatus::Initializing)),
35 discovery_service,
36 membership_service,
37 }
38 }
39
40 pub async fn initialize(&mut self) -> Result<(), HaError> {
42 info!("Initializing cluster manager");
43
44 let discovered_nodes = self
46 .discovery_service
47 .discover_nodes()
48 .await
49 .map_err(|e| HaError::ClusterError(format!("Failed to discover nodes: {e}")))?;
50
51 let mut nodes = self.nodes.write().await;
53 for node_addr in discovered_nodes {
54 let node_info = NodeInfo {
55 id: node_addr.clone(), address: node_addr,
57 status: NodeStatus::Unknown,
58 role: NodeRole::Unknown,
59 last_heartbeat: None,
60 metadata: HashMap::new(),
61 };
62 nodes.insert(node_info.id.clone(), node_info);
63 }
64
65 let self_node = NodeInfo {
67 id: self.node_id.clone(),
68 address: self
69 .membership_service
70 .get_local_address()
71 .map_err(|e| HaError::ClusterError(format!("Failed to get local address: {e}")))?,
72 status: NodeStatus::Starting,
73 role: NodeRole::Follower, last_heartbeat: Some(chrono::Utc::now()),
75 metadata: HashMap::new(),
76 };
77 nodes.insert(self.node_id.clone(), self_node);
78
79 self.membership_service.initialize().await.map_err(|e| {
81 HaError::ClusterError(format!("Failed to initialize membership service: {e}"))
82 })?;
83
84 *self.status.write().await = ClusterStatus::Initializing;
85
86 debug!("Cluster manager initialized with {} nodes", nodes.len());
87 Ok(())
88 }
89
90 pub async fn join_cluster(&mut self) -> Result<(), HaError> {
92 info!("Joining cluster {}", self.config.cluster.cluster_name);
93
94 self.membership_service
96 .join(self.node_id.clone())
97 .await
98 .map_err(|e| HaError::ClusterError(format!("Failed to join cluster: {e}")))?;
99
100 let mut nodes = self.nodes.write().await;
102 if let Some(node) = nodes.get_mut(&self.node_id) {
103 node.status = NodeStatus::Active;
104 }
105
106 let leader = self.current_leader.read().await;
108 if leader.is_none() {
109 drop(leader);
110 drop(nodes);
111 self.elect_leader().await?;
112 }
113
114 let mut status = self.status.write().await;
116 *status = ClusterStatus::Healthy;
117
118 info!(
119 "Successfully joined cluster {}",
120 self.config.cluster.cluster_name
121 );
122 Ok(())
123 }
124
125 pub async fn leave_cluster(&mut self) -> Result<(), HaError> {
127 info!("Leaving cluster {}", self.config.cluster.cluster_name);
128
129 self.membership_service
131 .leave(self.node_id.clone())
132 .await
133 .map_err(|e| HaError::ClusterError(format!("Failed to leave cluster: {e}")))?;
134
135 let mut nodes = self.nodes.write().await;
137 if let Some(node) = nodes.get_mut(&self.node_id) {
138 node.status = NodeStatus::Leaving;
139 }
140
141 let mut status = self.status.write().await;
143 *status = ClusterStatus::Down {
144 reason: "Node left the cluster".to_string(),
145 };
146
147 info!(
148 "Successfully left cluster {}",
149 self.config.cluster.cluster_name
150 );
151 Ok(())
152 }
153
154 pub async fn get_status(&self) -> Result<ClusterStatus, HaError> {
156 let status = self.status.read().await;
157 Ok(status.clone())
158 }
159
160 pub async fn update_config(&mut self, config: &HighAvailabilityConfig) -> Result<(), HaError> {
162 info!("Updating cluster configuration");
163 self.config = Arc::new(config.clone());
164
165 if self.discovery_service.needs_update(config) {
167 self.discovery_service = create_discovery_service(config);
168 }
169
170 if self.membership_service.needs_update(config) {
172 let old_membership = std::mem::replace(
173 &mut self.membership_service,
174 create_membership_service(config),
175 );
176 old_membership
177 .leave(self.node_id.clone())
178 .await
179 .map_err(|e| {
180 HaError::ClusterError(format!("Failed to leave old membership: {e}"))
181 })?;
182
183 self.membership_service.initialize().await.map_err(|e| {
184 HaError::ClusterError(format!("Failed to initialize new membership: {e}"))
185 })?;
186
187 self.membership_service
188 .join(self.node_id.clone())
189 .await
190 .map_err(|e| {
191 HaError::ClusterError(format!("Failed to join with new membership: {e}"))
192 })?;
193 }
194
195 Ok(())
196 }
197
198 async fn elect_leader(&mut self) -> Result<(), HaError> {
200 info!("Starting leader election");
201
202 let nodes = self.nodes.read().await;
204
205 let active_nodes: Vec<&NodeInfo> = nodes
207 .values()
208 .filter(|n| n.status == NodeStatus::Active)
209 .collect();
210
211 if active_nodes.is_empty() {
212 return Err(HaError::ClusterError(
213 "No active nodes for leader election".to_string(),
214 ));
215 }
216
217 let new_leader = active_nodes
219 .iter()
220 .min_by_key(|n| &n.id)
221 .map(|n| n.id.clone())
222 .ok_or_else(|| {
223 HaError::ClusterError("No active nodes available for leader election".to_string())
224 })?;
225
226 let mut leader = self.current_leader.write().await;
228 *leader = Some(new_leader.clone());
229
230 drop(leader);
232 drop(nodes);
233 let mut nodes = self.nodes.write().await;
234
235 for (id, node) in nodes.iter_mut() {
236 if node.status == NodeStatus::Active {
237 if *id == new_leader {
238 node.role = NodeRole::Leader;
239 } else {
240 node.role = NodeRole::Follower;
241 }
242 }
243 }
244
245 info!("Leader election completed, new leader: {}", new_leader);
246 Ok(())
247 }
248
249 pub async fn process_heartbeat(&mut self, node_id: &str) -> Result<(), HaError> {
251 let mut nodes = self.nodes.write().await;
252
253 if let Some(node) = nodes.get_mut(node_id) {
254 node.last_heartbeat = Some(chrono::Utc::now());
255 if node.status == NodeStatus::Unknown || node.status == NodeStatus::Suspected {
256 node.status = NodeStatus::Active;
257 }
258 } else {
259 let node_info = NodeInfo {
261 id: node_id.to_string(),
262 address: node_id.to_string(), status: NodeStatus::Active,
264 role: NodeRole::Follower,
265 last_heartbeat: Some(chrono::Utc::now()),
266 metadata: HashMap::new(),
267 };
268 nodes.insert(node_id.to_string(), node_info);
269 }
270
271 Ok(())
272 }
273
274 pub async fn check_node_health(&mut self) -> Result<(), HaError> {
276 debug!("Checking node health");
277
278 let now = chrono::Utc::now();
279 let node_timeout = chrono::Duration::from_std(self.config.cluster.node_timeout)
280 .map_err(|_| HaError::ClusterError("Invalid node timeout duration".to_string()))?;
281
282 let mut nodes = self.nodes.write().await;
283 let mut active_count = 0;
284 let mut total_count = 0;
285 let mut failures = Vec::new();
286
287 for (id, node) in nodes.iter_mut() {
288 if id == &self.node_id {
289 continue;
291 }
292
293 total_count += 1;
294
295 if let Some(last_heartbeat) = node.last_heartbeat {
296 let elapsed = now - last_heartbeat;
297
298 if elapsed > node_timeout {
299 if node.status == NodeStatus::Active {
301 warn!(
302 "Node {} has not sent heartbeat in {:?}, marking as suspected",
303 id, elapsed
304 );
305 node.status = NodeStatus::Suspected;
306 } else if node.status == NodeStatus::Suspected {
307 error!("Node {} has not recovered, marking as failed", id);
308 node.status = NodeStatus::Failed;
309 failures.push(id.clone());
310 }
311 } else if node.status == NodeStatus::Active {
312 active_count += 1;
313 }
314 }
315 }
316
317 drop(nodes);
319
320 let mut status = self.status.write().await;
321 if !failures.is_empty() {
322 if active_count < total_count && active_count > 0 {
324 *status = ClusterStatus::Degraded {
325 active_nodes: active_count,
326 total_nodes: total_count,
327 details: format!("Nodes failed: {}", failures.join(", ")),
328 };
329 } else if active_count == 0 {
330 *status = ClusterStatus::Down {
331 reason: "All nodes failed".to_string(),
332 };
333 }
334
335 let leader = self.current_leader.read().await;
337 if let Some(leader_id) = leader.as_ref() {
338 if failures.contains(leader_id) {
339 let leader_id_clone = leader_id.clone();
341 drop(leader);
342 drop(status);
343
344 let mut status = self.status.write().await;
346 *status = ClusterStatus::Failover {
347 primary_node: leader_id_clone.clone(),
348 failing_node: Some(leader_id_clone.clone()),
349 failover_phase: FailoverPhase::Detection,
350 };
351
352 info!(
355 "Leader node {} failed, cluster entering failover state",
356 leader_id_clone
357 );
358 }
359 }
360 } else if active_count == total_count {
361 *status = ClusterStatus::Healthy;
363 }
364
365 Ok(())
366 }
367
368 pub async fn get_nodes(&self) -> Result<Vec<NodeInfo>, HaError> {
370 let nodes = self.nodes.read().await;
371 Ok(nodes.values().cloned().collect())
372 }
373
374 pub async fn get_leader(&self) -> Result<Option<String>, HaError> {
376 let leader = self.current_leader.read().await;
377 Ok(leader.clone())
378 }
379
380 pub async fn is_leader(&self) -> Result<bool, HaError> {
382 let leader = self.current_leader.read().await;
383 Ok(leader.as_ref() == Some(&self.node_id))
384 }
385}
386
387fn create_discovery_service(
389 config: &HighAvailabilityConfig,
390) -> Box<dyn NodeDiscovery + Send + Sync> {
391 match config.cluster.discovery_method {
392 crate::infrastructure::high_availability::config::DiscoveryMethod::Static => {
393 Box::new(StaticDiscovery::new(config.cluster.static_nodes.clone()))
394 }
395 crate::infrastructure::high_availability::config::DiscoveryMethod::Dns => {
396 if let Some(ref dns_url) = config.cluster.dns_discovery_url {
397 Box::new(DnsDiscovery::new(dns_url.clone()))
398 } else {
399 Box::new(StaticDiscovery::new(config.cluster.static_nodes.clone()))
401 }
402 }
403 crate::infrastructure::high_availability::config::DiscoveryMethod::Kubernetes => {
404 if let Some(ref service_name) = config.cluster.k8s_service_name {
405 Box::new(KubernetesDiscovery::new(service_name.clone()))
406 } else {
407 Box::new(StaticDiscovery::new(config.cluster.static_nodes.clone()))
409 }
410 }
411 _ => {
412 Box::new(StaticDiscovery::new(config.cluster.static_nodes.clone()))
414 }
415 }
416}
417
418fn create_membership_service(
420 config: &HighAvailabilityConfig,
421) -> Box<dyn ClusterMembership + Send + Sync> {
422 Box::new(BasicMembership::new(config))
424}
425
426#[derive(Debug, Clone, Serialize, Deserialize)]
428pub struct NodeInfo {
429 pub id: String,
431
432 pub address: String,
434
435 pub status: NodeStatus,
437
438 pub role: NodeRole,
440
441 pub last_heartbeat: Option<chrono::DateTime<chrono::Utc>>,
443
444 pub metadata: HashMap<String, String>,
446}
447
448#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
450pub enum NodeStatus {
451 Unknown,
453
454 Starting,
456
457 Active,
459
460 Suspected,
462
463 Failed,
465
466 Leaving,
468}
469
470#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
472pub enum NodeRole {
473 Unknown,
475
476 Leader,
478
479 Follower,
481
482 Observer,
484}
485
486#[async_trait::async_trait]
488pub trait NodeDiscovery {
489 async fn discover_nodes(&self) -> Result<Vec<String>, String>;
491
492 fn needs_update(&self, config: &HighAvailabilityConfig) -> bool;
494}
495
496pub struct StaticDiscovery {
498 nodes: Vec<String>,
499}
500
501impl StaticDiscovery {
502 pub fn new(nodes: Vec<String>) -> Self {
503 Self { nodes }
504 }
505}
506
507#[async_trait::async_trait]
508impl NodeDiscovery for StaticDiscovery {
509 async fn discover_nodes(&self) -> Result<Vec<String>, String> {
510 Ok(self.nodes.clone())
511 }
512
513 fn needs_update(&self, config: &HighAvailabilityConfig) -> bool {
514 self.nodes != config.cluster.static_nodes
515 }
516}
517
518pub struct DnsDiscovery {
520 dns_url: String,
521}
522
523impl DnsDiscovery {
524 pub fn new(dns_url: String) -> Self {
525 Self { dns_url }
526 }
527}
528
529#[async_trait::async_trait]
530impl NodeDiscovery for DnsDiscovery {
531 async fn discover_nodes(&self) -> Result<Vec<String>, String> {
532 Ok(vec![
535 format!("node1.{}", self.dns_url),
536 format!("node2.{}", self.dns_url),
537 format!("node3.{}", self.dns_url),
538 ])
539 }
540
541 fn needs_update(&self, config: &HighAvailabilityConfig) -> bool {
542 if let Some(ref dns_url) = config.cluster.dns_discovery_url {
543 self.dns_url != *dns_url
544 } else {
545 true
546 }
547 }
548}
549
550pub struct KubernetesDiscovery {
552 service_name: String,
553}
554
555impl KubernetesDiscovery {
556 pub fn new(service_name: String) -> Self {
557 Self { service_name }
558 }
559}
560
561#[async_trait::async_trait]
562impl NodeDiscovery for KubernetesDiscovery {
563 async fn discover_nodes(&self) -> Result<Vec<String>, String> {
564 Ok(vec![
567 format!("{}-0.{}", self.service_name, self.service_name),
568 format!("{}-1.{}", self.service_name, self.service_name),
569 format!("{}-2.{}", self.service_name, self.service_name),
570 ])
571 }
572
573 fn needs_update(&self, config: &HighAvailabilityConfig) -> bool {
574 if let Some(ref service_name) = config.cluster.k8s_service_name {
575 self.service_name != *service_name
576 } else {
577 true
578 }
579 }
580}
581
582#[async_trait::async_trait]
584pub trait ClusterMembership {
585 async fn initialize(&mut self) -> Result<(), String>;
587
588 async fn join(&self, node_id: String) -> Result<(), String>;
590
591 async fn leave(&self, node_id: String) -> Result<(), String>;
593
594 fn get_local_address(&self) -> Result<String, String>;
596
597 fn needs_update(&self, _config: &HighAvailabilityConfig) -> bool {
599 false
600 }
601}
602
603pub struct BasicMembership {
605 config: HighAvailabilityConfig,
606 local_address: String,
607}
608
609impl BasicMembership {
610 pub fn new(config: &HighAvailabilityConfig) -> Self {
611 let local_address = "127.0.0.1:5001".to_string();
613
614 Self {
615 config: config.clone(),
616 local_address,
617 }
618 }
619}
620
621#[async_trait::async_trait]
622impl ClusterMembership for BasicMembership {
623 async fn initialize(&mut self) -> Result<(), String> {
624 Ok(())
626 }
627
628 async fn join(&self, node_id: String) -> Result<(), String> {
629 info!(
630 "Node {} joining cluster {}",
631 node_id, self.config.cluster.cluster_name
632 );
633 Ok(())
635 }
636
637 async fn leave(&self, node_id: String) -> Result<(), String> {
638 info!(
639 "Node {} leaving cluster {}",
640 node_id, self.config.cluster.cluster_name
641 );
642 Ok(())
644 }
645
646 fn get_local_address(&self) -> Result<String, String> {
647 Ok(self.local_address.clone())
648 }
649}