1use std::fmt;
4use std::sync::Arc;
5
6use hydracache::{ClusterMember, ClusterNodeId, ClusterRole, RaftMetadataSnapshot};
7use serde::Serialize;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub struct ClusterStatusRuntime {
12 pub ready: bool,
14 pub draining: bool,
16}
17
18impl ClusterStatusRuntime {
19 pub fn new(ready: bool, draining: bool) -> Self {
21 Self { ready, draining }
22 }
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
27#[serde(rename_all = "snake_case")]
28pub enum StatusSource {
29 Live,
31 Modeled,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
37#[serde(rename_all = "snake_case")]
38pub enum MemberRole {
39 Local,
41 Client,
43 Member,
45}
46
47impl From<ClusterRole> for MemberRole {
48 fn from(value: ClusterRole) -> Self {
49 match value {
50 ClusterRole::Local => Self::Local,
51 ClusterRole::Client => Self::Client,
52 ClusterRole::Member => Self::Member,
53 }
54 }
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
59#[serde(rename_all = "snake_case")]
60pub enum Reachability {
61 Reachable,
63 Suspect,
65 Unreachable,
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
71#[serde(rename_all = "snake_case")]
72pub enum ReshardPhase {
73 Idle,
75 Planning,
77 Moving,
79 Finalizing,
81}
82
83impl fmt::Display for ReshardPhase {
84 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
85 let value = match self {
86 Self::Idle => "idle",
87 Self::Planning => "planning",
88 Self::Moving => "moving",
89 Self::Finalizing => "finalizing",
90 };
91 formatter.write_str(value)
92 }
93}
94
95#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
97pub struct MemberStatus {
98 pub node_id: String,
100 pub role: MemberRole,
102 pub reachable: Reachability,
104 pub generation: u64,
106}
107
108#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
110pub struct ClusterStatus {
111 pub source: StatusSource,
113 pub leader: Option<String>,
115 pub term: u64,
117 pub epoch: u64,
119 pub quorum_ok: bool,
121 pub members: Vec<MemberStatus>,
123 pub voters: u32,
125 pub reshard_phase: ReshardPhase,
127 pub draining: bool,
129}
130
131pub trait ClusterStatusProvider: fmt::Debug + Send + Sync {
133 fn begin_drain(&self) {}
135
136 fn cluster_status(&self, runtime: ClusterStatusRuntime) -> ClusterStatus;
138}
139
140#[derive(Debug, Clone, Default)]
142pub struct ModeledClusterStatus;
143
144impl ClusterStatusProvider for ModeledClusterStatus {
145 fn cluster_status(&self, runtime: ClusterStatusRuntime) -> ClusterStatus {
146 ClusterStatus {
147 source: StatusSource::Modeled,
148 leader: runtime.ready.then(|| "local".to_owned()),
149 term: u64::from(runtime.ready),
150 epoch: 0,
151 quorum_ok: runtime.ready && !runtime.draining,
152 members: Vec::new(),
153 voters: 0,
154 reshard_phase: ReshardPhase::Idle,
155 draining: runtime.draining,
156 }
157 }
158}
159
160pub trait GridControlPlaneHandle: fmt::Debug + Send + Sync {
162 fn begin_drain(&self);
164
165 fn snapshot(&self) -> RaftMetadataSnapshot;
167 fn members(&self) -> Vec<ClusterMember>;
169 fn raft_leader_id(&self) -> Option<String>;
171 fn has_quorum(&self) -> bool;
173 fn voter_count(&self) -> u32;
175 fn reachability(&self, node: &ClusterNodeId) -> Reachability;
177 fn reshard_phase(&self) -> ReshardPhase;
179 fn is_draining(&self) -> bool;
181}
182
183#[derive(Debug, Clone)]
185pub struct LiveClusterStatus {
186 grid: Arc<dyn GridControlPlaneHandle>,
187}
188
189impl LiveClusterStatus {
190 pub fn new(grid: Arc<dyn GridControlPlaneHandle>) -> Self {
192 Self { grid }
193 }
194}
195
196impl ClusterStatusProvider for LiveClusterStatus {
197 fn begin_drain(&self) {
198 self.grid.begin_drain();
199 }
200
201 fn cluster_status(&self, runtime: ClusterStatusRuntime) -> ClusterStatus {
202 let snapshot = self.grid.snapshot();
203 let draining = runtime.draining || self.grid.is_draining();
204 let members = self
205 .grid
206 .members()
207 .into_iter()
208 .map(|member| MemberStatus {
209 node_id: member.node_id.to_string(),
210 role: MemberRole::from(member.role),
211 reachable: self.grid.reachability(&member.node_id),
212 generation: member.generation.value(),
213 })
214 .collect();
215
216 ClusterStatus {
217 source: StatusSource::Live,
218 leader: self.grid.raft_leader_id(),
219 term: snapshot.term,
220 epoch: snapshot.epoch.value(),
221 quorum_ok: runtime.ready && self.grid.has_quorum() && !draining,
222 members,
223 voters: self.grid.voter_count(),
224 reshard_phase: self.grid.reshard_phase(),
225 draining,
226 }
227 }
228}