Skip to main content

redis_enterprise/
nodes.rs

1//! Nodes management for Redis Enterprise
2//!
3//! ## Overview
4//! - List and query resources
5//! - Create and update configurations
6//! - Monitor status and metrics
7
8use crate::client::RestClient;
9use crate::error::Result;
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12use std::collections::BTreeMap;
13use typed_builder::TypedBuilder;
14
15/// Response from node action operations
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct NodeActionResponse {
18    /// The action UID for tracking async operations
19    pub action_uid: String,
20    /// Description of the action
21    pub description: Option<String>,
22}
23
24/// Node information
25#[derive(Debug, Clone, Serialize, Deserialize)]
26#[non_exhaustive]
27pub struct Node {
28    /// Cluster unique ID of node (read-only)
29    pub uid: u32,
30
31    /// Internal IP address of node
32    #[serde(rename = "addr")]
33    pub addr: Option<String>,
34
35    /// Node status (read-only)
36    pub status: String,
37
38    /// Node accepts new shards if true
39    pub accept_servers: Option<bool>,
40
41    /// Hardware architecture (read-only)
42    pub architecture: Option<String>,
43
44    /// Total number of CPU cores (read-only)
45    #[serde(rename = "cores")]
46    pub cores: Option<u32>,
47
48    /// External IP addresses of node
49    pub external_addr: Option<Vec<String>>,
50
51    /// Total memory in bytes
52    pub total_memory: Option<u64>,
53
54    /// Installed OS version (read-only)
55    pub os_version: Option<String>,
56    /// Operating system name (read-only)
57    pub os_name: Option<String>,
58    /// Operating system family (read-only)
59    pub os_family: Option<String>,
60    /// Full version number (read-only)
61    pub os_semantic_version: Option<String>,
62
63    /// Ephemeral storage size in bytes (read-only)
64    pub ephemeral_storage_size: Option<f64>,
65    /// Persistent storage size in bytes (read-only)
66    pub persistent_storage_size: Option<f64>,
67
68    /// Ephemeral storage path (read-only)
69    pub ephemeral_storage_path: Option<String>,
70    /// Persistent storage path (read-only)
71    pub persistent_storage_path: Option<String>,
72    /// Flash storage path (read-only)
73    pub bigredis_storage_path: Option<String>,
74
75    /// Rack ID where node is installed
76    pub rack_id: Option<String>,
77    /// Second rack ID where node is installed
78    pub second_rack_id: Option<String>,
79
80    /// Number of shards on the node (read-only)
81    pub shard_count: Option<u32>,
82    /// Cluster unique IDs of all node shards
83    pub shard_list: Option<Vec<u32>>,
84    /// RAM shard count
85    pub ram_shard_count: Option<u32>,
86    /// Flash shard count
87    pub flash_shard_count: Option<u32>,
88
89    /// Flash storage enabled for Auto Tiering databases
90    pub bigstore_enabled: Option<bool>,
91    /// FIPS mode enabled
92    pub fips_enabled: Option<bool>,
93    /// Use internal IPv6
94    pub use_internal_ipv6: Option<bool>,
95
96    /// Maximum number of listeners on the node
97    pub max_listeners: Option<u32>,
98    /// Maximum number of shards on the node
99    pub max_redis_servers: Option<u32>,
100    /// Maximum background processes forked from shards
101    pub max_redis_forks: Option<i32>,
102    /// Maximum simultaneous replica full syncs
103    pub max_slave_full_syncs: Option<i32>,
104
105    /// Node uptime in seconds
106    pub uptime: Option<u64>,
107    /// Installed Redis Enterprise cluster software version (read-only)
108    pub software_version: Option<String>,
109
110    /// Supported database versions
111    pub supported_database_versions: Option<Vec<Value>>,
112
113    /// Bigstore driver name (deprecated)
114    pub bigstore_driver: Option<String>,
115
116    /// Storage size of bigstore storage (read-only)
117    pub bigstore_size: Option<u64>,
118
119    /// Public IP address (deprecated)
120    pub public_addr: Option<String>,
121
122    /// Recovery files path
123    pub recovery_path: Option<String>,
124
125    /// Per-worker ingress throttling limit in operations per second.
126    ///
127    /// Observed on Redis Software 8.2 nodes.
128    pub node_guardrails_ingress_throttling_worker_limit_ops_per_sec: Option<i64>,
129
130    /// Additive or version-specific node fields.
131    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
132    pub additional_fields: BTreeMap<String, Value>,
133}
134
135/// Node stats
136#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct NodeStats {
138    /// Unique identifier (read-only).
139    pub uid: u32,
140    /// User-mode CPU time fraction.
141    pub cpu_user: Option<f64>,
142    /// System-mode CPU time fraction.
143    pub cpu_system: Option<f64>,
144    /// Idle CPU time fraction.
145    pub cpu_idle: Option<f64>,
146    /// Free memory in bytes.
147    pub free_memory: Option<u64>,
148    /// Incoming network bytes.
149    pub network_bytes_in: Option<u64>,
150    /// Outgoing network bytes.
151    pub network_bytes_out: Option<u64>,
152    /// Free persistent storage in bytes.
153    pub persistent_storage_free: Option<u64>,
154    /// Free ephemeral storage in bytes.
155    pub ephemeral_storage_free: Option<u64>,
156}
157
158/// Node action request
159#[derive(Debug, Serialize, TypedBuilder)]
160pub struct NodeActionRequest {
161    /// Action name to execute.
162    #[builder(setter(into))]
163    pub action: String,
164    /// Node UID this entity belongs to.
165    #[serde(skip_serializing_if = "Option::is_none")]
166    #[builder(default, setter(strip_option))]
167    pub node_uid: Option<u32>,
168}
169
170/// Node handler for executing node commands
171pub struct NodeHandler {
172    client: RestClient,
173}
174
175/// Alias for backwards compatibility and intuitive plural naming
176pub type NodesHandler = NodeHandler;
177
178impl NodeHandler {
179    /// Create a new handler bound to the given REST client.
180    pub fn new(client: RestClient) -> Self {
181        NodeHandler { client }
182    }
183
184    /// List all nodes
185    pub async fn list(&self) -> Result<Vec<Node>> {
186        self.client.get("/v1/nodes").await
187    }
188
189    /// Get specific node info
190    pub async fn get(&self, uid: u32) -> Result<Node> {
191        self.client.get(&format!("/v1/nodes/{}", uid)).await
192    }
193
194    /// Update node configuration
195    pub async fn update(&self, uid: u32, updates: Value) -> Result<Node> {
196        self.client
197            .put(&format!("/v1/nodes/{}", uid), &updates)
198            .await
199    }
200
201    /// Remove a node from the cluster through the documented node action.
202    pub async fn remove(&self, uid: u32) -> Result<()> {
203        self.client
204            .post_action(&format!("/v1/nodes/{}/actions/remove", uid), &Value::Null)
205            .await
206    }
207
208    /// Run node health checks - GET /v1/nodes/check/{uid}
209    pub async fn check(&self, uid: u32) -> Result<Value> {
210        self.client.get(&format!("/v1/nodes/check/{}", uid)).await
211    }
212
213    /// Get node stats
214    pub async fn stats(&self, uid: u32) -> Result<NodeStats> {
215        self.client.get(&format!("/v1/nodes/stats/{}", uid)).await
216    }
217
218    /// Get node actions
219    pub async fn actions(&self, uid: u32) -> Result<Value> {
220        self.client.get(&format!("/v1/nodes/{}/actions", uid)).await
221    }
222
223    /// Execute a named node action (e.g. `"maintenance_on"`, `"maintenance_off"`).
224    ///
225    /// `POST /v1/nodes/{uid}/actions/{action}`. The previous implementation
226    /// POSTed to `/v1/nodes/{uid}/actions` with the action name in the body
227    /// — that endpoint is the GET-list URL, and the action name was
228    /// effectively dropped on the wire, so this method did not work
229    /// against real clusters.
230    ///
231    /// Callers that need to send a custom JSON body (e.g. action-specific
232    /// parameters) should use [`Self::action_execute`] directly.
233    pub async fn execute_action(&self, uid: u32, action: &str) -> Result<NodeActionResponse> {
234        let response: Value = self
235            .client
236            .post(
237                &format!("/v1/nodes/{}/actions/{}", uid, action),
238                &serde_json::json!({}),
239            )
240            .await?;
241        serde_json::from_value(response).map_err(Into::into)
242    }
243
244    // raw variant removed in favor of typed execute_action
245
246    /// List all available node actions (global) - GET /v1/nodes/actions
247    pub async fn list_actions(&self) -> Result<Value> {
248        self.client.get("/v1/nodes/actions").await
249    }
250
251    /// Get node action detail - GET /v1/nodes/{uid}/actions/{action}
252    pub async fn action_detail(&self, uid: u32, action: &str) -> Result<Value> {
253        self.client
254            .get(&format!("/v1/nodes/{}/actions/{}", uid, action))
255            .await
256    }
257
258    /// Execute named node action - POST /v1/nodes/{uid}/actions/{action}
259    pub async fn action_execute(&self, uid: u32, action: &str, body: Value) -> Result<Value> {
260        self.client
261            .post(&format!("/v1/nodes/{}/actions/{}", uid, action), &body)
262            .await
263    }
264
265    /// Delete node action - DELETE /v1/nodes/{uid}/actions/{action}
266    pub async fn action_delete(&self, uid: u32, action: &str) -> Result<()> {
267        self.client
268            .delete(&format!("/v1/nodes/{}/actions/{}", uid, action))
269            .await
270    }
271
272    /// List snapshots for a node - GET /v1/nodes/{uid}/snapshots
273    pub async fn snapshots(&self, uid: u32) -> Result<Value> {
274        self.client
275            .get(&format!("/v1/nodes/{}/snapshots", uid))
276            .await
277    }
278
279    /// Create a snapshot - POST /v1/nodes/{uid}/snapshots/{name}
280    pub async fn snapshot_create(&self, uid: u32, name: &str) -> Result<Value> {
281        self.client
282            .post(
283                &format!("/v1/nodes/{}/snapshots/{}", uid, name),
284                &serde_json::json!({}),
285            )
286            .await
287    }
288
289    /// Delete a snapshot - DELETE /v1/nodes/{uid}/snapshots/{name}
290    pub async fn snapshot_delete(&self, uid: u32, name: &str) -> Result<()> {
291        self.client
292            .delete(&format!("/v1/nodes/{}/snapshots/{}", uid, name))
293            .await
294    }
295
296    /// All nodes status - GET /v1/nodes/status
297    pub async fn status_all(&self) -> Result<Value> {
298        self.client.get("/v1/nodes/status").await
299    }
300
301    /// Watchdog status for all nodes - GET /v1/nodes/wd_status
302    pub async fn wd_status_all(&self) -> Result<Value> {
303        self.client.get("/v1/nodes/wd_status").await
304    }
305
306    /// Node status - GET /v1/nodes/{uid}/status
307    pub async fn status(&self, uid: u32) -> Result<Value> {
308        self.client.get(&format!("/v1/nodes/{}/status", uid)).await
309    }
310
311    /// Node watchdog status - GET /v1/nodes/{uid}/wd_status
312    pub async fn wd_status(&self, uid: u32) -> Result<Value> {
313        self.client
314            .get(&format!("/v1/nodes/{}/wd_status", uid))
315            .await
316    }
317
318    /// All node alerts - GET /v1/nodes/alerts
319    pub async fn alerts_all(&self) -> Result<Value> {
320        self.client.get("/v1/nodes/alerts").await
321    }
322
323    /// Alerts for node - GET /v1/nodes/alerts/{uid}
324    pub async fn alerts_for(&self, uid: u32) -> Result<Value> {
325        self.client.get(&format!("/v1/nodes/alerts/{}", uid)).await
326    }
327
328    /// Alert detail - GET /v1/nodes/alerts/{uid}/{alert}
329    pub async fn alert_detail(&self, uid: u32, alert: &str) -> Result<Value> {
330        self.client
331            .get(&format!("/v1/nodes/alerts/{}/{}", uid, alert))
332            .await
333    }
334}