Skip to main content

nodedb_cluster/bootstrap/
config.rs

1//! Cluster configuration and post-start state.
2
3use std::net::SocketAddr;
4use std::time::Duration;
5
6use crate::multi_raft::MultiRaft;
7use crate::routing::RoutingTable;
8use crate::topology::ClusterTopology;
9
10/// Tunable retry policy for the join loop.
11///
12/// The schedule is computed by halving from the configured ceiling:
13/// for `max_attempts = 8` and `max_backoff_secs = 32`, the per-attempt
14/// delays are `0.25 s, 0.5 s, 1 s, 2 s, 4 s, 8 s, 16 s, 32 s` — i.e.
15/// each delay is `max_backoff_secs >> (max_attempts - attempt)`. This
16/// keeps the formula obvious from a single number while preserving
17/// exponential growth.
18///
19/// Defaults match the production schedule. Tests construct their own
20/// policy with a much smaller `max_backoff_secs` so the integration
21/// suite doesn't pay a ~minute backoff on every join failure path.
22#[derive(Debug, Clone, Copy)]
23pub struct JoinRetryPolicy {
24    /// Number of join attempts before the loop gives up.
25    pub max_attempts: u32,
26    /// Cap on the per-attempt backoff delay, in seconds. The schedule
27    /// is derived from this ceiling — see the struct doc comment.
28    pub max_backoff_secs: u64,
29}
30
31impl Default for JoinRetryPolicy {
32    fn default() -> Self {
33        Self {
34            max_attempts: 8,
35            max_backoff_secs: 32,
36        }
37    }
38}
39
40impl JoinRetryPolicy {
41    /// Backoff delay before `attempt` (1-indexed). Attempt 0 is the
42    /// initial try and never sleeps. Returns `Duration::ZERO` for
43    /// out-of-range attempts.
44    pub fn backoff_for(&self, attempt: u32) -> Duration {
45        if attempt == 0 || attempt > self.max_attempts {
46            return Duration::ZERO;
47        }
48        // Schedule grows exponentially toward `max_backoff_secs`. We
49        // compute in millis so small `max_backoff_secs` values (test
50        // configs) still produce non-zero delays for the early
51        // attempts instead of being floored to zero seconds.
52        let exp = self.max_attempts - attempt;
53        let max_ms = self.max_backoff_secs.saturating_mul(1_000);
54        let ms = max_ms >> exp;
55        Duration::from_millis(ms.max(1))
56    }
57}
58
59/// Configuration for cluster formation.
60#[derive(Debug, Clone)]
61pub struct ClusterConfig {
62    /// This node's unique ID.
63    pub node_id: u64,
64    /// Address to listen on for Raft RPCs.
65    pub listen_addr: SocketAddr,
66    /// Seed node addresses for bootstrap/join.
67    pub seed_nodes: Vec<SocketAddr>,
68    /// Number of Raft groups to create on bootstrap.
69    pub num_groups: u64,
70    /// Replication factor (number of replicas per group).
71    pub replication_factor: usize,
72    /// Data directory for persistent Raft log storage.
73    pub data_dir: std::path::PathBuf,
74    /// Operator escape hatch: bypass the probe phase and bootstrap this
75    /// node unconditionally even if it is not the lexicographically
76    /// smallest seed.
77    ///
78    /// Set this only on disaster recovery when the designated
79    /// bootstrapper is permanently unreachable. Requires `listen_addr`
80    /// to be present in `seed_nodes` (enforced at the caller's config
81    /// validation layer).
82    pub force_bootstrap: bool,
83    /// Retry policy for the join loop. Defaults to production values
84    /// (`8` attempts, `32 s` ceiling). Tests override this with a
85    /// faster policy.
86    pub join_retry: JoinRetryPolicy,
87    /// Optional UDP bind address for the SWIM failure detector. `None`
88    /// disables SWIM entirely — cluster startup then relies solely on
89    /// the existing raft transport for membership observations. When
90    /// `Some`, the operator is expected to spawn SWIM separately via
91    /// [`crate::spawn_swim`] after the cluster is up and feed the
92    /// seed list from `seed_nodes`.
93    pub swim_udp_addr: Option<SocketAddr>,
94    /// Raft election timeout range. Controls how long a follower waits
95    /// before starting an election after losing contact with the leader.
96    pub election_timeout_min: Duration,
97    pub election_timeout_max: Duration,
98}
99
100/// Result of cluster startup — everything needed to run the Raft loop.
101pub struct ClusterState {
102    pub topology: ClusterTopology,
103    pub routing: RoutingTable,
104    pub multi_raft: MultiRaft,
105}