pollen-types 0.1.0

Shared types for Pollen distributed task scheduler
Documentation
//! Cluster-related types.

use crate::NodeId;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::net::SocketAddr;

/// Information about a cluster member.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Member {
    /// Unique node identifier.
    pub id: NodeId,
    /// Network address.
    pub addr: SocketAddr,
    /// Current state.
    pub state: MemberState,
    /// Custom metadata.
    pub metadata: HashMap<String, String>,
    /// Last time this member was seen alive.
    pub last_seen: chrono::DateTime<chrono::Utc>,
}

impl Member {
    /// Create a new member.
    pub fn new(id: NodeId, addr: SocketAddr) -> Self {
        Self {
            id,
            addr,
            state: MemberState::Alive,
            metadata: HashMap::new(),
            last_seen: chrono::Utc::now(),
        }
    }

    /// Check if this member is alive.
    pub fn is_alive(&self) -> bool {
        self.state == MemberState::Alive
    }
}

/// State of a cluster member.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum MemberState {
    /// Node is responding normally.
    Alive,
    /// Node may be failing (intermediate state).
    Suspect,
    /// Node is considered dead.
    Dead,
    /// Node has left the cluster gracefully.
    Left,
}

impl MemberState {
    /// Convert to string representation.
    pub fn as_str(&self) -> &'static str {
        match self {
            MemberState::Alive => "alive",
            MemberState::Suspect => "suspect",
            MemberState::Dead => "dead",
            MemberState::Left => "left",
        }
    }
}

/// Event indicating a change in cluster membership.
#[derive(Clone, Debug)]
pub enum MembershipEvent {
    /// A new node joined the cluster.
    Joined(Member),
    /// A node left the cluster.
    Left(NodeId),
    /// A node's state changed.
    StateChanged {
        id: NodeId,
        old: MemberState,
        new: MemberState,
    },
    /// A node's metadata changed.
    MetadataChanged {
        id: NodeId,
        key: String,
        value: Option<String>,
    },
}

/// Current state of the cluster.
#[derive(Clone, Debug)]
pub struct ClusterState {
    /// Local node information.
    pub local: Member,
    /// All cluster members.
    pub members: Vec<Member>,
    /// Number of alive members.
    pub alive_count: usize,
}

impl ClusterState {
    /// Get a member by ID.
    pub fn get_member(&self, id: &NodeId) -> Option<&Member> {
        self.members.iter().find(|m| &m.id == id)
    }

    /// Get all alive members.
    pub fn alive_members(&self) -> impl Iterator<Item = &Member> {
        self.members.iter().filter(|m| m.is_alive())
    }
}

/// Configuration for cluster networking.
#[derive(Clone, Debug)]
pub struct ClusterConfig {
    /// Address to bind for cluster communication.
    pub bind_addr: SocketAddr,
    /// Seed nodes for initial discovery.
    pub seeds: Vec<SocketAddr>,
    /// Cluster name (for isolation).
    pub cluster_name: String,
    /// Gossip interval.
    pub gossip_interval: std::time::Duration,
    /// Failure detection timeout.
    pub failure_timeout: std::time::Duration,
    /// Node metadata to propagate via gossip.
    pub metadata: std::collections::HashMap<String, String>,
}

impl Default for ClusterConfig {
    fn default() -> Self {
        Self {
            bind_addr: "0.0.0.0:7000".parse().unwrap(),
            seeds: Vec::new(),
            cluster_name: "pollen".to_string(),
            gossip_interval: std::time::Duration::from_millis(200),
            failure_timeout: std::time::Duration::from_secs(5),
            metadata: std::collections::HashMap::new(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_member_is_alive() {
        let mut member = Member::new(NodeId::new(), "127.0.0.1:7000".parse().unwrap());
        assert!(member.is_alive());

        member.state = MemberState::Dead;
        assert!(!member.is_alive());
    }

    #[test]
    fn test_cluster_config_default() {
        let config = ClusterConfig::default();
        assert_eq!(config.cluster_name, "pollen");
        assert!(config.seeds.is_empty());
    }
}