d_engine_core/config/
cluster.rs1use std::net::SocketAddr;
2use std::path::PathBuf;
3
4use config::ConfigError;
5use d_engine_proto::common::NodeRole::Follower;
6use d_engine_proto::common::NodeStatus;
7use d_engine_proto::server::cluster::NodeMeta;
8use serde::Deserialize;
9use serde::Serialize;
10use tracing::warn;
11
12use super::validate_directory;
13use crate::Error;
14use crate::Result;
15
16#[derive(Debug, Serialize, Deserialize, Clone)]
26pub struct ClusterConfig {
27 #[serde(default = "default_node_id")]
31 pub node_id: u32,
32
33 #[serde(default = "default_listen_addr")]
37 pub listen_address: SocketAddr,
38
39 #[serde(default = "default_initial_cluster")]
46 pub initial_cluster: Vec<NodeMeta>,
47
48 #[serde(default = "default_db_dir")]
52 pub db_root_dir: PathBuf,
53
54 #[serde(default = "default_log_dir")]
58 pub log_dir: PathBuf,
59}
60impl Default for ClusterConfig {
61 fn default() -> Self {
62 Self {
63 node_id: default_node_id(),
64 listen_address: default_listen_addr(),
65 initial_cluster: vec![],
66 db_root_dir: default_db_dir(),
67 log_dir: default_log_dir(),
68 }
69 }
70}
71
72impl ClusterConfig {
73 pub fn validate(&self) -> Result<()> {
77 if self.node_id == 0 {
79 return Err(Error::Config(ConfigError::Message(
80 "node_id cannot be 0 (reserved for invalid nodes)".into(),
81 )));
82 }
83
84 if self.initial_cluster.is_empty() {
86 return Err(Error::Config(ConfigError::Message(
87 "initial_cluster must contain at least one node".into(),
88 )));
89 }
90
91 let self_in_cluster = self.initial_cluster.iter().any(|n| n.id == self.node_id);
93 if !self_in_cluster {
94 return Err(Error::Config(ConfigError::Message(format!(
95 "Current node {} not found in initial_cluster",
96 self.node_id
97 ))));
98 }
99
100 let mut ids = std::collections::HashSet::new();
102 for node in &self.initial_cluster {
103 if !ids.insert(node.id) {
104 return Err(Error::Config(ConfigError::Message(format!(
105 "Duplicate node_id {} in initial_cluster",
106 node.id
107 ))));
108 }
109 }
110
111 if self.listen_address.port() == 0 {
113 return Err(Error::Config(ConfigError::Message(
114 "listen_address must specify a non-zero port".into(),
115 )));
116 }
117
118 if self.db_root_dir.starts_with("/tmp") {
120 warn!(
121 "db_root_dir {:?} is a temporary path — data will be lost on reboot. \
122 Use a persistent path for production.",
123 self.db_root_dir
124 );
125 }
126
127 validate_directory(&self.db_root_dir, "db_root_dir")?;
128 validate_directory(&self.log_dir, "log_dir")?;
129
130 Ok(())
131 }
132}
133
134fn default_node_id() -> u32 {
135 1
136}
137fn default_initial_cluster() -> Vec<NodeMeta> {
138 vec![NodeMeta {
139 id: 1,
140 address: "127.0.0.1:8080".to_string(),
141 role: Follower as i32,
142 status: NodeStatus::Active.into(),
143 }]
144}
145fn default_listen_addr() -> SocketAddr {
146 "127.0.0.1:9081".parse().unwrap()
147}
148fn default_db_dir() -> PathBuf {
149 PathBuf::from("/tmp/db")
150}
151fn default_log_dir() -> PathBuf {
152 PathBuf::from("/tmp/logs")
153}