1use std::fmt::{Display, Formatter};
16use std::hash::{DefaultHasher, Hash, Hasher};
17use std::str::FromStr;
18
19use api::v1::meta::{DatanodeWorkloads, FlownodeWorkloads, HeartbeatRequest};
20use common_error::ext::ErrorExt;
21use lazy_static::lazy_static;
22use regex::Regex;
23use serde::{Deserialize, Serialize};
24use snafu::{OptionExt, ResultExt, ensure};
25
26use crate::datanode::RegionStat;
27use crate::error::{
28 DecodeJsonSnafu, EncodeJsonSnafu, Error, FromUtf8Snafu, InvalidNodeInfoKeySnafu,
29 InvalidRoleSnafu, ParseNumSnafu, Result,
30};
31use crate::key::flow::flow_state::FlowStat;
32use crate::peer::Peer;
33
34const CLUSTER_NODE_INFO_PREFIX: &str = "__meta_cluster_node_info";
35
36lazy_static! {
37 static ref CLUSTER_NODE_INFO_PREFIX_PATTERN: Regex = Regex::new(&format!(
38 "^{CLUSTER_NODE_INFO_PREFIX}-([0-9]+)-([0-9]+)-([0-9]+)$"
39 ))
40 .unwrap();
41}
42
43#[async_trait::async_trait]
45pub trait ClusterInfo {
46 type Error: ErrorExt;
47
48 async fn list_nodes(
50 &self,
51 role: Option<Role>,
52 ) -> std::result::Result<Vec<NodeInfo>, Self::Error>;
53
54 async fn list_region_stats(&self) -> std::result::Result<Vec<RegionStat>, Self::Error>;
56
57 async fn list_flow_stats(&self) -> std::result::Result<Option<FlowStat>, Self::Error>;
59
60 }
62
63#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, Serialize, Deserialize, PartialOrd, Ord)]
65pub struct NodeInfoKey {
66 pub role: Role,
68 pub node_id: u64,
70}
71
72impl NodeInfoKey {
73 pub fn new(request: &HeartbeatRequest) -> Option<Self> {
76 let HeartbeatRequest { header, peer, .. } = request;
77 let header = header.as_ref()?;
78 let peer = peer.as_ref()?;
79
80 let role = header.role.try_into().ok()?;
81 let node_id = match role {
82 Role::Frontend => calculate_node_id(&peer.addr),
85 _ => peer.id,
86 };
87
88 Some(NodeInfoKey { role, node_id })
89 }
90
91 pub fn key_prefix() -> String {
92 format!("{}-0-", CLUSTER_NODE_INFO_PREFIX)
93 }
94
95 pub fn key_prefix_with_role(role: Role) -> String {
96 format!("{}-0-{}-", CLUSTER_NODE_INFO_PREFIX, i32::from(role))
97 }
98}
99
100fn calculate_node_id(addr: &str) -> u64 {
102 let mut hasher = DefaultHasher::new();
103 addr.hash(&mut hasher);
104 hasher.finish()
105}
106
107#[derive(Debug, Serialize, Deserialize)]
109pub struct NodeInfo {
110 pub peer: Peer,
112 pub last_activity_ts: i64,
114 pub status: NodeStatus,
116 pub version: String,
118 pub git_commit: String,
120 pub start_time_ms: u64,
122 #[serde(default)]
124 pub total_cpu_millicores: i64,
125 #[serde(default)]
127 pub total_memory_bytes: i64,
128 #[serde(default)]
130 pub cpu_usage_millicores: i64,
131 #[serde(default)]
133 pub memory_usage_bytes: i64,
134 #[serde(default)]
136 pub hostname: String,
137}
138
139#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, Serialize, Deserialize, PartialOrd, Ord)]
140pub enum Role {
141 Datanode,
142 Frontend,
143 Flownode,
144 Metasrv,
145}
146
147#[derive(Debug, Serialize, Deserialize)]
148pub enum NodeStatus {
149 Datanode(DatanodeStatus),
150 Frontend(FrontendStatus),
151 Flownode(FlownodeStatus),
152 Metasrv(MetasrvStatus),
153 Standalone,
154}
155
156impl NodeStatus {
157 pub fn role_name(&self) -> &str {
159 match self {
160 NodeStatus::Datanode(_) => "DATANODE",
161 NodeStatus::Frontend(_) => "FRONTEND",
162 NodeStatus::Flownode(_) => "FLOWNODE",
163 NodeStatus::Metasrv(_) => "METASRV",
164 NodeStatus::Standalone => "STANDALONE",
165 }
166 }
167}
168
169#[derive(Debug, Serialize, Deserialize)]
171pub struct DatanodeStatus {
172 pub rcus: i64,
174 pub wcus: i64,
176 pub leader_regions: usize,
178 pub follower_regions: usize,
180 pub workloads: DatanodeWorkloads,
182}
183
184#[derive(Debug, Serialize, Deserialize)]
186pub struct FrontendStatus {}
187
188#[derive(Debug, Serialize, Deserialize)]
190pub struct FlownodeStatus {
191 #[serde(default)]
193 pub workloads: FlownodeWorkloads,
194}
195
196#[derive(Debug, Serialize, Deserialize)]
198pub struct MetasrvStatus {
199 pub is_leader: bool,
200}
201
202impl FromStr for NodeInfoKey {
203 type Err = Error;
204
205 fn from_str(key: &str) -> Result<Self> {
206 let caps = CLUSTER_NODE_INFO_PREFIX_PATTERN
207 .captures(key)
208 .context(InvalidNodeInfoKeySnafu { key })?;
209 ensure!(caps.len() == 4, InvalidNodeInfoKeySnafu { key });
210
211 let role = caps[2].to_string();
212 let node_id = caps[3].to_string();
213 let role: i32 = role.parse().context(ParseNumSnafu {
214 err_msg: format!("invalid role {role}"),
215 })?;
216 let role = Role::try_from(role)?;
217 let node_id: u64 = node_id.parse().context(ParseNumSnafu {
218 err_msg: format!("invalid node_id: {node_id}"),
219 })?;
220
221 Ok(Self { role, node_id })
222 }
223}
224
225impl TryFrom<Vec<u8>> for NodeInfoKey {
226 type Error = Error;
227
228 fn try_from(bytes: Vec<u8>) -> Result<Self> {
229 String::from_utf8(bytes)
230 .context(FromUtf8Snafu {
231 name: "NodeInfoKey",
232 })
233 .map(|x| x.parse())?
234 }
235}
236
237impl From<&NodeInfoKey> for Vec<u8> {
238 fn from(key: &NodeInfoKey) -> Self {
239 format!(
240 "{}-0-{}-{}",
241 CLUSTER_NODE_INFO_PREFIX,
242 i32::from(key.role),
243 key.node_id
244 )
245 .into_bytes()
246 }
247}
248
249impl Display for NodeInfoKey {
250 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
251 write!(f, "{:?}-{}", self.role, self.node_id)
252 }
253}
254
255impl FromStr for NodeInfo {
256 type Err = Error;
257
258 fn from_str(value: &str) -> Result<Self> {
259 serde_json::from_str(value).context(DecodeJsonSnafu)
260 }
261}
262
263impl TryFrom<Vec<u8>> for NodeInfo {
264 type Error = Error;
265
266 fn try_from(bytes: Vec<u8>) -> Result<Self> {
267 String::from_utf8(bytes)
268 .context(FromUtf8Snafu { name: "NodeInfo" })
269 .map(|x| x.parse())?
270 }
271}
272
273impl TryFrom<NodeInfo> for Vec<u8> {
274 type Error = Error;
275
276 fn try_from(info: NodeInfo) -> Result<Self> {
277 Ok(serde_json::to_string(&info)
278 .context(EncodeJsonSnafu)?
279 .into_bytes())
280 }
281}
282
283impl From<Role> for i32 {
284 fn from(role: Role) -> Self {
285 match role {
286 Role::Datanode => 0,
287 Role::Frontend => 1,
288 Role::Flownode => 2,
289 Role::Metasrv => 99,
290 }
291 }
292}
293
294impl TryFrom<i32> for Role {
295 type Error = Error;
296
297 fn try_from(role: i32) -> Result<Self> {
298 match role {
299 0 => Ok(Self::Datanode),
300 1 => Ok(Self::Frontend),
301 2 => Ok(Self::Flownode),
302 99 => Ok(Self::Metasrv),
303 _ => InvalidRoleSnafu { role }.fail(),
304 }
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use std::assert_matches;
311
312 use common_workload::DatanodeWorkloadType;
313
314 use super::*;
315 use crate::cluster::Role::{Datanode, Frontend};
316 use crate::cluster::{DatanodeStatus, FlownodeStatus, NodeInfo, NodeInfoKey, NodeStatus};
317 use crate::peer::Peer;
318
319 #[test]
320 fn test_node_info_key_round_trip() {
321 let key = NodeInfoKey {
322 role: Datanode,
323 node_id: 2,
324 };
325
326 let key_bytes: Vec<u8> = (&key).into();
327 let new_key: NodeInfoKey = key_bytes.try_into().unwrap();
328
329 assert_eq!(Datanode, new_key.role);
330 assert_eq!(2, new_key.node_id);
331 }
332
333 #[test]
334 fn test_node_info_round_trip() {
335 let node_info = NodeInfo {
336 peer: Peer {
337 id: 1,
338 addr: "127.0.0.1".to_string(),
339 },
340 last_activity_ts: 123,
341 status: NodeStatus::Datanode(DatanodeStatus {
342 rcus: 1,
343 wcus: 2,
344 leader_regions: 3,
345 follower_regions: 4,
346 workloads: DatanodeWorkloads {
347 types: vec![DatanodeWorkloadType::Hybrid.to_i32()],
348 },
349 }),
350 version: "".to_string(),
351 git_commit: "".to_string(),
352 start_time_ms: 1,
353 total_cpu_millicores: 0,
354 total_memory_bytes: 0,
355 cpu_usage_millicores: 0,
356 memory_usage_bytes: 0,
357 hostname: "test_hostname".to_string(),
358 };
359
360 let node_info_bytes: Vec<u8> = node_info.try_into().unwrap();
361 let new_node_info: NodeInfo = node_info_bytes.try_into().unwrap();
362
363 assert_matches!(
364 new_node_info,
365 NodeInfo {
366 peer: Peer { id: 1, .. },
367 last_activity_ts: 123,
368 status: NodeStatus::Datanode(DatanodeStatus {
369 rcus: 1,
370 wcus: 2,
371 leader_regions: 3,
372 follower_regions: 4,
373 ..
374 }),
375 start_time_ms: 1,
376 ..
377 }
378 );
379 }
380
381 #[test]
382 fn test_node_info_key_prefix() {
383 let prefix = NodeInfoKey::key_prefix();
384 assert_eq!(prefix, "__meta_cluster_node_info-0-");
385
386 let prefix = NodeInfoKey::key_prefix_with_role(Frontend);
387 assert_eq!(prefix, "__meta_cluster_node_info-0-1-");
388 }
389
390 #[test]
391 fn test_calculate_node_id_from_addr() {
392 assert_eq!(calculate_node_id(""), calculate_node_id(""));
394
395 let addr1 = "127.0.0.1:8080";
397 let id1 = calculate_node_id(addr1);
398 let id2 = calculate_node_id(addr1);
399 assert_eq!(id1, id2);
400
401 let addr2 = "127.0.0.1:8081";
403 let id3 = calculate_node_id(addr2);
404 assert_ne!(id1, id3);
405
406 let long_addr = "very.long.domain.name.example.com:9999";
408 let id4 = calculate_node_id(long_addr);
409 assert!(id4 > 0);
410 }
411
412 #[test]
413 fn test_flownode_status_backward_compatible_without_workloads() {
414 let raw = r#"{
415 "peer":{"id":1,"addr":"127.0.0.1"},
416 "last_activity_ts":123,
417 "status":{"Flownode":{}},
418 "version":"",
419 "git_commit":"",
420 "start_time_ms":1,
421 "total_cpu_millicores":0,
422 "total_memory_bytes":0,
423 "cpu_usage_millicores":0,
424 "memory_usage_bytes":0,
425 "hostname":""
426 }"#;
427
428 let node_info: NodeInfo = raw.parse().unwrap();
429 assert_matches!(
430 node_info.status,
431 NodeStatus::Flownode(FlownodeStatus { workloads }) if workloads.types.is_empty()
432 );
433 }
434
435 #[test]
436 fn test_flownode_status_round_trip_with_workloads() {
437 let node_info = NodeInfo {
438 peer: Peer {
439 id: 1,
440 addr: "127.0.0.1".to_string(),
441 },
442 last_activity_ts: 123,
443 status: NodeStatus::Flownode(FlownodeStatus {
444 workloads: FlownodeWorkloads { types: vec![7] },
445 }),
446 version: "".to_string(),
447 git_commit: "".to_string(),
448 start_time_ms: 1,
449 total_cpu_millicores: 0,
450 total_memory_bytes: 0,
451 cpu_usage_millicores: 0,
452 memory_usage_bytes: 0,
453 hostname: "test_hostname".to_string(),
454 };
455
456 let node_info_bytes: Vec<u8> = node_info.try_into().unwrap();
457 let new_node_info: NodeInfo = node_info_bytes.try_into().unwrap();
458
459 assert_matches!(
460 new_node_info,
461 NodeInfo {
462 status: NodeStatus::Flownode(FlownodeStatus { workloads }),
463 ..
464 } if workloads.types == vec![7]
465 );
466 }
467}