Skip to main content

nodedb_cluster/bootstrap/
start.rs

1//! Cluster startup entry point: dispatches to bootstrap, join, or restart.
2//!
3//! The decision tree is deliberately small and delegates every
4//! non-trivial choice to a dedicated module:
5//!
6//! - **restart** (`super::restart`) — if the catalog already reports
7//!   this node as bootstrapped, we always take the restart path,
8//!   regardless of `seed_nodes` or `force_bootstrap`. The catalog is
9//!   the authoritative source of truth once it exists.
10//! - **bootstrap** (`super::bootstrap_fn`) — taken when this node is
11//!   the elected bootstrapper (lowest-addr seed), or when the operator
12//!   forced it via `ClusterConfig::force_bootstrap`, or when no other
13//!   seed is running. See [`super::probe::should_bootstrap`].
14//! - **join** (`super::join`) — everything else. The join path owns
15//!   its own retry-with-backoff loop and leader-redirect handling, so
16//!   this dispatcher does not need to retry at this layer.
17
18use crate::catalog::ClusterCatalog;
19use crate::error::Result;
20use crate::lifecycle_state::ClusterLifecycleTracker;
21use crate::transport::NexarTransport;
22
23use super::bootstrap_fn::bootstrap;
24use super::config::{ClusterConfig, ClusterState};
25use super::join::join;
26use super::probe::should_bootstrap;
27use super::restart::restart;
28
29/// Start the cluster — bootstrap, join, or restart depending on state.
30///
31/// Returns the initialized cluster state ready for the Raft loop.
32///
33/// `lifecycle` is the caller-owned phase tracker. This function
34/// transitions it to `Restarting` / `Bootstrapping` / `Joining` as
35/// the dispatcher picks a branch, and to `Failed` on terminal error.
36/// The caller is responsible for the final `Ready` transition once
37/// listeners are up — see `nodedb::control::cluster::start_raft`.
38pub async fn start_cluster(
39    config: &ClusterConfig,
40    catalog: &ClusterCatalog,
41    transport: &NexarTransport,
42    lifecycle: &ClusterLifecycleTracker,
43) -> Result<ClusterState> {
44    // Authoritative catalog state wins — a previously bootstrapped
45    // node always takes the restart path on boot.
46    if catalog.is_bootstrapped()? {
47        lifecycle.to_restarting();
48        return restart(config, catalog, transport).inspect_err(|e| {
49            lifecycle.to_failed(format!("restart failed: {e}"));
50        });
51    }
52
53    // No existing state — decide bootstrap vs join.
54    let is_seed = config.seed_nodes.contains(&config.listen_addr);
55
56    if is_seed && should_bootstrap(config, transport).await {
57        lifecycle.to_bootstrapping();
58        bootstrap(config, catalog).inspect_err(|e| {
59            lifecycle.to_failed(format!("bootstrap failed: {e}"));
60        })
61    } else {
62        join(config, catalog, transport, lifecycle).await
63    }
64}