1use std::fmt;
4use std::sync::Arc;
5use std::time::Duration;
6
7use hydracache::{ClusterMember, ClusterNodeId, ClusterRole, RaftMetadataSnapshot};
8use serde::Serialize;
9use thiserror::Error;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct ClusterStatusRuntime {
14 pub ready: bool,
16 pub draining: bool,
18}
19
20impl ClusterStatusRuntime {
21 pub fn new(ready: bool, draining: bool) -> Self {
23 Self { ready, draining }
24 }
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
29#[serde(rename_all = "snake_case")]
30pub enum StatusSource {
31 Live,
33 Modeled,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
39#[serde(rename_all = "snake_case")]
40pub enum MemberRole {
41 Local,
43 Client,
45 Member,
47}
48
49impl From<ClusterRole> for MemberRole {
50 fn from(value: ClusterRole) -> Self {
51 match value {
52 ClusterRole::Local => Self::Local,
53 ClusterRole::Client => Self::Client,
54 ClusterRole::Member => Self::Member,
55 }
56 }
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
61#[serde(rename_all = "snake_case")]
62pub enum Reachability {
63 Reachable,
65 Suspect,
67 Unreachable,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
73#[serde(rename_all = "snake_case")]
74pub enum ReshardPhase {
75 Idle,
77 Planning,
79 Moving,
81 Finalizing,
83}
84
85impl fmt::Display for ReshardPhase {
86 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
87 let value = match self {
88 Self::Idle => "idle",
89 Self::Planning => "planning",
90 Self::Moving => "moving",
91 Self::Finalizing => "finalizing",
92 };
93 formatter.write_str(value)
94 }
95}
96
97#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
99pub struct MemberStatus {
100 pub node_id: String,
102 pub role: MemberRole,
104 pub reachable: Reachability,
106 pub generation: u64,
108}
109
110#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
112pub struct ClusterStatus {
113 pub source: StatusSource,
115 pub leader: Option<String>,
117 pub term: u64,
119 pub epoch: u64,
121 pub quorum_ok: bool,
123 pub members: Vec<MemberStatus>,
125 pub voters: u32,
127 pub voter_ids: Vec<u64>,
129 pub reshard_phase: ReshardPhase,
131 pub draining: bool,
133}
134
135#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
137pub struct RaftCompactionStatus {
138 pub available: bool,
140 pub enabled: bool,
142 pub applied_index: Option<u64>,
144 pub snapshot_index: Option<u64>,
146 pub first_log_index: Option<u64>,
148 pub last_log_index: Option<u64>,
150 pub snapshot_send_attempts: Option<u64>,
152 pub snapshot_send_successes: Option<u64>,
154 pub snapshot_send_failures: Option<u64>,
156 pub snapshot_sends_in_flight: Option<u64>,
158 pub snapshot_sender_tasks_current: Option<u64>,
160 pub snapshot_sender_tasks_high_water: Option<u64>,
163 pub snapshot_installs: Option<u64>,
165}
166
167impl RaftCompactionStatus {
168 pub(crate) fn unavailable() -> Self {
169 Self {
170 available: false,
171 enabled: false,
172 applied_index: None,
173 snapshot_index: None,
174 first_log_index: None,
175 last_log_index: None,
176 snapshot_send_attempts: None,
177 snapshot_send_successes: None,
178 snapshot_send_failures: None,
179 snapshot_sends_in_flight: None,
180 snapshot_sender_tasks_current: None,
181 snapshot_sender_tasks_high_water: None,
182 snapshot_installs: None,
183 }
184 }
185}
186
187#[derive(Debug, Clone, PartialEq, Eq, Error)]
189pub enum RaftCompactionError {
190 #[error("raft compaction control is unavailable for this runtime")]
192 Unavailable,
193 #[error("raft compaction control is disabled; set HYDRACACHE_RAFT_COMPACTION=true explicitly")]
195 Disabled,
196 #[error("raft compaction failed: {0}")]
198 Runtime(String),
199}
200
201pub trait ClusterStatusProvider: fmt::Debug + Send + Sync {
203 fn begin_drain(&self) {}
205
206 fn wait_for_drain_ready(&self, _timeout: Duration) -> bool {
211 true
212 }
213
214 fn cluster_status(&self, runtime: ClusterStatusRuntime) -> ClusterStatus;
216}
217
218#[derive(Debug, Clone, Default)]
220pub struct ModeledClusterStatus;
221
222impl ClusterStatusProvider for ModeledClusterStatus {
223 fn cluster_status(&self, runtime: ClusterStatusRuntime) -> ClusterStatus {
224 ClusterStatus {
225 source: StatusSource::Modeled,
226 leader: runtime.ready.then(|| "local".to_owned()),
227 term: u64::from(runtime.ready),
228 epoch: 0,
229 quorum_ok: runtime.ready && !runtime.draining,
230 members: Vec::new(),
231 voters: 0,
232 voter_ids: Vec::new(),
233 reshard_phase: ReshardPhase::Idle,
234 draining: runtime.draining,
235 }
236 }
237}
238
239pub trait GridControlPlaneHandle: fmt::Debug + Send + Sync {
241 fn begin_drain(&self);
243
244 fn wait_for_drain_ready(&self, _timeout: Duration) -> bool {
247 true
248 }
249
250 fn snapshot(&self) -> RaftMetadataSnapshot;
252 fn members(&self) -> Vec<ClusterMember>;
254 fn raft_leader_id(&self) -> Option<String>;
256 fn has_quorum(&self) -> bool;
258 fn metadata_authority_matches(&self, observed: &RaftMetadataSnapshot) -> bool {
265 let _ = observed;
266 true
267 }
268 fn voter_count(&self) -> u32;
270 fn voter_ids(&self) -> Vec<u64>;
272 fn reachability(&self, node: &ClusterNodeId) -> Reachability;
274 fn reshard_phase(&self) -> ReshardPhase;
276 fn is_draining(&self) -> bool;
278
279 fn raft_compaction_status(&self) -> Result<RaftCompactionStatus, RaftCompactionError> {
281 Ok(RaftCompactionStatus::unavailable())
282 }
283
284 fn compact_raft_log_at_applied(&self) -> Result<RaftCompactionStatus, RaftCompactionError> {
286 Err(RaftCompactionError::Unavailable)
287 }
288}
289
290#[derive(Debug, Clone)]
292pub struct LiveClusterStatus {
293 grid: Arc<dyn GridControlPlaneHandle>,
294}
295
296impl LiveClusterStatus {
297 pub fn new(grid: Arc<dyn GridControlPlaneHandle>) -> Self {
299 Self { grid }
300 }
301}
302
303impl ClusterStatusProvider for LiveClusterStatus {
304 fn begin_drain(&self) {
305 self.grid.begin_drain();
306 }
307
308 fn wait_for_drain_ready(&self, timeout: Duration) -> bool {
309 self.grid.wait_for_drain_ready(timeout)
310 }
311
312 fn cluster_status(&self, runtime: ClusterStatusRuntime) -> ClusterStatus {
313 let snapshot = self.grid.snapshot();
314 let draining = runtime.draining || self.grid.is_draining();
315 let members = self
316 .grid
317 .members()
318 .into_iter()
319 .map(|member| MemberStatus {
320 node_id: member.node_id.to_string(),
321 role: MemberRole::from(member.role),
322 reachable: self.grid.reachability(&member.node_id),
323 generation: member.generation.value(),
324 })
325 .collect();
326 let metadata_authoritative = self.grid.metadata_authority_matches(&snapshot);
327
328 ClusterStatus {
329 source: StatusSource::Live,
330 leader: metadata_authoritative
331 .then(|| self.grid.raft_leader_id())
332 .flatten(),
333 term: snapshot.term,
334 epoch: snapshot.epoch.value(),
335 quorum_ok: runtime.ready
336 && metadata_authoritative
337 && self.grid.has_quorum()
338 && !draining,
339 members,
340 voters: self.grid.voter_count(),
341 voter_ids: self.grid.voter_ids(),
342 reshard_phase: self.grid.reshard_phase(),
343 draining,
344 }
345 }
346}