Lodestone/
service.rs

1// src/service.rs
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::net::SocketAddr;
6use std::time::Duration;
7use uuid::Uuid;
8
9/// Health status of a service instance
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11pub enum ServiceHealth {
12    /// Service is healthy and available
13    Healthy,
14    
15    /// Service is unhealthy but still registered
16    Unhealthy,
17    
18    /// Service is in an unknown state
19    Unknown,
20    
21    /// Service is being monitored but not ready
22    Starting,
23    
24    /// Service is intentionally offline for maintenance
25    Maintenance,
26    
27    /// Service has been marked for deregistration
28    Deregistering,
29}
30
31impl Default for ServiceHealth {
32    fn default() -> Self {
33        ServiceHealth::Unknown
34    }
35}
36
37impl std::fmt::Display for ServiceHealth {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        match self {
40            ServiceHealth::Healthy => write!(f, "healthy"),
41            ServiceHealth::Unhealthy => write!(f, "unhealthy"),
42            ServiceHealth::Unknown => write!(f, "unknown"),
43            ServiceHealth::Starting => write!(f, "starting"),
44            ServiceHealth::Maintenance => write!(f, "maintenance"),
45            ServiceHealth::Deregistering => write!(f, "deregistering"),
46        }
47    }
48}
49
50/// A registered service
51#[derive(Debug, Clone, Serialize, Deserialize, Default)]
52pub struct Service {
53    /// Unique service name
54    pub name: String,
55    
56    /// Service instances
57    pub instances: Vec<ServiceInstance>,
58    
59    /// Service metadata
60    pub metadata: HashMap<String, String>,
61    
62    /// Service creation time
63    pub created_at: DateTime<Utc>,
64    
65    /// Last updated time
66    pub updated_at: DateTime<Utc>,
67}
68
69impl Service {
70    /// Create a new service
71    pub fn new(name: &str) -> Self {
72        let now = Utc::now();
73        Self {
74            name: name.to_string(),
75            instances: Vec::new(),
76            metadata: HashMap::new(),
77            created_at: now,
78            updated_at: now,
79        }
80    }
81    
82    /// Add an instance to this service
83    pub fn add_instance(&mut self, instance: ServiceInstance) {
84        self.instances.push(instance);
85        self.updated_at = Utc::now();
86    }
87    
88    /// Remove an instance by ID
89    pub fn remove_instance(&mut self, instance_id: &str) -> Option<ServiceInstance> {
90        let position = self.instances.iter().position(|i| i.id == instance_id)?;
91        let instance = self.instances.remove(position);
92        self.updated_at = Utc::now();
93        Some(instance)
94    }
95    
96    /// Get an instance by ID
97    pub fn get_instance(&self, instance_id: &str) -> Option<&ServiceInstance> {
98        self.instances.iter().find(|i| i.id == instance_id)
99    }
100    
101    /// Get a mutable reference to an instance by ID
102    pub fn get_instance_mut(&mut self, instance_id: &str) -> Option<&mut ServiceInstance> {
103        self.instances.iter_mut().find(|i| i.id == instance_id)
104    }
105    
106    /// Count the number of healthy instances
107    pub fn healthy_instance_count(&self) -> usize {
108        self.instances.iter().filter(|i| i.health == ServiceHealth::Healthy).count()
109    }
110    
111    /// Get all healthy instances
112    pub fn healthy_instances(&self) -> Vec<&ServiceInstance> {
113        self.instances.iter().filter(|i| i.health == ServiceHealth::Healthy).collect()
114    }
115    
116    /// Check if the service has any healthy instances
117    pub fn is_available(&self) -> bool {
118        self.instances.iter().any(|i| i.health == ServiceHealth::Healthy)
119    }
120    
121    /// Update the metadata
122    pub fn update_metadata(&mut self, key: &str, value: &str) {
123        self.metadata.insert(key.to_string(), value.to_string());
124        self.updated_at = Utc::now();
125    }
126    
127    /// Set multiple metadata values at once
128    pub fn set_metadata(&mut self, metadata: HashMap<String, String>) {
129        self.metadata = metadata;
130        self.updated_at = Utc::now();
131    }
132}
133
134/// A specific instance of a service
135#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct ServiceInstance {
137    /// Unique ID for this instance
138    pub id: String,
139    
140    /// Service name this instance belongs to
141    pub service_name: String,
142    
143    /// Host address (IP or hostname)
144    pub host: String,
145    
146    /// Port number
147    pub port: u16,
148    
149    /// Protocol (http, https, tcp, udp)
150    pub protocol: String,
151    
152    /// Health check path (for HTTP/HTTPS)
153    pub health_check_path: Option<String>,
154    
155    /// Current health status
156    pub health: ServiceHealth,
157    
158    /// Tags for categorization
159    pub tags: Vec<String>,
160    
161    /// Instance-specific metadata
162    pub metadata: HashMap<String, String>,
163    
164    /// Time to live in seconds before deregistration
165    pub ttl: Option<u64>,
166    
167    /// Last heartbeat time
168    pub last_heartbeat: Option<DateTime<Utc>>,
169    
170    /// Creation time
171    pub created_at: DateTime<Utc>,
172    
173    /// Last updated time
174    pub updated_at: DateTime<Utc>,
175    
176    /// Node ID hosting this instance
177    pub node_id: String,
178    
179    /// Weight for load balancing (higher is more traffic)
180    pub weight: u32,
181}
182
183impl ServiceInstance {
184    /// Create a new service instance
185    pub fn new(
186        service_name: &str,
187        host: &str,
188        port: u16,
189        protocol: &str,
190        node_id: &str,
191    ) -> Self {
192        let now = Utc::now();
193        Self {
194            id: Uuid::new_v4().to_string(),
195            service_name: service_name.to_string(),
196            host: host.to_string(),
197            port,
198            protocol: protocol.to_string(),
199            health_check_path: None,
200            health: ServiceHealth::Unknown,
201            tags: Vec::new(),
202            metadata: HashMap::new(),
203            ttl: None,
204            last_heartbeat: Some(now),
205            created_at: now,
206            updated_at: now,
207            node_id: node_id.to_string(),
208            weight: 100,
209        }
210    }
211    
212    /// Get the full address as a string
213    pub fn address(&self) -> String {
214        format!("{}://{}:{}", self.protocol, self.host, self.port)
215    }
216    
217    /// Get the socket address
218    pub fn socket_addr(&self) -> Option<SocketAddr> {
219        format!("{}:{}", self.host, self.port).parse().ok()
220    }
221    
222    /// Update the health status
223    pub fn update_health(&mut self, health: ServiceHealth) {
224        self.health = health;
225        self.updated_at = Utc::now();
226    }
227    
228    /// Record a heartbeat
229    pub fn heartbeat(&mut self) {
230        self.last_heartbeat = Some(Utc::now());
231    }
232    
233    /// Check if the TTL has expired
234    pub fn is_expired(&self) -> bool {
235        match (self.ttl, self.last_heartbeat) {
236            (Some(ttl), Some(last_heartbeat)) => {
237                let duration = Utc::now() - last_heartbeat;
238                let ttl_duration = chrono::Duration::from_std(Duration::from_secs(ttl)).unwrap();
239                duration > ttl_duration
240            }
241            _ => false,
242        }
243    }
244    
245    /// Set the TTL
246    pub fn set_ttl(&mut self, ttl_secs: u64) {
247        self.ttl = Some(ttl_secs);
248        self.updated_at = Utc::now();
249    }
250    
251    /// Add a tag
252    pub fn add_tag(&mut self, tag: &str) {
253        if !self.tags.contains(&tag.to_string()) {
254            self.tags.push(tag.to_string());
255            self.updated_at = Utc::now();
256        }
257    }
258    
259    /// Remove a tag
260    pub fn remove_tag(&mut self, tag: &str) -> bool {
261        let len = self.tags.len();
262        self.tags.retain(|t| t != tag);
263        let removed = self.tags.len() < len;
264        if removed {
265            self.updated_at = Utc::now();
266        }
267        removed
268    }
269    
270    /// Set health check path
271    pub fn set_health_check_path(&mut self, path: &str) {
272        self.health_check_path = Some(path.to_string());
273        self.updated_at = Utc::now();
274    }
275    
276    /// Update the instance weight
277    pub fn set_weight(&mut self, weight: u32) {
278        self.weight = weight;
279        self.updated_at = Utc::now();
280    }
281    
282    /// Update the metadata
283    pub fn update_metadata(&mut self, key: &str, value: &str) {
284        self.metadata.insert(key.to_string(), value.to_string());
285        self.updated_at = Utc::now();
286    }
287    
288    /// Set multiple metadata values at once
289    pub fn set_metadata(&mut self, metadata: HashMap<String, String>) {
290        self.metadata = metadata;
291        self.updated_at = Utc::now();
292    }
293}