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
//! Proxies 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;
/// Response for a single metric query
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetricResponse {
/// Interval label for the metric series.
pub interval: String,
/// List of Unix-epoch timestamps for the data points.
pub timestamps: Vec<i64>,
/// List of metric values, aligned to `timestamps`.
pub values: Vec<Value>,
}
/// Proxy information.
///
/// `GET /v1/proxies` returns both global proxy configuration entries
/// (no `bdb_uid`/`node_uid`) and per-database proxies, so the fields
/// that distinguish the two are `Option`. `maxmemory_clients` is `u64`
/// because real clusters report values that exceed `u32::MAX` (the
/// recorded fixture has `4_294_967_296`).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Proxy {
/// Cluster-unique ID of the proxy.
pub uid: u32,
/// Database UID this proxy serves, if any. Absent on global proxy
/// configuration entries.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bdb_uid: Option<u32>,
/// Node UID this proxy runs on, if any. Absent on global proxy
/// configuration entries.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub node_uid: Option<u32>,
/// Current proxy status (e.g. `"active"`, `"deactivated"`).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
/// Address.
#[serde(skip_serializing_if = "Option::is_none")]
pub addr: Option<String>,
/// TCP port.
#[serde(skip_serializing_if = "Option::is_none")]
pub port: Option<u16>,
/// Maximum number of client connections.
#[serde(skip_serializing_if = "Option::is_none")]
pub max_connections: Option<u32>,
/// Number of worker threads.
#[serde(skip_serializing_if = "Option::is_none")]
pub threads: Option<u32>,
// Additional fields from API audit
/// Maximum number of pending connections in the listen queue
#[serde(skip_serializing_if = "Option::is_none")]
pub backlog: Option<u32>,
/// Whether automatic client eviction is enabled when limits are reached
#[serde(skip_serializing_if = "Option::is_none")]
pub client_eviction: Option<bool>,
/// Number of TCP keepalive probes before connection is dropped
#[serde(skip_serializing_if = "Option::is_none")]
pub client_keepcnt: Option<u32>,
/// Time in seconds before TCP keepalive probes start
#[serde(skip_serializing_if = "Option::is_none")]
pub client_keepidle: Option<u32>,
/// Interval in seconds between TCP keepalive probes
#[serde(skip_serializing_if = "Option::is_none")]
pub client_keepintvl: Option<u32>,
/// Current number of active connections
#[serde(skip_serializing_if = "Option::is_none")]
pub conns: Option<u32>,
/// Whether core dump files are generated on crash
#[serde(skip_serializing_if = "Option::is_none")]
pub corefile: Option<bool>,
/// Threshold in milliseconds for slow operation logging
#[serde(skip_serializing_if = "Option::is_none")]
pub duration_usage_threshold: Option<u32>,
/// Whether proxy can dynamically adjust thread count based on load
#[serde(skip_serializing_if = "Option::is_none")]
pub dynamic_threads_scaling: Option<bool>,
/// Whether to bypass database connection limit checks
#[serde(skip_serializing_if = "Option::is_none")]
pub ignore_bdb_cconn_limit: Option<bool>,
/// Whether to bypass database output buffer limit checks
#[serde(skip_serializing_if = "Option::is_none")]
pub ignore_bdb_cconn_output_buff_limits: Option<bool>,
/// Maximum capacity for incoming connection handling
#[serde(skip_serializing_if = "Option::is_none")]
pub incoming_connections_capacity: Option<u32>,
/// Minimum reserved capacity for incoming connections
#[serde(skip_serializing_if = "Option::is_none")]
pub incoming_connections_min_capacity: Option<u32>,
/// Maximum rate of new incoming connections per second
#[serde(skip_serializing_if = "Option::is_none")]
pub incoming_connections_rate_limit: Option<u32>,
/// Logging level for proxy (e.g., 'debug', 'info', 'warning', 'error')
#[serde(skip_serializing_if = "Option::is_none")]
pub log_level: Option<String>,
/// Maximum number of listener sockets
#[serde(skip_serializing_if = "Option::is_none")]
pub max_listeners: Option<u32>,
/// Maximum number of backend server connections
#[serde(skip_serializing_if = "Option::is_none")]
pub max_servers: Option<u32>,
/// Maximum number of worker threads
#[serde(skip_serializing_if = "Option::is_none")]
pub max_threads: Option<u32>,
/// Maximum client connections per worker thread
#[serde(skip_serializing_if = "Option::is_none")]
pub max_worker_client_conns: Option<u32>,
/// Maximum server connections per worker thread
#[serde(skip_serializing_if = "Option::is_none")]
pub max_worker_server_conns: Option<u32>,
/// Maximum concurrent transactions per worker thread
#[serde(skip_serializing_if = "Option::is_none")]
pub max_worker_txns: Option<u32>,
/// Maximum memory in bytes allocated for client connections.
///
/// Typed as `u64` (not `u32`) because real clusters report values
/// that exceed `u32::MAX` — the recorded `tests/fixtures/proxies_list.json`
/// contains `4_294_967_296` (one byte over the `u32` ceiling).
#[serde(skip_serializing_if = "Option::is_none")]
pub maxmemory_clients: Option<u64>,
/// CPU usage threshold percentage for thread scaling decisions
#[serde(skip_serializing_if = "Option::is_none")]
pub threads_usage_threshold: Option<u32>,
}
/// Proxy stats information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProxyStats {
/// Unique identifier (read-only).
pub uid: u32,
/// Per-interval metric series for the resource.
pub intervals: Vec<StatsInterval>,
}
/// One interval of statistics, with aligned timestamps and values.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StatsInterval {
/// Interval label for the metric series.
pub interval: String,
/// List of Unix-epoch timestamps for the data points.
pub timestamps: Vec<i64>,
/// List of metric values, aligned to `timestamps`.
pub values: Vec<Value>,
}
/// Proxy handler for managing proxies
pub struct ProxyHandler {
client: RestClient,
}
impl ProxyHandler {
/// Create a new handler bound to the given REST client.
pub fn new(client: RestClient) -> Self {
ProxyHandler { client }
}
/// List all proxies
pub async fn list(&self) -> Result<Vec<Proxy>> {
self.client.get("/v1/proxies").await
}
/// Get specific proxy information
pub async fn get(&self, uid: u32) -> Result<Proxy> {
self.client.get(&format!("/v1/proxies/{}", uid)).await
}
/// Get proxy statistics
pub async fn stats(&self, uid: u32) -> Result<ProxyStats> {
self.client.get(&format!("/v1/proxies/{}/stats", uid)).await
}
/// Get proxy statistics for a specific metric
pub async fn stats_metric(&self, uid: u32, metric: &str) -> Result<MetricResponse> {
self.client
.get(&format!("/v1/proxies/{}/stats/{}", uid, metric))
.await
}
/// Get proxies for a specific database
pub async fn list_by_database(&self, bdb_uid: u32) -> Result<Vec<Proxy>> {
self.client
.get(&format!("/v1/bdbs/{}/proxies", bdb_uid))
.await
}
/// Get proxies for a specific node
pub async fn list_by_node(&self, node_uid: u32) -> Result<Vec<Proxy>> {
self.client
.get(&format!("/v1/nodes/{}/proxies", node_uid))
.await
}
/// Reload proxy configuration
pub async fn reload(&self, uid: u32) -> Result<()> {
self.client
.post_action(&format!("/v1/proxies/{}/actions/reload", uid), &Value::Null)
.await
}
/// Update proxies (bulk) - PUT /v1/proxies
pub async fn update_all(&self, update: ProxyUpdate) -> Result<Vec<Proxy>> {
self.client.put("/v1/proxies", &update).await
}
/// Update specific proxy - PUT /v1/proxies/{uid}
pub async fn update(&self, uid: u32, update: ProxyUpdate) -> Result<Proxy> {
self.client
.put(&format!("/v1/proxies/{}", uid), &update)
.await
}
}
/// Proxy update body
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProxyUpdate {
/// Maximum number of client connections.
#[serde(skip_serializing_if = "Option::is_none")]
pub max_connections: Option<u32>,
/// Number of worker threads.
#[serde(skip_serializing_if = "Option::is_none")]
pub threads: Option<u32>,
}