1use std::collections::{HashMap, HashSet};
14use std::time::Duration;
15
16use async_trait::async_trait;
17use serde::{Deserialize, Serialize};
18
19use crate::adaptive::{NodeId, performance::PerformanceMonitor, trust::EigenTrustEngine};
20use crate::placement::{GeographicLocation, NetworkRegion, PlacementDecision, PlacementResult};
21
22#[async_trait]
24pub trait PlacementStrategy: Send + Sync + std::fmt::Debug {
25 async fn select_nodes(
27 &mut self,
28 candidates: &HashSet<NodeId>,
29 replication_factor: u8,
30 trust_system: &EigenTrustEngine,
31 performance_monitor: &PerformanceMonitor,
32 node_metadata: &HashMap<NodeId, (GeographicLocation, u32, NetworkRegion)>,
33 ) -> PlacementResult<PlacementDecision>;
34
35 fn name(&self) -> &str;
37
38 fn supports_constraints(&self) -> bool;
40}
41
42#[async_trait]
44pub trait NetworkTopology: Send + Sync {
45 async fn get_location(&self, node_id: &NodeId) -> Option<GeographicLocation>;
47
48 async fn get_region(&self, node_id: &NodeId) -> Option<NetworkRegion>;
50
51 async fn get_asn(&self, node_id: &NodeId) -> Option<u32>;
53
54 async fn network_distance(&self, node_a: &NodeId, node_b: &NodeId) -> Option<Duration>;
56
57 async fn same_network_segment(&self, node_a: &NodeId, node_b: &NodeId) -> bool;
59}
60
61#[async_trait]
63pub trait PerformanceEstimator: Send + Sync {
64 async fn get_metrics(&self, node_id: &NodeId) -> Option<NodePerformanceMetrics>;
66
67 async fn predict_availability(&self, node_id: &NodeId, period: Duration) -> f64;
69
70 async fn get_capacity_utilization(&self, node_id: &NodeId) -> f64;
72
73 async fn estimate_latency(&self, node_id: &NodeId) -> Duration;
75}
76
77#[async_trait]
79pub trait PlacementConstraint: Send + Sync {
80 async fn validate_node(&self, node_id: &NodeId) -> bool;
82
83 async fn validate_selection(&self, nodes: &[NodeId]) -> bool;
85
86 fn name(&self) -> &str;
88
89 fn severity(&self) -> u8;
91}
92
93#[async_trait]
95pub trait PlacementValidator: Send + Sync {
96 async fn validate(&self, decision: &PlacementDecision) -> PlacementResult<()>;
98
99 fn name(&self) -> &str;
101}
102
103#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
105pub struct NodePerformanceMetrics {
106 pub availability: f64,
108 pub latency_ms: f64,
110 pub bandwidth_bps: u64,
112 pub storage_capacity: u64,
114 pub storage_utilization: f64,
116 pub cpu_utilization: f64,
118 pub memory_utilization: f64,
120 pub network_reliability: f64,
122 pub churn_rate: f64,
124 pub node_age_hours: f64,
126}
127
128impl NodePerformanceMetrics {
129 pub fn new(
131 availability: f64,
132 latency_ms: f64,
133 bandwidth_bps: u64,
134 storage_capacity: u64,
135 storage_utilization: f64,
136 cpu_utilization: f64,
137 memory_utilization: f64,
138 network_reliability: f64,
139 churn_rate: f64,
140 node_age_hours: f64,
141 ) -> PlacementResult<Self> {
142 for (name, value) in [
144 ("availability", availability),
145 ("storage_utilization", storage_utilization),
146 ("cpu_utilization", cpu_utilization),
147 ("memory_utilization", memory_utilization),
148 ("network_reliability", network_reliability),
149 ] {
150 if !(0.0..=1.0).contains(&value) {
151 return Err(crate::placement::PlacementError::InvalidMetrics {
152 field: name.to_string(),
153 value,
154 reason: "Must be between 0.0 and 1.0".to_string(),
155 });
156 }
157 }
158
159 if latency_ms < 0.0 {
161 return Err(crate::placement::PlacementError::InvalidMetrics {
162 field: "latency_ms".to_string(),
163 value: latency_ms,
164 reason: "Must be non-negative".to_string(),
165 });
166 }
167
168 if churn_rate < 0.0 {
169 return Err(crate::placement::PlacementError::InvalidMetrics {
170 field: "churn_rate".to_string(),
171 value: churn_rate,
172 reason: "Must be non-negative".to_string(),
173 });
174 }
175
176 if node_age_hours < 0.0 {
177 return Err(crate::placement::PlacementError::InvalidMetrics {
178 field: "node_age_hours".to_string(),
179 value: node_age_hours,
180 reason: "Must be non-negative".to_string(),
181 });
182 }
183
184 Ok(Self {
185 availability,
186 latency_ms,
187 bandwidth_bps,
188 storage_capacity,
189 storage_utilization,
190 cpu_utilization,
191 memory_utilization,
192 network_reliability,
193 churn_rate,
194 node_age_hours,
195 })
196 }
197
198 pub fn overall_score(&self) -> f64 {
200 let weights = [
201 (self.availability, 0.3),
202 (1.0 - self.latency_ms / 1000.0, 0.2), (1.0 - self.storage_utilization, 0.1),
204 (1.0 - self.cpu_utilization, 0.1),
205 (1.0 - self.memory_utilization, 0.1),
206 (self.network_reliability, 0.1),
207 ((1.0 / (1.0 + self.churn_rate)), 0.1), ];
209
210 let weighted_sum: f64 = weights.iter().map(|(score, weight)| score * weight).sum();
211 weighted_sum.clamp(0.0, 1.0)
212 }
213
214 pub fn is_suitable_for_storage(&self) -> bool {
216 self.availability >= 0.8
217 && self.storage_utilization <= 0.9
218 && self.network_reliability >= 0.7
219 && self.churn_rate <= 2.0 }
221
222 pub fn remaining_capacity(&self) -> u64 {
224 ((1.0 - self.storage_utilization) * self.storage_capacity as f64) as u64
225 }
226}
227
228impl Default for NodePerformanceMetrics {
229 fn default() -> Self {
230 Self {
231 availability: 0.9,
232 latency_ms: 50.0,
233 bandwidth_bps: 1_000_000, storage_capacity: 10_000_000_000, storage_utilization: 0.5,
236 cpu_utilization: 0.3,
237 memory_utilization: 0.4,
238 network_reliability: 0.8,
239 churn_rate: 0.5,
240 node_age_hours: 168.0, }
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248
249 #[test]
250 fn test_node_performance_metrics_validation() {
251 let metrics = NodePerformanceMetrics::new(
253 0.9,
254 50.0,
255 1_000_000,
256 10_000_000_000,
257 0.5,
258 0.3,
259 0.4,
260 0.8,
261 0.5,
262 168.0,
263 );
264 assert!(metrics.is_ok());
265
266 let metrics = NodePerformanceMetrics::new(
268 1.5,
269 50.0,
270 1_000_000,
271 10_000_000_000,
272 0.5,
273 0.3,
274 0.4,
275 0.8,
276 0.5,
277 168.0,
278 );
279 assert!(metrics.is_err());
280
281 let metrics = NodePerformanceMetrics::new(
283 0.9,
284 -10.0,
285 1_000_000,
286 10_000_000_000,
287 0.5,
288 0.3,
289 0.4,
290 0.8,
291 0.5,
292 168.0,
293 );
294 assert!(metrics.is_err());
295 }
296
297 #[test]
298 fn test_overall_score_calculation() {
299 let metrics = NodePerformanceMetrics::new(
300 1.0,
301 10.0,
302 1_000_000,
303 10_000_000_000,
304 0.2,
305 0.1,
306 0.1,
307 0.9,
308 0.1,
309 168.0,
310 )
311 .unwrap();
312
313 let score = metrics.overall_score();
314 assert!(score >= 0.0 && score <= 1.0);
315 assert!(score > 0.8); }
317
318 #[test]
319 fn test_storage_suitability() {
320 let good_metrics = NodePerformanceMetrics::new(
322 0.95,
323 20.0,
324 1_000_000,
325 10_000_000_000,
326 0.3,
327 0.2,
328 0.2,
329 0.9,
330 0.1,
331 168.0,
332 )
333 .unwrap();
334 assert!(good_metrics.is_suitable_for_storage());
335
336 let poor_availability = NodePerformanceMetrics::new(
338 0.5,
339 20.0,
340 1_000_000,
341 10_000_000_000,
342 0.3,
343 0.2,
344 0.2,
345 0.9,
346 0.1,
347 168.0,
348 )
349 .unwrap();
350 assert!(!poor_availability.is_suitable_for_storage());
351
352 let full_storage = NodePerformanceMetrics::new(
354 0.95,
355 20.0,
356 1_000_000,
357 10_000_000_000,
358 0.95,
359 0.2,
360 0.2,
361 0.9,
362 0.1,
363 168.0,
364 )
365 .unwrap();
366 assert!(!full_storage.is_suitable_for_storage());
367 }
368
369 #[test]
370 fn test_remaining_capacity_calculation() {
371 let metrics = NodePerformanceMetrics::new(
372 0.9,
373 50.0,
374 1_000_000,
375 1_000_000_000,
376 0.3,
377 0.3,
378 0.4,
379 0.8,
380 0.5,
381 168.0,
382 )
383 .unwrap();
384
385 let remaining = metrics.remaining_capacity();
386 assert_eq!(remaining, 700_000_000); }
388}