diff --git a/veilid-tools/src/virtual_network/router_server/config.rs b/veilid-tools/src/virtual_network/router_server/config.rs index 8971b237..13e9a753 100644 --- a/veilid-tools/src/virtual_network/router_server/config.rs +++ b/veilid-tools/src/virtual_network/router_server/config.rs @@ -14,202 +14,6 @@ pub enum ConfigError { ValidateError(validator::ValidationErrors), } -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum WeightedList { - Single(T), - List(Vec>), -} -impl Default for WeightedList { - fn default() -> Self { - Self::List(Vec::new()) - } -} -impl Validate for WeightedList { - fn validate(&self) -> Result<(), ValidationErrors> { - let mut errors = ValidationErrors::new(); - match self { - Self::List(v) => { - if v.is_empty() { - errors.add( - "List", - ValidationError::new("len") - .with_message("weighted list must not be empty".into()), - ) - } - errors.merge_self("List", v.validate()); - } - Self::Single(_addr) => {} - } - - if errors.is_empty() { - Ok(()) - } else { - Err(errors) - } - } -} - -impl WeightedList { - fn validate_once(&self) -> Result<(), ValidationError> { - match self { - Self::List(v) => { - if v.is_empty() { - return Err(ValidationError::new("len") - .with_message("weighted list must not be empty".into())); - } - } - Self::Single(_addr) => {} - } - Ok(()) - } - - pub fn try_for_each Result<(), E>>(&self, mut f: F) -> Result<(), E> { - match self { - WeightedList::Single(v) => f(v), - WeightedList::List(vec) => vec - .iter() - .map(|v| match v { - Weighted::Weighted { item, weight: _ } => item, - Weighted::Unweighted(item) => item, - }) - .try_for_each(f), - } - } - - pub fn filter(&self, mut filter: F) -> Option> - where - F: FnMut(&T) -> bool, - { - match self { - WeightedList::Single(v) => { - if filter(v) { - return Some(self.clone()); - } - return 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() { - None - } else { - Some(WeightedList::List(out)) - } - } - } - } - - 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 fn try_filter_map(&self, mut filter: F) -> Result>, E> - where - F: FnMut(&T) -> Result, E>, - S: fmt::Debug + Clone, - { - match self { - WeightedList::Single(v) => { - if let Some(item) = filter(v)? { - return Ok(Some(WeightedList::Single(item))); - } - return Ok(None); - } - WeightedList::List(vec) => { - let mut out = Vec::>::with_capacity(vec.len()); - for v in vec { - if let Some(item) = filter(v.item())? { - out.push(match v { - Weighted::Weighted { item: _, weight } => Weighted::Weighted { - item, - weight: *weight, - }, - Weighted::Unweighted(_) => Weighted::Unweighted(item), - }); - } - } - if out.is_empty() { - Ok(None) - } else { - Ok(Some(WeightedList::List(out))) - } - } - } - } -} - -pub type Probability = f32; - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum Weighted { - Weighted { item: T, weight: f32 }, - Unweighted(T), -} - -impl Validate for Weighted { - fn validate(&self) -> Result<(), ValidationErrors> { - let mut errors = ValidationErrors::new(); - if let Self::Weighted { item: _, weight } = self { - if *weight <= 0.0 { - errors.add( - "Weighted", - ValidationError::new("len") - .with_message("weight must be a positive value".into()), - ) - } - } - - if errors.is_empty() { - Ok(()) - } else { - Err(errors) - } - } -} - -impl Weighted { - pub fn item(&self) -> &T { - match self { - Weighted::Weighted { item, weight: _ } => item, - Weighted::Unweighted(item) => item, - } - } - pub fn weight(&self) -> f32 { - match self { - Weighted::Weighted { item: _, weight } => *weight, - Weighted::Unweighted(_) => 1.0f32, - } - } -} - #[derive(Debug, Clone, Serialize, Deserialize, Validate)] #[validate(context = "ValidateContext<'v_a>")] pub struct Profile { @@ -324,7 +128,7 @@ pub struct TemplateLimits { #[serde(default)] pub machine_count: Option>, #[validate(nested)] - pub machines_per_network: WeightedList, + pub machines_per_network: Option>, } fn validate_template_limits(limits: &TemplateLimits) -> Result<(), ValidationError> { @@ -337,13 +141,15 @@ fn validate_template_limits(limits: &TemplateLimits) -> Result<(), ValidationErr Ok(()) })?; } - limits.machines_per_network.try_for_each(|x| { - if *x == 0 { - return Err(ValidationError::new("badcount") - .with_message("template limits has zero machines per network count".into())); - } - Ok(()) - })?; + if let Some(machines_per_network) = &limits.machines_per_network { + machines_per_network.try_for_each(|x| { + if *x == 0 { + return Err(ValidationError::new("badcount") + .with_message("template limits has zero machines per network count".into())); + } + Ok(()) + })?; + } Ok(()) } 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 13f25246..ce57ba36 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 @@ -1,8 +1,11 @@ +use std::marker::PhantomData; + use super::*; #[derive(Debug)] pub(super) struct MachineRegistryInner { unlocked_inner: Arc, + allocated_machines: HashSet, profile_state_allocator: StateAllocator, machine_state_allocator: StateAllocator, template_state_allocator: StateAllocator, @@ -19,6 +22,7 @@ impl MachineRegistryInner { let srng = unlocked_inner.srng.clone(); MachineRegistryInner { unlocked_inner, + allocated_machines: HashSet::new(), profile_state_allocator: StateAllocator::new(), machine_state_allocator: StateAllocator::new(), template_state_allocator: StateAllocator::new(), @@ -27,6 +31,21 @@ impl MachineRegistryInner { address_pool: AddressPool::new(srng), } } + pub fn srng(&self) -> StableRng { + self.unlocked_inner.srng.clone() + } + + pub fn execute_config(&self, cfg: config::Config) -> MachineRegistryResult<()> { + // Create all networks + + // Create all blueprints + + // Create all templates + + // Create all machines + + Ok(()) + } pub fn allocate(&mut self, profile: String) -> MachineRegistryResult { // Get profile definition @@ -42,40 +61,89 @@ impl MachineRegistryInner { }); // Get the next instance from the definition - let Some(instance_def) = profile_state.next_instance() else { - return Err(MachineRegistryError::ProfileComplete); - }; + loop { + let Some(instance_def) = profile_state.next_instance() else { + return Err(MachineRegistryError::ProfileComplete); + }; - 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() - .expect("config validation is broken"); - self.get_or_create_machine_state(machine.clone(), machine_def)? - } - config::Instance::Template { template } => { - let template = self.unlocked_inner.srng.weighted_choice(&template); - let unlocked_inner = self.unlocked_inner.clone(); - let template_def = unlocked_inner - .config - .templates - .get(template) - .cloned() - .expect("config validation is broken"); - self.get_or_create_machine_state_from_template(template.clone(), template_def)? - } - }; - Ok(machine_state.id()) + let machine_state = match instance_def { + config::Instance::Machine { + machine: machine_names, + } => { + // Filter out machines that are already allocated + let opt_machine_states = machine_names.try_filter_map(|name| { + let Some(machine_state) = self.machine_states().get_state_by_name(name) + else { + return Err(MachineRegistryError::MachineNotFound); + }; + if self.allocated_machines.contains(&machine_state.id()) { + Ok(None) + } else { + Ok(Some(machine_state)) + } + })?; + let Some(machine_states) = opt_machine_states else { + // All machines in this instance are allocated + continue; + }; + + // Choose a machine state to activate + let machine_state = self + .unlocked_inner + .srng + .weighted_choice(&machine_states) + .clone(); + + // Activate it + self.allocated_machines.insert(machine_state.id()); + + machine_state + } + config::Instance::Template { + template: template_names, + } => { + // Filter out templates that are no longer active + let opt_template_states = template_names.try_filter_map(|name| { + let Some(template_state) = self.template_states().get_state_by_name(name) + else { + return Err(MachineRegistryError::TemplateNotFound); + }; + if !template_state.is_active(self)? { + Ok(None) + } else { + Ok(Some(template_state)) + } + })?; + let Some(template_states) = opt_template_states else { + // No templates in this instance are still active + continue; + }; + + let template_state = self.unlocked_inner.srng.weighted_choice(&template_states); + + template_state.generate(self)? + } + }; + + break Ok(machine_state.external_id()); + } } pub fn release(&mut self, machine_id: MachineId) -> MachineRegistryResult<()> { - // xxx - // xxx remember machines and networks may not be 'named' if they are generated by templates and blueprints + let id = StateId::::new(machine_id); + if self.allocated_machines.contains(&id) { + // Was a fixed machine, so we leave the machine state so it can + // be reallocated later + self.allocated_machines.remove(&id); + } else { + // Was a templated machine, so remove the machine state + let Some(machine_state) = self.machine_states().get_state(id)? else { + return Err(MachineRegistryError::InvalidId); + }; + machine_state.release(self); + self.machine_states().release_id(id)?; + } + Ok(()) } @@ -98,59 +166,59 @@ impl MachineRegistryInner { &mut self.blueprint_state_allocator } - pub(super) fn get_or_create_machine_state( - &mut self, - opt_name: Option, - params: config::Machine, - ) -> MachineRegistryResult { - // Ensure we don't already have this machine created (name must be unique) - if let Some(name) = &opt_name { - if let Some(machine_id) = self.resolve_to_manager_machine.add(name.clone()).get() { - return Ok(self - .machine_state_by_id - .get(&machine_id) - .cloned() - .expect("must exist")); - } - } + // pub(super) fn get_or_create_machine_state( + // &mut self, + // opt_name: Option, + // params: config::Machine, + // ) -> MachineRegistryResult { + // // Ensure we don't already have this machine created (name must be unique) + // if let Some(name) = &opt_name { + // if let Some(machine_id) = self.resolve_to_manager_machine.add(name.clone()).get() { + // return Ok(self + // .machine_state_by_id + // .get(&machine_id) + // .cloned() + // .expect("must exist")); + // } + // } - // 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 - }); + // // 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, - 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); - } - }; + // // Create a new machine state + // 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); + // // 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"); + // // 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")) - } + // // 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, 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 e82e6659..17129721 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 @@ -20,13 +20,16 @@ struct MachineRegistryUnlockedInner { #[derive(Debug, Clone)] pub enum MachineRegistryError { - InvalidMachineId, - InvalidAllocationName, - ProfileNotFound, + InvalidId, + InvalidName, + AlreadyAttached, + AlreadyDetached, + DuplicateName, ProfileComplete, TemplateComplete, NetworkComplete, BlueprintComplete, + ProfileNotFound, MachineNotFound, NetworkNotFound, TemplateNotFound, 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 dc72e964..665ff729 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 @@ -2,14 +2,14 @@ use super::*; #[derive(Debug)] struct BlueprintStateUnlockedInner { + id: BlueprintStateId, name: String, - blueprint_def: config::Blueprint, } #[derive(Debug)] struct BlueprintStateInner { - limit_network_count: Option, - networks: Vec, + limit_network_count: Option, + networks: Vec, } #[derive(Debug, Clone)] @@ -18,33 +18,22 @@ pub struct BlueprintState { inner: Arc>, } -impl BlueprintState { - pub fn try_new( - machine_registry_inner: &mut MachineRegistryInner, - name: String, - blueprint_def: config::Blueprint, - ) -> MachineRegistryResult { - let limit_network_count = blueprint_def.limits.network_count.as_ref().map(|nc| { - *machine_registry_inner - .unlocked_inner - .srng - .weighted_choice(nc) - }); +pub type BlueprintStateId = StateId; +impl BlueprintState { + pub fn new(id: BlueprintStateId, name: String) -> MachineRegistryResult { Ok(Self { - unlocked_inner: Arc::new(BlueprintStateUnlockedInner { - name, - blueprint_def, - }), + unlocked_inner: Arc::new(BlueprintStateUnlockedInner { id, name }), inner: Arc::new(Mutex::new(BlueprintStateInner { - limit_network_count, + limit_network_count: None, networks: Vec::new(), })), }) } - pub fn def(&self) -> &config::Blueprint { - &self.unlocked_inner.blueprint_def + pub fn set_limit_network_count(&self, limit_network_count: Option) { + let mut inner = self.inner.lock(); + inner.limit_network_count = limit_network_count; } pub fn is_active(&self) -> MachineRegistryResult { @@ -52,7 +41,7 @@ impl BlueprintState { // See if there's room for another network if let Some(limit_network_count) = inner.limit_network_count { - if inner.networks.len() >= limit_network_count.try_into().unwrap_or(usize::MAX) { + if inner.networks.len() >= limit_network_count { return Ok(false); } } @@ -63,13 +52,13 @@ impl BlueprintState { pub fn generate( &self, machine_registry_inner: &MachineRegistryInner, - ) -> MachineRegistryResult { + ) -> MachineRegistryResult { // } pub fn for_each_network_id(&self, mut callback: F) -> MachineRegistryResult> where - F: FnMut(NetworkId) -> MachineRegistryResult>, + F: FnMut(NetworkStateId) -> MachineRegistryResult>, { let inner = self.inner.lock(); for network_id in &inner.networks { @@ -80,3 +69,13 @@ impl BlueprintState { Ok(None) } } + +impl State for BlueprintState { + fn id(&self) -> StateId { + self.unlocked_inner.id.clone() + } + + fn name(&self) -> Option { + Some(self.unlocked_inner.name.clone()) + } +} 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 3b491cf6..e22458e7 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 @@ -3,7 +3,7 @@ use super::*; #[derive(Debug)] struct MachineStateInner { /// The current network interfaces definition - interfaces: Vec, + interfaces: HashMap, } #[derive(Debug)] @@ -36,140 +36,339 @@ impl MachineState { Self { unlocked_inner: Arc::new(MachineStateUnlockedInner { id, opt_name }), inner: Arc::new(Mutex::new(MachineStateInner { - interfaces: Vec::new(), + interfaces: HashMap::new(), })), } } pub fn release(&self, machine_registry_inner: &mut MachineRegistryInner) { - let interfaces = { - let mut inner = self.inner.lock(); - core::mem::take(&mut inner.interfaces) - }; - for intf in interfaces { - let network_state = machine_registry_inner - .network_states() - .get_state(intf.network_id) - .expect("must exist") - .expect("must be bound"); - let addrs = &intf.network_interface.addrs; - - network_state - .release_all_addresses(addrs.iter().map(|x| x.if_addr.ip())) - .expect("must succeed"); - } + self.release_all_interfaces(machine_registry_inner); } pub fn external_id(&self) -> MachineId { self.unlocked_inner.id.0 } - fn next_free_interface_name(&self) -> String {} + fn next_free_interface_name_inner(inner: &MachineStateInner) -> String { + let mut inum = 0usize; + loop { + let name = format!("vin{}", inum); + if !inner.interfaces.contains_key(&name) { + return name; + } + inum += 1; + } + } - xxx implement this pub fn allocate_interface( &self, - machine_registry_inner: &mut MachineRegistryInner, network_id: NetworkStateId, - address4: Option, - address6: Option, + opt_name: Option, + opt_interface_flags: Option, ) -> MachineRegistryResult { - // Find existing network or create a new one from network or blueprint definition - let network_state = match params { - MachineParameters::Direct { - network_id, - disable_capabilities: _, - bootstrap: _, - } => todo!(), - MachineParameters::Config { name, def } => { - machine_registry_inner - .get_or_create_network_state_from_machine_location(&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.clone(); - let (allocate_v4, opt_address4, allocate_v6, opt_address6) = match machine_location { - config::MachineLocation::Network { - network: _, - address4, - address6, - } => ( - network_state.is_ipv4() && address4.is_some(), - address4, - network_state.is_ipv6() && address6.is_some(), - address6, - ), - config::MachineLocation::Blueprint { blueprint: _ } => { - (network_state.is_ipv4(), None, network_state.is_ipv6(), None) - } - }; - - if allocate_v4 { - let if_addr4 = match network_state.allocate_address_v4(srng.clone(), id, opt_address4) { - Ok(v) => v, - Err(e) => { - network_state - .release_all_addresses(addrs.iter().map(|x| x.if_addr.ip())) - .expect("must succeed"); - return Err(e); - } - }; - addrs.push(InterfaceAddress { - if_addr: IfAddr::V4(if_addr4), - flags: AddressFlags { - is_dynamic: false, - is_temporary: false, - is_preferred: true, - }, - }); + let mut inner = self.inner.lock(); + let name = opt_name.unwrap_or_else(|| Self::next_free_interface_name_inner(&*inner)); + if inner.interfaces.contains_key(&name) { + return Err(MachineRegistryError::DuplicateName); } - if allocate_v6 { - let if_addr6 = match network_state.allocate_address_v6(srng.clone(), id, opt_address6) { - Ok(v) => v, - Err(e) => { - network_state - .release_all_addresses(addrs.iter().map(|x| x.if_addr.ip())) - .expect("must succeed"); - return Err(e); - } - }; - addrs.push(InterfaceAddress { - if_addr: IfAddr::V6(if_addr6), - flags: AddressFlags { - is_dynamic: false, - is_temporary: false, - is_preferred: true, - }, - }); - } - - // Allocate an address on the network and make an veilid-style interface record for it - let network_interface = NetworkInterface { - name: "vin0".to_owned(), - flags: InterfaceFlags { - is_loopback: false, - is_running: true, - is_point_to_point: false, - has_default_route: true, - }, - addrs, - }; - - interfaces.push(MachineStateInterface { - network_id: network_state.id(), - network_interface, + let flags = opt_interface_flags.unwrap_or_else(|| InterfaceFlags { + is_loopback: false, + is_running: true, + is_point_to_point: false, + has_default_route: true, }); + inner.interfaces.insert( + name.clone(), + MachineStateInterface { + network_id, + network_interface: NetworkInterface { + name: name.clone(), + flags, + addrs: Vec::new(), + }, + }, + ); + + Ok(name) } - pub fn release_interface(&self) -> () { - // + pub fn interfaces(&self) -> Vec { + let mut intfs: Vec = self.inner.lock().interfaces.keys().cloned().collect(); + intfs.sort(); + intfs } + + pub fn allocate_address_ipv4( + &self, + machine_registry_inner: &mut MachineRegistryInner, + interface: &str, + opt_address: Option, + opt_address_flags: Option, + ) -> MachineRegistryResult { + let mut inner = self.inner.lock(); + let Some(intf) = inner.interfaces.get_mut(interface) else { + return Err(MachineRegistryError::InvalidName); + }; + + // Get the network state + let Some(network_state) = machine_registry_inner + .network_states() + .get_state(intf.network_id)? + else { + return Err(MachineRegistryError::NetworkNotFound); + }; + + // Allocate interface address + let is_dynamic = opt_address.is_none(); + let ifv4_addr = + network_state.allocate_address_v4(machine_registry_inner, self.id(), opt_address)?; + + // Get address flags + let flags = opt_address_flags.unwrap_or_else(|| AddressFlags { + is_dynamic, + is_temporary: false, + is_preferred: true, + }); + + intf.network_interface.addrs.push(InterfaceAddress { + if_addr: IfAddr::V4(ifv4_addr.clone()), + flags, + }); + + Ok(ifv4_addr) + } + + pub fn allocate_address_ipv6( + &self, + machine_registry_inner: &mut MachineRegistryInner, + interface: &str, + opt_address: Option, + opt_address_flags: Option, + ) -> MachineRegistryResult { + let mut inner = self.inner.lock(); + let Some(intf) = inner.interfaces.get_mut(interface) else { + return Err(MachineRegistryError::InvalidName); + }; + + // Get the network state + let Some(network_state) = machine_registry_inner + .network_states() + .get_state(intf.network_id)? + else { + return Err(MachineRegistryError::NetworkNotFound); + }; + + // Allocate interface address + let is_dynamic = opt_address.is_none(); + let ifv6_addr = + network_state.allocate_address_v6(machine_registry_inner, self.id(), opt_address)?; + + // Get address flags + let flags = opt_address_flags.unwrap_or_else(|| AddressFlags { + is_dynamic, + is_temporary: false, + is_preferred: true, + }); + + intf.network_interface.addrs.push(InterfaceAddress { + if_addr: IfAddr::V6(ifv6_addr.clone()), + flags, + }); + + Ok(ifv6_addr) + } + + pub fn release_address( + &self, + machine_registry_inner: &mut MachineRegistryInner, + interface: &str, + address: IpAddr, + ) -> MachineRegistryResult<()> { + let mut inner = self.inner.lock(); + let Some(intf) = inner.interfaces.get_mut(interface) else { + return Err(MachineRegistryError::InvalidName); + }; + + // Get the network state + let Some(network_state) = machine_registry_inner + .network_states() + .get_state(intf.network_id)? + else { + return Err(MachineRegistryError::NetworkNotFound); + }; + + // Release the address from the network + match address { + IpAddr::V4(ipv4_addr) => network_state.release_address_v4(ipv4_addr)?, + IpAddr::V6(ipv6_addr) => network_state.release_address_v6(ipv6_addr)?, + } + + // Remove the address from the interface + intf.network_interface + .addrs + .retain(|x| x.if_addr().ip() != address); + + Ok(()) + } + + pub fn release_all_addresses( + &self, + machine_registry_inner: &mut MachineRegistryInner, + interface: &str, + ) -> MachineRegistryResult<()> { + let mut inner = self.inner.lock(); + Self::release_all_addresses_inner(&mut *inner, machine_registry_inner, interface) + } + + fn release_all_addresses_inner( + inner: &mut MachineStateInner, + machine_registry_inner: &mut MachineRegistryInner, + interface: &str, + ) -> MachineRegistryResult<()> { + let Some(intf) = inner.interfaces.get_mut(interface) else { + return Err(MachineRegistryError::InvalidName); + }; + + // Get the network state + let Some(network_state) = machine_registry_inner + .network_states() + .get_state(intf.network_id)? + else { + return Err(MachineRegistryError::NetworkNotFound); + }; + + // Release the addresses from the network + for addr in &intf.network_interface.addrs { + match addr.if_addr.ip() { + IpAddr::V4(ipv4_addr) => network_state.release_address_v4(ipv4_addr)?, + IpAddr::V6(ipv6_addr) => network_state.release_address_v6(ipv6_addr)?, + } + } + + // Remove the addresses from the interface + intf.network_interface.addrs.clear(); + + Ok(()) + } + + pub fn release_interface( + &self, + machine_registry_inner: &mut MachineRegistryInner, + interface: &str, + ) -> MachineRegistryResult<()> { + let mut inner = self.inner.lock(); + Self::release_all_addresses_inner(&mut *inner, machine_registry_inner, interface)?; + inner + .interfaces + .remove(interface) + .expect("interface must exist"); + Ok(()) + } + + pub fn release_all_interfaces( + &self, + machine_registry_inner: &mut MachineRegistryInner, + ) -> MachineRegistryResult<()> { + let mut inner = self.inner.lock(); + let interfaces: Vec = inner.interfaces.keys().cloned().collect(); + for interface in interfaces { + Self::release_all_addresses_inner(&mut *inner, machine_registry_inner, &interface)?; + } + inner.interfaces.clear(); + Ok(()) + } + + // let network_state = match params { + // MachineParameters::Direct { + // network_id, + // disable_capabilities: _, + // bootstrap: _, + // } => todo!(), + // MachineParameters::Config { name, def } => { + // machine_registry_inner + // .get_or_create_network_state_from_machine_location(&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.clone(); + // let (allocate_v4, opt_address4, allocate_v6, opt_address6) = match machine_location { + // config::MachineLocation::Network { + // network: _, + // address4, + // address6, + // } => ( + // network_state.is_ipv4() && address4.is_some(), + // address4, + // network_state.is_ipv6() && address6.is_some(), + // address6, + // ), + // config::MachineLocation::Blueprint { blueprint: _ } => { + // (network_state.is_ipv4(), None, network_state.is_ipv6(), None) + // } + // }; + + // if allocate_v4 { + // let if_addr4 = match network_state.allocate_address_v4(srng.clone(), id, opt_address4) { + // Ok(v) => v, + // Err(e) => { + // network_state + // .release_all_addresses(addrs.iter().map(|x| x.if_addr.ip())) + // .expect("must succeed"); + // return Err(e); + // } + // }; + // addrs.push(InterfaceAddress { + // if_addr: IfAddr::V4(if_addr4), + // flags: AddressFlags { + // is_dynamic: false, + // is_temporary: false, + // is_preferred: true, + // }, + // }); + // } + // if allocate_v6 { + // let if_addr6 = match network_state.allocate_address_v6(srng.clone(), id, opt_address6) { + // Ok(v) => v, + // Err(e) => { + // network_state + // .release_all_addresses(addrs.iter().map(|x| x.if_addr.ip())) + // .expect("must succeed"); + // return Err(e); + // } + // }; + // addrs.push(InterfaceAddress { + // if_addr: IfAddr::V6(if_addr6), + // flags: AddressFlags { + // is_dynamic: false, + // is_temporary: false, + // is_preferred: true, + // }, + // }); + // } + + // // Allocate an address on the network and make an veilid-style interface record for it + // let network_interface = NetworkInterface { + // name: "vin0".to_owned(), + // flags: InterfaceFlags { + // is_loopback: false, + // is_running: true, + // is_point_to_point: false, + // has_default_route: true, + // }, + // addrs, + // }; + + // interfaces.push(MachineStateInterface { + // network_id: network_state.id(), + // network_interface, + // }); + // } } impl State for MachineState { 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 31680ce3..65fd4adb 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 @@ -16,7 +16,7 @@ struct NetworkStateInner { /// Distance simulation metric distance: Option, /// Packet loss probability - loss: config::Probability, + loss: Probability, /// IPv4 state if it is enabled ipv4: Option, /// IPv6 state if it is enabled @@ -27,14 +27,14 @@ struct NetworkStateInner { struct NetworkStateIpv4 { allocation: Ipv4Net, gateway: Option, - machine_addresses: HashMap, + machine_addresses: HashMap, } #[derive(Debug)] struct NetworkStateIpv6 { allocation: Ipv6Net, gateway: Option, - machine_addresses: HashMap, + machine_addresses: HashMap, } #[derive(Debug)] @@ -181,8 +181,8 @@ impl NetworkState { pub fn allocate_address_v4( &self, - srng: StableRng, - machine_id: MachineId, + machine_registry_inner: &mut MachineRegistryInner, + machine_id: MachineStateId, opt_address: Option, ) -> MachineRegistryResult { let mut inner = self.inner.lock(); @@ -217,7 +217,9 @@ impl NetworkState { ip_addr } else { // Any address will do - let addr_end = srng.next_u32(first_host_bits, last_host_bits); + let addr_end = machine_registry_inner + .srng() + .next_u32(first_host_bits, last_host_bits); // Find a free address starting from here let mut addr = addr_end; @@ -269,8 +271,8 @@ impl NetworkState { pub fn allocate_address_v6( &self, - srng: StableRng, - machine_id: MachineId, + machine_registry_inner: &mut MachineRegistryInner, + machine_id: MachineStateId, opt_address: Option, ) -> MachineRegistryResult { let mut inner = self.inner.lock(); @@ -305,7 +307,9 @@ impl NetworkState { ip_addr } else { // Any address will do - let addr_end = srng.next_u128(first_host_bits, last_host_bits); + let addr_end = machine_registry_inner + .srng() + .next_u128(first_host_bits, last_host_bits); // Find a free address starting from here let mut addr = addr_end; diff --git a/veilid-tools/src/virtual_network/router_server/machine_registry/state/state_allocator.rs b/veilid-tools/src/virtual_network/router_server/machine_registry/state/state_allocator.rs index 4e90aa2c..00739412 100644 --- a/veilid-tools/src/virtual_network/router_server/machine_registry/state/state_allocator.rs +++ b/veilid-tools/src/virtual_network/router_server/machine_registry/state/state_allocator.rs @@ -1,46 +1,5 @@ use super::*; -#[derive(ThisError, Debug)] -pub enum StateAllocatorReleaseError { - #[error("invalid state id")] - InvalidId, -} -pub type StateAllocatorReleaseResult = Result; - -#[derive(ThisError, Debug)] -pub enum StateAllocatorAttachError { - #[error("invalid state id")] - InvalidId, - #[error("state already attached")] - AlreadyAttached, - #[error("duplicate name")] - DuplicateName, -} -pub type StateAllocatorAttachResult = Result; - -#[derive(ThisError, Debug)] -pub enum StateAllocatorDetachError { - #[error("invalid state id")] - InvalidId, - #[error("state already detached")] - AlreadyDetached, -} -pub type StateAllocatorDetachResult = Result; - -#[derive(ThisError, Debug)] -pub enum StateAllocatorGetStateError { - #[error("invalid state id")] - InvalidId, -} -pub type StateAllocatorGetStateResult = Result; - -#[derive(ThisError, Debug)] -pub enum StateAllocatorGetOrCreateByNameError { - #[error("duplicate name")] - DuplicateName, -} -pub type StateAllocatorGetOrCreateByNameResult = Result; - pub trait State: fmt::Debug + Clone { fn id(&self) -> StateId; fn name(&self) -> Option; @@ -48,8 +7,36 @@ pub trait State: fmt::Debug + Clone { type StateIdInternal = u64; -#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[derive(Debug, Clone)] pub struct StateId(pub StateIdInternal, core::marker::PhantomData); +impl StateId { + pub fn new(external_id: u64) -> Self { + Self(external_id, PhantomData {}) + } +} + +impl Copy for StateId {} +impl PartialEq for StateId { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} +impl Eq for StateId {} +impl PartialOrd for StateId { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.0.cmp(&other.0)) + } +} +impl Ord for StateId { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.0.cmp(&other.0) + } +} +impl core::hash::Hash for StateId { + fn hash(&self, state: &mut H) { + self.0.hash(state) + } +} #[derive(Debug)] pub struct StateAllocator { @@ -99,10 +86,10 @@ impl StateAllocator { StateId(state_id, PhantomData {}) } - pub fn release_id(&mut self, id: StateId) -> StateAllocatorReleaseResult<()> { + pub fn release_id(&mut self, id: StateId) -> MachineRegistryResult<()> { // Remove id to state mapping let Some(old_opt_state) = self.state_by_id.remove(&id.0) else { - return Err(StateAllocatorReleaseError::InvalidId); + return Err(MachineRegistryError::InvalidId); }; // Release state if it is attached @@ -121,24 +108,24 @@ impl StateAllocator { Ok(()) } - pub fn attach_state(&mut self, state: S) -> StateAllocatorAttachResult<()> { + pub fn attach_state(&mut self, state: S) -> MachineRegistryResult<()> { // Get the id from the state let id = state.id(); // Get the allocator slot let Some(opt_state) = self.state_by_id.get_mut(&id.0) else { - return Err(StateAllocatorAttachError::InvalidId); + return Err(MachineRegistryError::InvalidId); }; // Ensure the state slot isn't attached already if opt_state.is_some() { - return Err(StateAllocatorAttachError::AlreadyAttached); + return Err(MachineRegistryError::AlreadyAttached); } // Ensure the name isn't duplicated if let Some(name) = state.name() { if self.state_id_by_name.contains_key(&name) { - return Err(StateAllocatorAttachError::DuplicateName); + return Err(MachineRegistryError::DuplicateName); } // Register the named state self.state_id_by_name @@ -152,15 +139,15 @@ impl StateAllocator { Ok(()) } - pub fn detach_state(&mut self, id: StateId) -> StateAllocatorDetachResult { + pub fn detach_state(&mut self, id: StateId) -> MachineRegistryResult { // Get the allocator slot let Some(opt_state) = self.state_by_id.get_mut(&id.0) else { - return Err(StateAllocatorDetachError::InvalidId); + return Err(MachineRegistryError::InvalidId); }; // Take the state out of the slot and ensure the state slot isn't detached already let Some(state) = opt_state.take() else { - return Err(StateAllocatorDetachError::AlreadyDetached); + return Err(MachineRegistryError::AlreadyDetached); }; // Release the name if it exists @@ -175,10 +162,10 @@ impl StateAllocator { Ok(state) } - pub fn get_state(&self, id: StateId) -> StateAllocatorGetStateResult> { + pub fn get_state(&self, id: StateId) -> MachineRegistryResult> { // Get the allocator slot let Some(opt_state) = self.state_by_id.get(&id.0).cloned() else { - return Err(StateAllocatorGetStateError::InvalidId); + return Err(MachineRegistryError::InvalidId); }; Ok(opt_state) 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 dc736952..51c377a0 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 @@ -2,21 +2,33 @@ use super::*; #[derive(Debug)] struct TemplateStateUnlockedInner { + id: TemplateStateId, name: String, - template_def: config::Template, } #[derive(Debug)] struct PerNetworkInfo { - limit_machine_count: u32, - machines: HashSet, + limit_machine_count: Option, + machines: HashSet, +} + +#[derive(Debug)] +enum LocationsList { + Networks { + networks: WeightedList, + }, + Blueprints { + blueprints: WeightedList, + }, } #[derive(Debug)] struct TemplateStateInner { - limit_machine_count: Option, - machines: HashSet, - machines_per_network: HashMap, + limit_machine_count: Option, + limit_machines_per_network: Option>, + locations_list: Option, + machines: HashSet, + machines_per_network: HashMap, } #[derive(Debug, Clone)] @@ -25,35 +37,48 @@ pub struct TemplateState { inner: Arc>, } -impl TemplateState { - pub fn try_new( - machine_registry_inner: &mut MachineRegistryInner, - name: String, - template_def: config::Template, - ) -> MachineRegistryResult { - let limit_machine_count = template_def.limits.machine_count.as_ref().map(|mc| { - *machine_registry_inner - .unlocked_inner - .srng - .weighted_choice(mc) - }); +pub type TemplateStateId = StateId; - Ok(Self { - unlocked_inner: Arc::new(TemplateStateUnlockedInner { name, template_def }), +impl TemplateState { + pub fn new(id: TemplateStateId, name: String) -> Self { + Self { + unlocked_inner: Arc::new(TemplateStateUnlockedInner { id, name }), inner: Arc::new(Mutex::new(TemplateStateInner { - limit_machine_count, + limit_machine_count: None, + limit_machines_per_network: None, + locations_list: None, machines: HashSet::new(), machines_per_network: HashMap::new(), })), - }) + } } - pub fn name(&self) -> String { - self.unlocked_inner.name.clone() + pub fn set_networks_list(&self, networks: WeightedList) { + let mut inner = self.inner.lock(); + inner.locations_list = Some(LocationsList::Networks { networks }) } - pub fn def(&self) -> &config::Template { - &self.unlocked_inner.template_def + pub fn set_blueprints_list(&self, blueprints: WeightedList) { + let mut inner = self.inner.lock(); + inner.locations_list = Some(LocationsList::Blueprints { blueprints }) + } + + pub fn clear_locations_list(&self) { + let mut inner = self.inner.lock(); + inner.locations_list = None; + } + + pub fn set_limit_machine_count(&self, limit_machine_count: Option) { + let mut inner = self.inner.lock(); + inner.limit_machine_count = limit_machine_count; + } + + pub fn set_limit_machines_per_network( + &self, + limit_machines_per_network: Option>, + ) { + let mut inner = self.inner.lock(); + inner.limit_machines_per_network = limit_machines_per_network; } fn is_network_available_inner( @@ -71,28 +96,35 @@ impl TemplateState { return Ok(true); }; - // If this template has not yet allocated the maximum number of machines per-network - // for this network, then it is available - if pni.machines.len() < pni.limit_machine_count.try_into().unwrap_or(usize::MAX) { - return Ok(true); + // If this template has allocated the maximum number of machines per-network + // for this network, then it is not available + if let Some(limit_machine_count) = pni.limit_machine_count { + if pni.machines.len() >= limit_machine_count { + return Ok(false); + } } - Ok(false) + Ok(true) } +xxx should this be sensitive to already generated blueprint networks? + fn is_blueprint_available_inner( inner: &TemplateStateInner, machine_registry_inner: &MachineRegistryInner, blueprint_state: BlueprintState, - ) -> MachineRegistryResult> { + ) -> MachineRegistryResult> { // See if the networks generated from this blueprint so far have availability // in this template - if let Some(available_network_id) = blueprint_state.for_each_network_id(|network_id| { + if let Some(available_network_id) = blueprint_state.for_each_network_id(|id| { // Check the network's availability - let network_state = machine_registry_inner.get_network_state_by_id(network_id)?; + let network_state = machine_registry_inner + .network_states() + .get_state(id)? + .expect("must exist"); if Self::is_network_available_inner(inner, network_state)? { // We found one - return Ok(Some(network_id)); + return Ok(Some(id)); } // Try next network Ok(None) @@ -117,28 +149,23 @@ impl TemplateState { // See if we have reached our machine limit if let Some(limit_machine_count) = inner.limit_machine_count { - if inner.machines.len() < limit_machine_count.try_into().unwrap_or(usize::MAX) { + if inner.machines.len() >= limit_machine_count { return Ok(false); } } - // See if any of our existing networks have room to allocate (machines could have been removed) - for (_network_id, pni) in &inner.machines_per_network { - // If this template has not yet allocated the maximum number of machines per-network - // for this network, then it is available - if pni.machines.len() < pni.limit_machine_count.try_into().unwrap_or(usize::MAX) { - return Ok(true); - } - } + let Some(locations_list) = inner.locations_list.as_ref() else { + return Ok(false); + }; - // If existing networks are all full, we'd have to allocate one, see if we'd be able to do that - match self.def().location.clone() { - config::TemplateLocation::Network { network } => { + match locations_list { + LocationsList::Networks { networks } => { // Filter the weighted list of networks to those that are still active and or not yet started - if network - .try_filter(|n| { + if networks + .try_filter(|id| { machine_registry_inner - .get_network_state_by_name(&n) + .network_states() + .get_state(*id)? .clone() .map(|ns| Self::is_network_available_inner(&*inner, ns)) .unwrap_or(Ok(true)) @@ -148,12 +175,13 @@ impl TemplateState { return Ok(false); }; } - config::TemplateLocation::Blueprint { blueprint } => { + LocationsList::Blueprints { blueprints } => { // Filter the weighted list of blueprints to those that are still active or not yet started and can allocate - if blueprint - .try_filter(|b| { + if blueprints + .try_filter(|id| { machine_registry_inner - .get_blueprint_state(&b) + .blueprint_states() + .get_state(*id)? .clone() .map(|bs| { Self::is_blueprint_available_inner( @@ -180,19 +208,27 @@ impl TemplateState { machine_registry_inner: &mut MachineRegistryInner, ) -> MachineRegistryResult { let mut inner = self.inner.lock(); + + // See if we have reached our machine limit if let Some(limit_machine_count) = inner.limit_machine_count { if inner.machines.len() < limit_machine_count.try_into().unwrap_or(usize::MAX) { return Err(MachineRegistryError::TemplateComplete); } } - // Pick or instantiate an available network - let network_state = match self.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| { + // If existing networks are all full, we'd have to allocate one, see if we'd be able to do that + let Some(locations_list) = inner.locations_list.as_ref() else { + return Err(MachineRegistryError::TemplateComplete); + }; + + // Get a network to generate the machine on + let network_state = match locations_list { + LocationsList::Networks { networks } => { + // Filter the weighted list of networks to those that are still active and or not yet started + let Some(active_networks) = networks.try_filter(|id| { machine_registry_inner - .get_network_state_by_name(&n) + .network_states() + .get_state(*id)? .clone() .map(|ns| Self::is_network_available_inner(&*inner, ns)) .unwrap_or(Ok(true)) @@ -202,56 +238,85 @@ impl TemplateState { }; // Weighted choice of network now that we have a candidate list - let network = machine_registry_inner - .unlocked_inner - .srng + let network_id = machine_registry_inner + .srng() .weighted_choice(&active_networks); - // Instantiate the network if it doesn't yet exist - let network_state = machine_registry_inner.get_or_create_network_state(network.clone())?; + // Get the fixed network + let network_state = machine_registry_inner + .network_states() + .get_state(*network_id)? + .expect("must exist"); // Return network state to use network_state } - config::TemplateLocation::Blueprint { blueprint } => { + LocationsList::Blueprints { blueprints } => { // 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_map(|b| { + let Some(active_blueprints) = blueprints.try_filter(|id| { machine_registry_inner - .get_blueprint_state(&b) + .blueprint_states() + .get_state(*id)? .clone() .map(|bs| { - Self::is_blueprint_available_inner(inner, machine_registry_inner, bs) + Self::is_blueprint_available_inner(&*inner, machine_registry_inner, bs) + .map(|x| !matches!(x, Availability::None)) }) - .unwrap_or(Ok(Some())) + .unwrap_or(Ok(true)) })? else { return Err(MachineRegistryError::BlueprintComplete); }; // Weighted choice of blueprint now that we have a candidate list - let blueprint_name = machine_registry_inner - .unlocked_inner - .srng + let blueprint_id = machine_registry_inner + .srng() .weighted_choice(&active_blueprints); - config::MachineLocation::Blueprint { - blueprint: blueprint_name.clone(), - } + xxx do not always generate... use most recent network for this blueprint in this template. + + // Instantiate a blueprint network + let blueprint_state = machine_registry_inner + .blueprint_states() + .get_state(*blueprint_id)? + .expect("must exist"); + + blueprint_state.generate(machine_registry_inner)? } }; -xxx - // 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) + // Allocate a machine id + let machine_id = machine_registry_inner.machine_states().allocate_id(); - Ok(MachineParameters::Direct { - disable_capabilities: self.def().disable_capabilities.clone(), - bootstrap: false, - }) + // Create an anonymous machine state + let mut machine_state = MachineState::new(machine_id, None); + + // Build out the machine state from the template + //inner. + + // Attach the state to the id + machine_registry_inner + .machine_states() + .attach_state(machine_state.clone()); + + // Record the newly instantiated machine + inner.machines.insert(machine_id); + let per_network_info = inner.machines_per_network.entry(network_state).or_insert_with(|| { + let limit_machine_count = inner.limit_machines_per_network.map(|wl| machine_registry_inner.srng().weighted_choice(&wl)).copied(); + PerNetworkInfo{ limit_machine_count, machines: HashSet::new() } + }); + per_network_info.machines.insert(machine_id); + + Ok(machine_state) + } +} + +impl State for TemplateState { + fn id(&self) -> StateId { + self.unlocked_inner.id.clone() + } + + fn name(&self) -> Option { + Some(self.unlocked_inner.name.clone()) } } diff --git a/veilid-tools/src/virtual_network/router_server/machine_registry/weighted_list.rs b/veilid-tools/src/virtual_network/router_server/machine_registry/weighted_list.rs new file mode 100644 index 00000000..e69de29b diff --git a/veilid-tools/src/virtual_network/router_server/mod.rs b/veilid-tools/src/virtual_network/router_server/mod.rs index 2b4a26ad..0926f35c 100644 --- a/veilid-tools/src/virtual_network/router_server/mod.rs +++ b/veilid-tools/src/virtual_network/router_server/mod.rs @@ -2,12 +2,14 @@ pub mod config; mod machine_registry; mod server_processor; mod stable_rng; +mod weighted_list; use super::*; use machine_registry::*; use server_processor::*; use stable_rng::*; +use weighted_list::*; use async_tungstenite::accept_async; use futures_codec::{Bytes, BytesCodec, FramedRead, FramedWrite}; diff --git a/veilid-tools/src/virtual_network/router_server/stable_rng.rs b/veilid-tools/src/virtual_network/router_server/stable_rng.rs index a05f37e7..6801872b 100644 --- a/veilid-tools/src/virtual_network/router_server/stable_rng.rs +++ b/veilid-tools/src/virtual_network/router_server/stable_rng.rs @@ -35,11 +35,11 @@ impl StableRng { } pub fn weighted_choice<'a, T: fmt::Debug + Clone>( &self, - weighted_list: &'a config::WeightedList, + weighted_list: &'a WeightedList, ) -> &'a T { match weighted_list { - config::WeightedList::Single(x) => x, - config::WeightedList::List(vec) => { + WeightedList::Single(x) => x, + WeightedList::List(vec) => { let total_weight = vec .iter() .map(|x| x.weight()) diff --git a/veilid-tools/src/virtual_network/router_server/weighted_list.rs b/veilid-tools/src/virtual_network/router_server/weighted_list.rs new file mode 100644 index 00000000..92ba60bc --- /dev/null +++ b/veilid-tools/src/virtual_network/router_server/weighted_list.rs @@ -0,0 +1,199 @@ +use super::*; +use serde::*; +use validator::{Validate, ValidationError, ValidationErrors}; + +pub type Probability = f32; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum WeightedList { + Single(T), + List(Vec>), +} +impl Default for WeightedList { + fn default() -> Self { + Self::List(Vec::new()) + } +} +impl Validate for WeightedList { + fn validate(&self) -> Result<(), ValidationErrors> { + let mut errors = ValidationErrors::new(); + match self { + Self::List(v) => { + if v.is_empty() { + errors.add( + "List", + ValidationError::new("len") + .with_message("weighted list must not be empty".into()), + ) + } + errors.merge_self("List", v.validate()); + } + Self::Single(_addr) => {} + } + + if errors.is_empty() { + Ok(()) + } else { + Err(errors) + } + } +} + +impl WeightedList { + pub fn validate_once(&self) -> Result<(), ValidationError> { + match self { + Self::List(v) => { + if v.is_empty() { + return Err(ValidationError::new("len") + .with_message("weighted list must not be empty".into())); + } + } + Self::Single(_addr) => {} + } + Ok(()) + } + + pub fn try_for_each Result<(), E>>(&self, mut f: F) -> Result<(), E> { + match self { + WeightedList::Single(v) => f(v), + WeightedList::List(vec) => vec + .iter() + .map(|v| match v { + Weighted::Weighted { item, weight: _ } => item, + Weighted::Unweighted(item) => item, + }) + .try_for_each(f), + } + } + + pub fn filter(&self, mut filter: F) -> Option> + where + F: FnMut(&T) -> bool, + { + match self { + WeightedList::Single(v) => { + if filter(v) { + return Some(self.clone()); + } + return 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() { + None + } else { + Some(WeightedList::List(out)) + } + } + } + } + + 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 fn try_filter_map(&self, mut filter: F) -> Result>, E> + where + F: FnMut(&T) -> Result, E>, + S: fmt::Debug + Clone, + { + match self { + WeightedList::Single(v) => { + if let Some(item) = filter(v)? { + return Ok(Some(WeightedList::Single(item))); + } + return Ok(None); + } + WeightedList::List(vec) => { + let mut out = Vec::>::with_capacity(vec.len()); + for v in vec { + if let Some(item) = filter(v.item())? { + out.push(match v { + Weighted::Weighted { item: _, weight } => Weighted::Weighted { + item, + weight: *weight, + }, + Weighted::Unweighted(_) => Weighted::Unweighted(item), + }); + } + } + if out.is_empty() { + Ok(None) + } else { + Ok(Some(WeightedList::List(out))) + } + } + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum Weighted { + Weighted { item: T, weight: f32 }, + Unweighted(T), +} + +impl Validate for Weighted { + fn validate(&self) -> Result<(), ValidationErrors> { + let mut errors = ValidationErrors::new(); + if let Self::Weighted { item: _, weight } = self { + if *weight <= 0.0 { + errors.add( + "Weighted", + ValidationError::new("len") + .with_message("weight must be a positive value".into()), + ) + } + } + + if errors.is_empty() { + Ok(()) + } else { + Err(errors) + } + } +} + +impl Weighted { + pub fn item(&self) -> &T { + match self { + Weighted::Weighted { item, weight: _ } => item, + Weighted::Unweighted(item) => item, + } + } + pub fn weight(&self) -> f32 { + match self { + Weighted::Weighted { item: _, weight } => *weight, + Weighted::Unweighted(_) => 1.0f32, + } + } +}