redis_enterprise/
bootstrap.rs

1//! Cluster bootstrap and node joining operations
2//!
3//! ## Overview
4//! - Bootstrap new clusters
5//! - Join nodes to existing clusters
6//! - Configure initial settings
7
8use crate::client::RestClient;
9use crate::error::Result;
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12
13/// Bootstrap configuration for cluster initialization
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct BootstrapConfig {
16    /// Action to perform (e.g., 'create', 'join', 'recover_cluster')
17    pub action: String,
18    #[serde(skip_serializing_if = "Option::is_none")]
19    /// Cluster configuration for initialization
20    pub cluster: Option<ClusterBootstrap>,
21    #[serde(skip_serializing_if = "Option::is_none")]
22    /// Node configuration for bootstrap
23    pub node: Option<NodeBootstrap>,
24    #[serde(skip_serializing_if = "Option::is_none")]
25    /// Admin credentials for cluster access
26    pub credentials: Option<CredentialsBootstrap>,
27
28    #[serde(flatten)]
29    pub extra: Value,
30}
31
32/// Cluster bootstrap configuration
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct ClusterBootstrap {
35    /// Cluster name for identification
36    pub name: String,
37    #[serde(skip_serializing_if = "Option::is_none")]
38    /// DNS suffixes for cluster FQDN resolution
39    pub dns_suffixes: Option<Vec<String>>,
40    #[serde(skip_serializing_if = "Option::is_none")]
41    /// Enable rack-aware placement for high availability
42    pub rack_aware: Option<bool>,
43}
44
45/// Node bootstrap configuration
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct NodeBootstrap {
48    #[serde(skip_serializing_if = "Option::is_none")]
49    /// Storage paths configuration for the node
50    pub paths: Option<NodePaths>,
51}
52
53/// Node paths configuration
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct NodePaths {
56    #[serde(skip_serializing_if = "Option::is_none")]
57    /// Path for persistent storage (databases, configuration, logs)
58    pub persistent_path: Option<String>,
59    #[serde(skip_serializing_if = "Option::is_none")]
60    /// Path for ephemeral storage (temporary files, caches)
61    pub ephemeral_path: Option<String>,
62}
63
64/// Credentials bootstrap configuration
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct CredentialsBootstrap {
67    /// Admin username for cluster management
68    pub username: String,
69    /// Admin password for authentication
70    pub password: String,
71}
72
73/// Bootstrap status response
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct BootstrapStatus {
76    /// Current status of the bootstrap operation
77    pub status: String,
78    #[serde(skip_serializing_if = "Option::is_none")]
79    /// Progress percentage (0.0-100.0) of the bootstrap operation
80    pub progress: Option<f32>,
81    #[serde(skip_serializing_if = "Option::is_none")]
82    /// Status message or error description
83    pub message: Option<String>,
84
85    #[serde(flatten)]
86    pub extra: Value,
87}
88
89/// Bootstrap handler for cluster initialization
90pub struct BootstrapHandler {
91    client: RestClient,
92}
93
94impl BootstrapHandler {
95    pub fn new(client: RestClient) -> Self {
96        BootstrapHandler { client }
97    }
98
99    /// Initialize cluster bootstrap
100    pub async fn create(&self, config: BootstrapConfig) -> Result<BootstrapStatus> {
101        self.client.post("/v1/bootstrap", &config).await
102    }
103
104    /// Get bootstrap status
105    pub async fn status(&self) -> Result<BootstrapStatus> {
106        self.client.get("/v1/bootstrap").await
107    }
108
109    /// Join node to existing cluster
110    pub async fn join(&self, config: BootstrapConfig) -> Result<BootstrapStatus> {
111        self.client.post("/v1/bootstrap/join", &config).await
112    }
113
114    /// Reset bootstrap (dangerous operation)
115    pub async fn reset(&self) -> Result<()> {
116        self.client.delete("/v1/bootstrap").await
117    }
118
119    /// Validate bootstrap for a specific UID - POST /v1/bootstrap/validate/{uid}
120    pub async fn validate_for(&self, uid: u32, body: Value) -> Result<Value> {
121        self.client
122            .post(&format!("/v1/bootstrap/validate/{}", uid), &body)
123            .await
124    }
125
126    /// Post a specific bootstrap action - POST /v1/bootstrap/{action}
127    pub async fn post_action(&self, action: &str, body: Value) -> Result<Value> {
128        self.client
129            .post(&format!("/v1/bootstrap/{}", action), &body)
130            .await
131    }
132}