eshanized_polaris_core/
config.rs

1//! Configuration types for Polaris clusters and nodes.
2
3use crate::errors::{PolarisError, PolarisResult};
4use serde::{Deserialize, Serialize};
5use std::net::SocketAddr;
6use std::path::PathBuf;
7use std::time::Duration;
8
9/// Cluster configuration
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct ClusterConfig {
12    /// Cluster name
13    pub name: String,
14
15    /// Seed nodes for cluster discovery
16    pub seed_nodes: Vec<String>,
17
18    /// Network configuration
19    pub network: NetworkConfig,
20
21    /// Security configuration
22    pub security: SecurityConfig,
23
24    /// Storage configuration
25    pub storage: StorageConfig,
26
27    /// Scheduler configuration
28    pub scheduler: SchedulerConfig,
29}
30
31impl Default for ClusterConfig {
32    fn default() -> Self {
33        Self {
34            name: "polaris-cluster".to_string(),
35            seed_nodes: vec![],
36            network: NetworkConfig::default(),
37            security: SecurityConfig::default(),
38            storage: StorageConfig::default(),
39            scheduler: SchedulerConfig::default(),
40        }
41    }
42}
43
44impl ClusterConfig {
45    /// Validate the configuration
46    pub fn validate(&self) -> PolarisResult<()> {
47        if self.name.is_empty() {
48            return Err(PolarisError::InvalidConfig("cluster name cannot be empty".into()));
49        }
50        self.network.validate()?;
51        self.security.validate()?;
52        self.storage.validate()?;
53        Ok(())
54    }
55
56    /// Load configuration from TOML file
57    pub fn from_file(path: impl Into<PathBuf>) -> PolarisResult<Self> {
58        let contents = std::fs::read_to_string(path.into())
59            .map_err(|e| PolarisError::InvalidConfig(format!("Failed to read config: {}", e)))?;
60        toml::from_str(&contents)
61            .map_err(|e| PolarisError::InvalidConfig(format!("Failed to parse config: {}", e)))
62    }
63}
64
65/// Node configuration
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct NodeConfig {
68    /// Node name (optional, defaults to hostname)
69    pub name: Option<String>,
70
71    /// Bind address
72    pub bind_addr: SocketAddr,
73
74    /// Advertise address (for NAT scenarios)
75    pub advertise_addr: Option<SocketAddr>,
76
77    /// Resource limits
78    pub resources: ResourceLimits,
79
80    /// Heartbeat interval
81    #[serde(with = "humantime_serde")]
82    pub heartbeat_interval: Duration,
83
84    /// Heartbeat timeout
85    #[serde(with = "humantime_serde")]
86    pub heartbeat_timeout: Duration,
87}
88
89impl Default for NodeConfig {
90    fn default() -> Self {
91        Self {
92            name: None,
93            bind_addr: "127.0.0.1:7001".parse().unwrap(),
94            advertise_addr: None,
95            resources: ResourceLimits::default(),
96            heartbeat_interval: Duration::from_secs(5),
97            heartbeat_timeout: Duration::from_secs(15),
98        }
99    }
100}
101
102/// Network configuration
103#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct NetworkConfig {
105    /// Transport protocol
106    pub transport: TransportType,
107
108    /// Connection timeout
109    #[serde(with = "humantime_serde")]
110    pub connect_timeout: Duration,
111
112    /// Request timeout
113    #[serde(with = "humantime_serde")]
114    pub request_timeout: Duration,
115
116    /// Maximum concurrent connections
117    pub max_connections: usize,
118
119    /// Enable compression
120    pub compression: bool,
121}
122
123impl Default for NetworkConfig {
124    fn default() -> Self {
125        Self {
126            transport: TransportType::Quic,
127            connect_timeout: Duration::from_secs(10),
128            request_timeout: Duration::from_secs(30),
129            max_connections: 1000,
130            compression: false,
131        }
132    }
133}
134
135impl NetworkConfig {
136    fn validate(&self) -> PolarisResult<()> {
137        if self.max_connections == 0 {
138            return Err(PolarisError::InvalidConfig(
139                "max_connections must be > 0".into(),
140            ));
141        }
142        Ok(())
143    }
144}
145
146/// Transport protocol type
147#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
148#[serde(rename_all = "lowercase")]
149pub enum TransportType {
150    /// QUIC transport (default)
151    Quic,
152    /// TLS over TCP
153    Tls,
154    /// gRPC
155    Grpc,
156}
157
158/// Security configuration
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct SecurityConfig {
161    /// Enable mTLS
162    pub mtls_enabled: bool,
163
164    /// Certificate path
165    pub cert_path: Option<PathBuf>,
166
167    /// Private key path
168    pub key_path: Option<PathBuf>,
169
170    /// CA certificate path
171    pub ca_cert_path: Option<PathBuf>,
172
173    /// Enable JWT authentication
174    pub jwt_enabled: bool,
175
176    /// JWT secret (for development only)
177    pub jwt_secret: Option<String>,
178}
179
180impl Default for SecurityConfig {
181    fn default() -> Self {
182        Self {
183            mtls_enabled: false, // Disabled by default for easier local development
184            cert_path: None,
185            key_path: None,
186            ca_cert_path: None,
187            jwt_enabled: false,
188            jwt_secret: None,
189        }
190    }
191}
192
193impl SecurityConfig {
194    fn validate(&self) -> PolarisResult<()> {
195        if self.mtls_enabled {
196            if self.cert_path.is_none() || self.key_path.is_none() {
197                return Err(PolarisError::InvalidConfig(
198                    "mTLS enabled but cert/key paths not provided".into(),
199                ));
200            }
201        }
202        Ok(())
203    }
204}
205
206/// Storage configuration
207#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct StorageConfig {
209    /// Storage backend type
210    pub backend: StorageBackend,
211
212    /// Storage directory path
213    pub path: Option<PathBuf>,
214
215    /// Enable persistence
216    pub persistence_enabled: bool,
217}
218
219impl Default for StorageConfig {
220    fn default() -> Self {
221        Self {
222            backend: StorageBackend::InMemory,
223            path: None,
224            persistence_enabled: false,
225        }
226    }
227}
228
229impl StorageConfig {
230    fn validate(&self) -> PolarisResult<()> {
231        if self.persistence_enabled && self.path.is_none() {
232            return Err(PolarisError::InvalidConfig(
233                "persistence enabled but path not provided".into(),
234            ));
235        }
236        Ok(())
237    }
238}
239
240/// Storage backend type
241#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
242#[serde(rename_all = "lowercase")]
243pub enum StorageBackend {
244    /// In-memory storage (default)
245    InMemory,
246    /// RocksDB backend
247    RocksDb,
248    /// Sled backend
249    Sled,
250}
251
252/// Scheduler configuration
253#[derive(Debug, Clone, Serialize, Deserialize)]
254pub struct SchedulerConfig {
255    /// Scheduler type
256    pub scheduler_type: SchedulerType,
257
258    /// Maximum retry attempts
259    pub max_retries: u32,
260
261    /// Retry backoff multiplier
262    pub retry_backoff_multiplier: f64,
263
264    /// Initial retry delay
265    #[serde(with = "humantime_serde")]
266    pub initial_retry_delay: Duration,
267
268    /// Maximum retry delay
269    #[serde(with = "humantime_serde")]
270    pub max_retry_delay: Duration,
271}
272
273impl Default for SchedulerConfig {
274    fn default() -> Self {
275        Self {
276            scheduler_type: SchedulerType::RoundRobin,
277            max_retries: 3,
278            retry_backoff_multiplier: 2.0,
279            initial_retry_delay: Duration::from_millis(100),
280            max_retry_delay: Duration::from_secs(60),
281        }
282    }
283}
284
285/// Scheduler type
286#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
287#[serde(rename_all = "snake_case")]
288pub enum SchedulerType {
289    /// Round-robin scheduler
290    RoundRobin,
291    /// Load-aware scheduler
292    LoadAware,
293    /// Custom AI-based scheduler
294    AiScheduler,
295}
296
297/// Resource limits for a node
298#[derive(Debug, Clone, Serialize, Deserialize)]
299pub struct ResourceLimits {
300    /// Maximum CPU cores (0 = unlimited)
301    pub max_cpu_cores: usize,
302
303    /// Maximum memory in bytes (0 = unlimited)
304    pub max_memory_bytes: u64,
305
306    /// Maximum concurrent tasks
307    pub max_concurrent_tasks: usize,
308
309    /// Maximum network bandwidth in bytes/sec (0 = unlimited)
310    pub max_network_bandwidth: u64,
311}
312
313impl Default for ResourceLimits {
314    fn default() -> Self {
315        Self {
316            max_cpu_cores: num_cpus::get(),
317            max_memory_bytes: 0, // unlimited
318            max_concurrent_tasks: 100,
319            max_network_bandwidth: 0, // unlimited
320        }
321    }
322}
323
324// Helper module for CPU detection
325mod num_cpus {
326    pub(super) fn get() -> usize {
327        std::thread::available_parallelism()
328            .map(|n| n.get())
329            .unwrap_or(4)
330    }
331}
332
333#[cfg(test)]
334mod tests {
335    use super::*;
336
337    #[test]
338    fn test_cluster_config_default() {
339        let config = ClusterConfig::default();
340        assert_eq!(config.name, "polaris-cluster");
341        assert!(config.seed_nodes.is_empty());
342    }
343
344    #[test]
345    fn test_cluster_config_validation() {
346        let mut config = ClusterConfig::default();
347        assert!(config.validate().is_ok());
348
349        config.name = String::new();
350        assert!(config.validate().is_err());
351    }
352
353    #[test]
354    fn test_node_config_default() {
355        let config = NodeConfig::default();
356        assert!(config.name.is_none());
357        assert_eq!(config.heartbeat_interval, Duration::from_secs(5));
358    }
359}