datum-cluster 0.10.1

Datum cluster membership over SWIM gossip
Documentation
use std::{
    net::{IpAddr, Ipv4Addr, SocketAddr},
    time::Duration,
};

/// Configuration for one Datum cluster member.
#[derive(Debug, Clone)]
pub struct ClusterConfig {
    /// Stable logical node id used by Datum control-plane APIs.
    pub node_id: String,
    /// Akka-style role labels advertised in membership gossip.
    pub roles: Vec<String>,
    /// Local UDP address used for membership gossip.
    pub bind_addr: SocketAddr,
    /// Address peers should use to contact this node. If the port is `0`, the
    /// bound UDP port is substituted at startup.
    pub advertise_addr: SocketAddr,
    /// DCP agent address peers should use for node-to-node control sessions.
    ///
    /// `ClusterAgent` fills this from the bound DCP listener when it starts the
    /// agent and membership together. Bare membership nodes may leave it empty.
    pub agent_addr: Option<SocketAddr>,
    /// Seed node UDP addresses. The seed identity is resolved by foca from the
    /// first announce reply, so only addresses are required here.
    pub seed_nodes: Vec<SocketAddr>,
    /// Foca probe period and periodic gossip cadence.
    pub gossip_interval: Duration,
    /// Direct probe round-trip timeout before foca asks indirect probes.
    pub probe_timeout: Duration,
    /// Foca suspect-to-down timeout. Keep this longer than the downing timeout
    /// when `TimeoutDowning` should make the public downing decision first.
    pub suspect_timeout: Duration,
    /// Default timeout used by [`crate::TimeoutDowning`].
    pub downing_timeout: Duration,
    /// How long foca remembers down identities before accepting the exact same
    /// identity again. New incarnations can rejoin before this expires.
    pub remove_down_after: Duration,
    /// Maximum UDP packet size foca will produce and consume.
    pub max_packet_size: usize,
    /// Bounded event-feed capacity for [`crate::MemberEvent`] subscribers.
    pub event_buffer: usize,
    /// Timeout for `leave()` and `abort()` oneshot replies.
    pub request_timeout: Duration,
}

impl ClusterConfig {
    /// Create a config with loopback defaults and no seed nodes.
    #[must_use]
    pub fn new(node_id: impl Into<String>) -> Self {
        Self {
            node_id: node_id.into(),
            ..Self::default()
        }
    }

    /// Return a copy using the supplied roles.
    #[must_use]
    pub fn with_roles(mut self, roles: impl IntoIterator<Item = impl Into<String>>) -> Self {
        self.roles = roles.into_iter().map(Into::into).collect();
        self
    }

    /// Return a copy using the supplied seed-node addresses.
    #[must_use]
    pub fn with_seed_nodes(mut self, seed_nodes: impl IntoIterator<Item = SocketAddr>) -> Self {
        self.seed_nodes = seed_nodes.into_iter().collect();
        self
    }

    pub(crate) fn normalized_roles(&self) -> Vec<String> {
        let mut roles = self
            .roles
            .iter()
            .map(|role| role.trim().to_owned())
            .filter(|role| !role.is_empty())
            .collect::<Vec<_>>();
        roles.sort();
        roles.dedup();
        roles
    }
}

impl Default for ClusterConfig {
    fn default() -> Self {
        let loopback = IpAddr::V4(Ipv4Addr::LOCALHOST);
        Self {
            node_id: format!("datum-node-{}", std::process::id()),
            roles: Vec::new(),
            bind_addr: SocketAddr::new(loopback, 0),
            advertise_addr: SocketAddr::new(loopback, 0),
            agent_addr: None,
            seed_nodes: Vec::new(),
            gossip_interval: Duration::from_millis(250),
            probe_timeout: Duration::from_millis(75),
            suspect_timeout: Duration::from_secs(2),
            downing_timeout: Duration::from_millis(500),
            remove_down_after: Duration::from_secs(60),
            max_packet_size: 1400,
            event_buffer: 256,
            request_timeout: Duration::from_secs(5),
        }
    }
}