From c70e1796f372507e8693c236639df9385bbf20c2 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 8 Dec 2024 19:37:35 -0500 Subject: [PATCH] [skip ci] checkpoint --- .../virtual_network/router_server/config.rs | 152 +++-- .../machine_registry_inner.rs | 519 ++++++++++++------ .../router_server/machine_registry/mod.rs | 1 + .../machine_registry/state/blueprint_state.rs | 62 ++- .../machine_registry/state/machine_state.rs | 85 +-- .../machine_registry/state/network_state.rs | 202 +++++-- .../machine_registry/state/resolves_to.rs | 10 + .../machine_registry/state/template_state.rs | 36 +- 8 files changed, 730 insertions(+), 337 deletions(-) diff --git a/veilid-tools/src/virtual_network/router_server/config.rs b/veilid-tools/src/virtual_network/router_server/config.rs index 3f4d76b0..48a4644c 100644 --- a/veilid-tools/src/virtual_network/router_server/config.rs +++ b/veilid-tools/src/virtual_network/router_server/config.rs @@ -103,6 +103,33 @@ impl WeightedList { } } } + + pub fn try_filter(&self, mut filter: F) -> Result>, E> + where + F: FnMut(&T) -> Result, + { + match self { + WeightedList::Single(v) => { + if filter(v)? { + return Ok(Some(self.clone())); + } + return Ok(None); + } + WeightedList::List(vec) => { + let mut out = Vec::>::with_capacity(vec.len()); + for v in vec { + if filter(v.item())? { + out.push(v.clone()); + } + } + if out.is_empty() { + Ok(None) + } else { + Ok(Some(WeightedList::List(out))) + } + } + } + } } pub type Probability = f32; @@ -174,7 +201,7 @@ pub enum Instance { )] pub struct Machine { #[serde(flatten)] - #[validate(custom(function = "validate_machine_location_exists", use_context))] + #[validate(custom(function = "validate_machine_location", use_context))] pub location: MachineLocation, #[serde(default)] pub disable_capabilities: Vec, @@ -183,10 +210,6 @@ pub struct Machine { } fn validate_machine(machine: &Machine, _context: &ValidateContext) -> Result<(), ValidationError> { - if machine.address4.is_none() && machine.address6.is_none() { - return Err(ValidationError::new("badaddr") - .with_message("machine must have at least one address".into())); - } if machine.disable_capabilities.contains(&("".to_string())) { return Err(ValidationError::new("badcap") .with_message("machine has empty disabled capability".into())); @@ -201,11 +224,11 @@ fn validate_machine(machine: &Machine, _context: &ValidateContext) -> Result<(), )] pub struct Template { #[serde(flatten)] - #[validate(custom(function = "validate_template_location_exists", use_context))] + #[validate(custom(function = "validate_template_location", use_context))] pub location: TemplateLocation, #[serde(flatten)] #[validate(nested)] - pub limits: Limits, + pub limits: TemplateLimits, #[serde(default)] pub disable_capabilities: Vec, } @@ -222,17 +245,17 @@ fn validate_template( } #[derive(Debug, Clone, Serialize, Deserialize, Validate)] -#[validate(schema(function = "validate_limits"))] -pub struct Limits { +#[validate(schema(function = "validate_template_limits"))] +pub struct TemplateLimits { #[validate(nested)] pub machine_count: WeightedList, } -fn validate_limits(limits: &Limits) -> Result<(), ValidationError> { +fn validate_template_limits(limits: &TemplateLimits) -> Result<(), ValidationError> { limits.machine_count.try_for_each(|x| { if *x == 0 { return Err(ValidationError::new("badcount") - .with_message("limits has zero machine count".into())); + .with_message("template limits has zero machine count".into())); } Ok(()) })?; @@ -250,12 +273,27 @@ pub enum MachineLocation { #[serde(default)] address6: Option, }, - Network { - network: WeightedList, - }, - Blueprint { - blueprint: WeightedList, - }, +} + +fn validate_machine_location( + value: &MachineLocation, + context: &ValidateContext, +) -> Result<(), ValidationError> { + match value { + MachineLocation::Specific { + network, + address4, + address6, + } => { + if address4.is_none() && address6.is_none() { + return Err(ValidationError::new("badaddr") + .with_message("machine must have at least one address".into())); + } + validate_network_exists(network, context)?; + } + } + + Ok(()) } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -265,6 +303,22 @@ pub enum TemplateLocation { Blueprint { blueprint: WeightedList }, } +fn validate_template_location( + value: &TemplateLocation, + context: &ValidateContext, +) -> Result<(), ValidationError> { + match value { + TemplateLocation::Network { network } => { + network.try_for_each(|m| validate_network_exists(m, context))?; + } + TemplateLocation::Blueprint { blueprint } => { + blueprint.try_for_each(|t| validate_blueprint_exists(t, context))?; + } + } + + Ok(()) +} + //////////////////////////////////////////////////////////////// #[derive(Debug, Clone, Serialize, Deserialize, Validate)] @@ -355,6 +409,8 @@ pub struct Blueprint { #[serde(default)] #[validate(custom(function = "validate_models_exist", use_context))] pub model: Option>, + #[validate(nested)] + pub limits: BlueprintLimits, #[serde(default)] #[validate(custom(function = "validate_blueprint_ipv4", use_context))] pub ipv4: Option, @@ -374,6 +430,25 @@ fn validate_blueprint( Ok(()) } +#[derive(Debug, Clone, Serialize, Deserialize, Validate)] +#[validate(schema(function = "validate_blueprint_limits"))] +pub struct BlueprintLimits { + #[validate(nested)] + pub network_count: WeightedList, +} + +fn validate_blueprint_limits(limits: &BlueprintLimits) -> Result<(), ValidationError> { + limits.network_count.try_for_each(|x| { + if *x == 0 { + return Err(ValidationError::new("badcount") + .with_message("blueprint limits has zero machine count".into())); + } + Ok(()) + })?; + + Ok(()) +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BlueprintIpv4 { #[serde(default)] @@ -635,49 +710,6 @@ fn validate_instances_exist( Ok(()) } -fn validate_machine_location_exists( - value: &MachineLocation, - context: &ValidateContext, -) -> Result<(), ValidationError> { - match value { - MachineLocation::Specific { - network, - address4, - address6, - } => { - if address4.is_none() && address6.is_none() { - return Err(ValidationError::new("badaddr") - .with_message("machine must have at least one address".into())); - } - validate_network_exists(network, context)?; - } - MachineLocation::Network { network } => { - network.try_for_each(|n| validate_network_exists(n, context))?; - } - MachineLocation::Blueprint { blueprint } => { - blueprint.try_for_each(|b| validate_blueprint_exists(b, context))?; - } - } - - Ok(()) -} - -fn validate_template_location_exists( - value: &TemplateLocation, - context: &ValidateContext, -) -> Result<(), ValidationError> { - match value { - TemplateLocation::Network { network } => { - network.try_for_each(|m| validate_network_exists(m, context))?; - } - TemplateLocation::Blueprint { blueprint } => { - blueprint.try_for_each(|t| validate_blueprint_exists(t, context))?; - } - } - - Ok(()) -} - fn validate_network_exists(value: &str, context: &ValidateContext) -> Result<(), ValidationError> { if !context.config.networks.contains_key(value) { return Err(ValidationError::new("noexist").with_message("network does not exist".into())); diff --git a/veilid-tools/src/virtual_network/router_server/machine_registry/machine_registry_inner.rs b/veilid-tools/src/virtual_network/router_server/machine_registry/machine_registry_inner.rs index f6834e11..5c4785a6 100644 --- a/veilid-tools/src/virtual_network/router_server/machine_registry/machine_registry_inner.rs +++ b/veilid-tools/src/virtual_network/router_server/machine_registry/machine_registry_inner.rs @@ -54,24 +54,23 @@ impl MachineRegistryInner { .or_insert_with(|| ProfileState::default()); // Get the next instance from the definition - let Some(instance_def) = profile_def - .instances - .get(profile_state.next_instance_index) + let Some(instance_def) = profile_def.instances.get(profile_state.next_instance_index) else { // return Err(MachineRegistryError::ProfileComplete); }; - match instance_def { + let machine_state = match instance_def { config::Instance::Machine { machine } => { let machine = self.unlocked_inner.srng.weighted_choice(machine); let unlocked_inner = self.unlocked_inner.clone(); let machine_def = unlocked_inner .config .machines - .get(machine).cloned() + .get(machine) + .cloned() .expect("config validation is broken"); - self.get_or_create_machine_state_id(machine.clone(), machine_def) + self.get_or_create_machine_state(machine.clone(), machine_def)? } config::Instance::Template { template } => { let template = self.unlocked_inner.srng.weighted_choice(template); @@ -79,31 +78,36 @@ impl MachineRegistryInner { let template_def = unlocked_inner .config .templates - .get(template).cloned() + .get(template) + .cloned() .expect("config validation is broken"); - self.create_machine_state_from_template(template.clone(), template_def) + self.get_or_create_machine_state_from_template(template.clone(), template_def)? } - } + }; + Ok(machine_state.id()) } pub fn release(&self, machine_id: MachineId) -> MachineRegistryResult<()> { // xxx + // xxx remember machines and networks may not be 'named' if they are generated by templates and blueprints Ok(()) } - /////////////////////////////////////////////////////////// /// Private Implementation - pub(super) fn get_or_create_machine_state_id( + pub(super) fn get_or_create_machine_state( &mut self, name: String, machine_def: config::Machine, - ) -> MachineRegistryResult { - + ) -> MachineRegistryResult { // Ensure we don't already have this machine created (name must be unique) if let Some(machine_id) = self.resolve_to_manager_machine.add(name.clone()).get() { - return Ok(machine_id); + return Ok(self + .machine_state_by_id + .get(&machine_id) + .cloned() + .expect("must exist")); } // Allocate a machine id @@ -114,9 +118,211 @@ impl MachineRegistryInner { }); // Create a new machine state - let machine_state = match MachineState::try_new(self, + let machine_state = match MachineState::try_new( + self, + machine_id, MachineStateName::Machine(name.clone()), machine_def.clone(), + ) { + Ok(v) => v, + Err(e) => { + // Release the machine id + self.free_machine_ids.push(machine_id); + return Err(e); + } + }; + + // Store the machine state with its unique id + self.machine_state_by_id.insert(machine_id, machine_state); + + // Bind the name to the id + self.resolve_to_manager_machine + .resolve(&name, machine_id) + .expect("must resolve"); + + // Return the state + Ok(self + .machine_state_by_id + .get(&machine_id) + .cloned() + .expect("must exist")) + } + + pub(super) fn get_machine_state_by_id( + &mut self, + machine_id: MachineId, + ) -> MachineRegistryResult<&mut MachineState> { + self.machine_state_by_id + .get_mut(&machine_id) + .ok_or_else(|| MachineRegistryError::MachineNotFound) + } + + pub(super) fn get_or_create_network_state( + &mut self, + name: String, + network_def: config::Network, + ) -> MachineRegistryResult { + // Ensure we don't already have this network created (name must be unique) + if let Some(network_id) = self.resolve_to_manager_network.add(name.clone()).get() { + return Ok(self + .network_state_by_id + .get(&network_id) + .cloned() + .expect("must exist")); + } + + // Allocate a network id + let network_id = self.free_network_ids.pop().unwrap_or_else(|| { + let x = self.next_network_id; + self.next_network_id += 1; + x + }); + + // Create a new network state + let network_state = match NetworkState::try_new( + self, + network_id, + NetworkStateName::Network(name.clone()), + network_def.clone(), + ) { + Ok(v) => v, + Err(e) => { + // Release the network id + self.free_network_ids.push(network_id); + return Err(e); + } + }; + + // Store the network state with its unique id + self.network_state_by_id.insert(network_id, network_state); + + // Bind the name to the id + self.resolve_to_manager_network + .resolve(&name, network_id) + .expect("must resolve"); + + // Return the unique id + Ok(self + .network_state_by_id + .get(&network_id) + .cloned() + .expect("must exist")) + } + + pub(super) fn get_network_state_by_name(&self, name: &String) -> Option { + let network_id = self.resolve_to_manager_network.get(name)?; + self.network_state_by_id.get(&network_id).cloned() + } + + pub(super) fn get_network_state_by_id( + &self, + network_id: NetworkId, + ) -> MachineRegistryResult { + self.network_state_by_id + .get(&network_id) + .cloned() + .ok_or_else(|| MachineRegistryError::NetworkNotFound) + } + + pub(super) fn get_or_create_template_state( + &mut self, + name: &String, + template_def: config::Template, + ) -> MachineRegistryResult<&mut TemplateState> { + // Ensure we don't already have this template created (name must be unique) + if self.template_state_by_name.contains_key(name) { + return Ok(self + .template_state_by_name + .get_mut(name) + .expect("must exist")); + } + + // Create a new template state + let template_state = match TemplateState::try_new(self, name.clone(), template_def.clone()) + { + Ok(v) => v, + Err(e) => { + return Err(e); + } + }; + + // Store the template state with its name + self.template_state_by_name + .insert(name.clone(), template_state); + Ok(self + .template_state_by_name + .get_mut(name) + .expect("must exist")) + } + + pub(super) fn get_or_create_machine_state_from_template( + &mut self, + name: String, + template_def: config::Template, + ) -> MachineRegistryResult { + // Make machine def from current template state + let machine_def = { + // Get the active template state + let template_state = self.get_or_create_template_state(&name, template_def)?; + if !template_state.is_active()? { + return Err(MachineRegistryError::TemplateComplete); + } + + // Pick or instantiate an available network + xxx add 'def()' selector to all types + let active_networks = match template_state.template_def.location.clone() { + config::TemplateLocation::Network { network } => { + // Filter the weighted list of networks to those that are still active or not yet started and can allocate + let Some(active_networks) = network.try_filter(|n| { + self.get_network_state_by_name(&n) + .map(|ns| ns.is_active()) + .unwrap_or(Ok(true)) + })? + else { + return Err(MachineRegistryError::NetworkComplete); + }; + } + config::TemplateLocation::Blueprint { blueprint } => { + // Filter the weighted list of blueprints to those that are still active or not yet started and can allocate + let Some(active_blueprints) = blueprint.try_filter(|b| { + self.get_blueprint_state(&b) + .map(|bs| bs.is_active(self)) + .unwrap_or(Ok(true)) + })? + else { + return Err(MachineRegistryError::BlueprintComplete); + }; + + // Activate some blueprint and pick a network + } + }; + + // Weighted choice of network now that we have a candidate list + //let network = + + config::Machine { + location: config::MachineLocation::Specific { + network: todo!(), + address4: None, + address6: None, + }, + disable_capabilities: template_state.template_def.disable_capabilities.clone(), + bootstrap: false, + } + }; + + // Allocate a machine id + let machine_id = self.free_machine_ids.pop().unwrap_or_else(|| { + let x = self.next_machine_id; + self.next_machine_id += 1; + x + }); + + // Create a new machine state + let machine_state = match MachineState::try_new( + self, + MachineStateName::Template(name.clone()), + machine_def.clone(), machine_id, ) { Ok(v) => v, @@ -130,172 +336,82 @@ impl MachineRegistryInner { // Store the machine state with its unique id self.machine_state_by_id.insert(machine_id, machine_state); - // Bind the name to the id - self.resolve_to_manager_machine.resolve(&name, machine_id).expect("must resolve"); + // Add to machines for this template + { + let template_state = self.get_template_state(&name).expect("must exist"); + template_state.machines.insert(machine_id); + } // Return the unique id Ok(machine_id) } - - pub(super) fn get_machine_state_by_id( - &mut self, - machine_id: MachineId, - ) -> MachineRegistryResult<&mut MachineState> { - self - .machine_state_by_id - .get_mut(&machine_id).ok_or_else(|| MachineRegistryError::MachineNotFound) - } - - pub(super) fn get_or_create_network_state_id( - &mut self, - name: String, - network_def: config::Network, - ) -> MachineRegistryResult { - - // Ensure we don't already have this network created (name must be unique) - if let Some(network_id) = self.resolve_to_manager_network.add(name.clone()).get() { - return Ok(network_id); - } - - // Allocate a network id - let network_id = self.free_network_ids.pop().unwrap_or_else(|| { - let x = self.next_network_id; - self.next_network_id += 1; - x - }); - - // Create a new network state - let network_state = match NetworkState::try_new(self, - NetworkStateName::Network(name.clone()), - network_def.clone(), - network_id, - ){ - Ok(v) => v, - Err(e) => { - // Release the network id - self.free_network_ids.push(network_id); - return Err(e); - } - }; - - // Store the network state with its unique id - self.network_state_by_id.insert(network_id, network_state); - - // Bind the name to the id - self.resolve_to_manager_network.resolve(&name, network_id).expect("must resolve"); - - // Return the unique id - Ok(network_id) - } - - pub(super) fn get_network_state_by_id ( - &mut self, - network_id: NetworkId, - ) -> MachineRegistryResult<&mut NetworkState> { - self - .network_state_by_id - .get_mut(&network_id).ok_or_else(|| MachineRegistryError::NetworkNotFound) - } - - pub(super) fn get_or_create_template_state( - &mut self, - name: &String, - template_def: config::Template, - ) -> MachineRegistryResult<&mut TemplateState> { - - // Ensure we don't already have this template created (name must be unique) - if self.template_state_by_name.contains_key(name) { - return Ok(self.template_state_by_name.get_mut(name).expect("must exist")); - } - - // Create a new template state - let template_state = match TemplateState::try_new(self, - name.clone(), - template_def.clone(), - ) { - Ok(v) => v, - Err(e) => { - return Err(e); - } - }; - - // Store the template state with its name - self.template_state_by_name.insert(name.clone(), template_state); - Ok(self.template_state_by_name.get_mut(name).expect("must exist")) - } - - pub(super) fn create_machine_state_from_template( - &mut self, - name: String, - template_def: config::Template, - ) -> MachineRegistryResult { - - // Get the active template state - let template_state = self.get_or_create_template_state(&name, template_def)?; - if !template_state.is_active() { - return Err(MachineRegistryError::TemplateComplete); - } - - // Make machine def from current template state - let machine_def = config::Machine { - location: template_state.template_def.location.clone(), - address4: template_state.template_def. - address6: todo!(), - disable_capabilities: todo!(), - bootstrap: todo!(), - }; - - } pub(super) fn get_template_state( &mut self, name: &String, ) -> MachineRegistryResult<&mut TemplateState> { - self - .template_state_by_name - .get_mut(name).ok_or_else(|| MachineRegistryError::TemplateNotFound) + self.template_state_by_name + .get_mut(name) + .ok_or_else(|| MachineRegistryError::TemplateNotFound) } - pub(super) fn get_or_create_network_state_from_location( + pub(super) fn get_or_create_network_state_from_machine_location( &mut self, - location_def: &config::Location, + machine_location: &config::MachineLocation, + ) -> MachineRegistryResult { + match machine_location { + config::MachineLocation::Specific { + network: name, + address4: _, + address6: _, + } => { + let network_def = self + .unlocked_inner + .config + .networks + .get(name) + .cloned() + .expect("config validation is broken"); + self.get_or_create_network_state(name.clone(), network_def) + } + } + } + pub(super) fn get_or_create_network_state_from_template_location( + &mut self, + template_location: &config::TemplateLocation, ) -> MachineRegistryResult { - match location_def { - config::Location::Network { network } => { + match template_location { + config::TemplateLocation::Network { network } => { let name = self.unlocked_inner.srng.weighted_choice(network); let network_def = self .unlocked_inner .config .networks - .get(name).cloned() + .get(name) + .cloned() .expect("config validation is broken"); - self.get_or_create_network_state( - name.clone(), - network_def, - ) + self.get_or_create_network_state(name.clone(), network_def) } - config::Location::Blueprint { blueprint } => { + config::TemplateLocation::Blueprint { blueprint } => { let name = self.unlocked_inner.srng.weighted_choice(blueprint); let blueprint_def = self .unlocked_inner .config .blueprints - .get(name).cloned() + .get(name) + .cloned() .expect("config validation is broken"); - self.get_or_create_network_state_from_blueprint( - name.clone(), - blueprint_def) + self.get_or_create_network_state_from_blueprint(name.clone(), blueprint_def) } } } - pub(super) fn get_blueprint_state( &mut self, name: &String, ) -> MachineRegistryResult<&mut BlueprintState> { - self - .blueprint_state_by_name - .get_mut(name).ok_or_else(|| MachineRegistryError::BlueprintNotFound) + self.blueprint_state_by_name + .get_mut(name) + .ok_or_else(|| MachineRegistryError::BlueprintNotFound) } pub(super) fn choose_allocation_v4( @@ -379,39 +495,92 @@ impl MachineRegistryInner { // No available allocations left Err(MachineRegistryError::NoAllocation) } + + pub(super) fn get_or_create_blueprint_state( + &mut self, + name: &String, + blueprint_def: config::Blueprint, + ) -> MachineRegistryResult { + // Ensure we don't already have this blueprint created (name must be unique) + if self.blueprint_state_by_name.contains_key(name) { + return Ok(self + .blueprint_state_by_name + .get(name) + .cloned() + .expect("must exist")); + } + + // Create a new blueprint state + let blueprint_state = + match BlueprintState::try_new(self, name.clone(), blueprint_def.clone()) { + Ok(v) => v, + Err(e) => { + return Err(e); + } + }; + + // Store the blueprint state with its name + self.blueprint_state_by_name + .insert(name.clone(), blueprint_state); + Ok(self + .blueprint_state_by_name + .get(name) + .cloned() + .expect("must exist")) + } + pub(super) fn get_or_create_network_state_from_blueprint( &mut self, name: String, blueprint_def: config::Blueprint, ) -> MachineRegistryResult { + // Get the active blueprint state + let blueprint_state = self.get_or_create_blueprint_state(&name, blueprint_def)?; + if !blueprint_state.is_active(self)? { + return Err(MachineRegistryError::BlueprintComplete); + } - xxx - - self.with_blueprint_state( - name, - blueprint_def, - |blueprint_state| { - // Make network def from current blueprint state - let network_def = config::Network { - model: blueprint_state - .blueprint_def - .model - .as_ref() - .map(|model| self.unlocked_inner.srng.weighted_choice(model).clone()), - ipv4: blueprint_state - .blueprint_def - .ipv4 - .as_ref() - .map(|bpv4| self.unlocked_inner.srng.weighted_choice(bpv4).clone()), - ipv6: blueprint_def - .ipv6 - .as_ref() - .map(|bpv6| self.unlocked_inner.srng.weighted_choice(bpv6).clone()), - }; - - //xxx self. + //xxx + // Make machine def from current template state + let machine_def = config::Machine { + location: match template_state.template_def.location.clone() { + config::TemplateLocation::Network { network } => { + config::MachineLocation::Network { network } + } + config::TemplateLocation::Blueprint { blueprint } => { + config::MachineLocation::Blueprint { blueprint } + } }, - ) - } + disable_capabilities: template_state.template_def.disable_capabilities.clone(), + bootstrap: false, + }; + // Allocate a machine id + let machine_id = self.free_machine_ids.pop().unwrap_or_else(|| { + let x = self.next_machine_id; + self.next_machine_id += 1; + x + }); + + // Create a new machine state + let machine_state = match MachineState::try_new( + self, + MachineStateName::Template(name.clone()), + machine_def.clone(), + machine_id, + ) { + Ok(v) => v, + Err(e) => { + // Release the machine id + self.free_machine_ids.push(machine_id); + return Err(e); + } + }; + + // Store the machine state with its unique id + self.machine_state_by_id.insert(machine_id, machine_state); + + // Return the unique id + Ok(machine_id) + } } diff --git a/veilid-tools/src/virtual_network/router_server/machine_registry/mod.rs b/veilid-tools/src/virtual_network/router_server/machine_registry/mod.rs index 4711a5a6..e82e6659 100644 --- a/veilid-tools/src/virtual_network/router_server/machine_registry/mod.rs +++ b/veilid-tools/src/virtual_network/router_server/machine_registry/mod.rs @@ -25,6 +25,7 @@ pub enum MachineRegistryError { ProfileNotFound, ProfileComplete, TemplateComplete, + NetworkComplete, BlueprintComplete, MachineNotFound, NetworkNotFound, diff --git a/veilid-tools/src/virtual_network/router_server/machine_registry/state/blueprint_state.rs b/veilid-tools/src/virtual_network/router_server/machine_registry/state/blueprint_state.rs index f205b9a0..e9f3adec 100644 --- a/veilid-tools/src/virtual_network/router_server/machine_registry/state/blueprint_state.rs +++ b/veilid-tools/src/virtual_network/router_server/machine_registry/state/blueprint_state.rs @@ -1,7 +1,63 @@ use super::*; #[derive(Debug)] -pub struct BlueprintState { - pub name: String, - pub blueprint_def: config::Blueprint, +struct BlueprintStateUnlockedInner { + name: String, + blueprint_def: config::Blueprint, +} + +#[derive(Debug)] +struct BlueprintStateInner { + limit_network_count: u32, + networks: HashSet, +} + +#[derive(Debug, Clone)] +pub struct BlueprintState { + unlocked_inner: Arc, + inner: Arc>, +} + +impl BlueprintState { + pub fn try_new( + machine_registry_inner: &mut MachineRegistryInner, + name: String, + blueprint_def: config::Blueprint, + ) -> MachineRegistryResult { + let limit_network_count = *machine_registry_inner + .unlocked_inner + .srng + .weighted_choice(&blueprint_def.limits.network_count); + + Ok(Self { + unlocked_inner: Arc::new(BlueprintStateUnlockedInner { + name, + blueprint_def, + }), + inner: Arc::new(Mutex::new(BlueprintStateInner { + limit_network_count, + networks: HashSet::new(), + })), + }) + } + + pub fn is_active( + &self, + machine_registry_inner: &MachineRegistryInner, + ) -> MachineRegistryResult { + let inner = self.inner.lock(); + + // Check to see if any of our networks are still active, if so the blueprint is still active + for network_id in &inner.networks { + let network_state = machine_registry_inner + .get_network_state_by_id(*network_id) + .expect("must exist"); + if network_state.is_active()? { + return Ok(true); + } + } + + // If no existing networks are active, then we see if there's room for another + Ok(inner.networks.len() < inner.limit_network_count.try_into().unwrap_or(usize::MAX)) + } } diff --git a/veilid-tools/src/virtual_network/router_server/machine_registry/state/machine_state.rs b/veilid-tools/src/virtual_network/router_server/machine_registry/state/machine_state.rs index 118895c5..cb1a46ba 100644 --- a/veilid-tools/src/virtual_network/router_server/machine_registry/state/machine_state.rs +++ b/veilid-tools/src/virtual_network/router_server/machine_registry/state/machine_state.rs @@ -1,36 +1,48 @@ use super::*; -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum MachineStateName { Machine(String), Template(String), } #[derive(Debug)] -pub struct MachineState { - /// The name of this machine state if it was made directly - /// or the name of the template used to create it - pub name: MachineStateName, - /// The definition this machine was created with - pub machine_def: config::Machine, +struct MachineStateInner { /// The current network interfaces definition - pub interfaces: Vec, + interfaces: Vec, } #[derive(Debug)] pub struct MachineStateInterface { - /// The network id + /// The network this interface belongs to pub network_id: NetworkId, /// The veilid NetworkInterface state pub network_interface: NetworkInterface, } +#[derive(Debug)] +struct MachineStateUnlockedInner { + /// The id of this machine + id: MachineId, + /// The name of this machine state if it was made directly + /// or the name of the template used to create it + name: MachineStateName, + /// The definition this machine was created with + machine_def: config::Machine, +} + +#[derive(Debug, Clone)] +pub struct MachineState { + unlocked_inner: Arc, + inner: Arc>, +} + impl MachineState { pub fn try_new( machine_registry_inner: &mut MachineRegistryInner, + id: MachineId, name: MachineStateName, machine_def: config::Machine, - machine_id: MachineId, ) -> MachineRegistryResult { // Build list of machinestate interfaces let mut interfaces = Vec::::new(); @@ -38,42 +50,31 @@ impl MachineState { // Make default route interface { // Find existing network or create a new one from network or blueprint definition - let network_id = machine_registry_inner - .get_or_create_network_state_from_location(&machine_def.location)?; - let srng = machine_registry_inner.unlocked_inner.srng.clone(); let network_state = machine_registry_inner - .get_network_state_by_id(network_id) - .expect("must exist"); + .get_or_create_network_state_from_machine_location(&machine_def.location)?; + let srng = machine_registry_inner.unlocked_inner.srng.clone(); // Build list of default route interface addresses let mut addrs = Vec::::new(); // Make the default route interface - let machine_location = machine_def.location; + let machine_location = machine_def.location.clone(); let (allocate_v4, opt_address4, allocate_v6, opt_address6) = match machine_location { config::MachineLocation::Specific { network: _, address4, address6, } => ( - network_state.ipv4.is_some() && address4.is_some(), + network_state.is_ipv4() && address4.is_some(), address4, - network_state.ipv6.is_some() && address6.is_some(), + network_state.is_ipv6() && address6.is_some(), address6, ), - config::MachineLocation::Network { network: _ } - | config::MachineLocation::Blueprint { blueprint: _ } => ( - network_state.ipv4.is_some(), - None, - network_state.ipv6.is_some(), - None, - ), }; if allocate_v4 { let if_addr4 = - match network_state.allocate_address_v4(srng.clone(), machine_id, opt_address4) - { + match network_state.allocate_address_v4(srng.clone(), id, opt_address4) { Ok(v) => v, Err(e) => { network_state @@ -93,8 +94,7 @@ impl MachineState { } if allocate_v6 { let if_addr6 = - match network_state.allocate_address_v6(srng.clone(), machine_id, opt_address6) - { + match network_state.allocate_address_v6(srng.clone(), id, opt_address6) { Ok(v) => v, Err(e) => { network_state @@ -126,21 +126,28 @@ impl MachineState { }; interfaces.push(MachineStateInterface { - network_id, + network_id: network_state.id(), network_interface, }); } // Create a localhost interface for this machine Ok(Self { - name, - machine_def, - interfaces, + unlocked_inner: Arc::new(MachineStateUnlockedInner { + id, + name, + machine_def, + }), + inner: Arc::new(Mutex::new(MachineStateInner { interfaces })), }) } - pub fn release(self, machine_registry_inner: &mut MachineRegistryInner) { - for intf in self.interfaces { + pub fn release(&self, machine_registry_inner: &mut MachineRegistryInner) { + let network_states = { + let mut inner = self.inner.lock(); + core::mem::take(&mut inner.interfaces) + }; + for intf in network_states { let network_state = machine_registry_inner .get_network_state_by_id(intf.network_id) .expect("must exist"); @@ -151,4 +158,12 @@ impl MachineState { .expect("must succeed"); } } + + pub fn name(&self) -> MachineStateName { + self.unlocked_inner.name.clone() + } + + pub fn id(&self) -> MachineId { + self.unlocked_inner.id + } } diff --git a/veilid-tools/src/virtual_network/router_server/machine_registry/state/network_state.rs b/veilid-tools/src/virtual_network/router_server/machine_registry/state/network_state.rs index 1c4b2783..3f7abd43 100644 --- a/veilid-tools/src/virtual_network/router_server/machine_registry/state/network_state.rs +++ b/veilid-tools/src/virtual_network/router_server/machine_registry/state/network_state.rs @@ -1,21 +1,37 @@ use super::*; use ipnet::*; -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum NetworkStateName { Network(String), Blueprint(String), } #[derive(Debug)] -pub struct NetworkState { +pub struct NetworkStateUnlockedInner { + /// The unique id of this network + id: NetworkId, /// The name of this network state if it was made directly /// or the name of the blueprint used to create it - pub name: NetworkStateName, - pub network_def: config::Network, - pub model: String, - pub ipv4: Option, - pub ipv6: Option, + name: NetworkStateName, + /// The network definition used to create this network + network_def: config::Network, +} + +#[derive(Debug)] +pub struct NetworkStateInner { + /// The model chosen for this network + model: String, + /// IPv4 state if it is enabled + ipv4: Option, + /// IPv6 state if it is enabled + ipv6: Option, +} + +#[derive(Debug, Clone)] +pub struct NetworkState { + unlocked_inner: Arc, + inner: Arc>, } #[derive(Debug)] @@ -44,9 +60,9 @@ pub type NetworkId = u64; impl NetworkState { pub fn try_new( machine_registry_inner: &mut MachineRegistryInner, + id: NetworkId, name: NetworkStateName, network_def: config::Network, - network_id: NetworkId, ) -> MachineRegistryResult { let model = network_def.model.clone().unwrap_or_else(|| { machine_registry_inner @@ -114,21 +130,80 @@ impl NetworkState { }; Ok(Self { - name, - network_def, - model, - ipv4, - ipv6, + unlocked_inner: Arc::new(NetworkStateUnlockedInner { + id, + name, + network_def, + }), + inner: Arc::new(Mutex::new(NetworkStateInner { model, ipv4, ipv6 })), }) } + pub fn name(&self) -> NetworkStateName { + self.unlocked_inner.name.clone() + } + + pub fn id(&self) -> NetworkId { + self.unlocked_inner.id + } + + pub fn is_ipv4(&self) -> bool { + self.inner.lock().ipv4.is_some() + } + + pub fn is_ipv6(&self) -> bool { + self.inner.lock().ipv6.is_some() + } + + pub fn is_active(&self) -> MachineRegistryResult { + let inner = self.inner.lock(); + let mut can_allocate = false; + if let Some(network_state_ipv4) = &inner.ipv4 { + let hosts_range = network_state_ipv4.allocation.hosts(); + let Some(first_host) = std::iter::Iterator::min(hosts_range) else { + return Err(MachineRegistryError::NoAllocation); + }; + let Some(last_host) = std::iter::Iterator::max(hosts_range) else { + return Err(MachineRegistryError::NoAllocation); + }; + let first_host_bits = first_host.to_bits(); + let last_host_bits = last_host.to_bits(); + let count = last_host_bits - first_host_bits + 1; + + if network_state_ipv4.machine_addresses.len() >= count.try_into().unwrap_or(usize::MAX) + { + can_allocate = false; + } + }; + if let Some(network_state_ipv6) = &inner.ipv6 { + let hosts_range = network_state_ipv6.allocation.hosts(); + let Some(first_host) = std::iter::Iterator::min(hosts_range) else { + return Err(MachineRegistryError::NoAllocation); + }; + let Some(last_host) = std::iter::Iterator::max(hosts_range) else { + return Err(MachineRegistryError::NoAllocation); + }; + let first_host_bits = first_host.to_bits(); + let last_host_bits = last_host.to_bits(); + let count = last_host_bits - first_host_bits + 1; + + if network_state_ipv6.machine_addresses.len() >= count.try_into().unwrap_or(usize::MAX) + { + can_allocate = false; + } + }; + Ok(can_allocate) + } + pub(super) fn allocate_address_v4( - &mut self, + &self, srng: StableRng, machine_id: MachineId, opt_address: Option, ) -> MachineRegistryResult { - let Some(network_state_ipv4) = &mut self.ipv4 else { + let mut inner = self.inner.lock(); + + let Some(network_state_ipv4) = &mut inner.ipv4 else { return Err(MachineRegistryError::NoAllocation); }; @@ -191,8 +266,16 @@ impl NetworkState { Ok(ifaddr) } - pub(super) fn release_address_v4(&mut self, addr: Ipv4Addr) -> MachineRegistryResult<()> { - if let Some(ipv4) = self.ipv4.as_mut() { + pub(super) fn release_address_v4(&self, addr: Ipv4Addr) -> MachineRegistryResult<()> { + let mut inner = self.inner.lock(); + Self::release_address_v4_inner(&mut *inner, addr) + } + + fn release_address_v4_inner( + inner: &mut NetworkStateInner, + addr: Ipv4Addr, + ) -> MachineRegistryResult<()> { + if let Some(ipv4) = inner.ipv4.as_mut() { if ipv4.machine_addresses.remove(&addr).is_some() { return Ok(()); } @@ -200,48 +283,15 @@ impl NetworkState { Err(MachineRegistryError::NoAllocation) } - pub(super) fn release_address_v6(&mut self, addr: Ipv6Addr) -> MachineRegistryResult<()> { - if let Some(ipv6) = self.ipv6.as_mut() { - if ipv6.machine_addresses.remove(&addr).is_some() { - return Ok(()); - } - } - Err(MachineRegistryError::NoAllocation) - } - - pub(super) fn release_all_addresses>( - &mut self, - addrs: I, - ) -> MachineRegistryResult<()> { - let mut ok = true; - for addr in addrs { - match addr { - IpAddr::V4(ipv4_addr) => { - if self.release_address_v4(ipv4_addr).is_err() { - ok = false; - } - } - IpAddr::V6(ipv6_addr) => { - if self.release_address_v6(ipv6_addr).is_err() { - ok = false; - } - } - } - } - if ok { - Ok(()) - } else { - Err(MachineRegistryError::NoAllocation) - } - } - pub(super) fn allocate_address_v6( - &mut self, + &self, srng: StableRng, machine_id: MachineId, opt_address: Option, ) -> MachineRegistryResult { - let Some(network_state_ipv6) = &mut self.ipv6 else { + let mut inner = self.inner.lock(); + + let Some(network_state_ipv6) = &mut inner.ipv6 else { return Err(MachineRegistryError::NoAllocation); }; @@ -303,4 +353,48 @@ impl NetworkState { Ok(ifaddr) } + + pub(super) fn release_address_v6(&self, addr: Ipv6Addr) -> MachineRegistryResult<()> { + let mut inner = self.inner.lock(); + Self::release_address_v6_inner(&mut *inner, addr) + } + fn release_address_v6_inner( + inner: &mut NetworkStateInner, + addr: Ipv6Addr, + ) -> MachineRegistryResult<()> { + if let Some(ipv6) = inner.ipv6.as_mut() { + if ipv6.machine_addresses.remove(&addr).is_some() { + return Ok(()); + } + } + Err(MachineRegistryError::NoAllocation) + } + + pub(super) fn release_all_addresses>( + &self, + addrs: I, + ) -> MachineRegistryResult<()> { + let mut inner = self.inner.lock(); + + let mut ok = true; + for addr in addrs { + match addr { + IpAddr::V4(ipv4_addr) => { + if Self::release_address_v4_inner(&mut *inner, ipv4_addr).is_err() { + ok = false; + } + } + IpAddr::V6(ipv6_addr) => { + if Self::release_address_v6_inner(&mut *inner, ipv6_addr).is_err() { + ok = false; + } + } + } + } + if ok { + Ok(()) + } else { + Err(MachineRegistryError::NoAllocation) + } + } } diff --git a/veilid-tools/src/virtual_network/router_server/machine_registry/state/resolves_to.rs b/veilid-tools/src/virtual_network/router_server/machine_registry/state/resolves_to.rs index 334363e9..70b177de 100644 --- a/veilid-tools/src/virtual_network/router_server/machine_registry/state/resolves_to.rs +++ b/veilid-tools/src/virtual_network/router_server/machine_registry/state/resolves_to.rs @@ -69,4 +69,14 @@ where None => Err(ResolveToError::MissingSymbol), } } + + pub fn get(&self, symbol: &T) -> Option { + self.symbols + .get(symbol) + .map(|s| { + let inner = s.lock(); + inner.clone() + }) + .flatten() + } } diff --git a/veilid-tools/src/virtual_network/router_server/machine_registry/state/template_state.rs b/veilid-tools/src/virtual_network/router_server/machine_registry/state/template_state.rs index d875e693..7d4be729 100644 --- a/veilid-tools/src/virtual_network/router_server/machine_registry/state/template_state.rs +++ b/veilid-tools/src/virtual_network/router_server/machine_registry/state/template_state.rs @@ -1,11 +1,21 @@ use super::*; #[derive(Debug)] +struct TemplateStateUnlockedInner { + name: String, + template_def: config::Template, +} + +#[derive(Debug)] +struct TemplateStateInner { + limit_machine_count: u32, + machines: HashSet, +} + +#[derive(Debug, Clone)] pub struct TemplateState { - pub name: String, - pub template_def: config::Template, - pub limit_machine_count: u32, - pub machines: HashSet, + unlocked_inner: Arc, + inner: Arc>, } impl TemplateState { @@ -20,14 +30,20 @@ impl TemplateState { .weighted_choice(&template_def.limits.machine_count); Ok(Self { - name, - template_def, - limit_machine_count, - machines: HashSet::new(), + unlocked_inner: Arc::new(TemplateStateUnlockedInner { name, template_def }), + inner: Arc::new(Mutex::new(TemplateStateInner { + limit_machine_count, + machines: HashSet::new(), + })), }) } - pub fn is_active(&self) -> bool { - self.machines.len() < self.limit_machine_count as usize + pub fn name(&self) -> String { + self.unlocked_inner.name.clone() + } + + pub fn is_active(&self) -> MachineRegistryResult { + let inner = self.inner.lock(); + Ok(inner.machines.len() < inner.limit_machine_count.try_into().unwrap_or(usize::MAX)) } }