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