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}