laminar_core/cluster/discovery/
mod.rs1#![allow(clippy::disallowed_types)] mod static_discovery;
6pub use static_discovery::{StaticDiscovery, StaticDiscoveryConfig};
7
8mod gossip_discovery;
9pub use gossip_discovery::{keys, GossipDiscovery, GossipDiscoveryConfig};
10
11use std::collections::HashMap;
12use std::fmt;
13
14use serde::{Deserialize, Serialize};
15use tokio::sync::watch;
16
17pub use crate::state::NodeId;
18
19pub(crate) fn publish_if_changed(tx: &watch::Sender<Vec<NodeInfo>>, mut peer_list: Vec<NodeInfo>) {
23 peer_list.sort_by_key(|n| n.id.0); tx.send_if_modified(|cur| {
25 fn same_member(a: &NodeInfo, b: &NodeInfo) -> bool {
27 let NodeInfo {
30 id,
31 name,
32 rpc_address,
33 state,
34 metadata,
35 last_heartbeat_ms: _,
36 } = a;
37 id == &b.id
38 && name == &b.name
39 && rpc_address == &b.rpc_address
40 && state == &b.state
41 && metadata == &b.metadata
42 }
43 let same = cur.len() == peer_list.len()
44 && cur.iter().zip(&peer_list).all(|(a, b)| same_member(a, b));
45 if same {
46 false
47 } else {
48 *cur = peer_list;
49 true
50 }
51 });
52}
53
54#[derive(
56 Debug,
57 Clone,
58 Copy,
59 PartialEq,
60 Eq,
61 Serialize,
62 Deserialize,
63 rkyv::Archive,
64 rkyv::Serialize,
65 rkyv::Deserialize,
66)]
67pub enum NodeState {
68 Joining,
70 Active,
72 Suspected,
74 Draining,
76 Left,
78}
79
80impl fmt::Display for NodeState {
81 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82 match self {
83 Self::Joining => write!(f, "joining"),
84 Self::Active => write!(f, "active"),
85 Self::Suspected => write!(f, "suspected"),
86 Self::Draining => write!(f, "draining"),
87 Self::Left => write!(f, "left"),
88 }
89 }
90}
91
92#[derive(
94 Debug,
95 Clone,
96 PartialEq,
97 Serialize,
98 Deserialize,
99 rkyv::Archive,
100 rkyv::Serialize,
101 rkyv::Deserialize,
102)]
103pub struct NodeMetadata {
104 pub cores: u32,
106 pub memory_bytes: u64,
108 pub failure_domain: Option<String>,
110 pub tags: HashMap<String, String>,
112 pub version: String,
114}
115
116impl Default for NodeMetadata {
117 fn default() -> Self {
118 Self {
119 cores: 1,
120 memory_bytes: 0,
121 failure_domain: None,
122 tags: HashMap::new(),
123 version: String::new(),
124 }
125 }
126}
127
128#[derive(
130 Debug,
131 Clone,
132 PartialEq,
133 Serialize,
134 Deserialize,
135 rkyv::Archive,
136 rkyv::Serialize,
137 rkyv::Deserialize,
138)]
139pub struct NodeInfo {
140 pub id: NodeId,
142 pub name: String,
144 pub rpc_address: String,
146 pub state: NodeState,
148 pub metadata: NodeMetadata,
150 pub last_heartbeat_ms: i64,
152}
153
154#[must_use]
158pub fn assignable_node_ids(members: &[NodeInfo]) -> Vec<NodeId> {
159 let mut ids: Vec<NodeId> = members
160 .iter()
161 .filter(|m| matches!(m.state, NodeState::Active))
162 .map(|m| m.id)
163 .filter(|id| !id.is_unassigned())
164 .collect();
165 ids.sort_unstable();
166 ids.dedup();
167 ids
168}
169
170#[derive(Debug, Clone)]
172pub enum MembershipEvent {
173 NodeJoined(Box<NodeInfo>),
175 NodeStateChanged {
177 node_id: NodeId,
179 old_state: NodeState,
181 new_state: NodeState,
183 },
184 NodeLeft(NodeId),
186}
187
188#[derive(Debug, thiserror::Error)]
190pub enum DiscoveryError {
191 #[error("bind error: {0}")]
193 Bind(String),
194
195 #[error("connection error to {address}: {reason}")]
197 Connection {
198 address: String,
200 reason: String,
202 },
203
204 #[error("serialization error: {0}")]
206 Serialization(String),
207
208 #[error("discovery not started")]
210 NotStarted,
211
212 #[error("discovery shut down")]
214 ShutDown,
215
216 #[error("I/O error: {0}")]
218 Io(#[from] std::io::Error),
219}
220
221#[allow(async_fn_in_trait)]
227pub trait Discovery: Send + Sync + 'static {
228 async fn start(&mut self) -> Result<(), DiscoveryError>;
232
233 async fn peers(&self) -> Result<Vec<NodeInfo>, DiscoveryError>;
235
236 async fn announce(&self, info: NodeInfo) -> Result<(), DiscoveryError>;
238
239 fn membership_watch(&self) -> watch::Receiver<Vec<NodeInfo>>;
244
245 async fn stop(&mut self) -> Result<(), DiscoveryError>;
247}
248
249#[cfg(test)]
250mod tests;