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 typed_builder::TypedBuilder;
13
14/// Response from node action operations
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct NodeActionResponse {
17    /// The action UID for tracking async operations
18    pub action_uid: String,
19    /// Description of the action
20    pub description: Option<String>,
21    /// Additional fields from the response
22    #[serde(flatten)]
23    pub extra: Value,
24}
25
26/// Node information
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct Node {
29    pub uid: u32,
30
31    /// IP address of the node (renamed from 'address' to match API)
32    #[serde(rename = "addr")]
33    pub addr: Option<String>,
34
35    pub status: String,
36
37    /// Whether node accepts new shards
38    pub accept_servers: Option<bool>,
39
40    /// System architecture (e.g., "aarch64", "x86_64")
41    pub architecture: Option<String>,
42
43    /// CPU cores (renamed from 'cpu_cores' to match API)
44    #[serde(rename = "cores")]
45    pub cores: Option<u32>,
46
47    /// External IP addresses
48    pub external_addr: Option<Vec<String>>,
49
50    /// Total memory in bytes
51    pub total_memory: Option<u64>,
52
53    /// OS version information
54    pub os_version: Option<String>,
55    pub os_name: Option<String>,
56    pub os_family: Option<String>,
57    pub os_semantic_version: Option<String>,
58
59    /// Storage sizes (API returns f64, not u64)
60    pub ephemeral_storage_size: Option<f64>,
61    pub persistent_storage_size: Option<f64>,
62
63    /// Storage paths
64    pub ephemeral_storage_path: Option<String>,
65    pub persistent_storage_path: Option<String>,
66    pub bigredis_storage_path: Option<String>,
67
68    /// Rack configuration
69    pub rack_id: Option<String>,
70    pub second_rack_id: Option<String>,
71
72    /// Shard information
73    pub shard_count: Option<u32>,
74    pub shard_list: Option<Vec<u32>>,
75    pub ram_shard_count: Option<u32>,
76    pub flash_shard_count: Option<u32>,
77
78    /// Features and capabilities
79    pub bigstore_enabled: Option<bool>,
80    pub fips_enabled: Option<bool>,
81    pub use_internal_ipv6: Option<bool>,
82
83    /// Limits and settings
84    pub max_listeners: Option<u32>,
85    pub max_redis_servers: Option<u32>,
86    pub max_redis_forks: Option<i32>,
87    pub max_slave_full_syncs: Option<i32>,
88
89    /// Runtime information
90    pub uptime: Option<u64>,
91    pub software_version: Option<String>,
92
93    /// Supported Redis versions
94    pub supported_database_versions: Option<Vec<Value>>,
95
96    /// Capture any additional fields not explicitly defined
97    #[serde(flatten)]
98    pub extra: Value,
99}
100
101/// Node stats
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct NodeStats {
104    pub uid: u32,
105    pub cpu_user: Option<f64>,
106    pub cpu_system: Option<f64>,
107    pub cpu_idle: Option<f64>,
108    pub free_memory: Option<u64>,
109    pub network_bytes_in: Option<u64>,
110    pub network_bytes_out: Option<u64>,
111    pub persistent_storage_free: Option<u64>,
112    pub ephemeral_storage_free: Option<u64>,
113
114    #[serde(flatten)]
115    pub extra: Value,
116}
117
118/// Node action request
119#[derive(Debug, Serialize, TypedBuilder)]
120pub struct NodeActionRequest {
121    #[builder(setter(into))]
122    pub action: String,
123    #[serde(skip_serializing_if = "Option::is_none")]
124    #[builder(default, setter(strip_option))]
125    pub node_uid: Option<u32>,
126}
127
128/// Node handler for executing node commands
129pub struct NodeHandler {
130    client: RestClient,
131}
132
133/// Alias for backwards compatibility and intuitive plural naming
134pub type NodesHandler = NodeHandler;
135
136impl NodeHandler {
137    pub fn new(client: RestClient) -> Self {
138        NodeHandler { client }
139    }
140
141    /// List all nodes
142    pub async fn list(&self) -> Result<Vec<Node>> {
143        self.client.get("/v1/nodes").await
144    }
145
146    /// Get specific node info
147    pub async fn get(&self, uid: u32) -> Result<Node> {
148        self.client.get(&format!("/v1/nodes/{}", uid)).await
149    }
150
151    /// Update node configuration
152    pub async fn update(&self, uid: u32, updates: Value) -> Result<Node> {
153        self.client
154            .put(&format!("/v1/nodes/{}", uid), &updates)
155            .await
156    }
157
158    /// Remove node from cluster
159    pub async fn remove(&self, uid: u32) -> Result<()> {
160        self.client.delete(&format!("/v1/nodes/{}", uid)).await
161    }
162
163    /// Get node stats
164    pub async fn stats(&self, uid: u32) -> Result<NodeStats> {
165        self.client.get(&format!("/v1/nodes/{}/stats", uid)).await
166    }
167
168    /// Get node actions
169    pub async fn actions(&self, uid: u32) -> Result<Value> {
170        self.client.get(&format!("/v1/nodes/{}/actions", uid)).await
171    }
172
173    /// Execute node action (e.g., "maintenance_on", "maintenance_off")
174    pub async fn execute_action(&self, uid: u32, action: &str) -> Result<NodeActionResponse> {
175        let request = NodeActionRequest {
176            action: action.to_string(),
177            node_uid: Some(uid),
178        };
179        self.client
180            .post(&format!("/v1/nodes/{}/actions", uid), &request)
181            .await
182    }
183
184    // raw variant removed in favor of typed execute_action
185
186    /// List all available node actions (global) - GET /v1/nodes/actions
187    pub async fn list_actions(&self) -> Result<Value> {
188        self.client.get("/v1/nodes/actions").await
189    }
190
191    /// Get node action detail - GET /v1/nodes/{uid}/actions/{action}
192    pub async fn action_detail(&self, uid: u32, action: &str) -> Result<Value> {
193        self.client
194            .get(&format!("/v1/nodes/{}/actions/{}", uid, action))
195            .await
196    }
197
198    /// Execute named node action - POST /v1/nodes/{uid}/actions/{action}
199    pub async fn action_execute(&self, uid: u32, action: &str, body: Value) -> Result<Value> {
200        self.client
201            .post(&format!("/v1/nodes/{}/actions/{}", uid, action), &body)
202            .await
203    }
204
205    /// Delete node action - DELETE /v1/nodes/{uid}/actions/{action}
206    pub async fn action_delete(&self, uid: u32, action: &str) -> Result<()> {
207        self.client
208            .delete(&format!("/v1/nodes/{}/actions/{}", uid, action))
209            .await
210    }
211
212    /// List snapshots for a node - GET /v1/nodes/{uid}/snapshots
213    pub async fn snapshots(&self, uid: u32) -> Result<Value> {
214        self.client
215            .get(&format!("/v1/nodes/{}/snapshots", uid))
216            .await
217    }
218
219    /// Create a snapshot - POST /v1/nodes/{uid}/snapshots/{name}
220    pub async fn snapshot_create(&self, uid: u32, name: &str) -> Result<Value> {
221        self.client
222            .post(
223                &format!("/v1/nodes/{}/snapshots/{}", uid, name),
224                &serde_json::json!({}),
225            )
226            .await
227    }
228
229    /// Delete a snapshot - DELETE /v1/nodes/{uid}/snapshots/{name}
230    pub async fn snapshot_delete(&self, uid: u32, name: &str) -> Result<()> {
231        self.client
232            .delete(&format!("/v1/nodes/{}/snapshots/{}", uid, name))
233            .await
234    }
235
236    /// All nodes status - GET /v1/nodes/status
237    pub async fn status_all(&self) -> Result<Value> {
238        self.client.get("/v1/nodes/status").await
239    }
240
241    /// Watchdog status for all nodes - GET /v1/nodes/wd_status
242    pub async fn wd_status_all(&self) -> Result<Value> {
243        self.client.get("/v1/nodes/wd_status").await
244    }
245
246    /// Node status - GET /v1/nodes/{uid}/status
247    pub async fn status(&self, uid: u32) -> Result<Value> {
248        self.client.get(&format!("/v1/nodes/{}/status", uid)).await
249    }
250
251    /// Node watchdog status - GET /v1/nodes/{uid}/wd_status
252    pub async fn wd_status(&self, uid: u32) -> Result<Value> {
253        self.client
254            .get(&format!("/v1/nodes/{}/wd_status", uid))
255            .await
256    }
257
258    /// All node alerts - GET /v1/nodes/alerts
259    pub async fn alerts_all(&self) -> Result<Value> {
260        self.client.get("/v1/nodes/alerts").await
261    }
262
263    /// Alerts for node - GET /v1/nodes/alerts/{uid}
264    pub async fn alerts_for(&self, uid: u32) -> Result<Value> {
265        self.client.get(&format!("/v1/nodes/alerts/{}", uid)).await
266    }
267
268    /// Alert detail - GET /v1/nodes/alerts/{uid}/{alert}
269    pub async fn alert_detail(&self, uid: u32, alert: &str) -> Result<Value> {
270        self.client
271            .get(&format!("/v1/nodes/alerts/{}/{}", uid, alert))
272            .await
273    }
274}