Skip to main content

pollen_types/
cluster.rs

1//! Cluster-related types.
2
3use crate::NodeId;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::net::SocketAddr;
7
8/// Information about a cluster member.
9#[derive(Clone, Debug, Serialize, Deserialize)]
10pub struct Member {
11    /// Unique node identifier.
12    pub id: NodeId,
13    /// Network address.
14    pub addr: SocketAddr,
15    /// Current state.
16    pub state: MemberState,
17    /// Custom metadata.
18    pub metadata: HashMap<String, String>,
19    /// Last time this member was seen alive.
20    pub last_seen: chrono::DateTime<chrono::Utc>,
21}
22
23impl Member {
24    /// Create a new member.
25    pub fn new(id: NodeId, addr: SocketAddr) -> Self {
26        Self {
27            id,
28            addr,
29            state: MemberState::Alive,
30            metadata: HashMap::new(),
31            last_seen: chrono::Utc::now(),
32        }
33    }
34
35    /// Check if this member is alive.
36    pub fn is_alive(&self) -> bool {
37        self.state == MemberState::Alive
38    }
39}
40
41/// State of a cluster member.
42#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
43pub enum MemberState {
44    /// Node is responding normally.
45    Alive,
46    /// Node may be failing (intermediate state).
47    Suspect,
48    /// Node is considered dead.
49    Dead,
50    /// Node has left the cluster gracefully.
51    Left,
52}
53
54impl MemberState {
55    /// Convert to string representation.
56    pub fn as_str(&self) -> &'static str {
57        match self {
58            MemberState::Alive => "alive",
59            MemberState::Suspect => "suspect",
60            MemberState::Dead => "dead",
61            MemberState::Left => "left",
62        }
63    }
64}
65
66/// Event indicating a change in cluster membership.
67#[derive(Clone, Debug)]
68pub enum MembershipEvent {
69    /// A new node joined the cluster.
70    Joined(Member),
71    /// A node left the cluster.
72    Left(NodeId),
73    /// A node's state changed.
74    StateChanged {
75        id: NodeId,
76        old: MemberState,
77        new: MemberState,
78    },
79    /// A node's metadata changed.
80    MetadataChanged {
81        id: NodeId,
82        key: String,
83        value: Option<String>,
84    },
85}
86
87/// Current state of the cluster.
88#[derive(Clone, Debug)]
89pub struct ClusterState {
90    /// Local node information.
91    pub local: Member,
92    /// All cluster members.
93    pub members: Vec<Member>,
94    /// Number of alive members.
95    pub alive_count: usize,
96}
97
98impl ClusterState {
99    /// Get a member by ID.
100    pub fn get_member(&self, id: &NodeId) -> Option<&Member> {
101        self.members.iter().find(|m| &m.id == id)
102    }
103
104    /// Get all alive members.
105    pub fn alive_members(&self) -> impl Iterator<Item = &Member> {
106        self.members.iter().filter(|m| m.is_alive())
107    }
108}
109
110/// Configuration for cluster networking.
111#[derive(Clone, Debug)]
112pub struct ClusterConfig {
113    /// Address to bind for cluster communication.
114    pub bind_addr: SocketAddr,
115    /// Seed nodes for initial discovery.
116    pub seeds: Vec<SocketAddr>,
117    /// Cluster name (for isolation).
118    pub cluster_name: String,
119    /// Gossip interval.
120    pub gossip_interval: std::time::Duration,
121    /// Failure detection timeout.
122    pub failure_timeout: std::time::Duration,
123    /// Node metadata to propagate via gossip.
124    pub metadata: std::collections::HashMap<String, String>,
125}
126
127impl Default for ClusterConfig {
128    fn default() -> Self {
129        Self {
130            bind_addr: "0.0.0.0:7000".parse().unwrap(),
131            seeds: Vec::new(),
132            cluster_name: "pollen".to_string(),
133            gossip_interval: std::time::Duration::from_millis(200),
134            failure_timeout: std::time::Duration::from_secs(5),
135            metadata: std::collections::HashMap::new(),
136        }
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    #[test]
145    fn test_member_is_alive() {
146        let mut member = Member::new(NodeId::new(), "127.0.0.1:7000".parse().unwrap());
147        assert!(member.is_alive());
148
149        member.state = MemberState::Dead;
150        assert!(!member.is_alive());
151    }
152
153    #[test]
154    fn test_cluster_config_default() {
155        let config = ClusterConfig::default();
156        assert_eq!(config.cluster_name, "pollen");
157        assert!(config.seeds.is_empty());
158    }
159}