- Remove gitlinks (160000 mode) for chainfire, flaredb, iam - Add workspace contents as regular tracked files - Update flake.nix to use simple paths instead of builtins.fetchGit This resolves the nix build failure where submodule directories appeared empty in the nix store. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
141 lines
3.9 KiB
Rust
141 lines
3.9 KiB
Rust
//! Membership state management
|
|
|
|
use crate::identity::GossipId;
|
|
use chainfire_types::NodeId;
|
|
use dashmap::DashMap;
|
|
use std::net::SocketAddr;
|
|
use tracing::debug;
|
|
|
|
/// Membership change event
|
|
#[derive(Debug, Clone)]
|
|
pub enum MembershipChange {
|
|
/// A member joined or became reachable
|
|
MemberUp(GossipId),
|
|
/// A member left or became unreachable
|
|
MemberDown(GossipId),
|
|
}
|
|
|
|
/// Manages cluster membership state
|
|
pub struct MembershipState {
|
|
/// Known members
|
|
members: DashMap<NodeId, GossipId>,
|
|
}
|
|
|
|
impl MembershipState {
|
|
/// Create a new membership state
|
|
pub fn new() -> Self {
|
|
Self {
|
|
members: DashMap::new(),
|
|
}
|
|
}
|
|
|
|
/// Handle a membership change
|
|
pub fn handle_change(&self, change: MembershipChange) {
|
|
match change {
|
|
MembershipChange::MemberUp(id) => {
|
|
debug!(node_id = id.node_id, addr = %id.addr, "Adding member");
|
|
self.members.insert(id.node_id, id);
|
|
}
|
|
MembershipChange::MemberDown(id) => {
|
|
debug!(node_id = id.node_id, "Removing member");
|
|
self.members.remove(&id.node_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Get a member by node ID
|
|
pub fn get(&self, node_id: NodeId) -> Option<GossipId> {
|
|
self.members.get(&node_id).map(|r| r.clone())
|
|
}
|
|
|
|
/// Get all members
|
|
pub fn all(&self) -> Vec<GossipId> {
|
|
self.members.iter().map(|r| r.clone()).collect()
|
|
}
|
|
|
|
/// Get member count
|
|
pub fn count(&self) -> usize {
|
|
self.members.len()
|
|
}
|
|
|
|
/// Check if a node is a member
|
|
pub fn contains(&self, node_id: NodeId) -> bool {
|
|
self.members.contains_key(&node_id)
|
|
}
|
|
|
|
/// Get all member addresses
|
|
pub fn addresses(&self) -> Vec<SocketAddr> {
|
|
self.members.iter().map(|r| r.addr).collect()
|
|
}
|
|
|
|
/// Get all control plane members
|
|
pub fn control_plane_members(&self) -> Vec<GossipId> {
|
|
self.members
|
|
.iter()
|
|
.filter(|r| r.is_control_plane())
|
|
.map(|r| r.clone())
|
|
.collect()
|
|
}
|
|
|
|
/// Get all worker members
|
|
pub fn worker_members(&self) -> Vec<GossipId> {
|
|
self.members
|
|
.iter()
|
|
.filter(|r| r.is_worker())
|
|
.map(|r| r.clone())
|
|
.collect()
|
|
}
|
|
}
|
|
|
|
impl Default for MembershipState {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use chainfire_types::node::NodeRole;
|
|
|
|
fn create_id(node_id: NodeId, role: NodeRole) -> GossipId {
|
|
GossipId::new(
|
|
node_id,
|
|
format!("127.0.0.1:{}", 5000 + node_id).parse().unwrap(),
|
|
role,
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_membership_changes() {
|
|
let state = MembershipState::new();
|
|
|
|
let id1 = create_id(1, NodeRole::ControlPlane);
|
|
let id2 = create_id(2, NodeRole::Worker);
|
|
|
|
state.handle_change(MembershipChange::MemberUp(id1.clone()));
|
|
state.handle_change(MembershipChange::MemberUp(id2.clone()));
|
|
|
|
assert_eq!(state.count(), 2);
|
|
assert!(state.contains(1));
|
|
assert!(state.contains(2));
|
|
|
|
state.handle_change(MembershipChange::MemberDown(id1));
|
|
assert_eq!(state.count(), 1);
|
|
assert!(!state.contains(1));
|
|
}
|
|
|
|
#[test]
|
|
fn test_role_filtering() {
|
|
let state = MembershipState::new();
|
|
|
|
state.handle_change(MembershipChange::MemberUp(create_id(1, NodeRole::ControlPlane)));
|
|
state.handle_change(MembershipChange::MemberUp(create_id(2, NodeRole::ControlPlane)));
|
|
state.handle_change(MembershipChange::MemberUp(create_id(3, NodeRole::Worker)));
|
|
state.handle_change(MembershipChange::MemberUp(create_id(4, NodeRole::Worker)));
|
|
state.handle_change(MembershipChange::MemberUp(create_id(5, NodeRole::Worker)));
|
|
|
|
assert_eq!(state.control_plane_members().len(), 2);
|
|
assert_eq!(state.worker_members().len(), 3);
|
|
}
|
|
}
|