Created
March 25, 2025 17:00
-
-
Save aderumier/ecfd441540f8528fe667f5361afda5c3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use std::collections::{HashMap, HashSet}; | |
| use std::fmt; | |
| use std::io::Write; | |
| use rand::Rng; | |
| use rand::seq::SliceRandom; | |
| use std::time::Instant; | |
| // Structure representing a Virtual Machine | |
| #[derive(Debug, Clone)] | |
| struct VM { | |
| id: String, | |
| cpu_req: f64, | |
| mem_req: f64, | |
| io_req: f64, | |
| host: Option<String>, | |
| initial_host: Option<String>, // Keep track of the initial host for migration purposes | |
| } | |
| impl VM { | |
| fn new(id: &str, cpu_req: f64, mem_req: f64, io_req: f64) -> Self { | |
| VM { | |
| id: id.to_string(), | |
| cpu_req, | |
| mem_req, | |
| io_req, | |
| host: None, | |
| initial_host: None, | |
| } | |
| } | |
| fn new_with_host(id: &str, cpu_req: f64, mem_req: f64, io_req: f64, host: &str) -> Self { | |
| VM { | |
| id: id.to_string(), | |
| cpu_req, | |
| mem_req, | |
| io_req, | |
| host: Some(host.to_string()), | |
| initial_host: Some(host.to_string()), | |
| } | |
| } | |
| fn get_id(&self) -> &str { | |
| &self.id | |
| } | |
| fn get_cpu_req(&self) -> f64 { | |
| self.cpu_req | |
| } | |
| fn get_mem_req(&self) -> f64 { | |
| self.mem_req | |
| } | |
| fn get_io_req(&self) -> f64 { | |
| self.io_req | |
| } | |
| fn set_host(&mut self, host: &str) { | |
| self.host = Some(host.to_string()); | |
| } | |
| fn get_host(&self) -> Option<&String> { | |
| self.host.as_ref() | |
| } | |
| fn get_initial_host(&self) -> Option<&String> { | |
| self.initial_host.as_ref() | |
| } | |
| } | |
| // Structure representing a Hypervisor | |
| #[derive(Debug)] | |
| struct Hypervisor { | |
| id: String, | |
| cpu_total: f64, | |
| mem_total: f64, | |
| io_capacity: f64, | |
| current_load: f64, | |
| vms: Vec<VM>, | |
| } | |
| impl Hypervisor { | |
| fn new(id: &str, cpu_total: f64, mem_total: f64, io_capacity: f64, current_load: f64) -> Self { | |
| Hypervisor { | |
| id: id.to_string(), | |
| cpu_total, | |
| mem_total, | |
| io_capacity, | |
| current_load, | |
| vms: Vec::new(), | |
| } | |
| } | |
| fn get_id(&self) -> &str { | |
| &self.id | |
| } | |
| fn get_cpu_total(&self) -> f64 { | |
| self.cpu_total | |
| } | |
| fn get_mem_total(&self) -> f64 { | |
| self.mem_total | |
| } | |
| fn get_io_capacity(&self) -> f64 { | |
| self.io_capacity | |
| } | |
| fn get_current_load(&self) -> f64 { | |
| self.current_load | |
| } | |
| fn get_cpu_avail(&self) -> f64 { | |
| self.cpu_total - self.current_load | |
| } | |
| fn add_vm(&mut self, mut vm: VM) { | |
| self.current_load += vm.get_cpu_req(); | |
| vm.set_host(&self.id); | |
| self.vms.push(vm); | |
| } | |
| // Initialize the hypervisor with a VM that's already assigned to it | |
| fn add_initial_vm(&mut self, mut vm: VM) { | |
| if vm.get_host().is_none() { | |
| vm.set_host(&self.id); | |
| } | |
| self.current_load += vm.get_cpu_req(); | |
| self.vms.push(vm); | |
| } | |
| fn get_vms(&self) -> &Vec<VM> { | |
| &self.vms | |
| } | |
| fn has_vm_with_id(&self, vm_id: &str) -> bool { | |
| self.vms.iter().any(|vm| vm.get_id() == vm_id) | |
| } | |
| fn calc_cpu_usage(&self) -> f64 { | |
| self.current_load / self.cpu_total | |
| } | |
| fn calc_mem_usage(&self) -> f64 { | |
| let mem_used: f64 = self.vms.iter().map(|vm| vm.get_mem_req()).sum(); | |
| mem_used / self.mem_total | |
| } | |
| fn calc_io_usage(&self) -> f64 { | |
| let io_used: f64 = self.vms.iter().map(|vm| vm.get_io_req()).sum(); | |
| io_used / self.io_capacity | |
| } | |
| } | |
| // Structure for constraint with weight | |
| #[derive(Debug, Clone)] | |
| struct WeightedConstraint { | |
| vm_id: String, | |
| weight: f64, | |
| } | |
| impl WeightedConstraint { | |
| fn new(vm_id: &str, weight: f64) -> Self { | |
| WeightedConstraint { | |
| vm_id: vm_id.to_string(), | |
| weight, | |
| } | |
| } | |
| } | |
| // Structure for placement constraint | |
| #[derive(Debug, Clone)] | |
| struct PlacementConstraint { | |
| allowed_hypervisors: Vec<String>, | |
| weight: f64, | |
| } | |
| impl PlacementConstraint { | |
| fn new(allowed_hypervisors: Vec<&str>, weight: f64) -> Self { | |
| PlacementConstraint { | |
| allowed_hypervisors: allowed_hypervisors.into_iter().map(|s| s.to_string()).collect(), | |
| weight, | |
| } | |
| } | |
| } | |
| // TOPSIS Load Balancer with soft constraints | |
| #[derive(Debug)] | |
| struct TOPSISLoadBalancer { | |
| hypervisors: Vec<Hypervisor>, | |
| weights: HashMap<String, f64>, | |
| criteria_benefit: HashMap<String, bool>, | |
| anti_affinity_constraints: HashMap<String, Vec<WeightedConstraint>>, | |
| affinity_constraints: HashMap<String, Vec<WeightedConstraint>>, | |
| placement_constraints: HashMap<String, PlacementConstraint>, | |
| constraint_weights: HashMap<String, f64>, | |
| strict_mode: bool, | |
| } | |
| impl TOPSISLoadBalancer { | |
| fn new(hypervisors: Vec<Hypervisor>) -> Self { | |
| let mut weights = HashMap::new(); | |
| weights.insert("cpu".to_string(), 0.3); | |
| weights.insert("mem".to_string(), 0.3); | |
| weights.insert("io".to_string(), 0.2); | |
| weights.insert("constraints".to_string(), 0.2); | |
| let mut criteria_benefit = HashMap::new(); | |
| criteria_benefit.insert("cpu".to_string(), false); | |
| criteria_benefit.insert("mem".to_string(), false); | |
| criteria_benefit.insert("io".to_string(), false); | |
| criteria_benefit.insert("constraints".to_string(), false); | |
| let mut constraint_weights = HashMap::new(); | |
| constraint_weights.insert("anti_affinity".to_string(), 5.0); | |
| constraint_weights.insert("affinity".to_string(), 5.0); | |
| constraint_weights.insert("placement".to_string(), 10.0); | |
| TOPSISLoadBalancer { | |
| hypervisors, | |
| weights, | |
| criteria_benefit, | |
| anti_affinity_constraints: HashMap::new(), | |
| affinity_constraints: HashMap::new(), | |
| placement_constraints: HashMap::new(), | |
| constraint_weights, | |
| strict_mode: false, | |
| } | |
| } | |
| fn set_weights(&mut self, cpu_weight: f64, mem_weight: f64, io_weight: f64, constraints_weight: f64) { | |
| self.weights.insert("cpu".to_string(), cpu_weight); | |
| self.weights.insert("mem".to_string(), mem_weight); | |
| self.weights.insert("io".to_string(), io_weight); | |
| self.weights.insert("constraints".to_string(), constraints_weight); | |
| } | |
| fn set_constraint_weights(&mut self, anti_affinity_weight: f64, affinity_weight: f64, placement_weight: f64) { | |
| self.constraint_weights.insert("anti_affinity".to_string(), anti_affinity_weight); | |
| self.constraint_weights.insert("affinity".to_string(), affinity_weight); | |
| self.constraint_weights.insert("placement".to_string(), placement_weight); | |
| } | |
| fn set_strict_mode(&mut self, strict: bool) { | |
| self.strict_mode = strict; | |
| } | |
| // Initialize the load balancer with VMs already assigned to hypervisors | |
| fn initialize_with_placement(&mut self, vms: &[VM]) { | |
| // First, we need to map each VM to its initial hypervisor | |
| let mut vm_map: HashMap<&str, Vec<VM>> = HashMap::new(); | |
| for vm in vms { | |
| if let Some(initial_host) = vm.get_initial_host() { | |
| vm_map.entry(initial_host).or_insert_with(Vec::new).push(vm.clone()); | |
| } | |
| } | |
| // Then assign each VM to its initial hypervisor | |
| for hyp in &mut self.hypervisors { | |
| let hyp_id = hyp.get_id(); | |
| if let Some(assigned_vms) = vm_map.get(hyp_id) { | |
| for vm in assigned_vms { | |
| hyp.add_initial_vm(vm.clone()); | |
| println!("VM {} initially placed on hypervisor {}", vm.get_id(), hyp_id); | |
| } | |
| } | |
| } | |
| } | |
| fn add_anti_affinity_constraint(&mut self, vm_id1: &str, vm_id2: &str, weight: Option<f64>) { | |
| let weight_value = weight.unwrap_or_else(|| *self.constraint_weights.get("anti_affinity").unwrap()); | |
| // Add constraint in first direction | |
| self.anti_affinity_constraints | |
| .entry(vm_id1.to_string()) | |
| .or_insert_with(Vec::new) | |
| .push(WeightedConstraint::new(vm_id2, weight_value)); | |
| // Add constraint in the other direction | |
| self.anti_affinity_constraints | |
| .entry(vm_id2.to_string()) | |
| .or_insert_with(Vec::new) | |
| .push(WeightedConstraint::new(vm_id1, weight_value)); | |
| } | |
| fn add_affinity_constraint(&mut self, vm_id1: &str, vm_id2: &str, weight: Option<f64>) { | |
| let weight_value = weight.unwrap_or_else(|| *self.constraint_weights.get("affinity").unwrap()); | |
| // Add constraint in first direction | |
| self.affinity_constraints | |
| .entry(vm_id1.to_string()) | |
| .or_insert_with(Vec::new) | |
| .push(WeightedConstraint::new(vm_id2, weight_value)); | |
| // Add constraint in the other direction | |
| self.affinity_constraints | |
| .entry(vm_id2.to_string()) | |
| .or_insert_with(Vec::new) | |
| .push(WeightedConstraint::new(vm_id1, weight_value)); | |
| } | |
| fn add_placement_constraint(&mut self, vm_id: &str, allowed_hypervisors: Vec<&str>, weight: Option<f64>) { | |
| let weight_value = weight.unwrap_or_else(|| *self.constraint_weights.get("placement").unwrap()); | |
| self.placement_constraints.insert( | |
| vm_id.to_string(), | |
| PlacementConstraint::new(allowed_hypervisors, weight_value) | |
| ); | |
| } | |
| fn calculate_constraint_score(&self, vm: &VM, hypervisor: &Hypervisor) -> f64 { | |
| let vm_id = vm.get_id(); | |
| let mut score = 0.0; | |
| // Simulate adding the VM to calculate projected resource usage | |
| let projected_cpu = hypervisor.calc_cpu_usage() + | |
| (vm.get_cpu_req() / hypervisor.get_cpu_total()); | |
| let mut mem_used = 0.0; | |
| for existing_vm in hypervisor.get_vms() { | |
| mem_used += existing_vm.get_mem_req(); | |
| } | |
| let projected_mem = (mem_used + vm.get_mem_req()) / hypervisor.get_mem_total(); | |
| // Check hard resource constraints first | |
| if hypervisor.get_cpu_avail() < vm.get_cpu_req() { | |
| return if self.strict_mode { -1000.0 } else { -10.0 }; | |
| } | |
| // Check maximum capacity constraint of 80% for CPU and memory | |
| let max_capacity = 0.8; // 80% | |
| if projected_cpu > max_capacity || projected_mem > max_capacity { | |
| if self.strict_mode { | |
| return -1000.0; // Very large penalty in strict mode (hard constraint) | |
| } | |
| // Add significant penalty for exceeding 80% threshold | |
| let mut penalty = 0.0; | |
| if projected_cpu > max_capacity { | |
| penalty += (projected_cpu - max_capacity) * 100.0; // More penalty for higher excess | |
| } | |
| if projected_mem > max_capacity { | |
| penalty += (projected_mem - max_capacity) * 100.0; | |
| } | |
| score -= penalty; | |
| } | |
| // Check placement constraints (which hypervisors are allowed) | |
| if let Some(constraint) = self.placement_constraints.get(vm_id) { | |
| let hyp_id = hypervisor.get_id(); | |
| let allowed = constraint.allowed_hypervisors.iter().any(|allowed_hyp_id| allowed_hyp_id == hyp_id); | |
| if !allowed { | |
| if self.strict_mode { | |
| return -1000.0; // Very large penalty in strict mode (hard constraint) | |
| } | |
| score -= constraint.weight; // Penalty in soft mode | |
| } else { | |
| score += constraint.weight * 0.5; // Reward for allowed hypervisor | |
| } | |
| } | |
| // Anti-affinity penalties (soft constraints) | |
| if let Some(constraints) = self.anti_affinity_constraints.get(vm_id) { | |
| for constraint in constraints { | |
| let conflicting_vm_id = &constraint.vm_id; | |
| let weight = constraint.weight; | |
| // Apply penalty if the conflicting VM is already on this hypervisor | |
| if hypervisor.has_vm_with_id(conflicting_vm_id) { | |
| if self.strict_mode { | |
| return -1000.0; // Very large penalty in strict mode (hard constraint) | |
| } | |
| score -= weight; // Subtract the penalty weight in soft mode | |
| } | |
| } | |
| } | |
| // Affinity rewards (soft constraints) | |
| if let Some(constraints) = self.affinity_constraints.get(vm_id) { | |
| for constraint in constraints { | |
| let affinity_vm_id = &constraint.vm_id; | |
| let weight = constraint.weight; | |
| // Check if the affinity VM is already placed | |
| for hyp in &self.hypervisors { | |
| if hyp.has_vm_with_id(affinity_vm_id) { | |
| if hyp.get_id() == hypervisor.get_id() { | |
| // The affinity VM is already on this hypervisor, add reward | |
| score += weight; | |
| } else { | |
| // The affinity VM is on a different hypervisor, add penalty | |
| if self.strict_mode { | |
| return -1000.0; // Very large penalty in strict mode (hard constraint) | |
| } | |
| score -= weight; // Penalty in soft mode | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| score | |
| } | |
| fn find_best_hypervisor_for_vm(&self, vm: &VM) -> Option<usize> { | |
| // Build the decision matrix including constraint scores | |
| let mut decision_matrix = Vec::new(); | |
| for (i, hyp) in self.hypervisors.iter().enumerate() { | |
| // Calculate constraint score | |
| let constraint_score = self.calculate_constraint_score(vm, hyp); | |
| // In strict mode, skip hypervisors with very negative constraint scores | |
| if self.strict_mode && constraint_score <= -1000.0 { | |
| continue; | |
| } | |
| // Add to decision matrix | |
| decision_matrix.push(( | |
| i, // hypervisor index | |
| [ | |
| ("cpu".to_string(), hyp.calc_cpu_usage()), | |
| ("mem".to_string(), hyp.calc_mem_usage()), | |
| ("io".to_string(), hyp.calc_io_usage()), | |
| ("constraints".to_string(), -constraint_score), // Negative because lower is better in TOPSIS | |
| ].iter().cloned().collect::<HashMap<String, f64>>() | |
| )); | |
| } | |
| // If no suitable hypervisor found | |
| if decision_matrix.is_empty() { | |
| println!("No suitable hypervisor for VM {} (resources or constraints)", vm.get_id()); | |
| return None; | |
| } | |
| // If only one suitable hypervisor found | |
| if decision_matrix.len() == 1 { | |
| return Some(decision_matrix[0].0); | |
| } | |
| // Normalize the decision matrix | |
| let norm_matrix = self.normalize_matrix(&decision_matrix); | |
| // Apply weights | |
| let weighted_matrix = self.apply_weights(&norm_matrix); | |
| // Determine the positive and negative ideal solutions | |
| let (positive_ideal, negative_ideal) = self.find_ideal_solutions(&weighted_matrix); | |
| // Calculate distances for each hypervisor | |
| let mut distances = Vec::new(); | |
| for (hyp_idx, criteria) in &weighted_matrix { | |
| let d_positive = self.calculate_distance(criteria, &positive_ideal); | |
| let d_negative = self.calculate_distance(criteria, &negative_ideal); | |
| let closeness = d_negative / (d_positive + d_negative); | |
| distances.push((*hyp_idx, closeness)); | |
| } | |
| // Sort by closeness score (highest to lowest) | |
| distances.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); | |
| // Return the hypervisor with the best score | |
| if !distances.is_empty() { | |
| Some(distances[0].0) | |
| } else { | |
| None | |
| } | |
| } | |
| fn normalize_matrix(&self, matrix: &[(usize, HashMap<String, f64>)]) -> Vec<(usize, HashMap<String, f64>)> { | |
| let mut denominators = HashMap::new(); | |
| // Calculate the square roots of the sums of squares for each criterion | |
| for criterion in self.weights.keys() { | |
| let sum_of_squares: f64 = matrix.iter() | |
| .map(|(_, criteria)| criteria.get(criterion).unwrap_or(&0.0).powi(2)) | |
| .sum(); | |
| denominators.insert(criterion.clone(), sum_of_squares.sqrt()); | |
| } | |
| // Normalize | |
| let mut normalized = Vec::new(); | |
| for (hyp_idx, criteria) in matrix { | |
| let mut normalized_criteria = HashMap::new(); | |
| for (criterion, value) in criteria { | |
| let denominator = denominators.get(criterion).unwrap_or(&1.0); | |
| if *denominator > 0.0 { | |
| normalized_criteria.insert(criterion.clone(), value / denominator); | |
| } else { | |
| normalized_criteria.insert(criterion.clone(), 0.0); | |
| } | |
| } | |
| normalized.push((*hyp_idx, normalized_criteria)); | |
| } | |
| normalized | |
| } | |
| fn apply_weights(&self, norm_matrix: &[(usize, HashMap<String, f64>)]) -> Vec<(usize, HashMap<String, f64>)> { | |
| let mut weighted = Vec::new(); | |
| for (hyp_idx, criteria) in norm_matrix { | |
| let mut weighted_criteria = HashMap::new(); | |
| for (criterion, value) in criteria { | |
| if let Some(weight) = self.weights.get(criterion) { | |
| weighted_criteria.insert(criterion.clone(), value * weight); | |
| } | |
| } | |
| weighted.push((*hyp_idx, weighted_criteria)); | |
| } | |
| weighted | |
| } | |
| fn find_ideal_solutions(&self, weighted_matrix: &[(usize, HashMap<String, f64>)]) -> (HashMap<String, f64>, HashMap<String, f64>) { | |
| let mut positive_ideal = HashMap::new(); | |
| let mut negative_ideal = HashMap::new(); | |
| // Initialize with extreme values | |
| for criterion in self.weights.keys() { | |
| let is_benefit = *self.criteria_benefit.get(criterion).unwrap_or(&false); | |
| positive_ideal.insert(criterion.clone(), if is_benefit { f64::MIN } else { f64::MAX }); | |
| negative_ideal.insert(criterion.clone(), if is_benefit { f64::MAX } else { f64::MIN }); | |
| } | |
| // Find the ideal values | |
| for (_, criteria) in weighted_matrix { | |
| for (criterion, value) in criteria { | |
| let is_benefit = *self.criteria_benefit.get(criterion).unwrap_or(&false); | |
| if is_benefit { | |
| // For benefit (larger = better) | |
| if let Some(current) = positive_ideal.get_mut(criterion) { | |
| *current = current.max(*value); | |
| } | |
| if let Some(current) = negative_ideal.get_mut(criterion) { | |
| *current = current.min(*value); | |
| } | |
| } else { | |
| // For cost (smaller = better) | |
| if let Some(current) = positive_ideal.get_mut(criterion) { | |
| *current = current.min(*value); | |
| } | |
| if let Some(current) = negative_ideal.get_mut(criterion) { | |
| *current = current.max(*value); | |
| } | |
| } | |
| } | |
| } | |
| (positive_ideal, negative_ideal) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment