diff --git a/Cargo.lock b/Cargo.lock index 93694114..38aeed29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6618,7 +6618,6 @@ dependencies = [ "cfg-if 1.0.0", "chrono", "clap 4.5.21", - "config 0.14.1", "console_error_panic_hook", "ctrlc", "eyre", @@ -6629,6 +6628,7 @@ dependencies = [ "getrandom", "ifstructs", "imbl", + "indent", "ipnet", "jni", "jni-sys", @@ -6669,6 +6669,7 @@ dependencies = [ "tracing-subscriber", "tracing-wasm", "validator", + "veilid-bugsalot", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test", diff --git a/veilid-tools/Cargo.toml b/veilid-tools/Cargo.toml index 7b3c3451..9c73b2c2 100644 --- a/veilid-tools/Cargo.toml +++ b/veilid-tools/Cargo.toml @@ -52,14 +52,19 @@ virtual-network = [] virtual-network-server = [ "dep:async-tungstenite", "dep:clap", - "dep:config", + "dep:indent", "dep:ipnet", "dep:serde_yaml", "dep:validator", "dep:ws_stream_tungstenite", "dep:rand_chacha", ] -virtual-router = ["tracing", "virtual-network-server", "dep:time"] +virtual-router = [ + "tracing", + "virtual-network-server", + "dep:time", + "dep:bugsalot", +] [dependencies] tracing = { version = "0.1.40", features = [ @@ -99,6 +104,7 @@ imbl = { version = "3.0.0", features = ["serde"] } [target.'cfg(not(all(target_arch = "wasm32", target_os = "unknown")))'.dependencies] async-io = { version = "1.13.0" } async-std = { version = "1.12.0", features = ["unstable"], optional = true } +bugsalot = { package = "veilid-bugsalot", version = "0.2.0", optional = true } time = { version = "0.3.36", features = [ "local-offset", "formatting", @@ -111,6 +117,7 @@ futures-util = { version = "0.3.30", default-features = false, features = [ "std", "io", ] } +indent = { version = "0.1.1", optional = true } libc = "0.2.155" nix = { version = "0.27.1", features = ["user"] } socket2 = { version = "0.5.7", features = ["all"] } @@ -121,9 +128,6 @@ tokio-stream = { version = "0.1.15", features = ["net"], optional = true } ws_stream_tungstenite = { version = "0.14.0", optional = true } async-tungstenite = { version = "0.28.0", optional = true } clap = { version = "4", features = ["derive"], optional = true } -config = { version = "^0", default-features = false, features = [ - "yaml", -], optional = true } ipnet = { version = "2", features = ["serde"], optional = true } serde_yaml = { package = "serde_yaml_ng", version = "^0.10.0", optional = true } validator = { version = "0.19.0", features = ["derive"], optional = true } diff --git a/veilid-tools/src/bin/virtual_router/main.rs b/veilid-tools/src/bin/virtual_router/main.rs index ef38ed24..29430ada 100644 --- a/veilid-tools/src/bin/virtual_router/main.rs +++ b/veilid-tools/src/bin/virtual_router/main.rs @@ -27,7 +27,7 @@ cfg_if! { } } -const DEFAULT_IGNORE_LOG_TARGETS: &[&str] = &["tokio"]; +const DEFAULT_IGNORE_LOG_TARGETS: &[&str] = &["tokio", "runtime"]; #[derive(Args, Debug, Clone)] #[group(multiple = false)] @@ -70,6 +70,10 @@ struct CmdlineArgs { /// Instead of running the virtual router, print the configuration it would use to the console #[arg(long)] dump_config: bool, + /// Wait for debugger to attach + #[cfg(debug_assertions)] + #[arg(long)] + wait_for_debug: bool, #[command(flatten)] logging: Logging, @@ -116,7 +120,7 @@ fn setup_tracing(logging: &Logging) -> Result<(), String> { } let layer = fmt::Layer::new() - .compact() + .pretty() .with_timer(timer) .with_ansi(true) .with_writer(std::io::stdout) @@ -132,7 +136,14 @@ fn setup_tracing(logging: &Logging) -> Result<(), String> { Ok(()) } -fn main() -> Result<(), String> { +fn main() { + if let Err(e) = real_main() { + eprintln!("{}", e); + std::process::exit(1); + } + std::process::exit(0); +} +fn real_main() -> Result<(), String> { let stop_source = StopSource::new(); let stop_token = stop_source.token(); let stop_mutex = Mutex::new(Some(stop_source)); @@ -148,6 +159,12 @@ fn main() -> Result<(), String> { let args = CmdlineArgs::parse(); + #[cfg(debug_assertions)] + if args.wait_for_debug { + use bugsalot::debugger; + debugger::wait_until_attached(None).expect("state() not implemented on this platform"); + } + setup_tracing(&args.logging)?; let initial_config = config::Config::new(&args.config_file, args.no_predefined_config) @@ -188,9 +205,12 @@ fn main() -> Result<(), String> { None }; + println!("Running..."); router_server .run(stop_token) .await - .map_err(|e| e.to_string()) + .map_err(|e| e.to_string())?; + println!("Done"); + Ok(()) }) } diff --git a/veilid-tools/src/virtual_network/router_server/config.rs b/veilid-tools/src/virtual_network/router_server/config.rs index aad6c210..a234d7d3 100644 --- a/veilid-tools/src/virtual_network/router_server/config.rs +++ b/veilid-tools/src/virtual_network/router_server/config.rs @@ -9,10 +9,12 @@ const DEFAULT_CONFIG: &str = include_str!("default_config.yml"); #[derive(Debug, ThisError)] pub enum ConfigError { - #[error("parse error")] - ParseError(::config::ConfigError), - #[error("validate error")] - ValidateError(validator::ValidationErrors), + #[error("io error: {0}")] + IoError(std::io::Error), + #[error("parse error: {0}: {1}")] + ParseError(String, serde_yaml::Error), + #[error("validate error: {0}")] + ValidateError(String), #[error("no configuration files specified")] NoConfigFiles, } @@ -227,6 +229,7 @@ pub struct Blueprint { #[serde(default)] #[validate(nested)] pub model: Option>, + #[serde(flatten)] #[validate(nested)] pub limits: BlueprintLimits, #[serde(default)] @@ -407,9 +410,9 @@ fn validate_distribution(distribution: &Distribution) -> Result<(), ValidationEr return Err(ValidationError::new("baddistrib") .with_message("distribution mean must not be negative".into())); } - if distribution.sigma < distribution.mean { + if distribution.sigma < 0.0 { return Err(ValidationError::new("baddistrib") - .with_message("distribution sigma must not be less than the mean".into())); + .with_message("distribution sigma must not be negative".into())); } if distribution.max < distribution.min { return Err(ValidationError::new("baddistrib") @@ -535,6 +538,48 @@ impl Validate for Config { } } +fn expand_validation_errors(errors: ValidationErrors) -> String { + let mut out = String::new(); + let errors = errors.into_errors(); + let mut keys: Vec<&str> = errors.keys().copied().collect(); + keys.sort(); + for k in keys { + let v = errors.get(k).unwrap(); + let v_out = match v.clone() { + validator::ValidationErrorsKind::Struct(validation_errors) => { + expand_validation_errors(*validation_errors) + } + validator::ValidationErrorsKind::List(btree_map) => { + let mut l_out = String::new(); + for (_, v) in btree_map { + l_out += &expand_validation_errors(*v); + } + l_out + } + validator::ValidationErrorsKind::Field(vec) => { + let mut v_out = String::new(); + for v in vec { + v_out += &format!("{}\n", v); + } + v_out + } + }; + let v_out = indent::indent_all_by(4, v_out); + + out += &format!("{k}:\n{v_out}\n"); + } + out +} + +fn map_validation_error>( + name: S, +) -> impl FnOnce(validator::ValidationErrors) -> ConfigError { + let name = name.as_ref().to_string(); + move |errors| { + ConfigError::ValidateError(format!("{name}: {}", expand_validation_errors(errors))) + } +} + impl Config { pub fn new>( config_files: &[P], @@ -543,19 +588,15 @@ impl Config { let mut out = Self::default(); if !no_predefined_config { - out = load_predefined_config() - .map_err(ConfigError::ParseError)? - .try_deserialize() - .map_err(ConfigError::ParseError)?; - out.validate().map_err(ConfigError::ValidateError)?; + out = load_predefined_config()?; + out.validate() + .map_err(map_validation_error(""))?; // Load default config file if config_files.is_empty() { - let cfg: Self = load_default_config() - .map_err(ConfigError::ParseError)? - .try_deserialize() - .map_err(ConfigError::ParseError)?; - cfg.validate().map_err(ConfigError::ValidateError)?; + let cfg: Self = load_default_config()?; + cfg.validate() + .map_err(map_validation_error(""))?; out = out.combine(cfg)?; } @@ -568,11 +609,11 @@ impl Config { // Load specified config files for config_file in config_files { - let cfg: Self = load_config_file(config_file) - .map_err(ConfigError::ParseError)? - .try_deserialize() - .map_err(ConfigError::ParseError)?; - cfg.validate().map_err(ConfigError::ValidateError)?; + let cfg: Self = load_config_file(config_file)?; + cfg.validate().map_err(map_validation_error(format!( + "{}", + config_file.as_ref().to_string_lossy() + )))?; out = out.combine(cfg)?; } @@ -603,8 +644,8 @@ impl Config { models: self.models.into_iter().chain(other.models).collect(), }; - // Validate config - out.validate().map_err(ConfigError::ValidateError)?; + // Validate config (should never fail if combine inputs also validated) + out.validate().map_err(map_validation_error(""))?; Ok(out) } } @@ -620,42 +661,18 @@ fn validate_hash_map(value: &HashMap) -> Result<(), Vali Ok(()) } -fn load_predefined_config() -> Result<::config::Config, ::config::ConfigError> { - ::config::Config::builder() - .add_source(::config::File::from_str( - PREDEFINED_CONFIG, - ::config::FileFormat::Yaml, - )) - .build() +fn load_predefined_config() -> Result { + serde_yaml::from_str(PREDEFINED_CONFIG) + .map_err(|x| ConfigError::ParseError("".to_string(), x)) } -fn load_default_config() -> Result<::config::Config, ::config::ConfigError> { - ::config::Config::builder() - .add_source(::config::File::from_str( - PREDEFINED_CONFIG, - ::config::FileFormat::Yaml, - )) - .add_source(::config::File::from_str( - DEFAULT_CONFIG, - ::config::FileFormat::Yaml, - )) - .build() +fn load_default_config() -> Result { + serde_yaml::from_str(DEFAULT_CONFIG) + .map_err(|x| ConfigError::ParseError("".to_string(), x)) } -fn load_config_file>( - config_file: P, -) -> Result<::config::Config, ::config::ConfigError> { - let config_path = config_file.as_ref(); - let Some(config_file_str) = config_path.to_str() else { - return Err(::config::ConfigError::Message( - "config file path is not valid UTF-8".to_owned(), - )); - }; - let config = ::config::Config::builder() - .add_source(::config::File::new( - config_file_str, - ::config::FileFormat::Yaml, - )) - .build()?; - Ok(config) +fn load_config_file>(config_file: P) -> Result { + let rdr = std::fs::File::open(&config_file).map_err(ConfigError::IoError)?; + serde_yaml::from_reader(rdr) + .map_err(|x| ConfigError::ParseError(config_file.as_ref().to_string_lossy().to_string(), x)) } diff --git a/veilid-tools/src/virtual_network/router_server/default_config.yml b/veilid-tools/src/virtual_network/router_server/default_config.yml index a798c15f..56dc4e68 100644 --- a/veilid-tools/src/virtual_network/router_server/default_config.yml +++ b/veilid-tools/src/virtual_network/router_server/default_config.yml @@ -117,7 +117,10 @@ networks: # # Predefined networks # $internet: - # allocation: "$internet" + # ipv4: + # allocation: "$internet" + # ipv6: + # allocation: "$internet" # model: "$internet" ################################################################# diff --git a/veilid-tools/src/virtual_network/router_server/global_state_manager/address_pool.rs b/veilid-tools/src/virtual_network/router_server/global_state_manager/address_pool.rs index 8a3e6a3d..1d429ad5 100644 --- a/veilid-tools/src/virtual_network/router_server/global_state_manager/address_pool.rs +++ b/veilid-tools/src/virtual_network/router_server/global_state_manager/address_pool.rs @@ -91,6 +91,7 @@ impl AddressPool { Ok(opt_allocation.is_some()) } + #[instrument(level = "debug", skip(self), err)] pub fn reserve_allocation_v4( &mut self, allocation: Ipv4Net, @@ -113,6 +114,7 @@ impl AddressPool { Ok(scope) } + #[instrument(level = "debug", skip(self), err)] pub fn reserve_allocation_v6( &mut self, allocation: Ipv6Net, @@ -157,6 +159,7 @@ impl AddressPool { overlaps } + #[instrument(level = "debug", skip(self, srng), err)] pub fn allocate_random_v4( &mut self, srng: &mut StableRng, @@ -181,6 +184,7 @@ impl AddressPool { Ok(None) } + #[instrument(level = "debug", skip(self, srng), err)] pub fn allocate_random_v6( &mut self, srng: &mut StableRng, @@ -205,6 +209,7 @@ impl AddressPool { Ok(None) } + #[instrument(level = "debug", skip(self), err)] pub fn release_allocation_v4( &mut self, allocation: Ipv4Net, @@ -222,6 +227,7 @@ impl AddressPool { Ok(opt_tag) } + #[instrument(level = "debug", skip(self), err)] pub fn release_allocation_v6( &mut self, allocation: Ipv6Net, @@ -274,6 +280,7 @@ impl AddressPool { false } + #[instrument(level = "debug", skip_all, err)] pub fn clear_ipv4 bool>( &mut self, mut check: F, @@ -296,6 +303,7 @@ impl AddressPool { Ok(()) } + #[instrument(level = "debug", skip_all, err)] pub fn clear_ipv6 bool>( &mut self, mut check: F, diff --git a/veilid-tools/src/virtual_network/router_server/global_state_manager/global_state_manager_inner.rs b/veilid-tools/src/virtual_network/router_server/global_state_manager/global_state_manager_inner.rs index 97f11903..52d5f5a4 100644 --- a/veilid-tools/src/virtual_network/router_server/global_state_manager/global_state_manager_inner.rs +++ b/veilid-tools/src/virtual_network/router_server/global_state_manager/global_state_manager_inner.rs @@ -45,6 +45,7 @@ impl GlobalStateManagerInner { } } + #[instrument(level = "debug", skip_all, err)] pub fn execute_config(&mut self, cfg: config::Config) -> GlobalStateManagerResult<()> { // Create random number generator if let Some(seed) = cfg.seed { @@ -71,77 +72,77 @@ impl GlobalStateManagerInner { // Import all models for (name, model) in cfg.models { - self.execute_config_model(name, model)?; + self.execute_config_model(&name, &model)?; } // Create all profile states for (name, profile) in cfg.profiles { - self.execute_config_profile(name, profile)?; + self.execute_config_profile(&name, &profile)?; } // Create all network states // Don't process gateways yet because they will depend on networks existing - for (name, network) in cfg.networks.clone() { + for (name, network) in &cfg.networks { self.execute_config_network(name, network)?; } // Process all ipv4 and ipv6 configurations - for (name, network) in cfg.networks.clone() { + for (name, network) in &cfg.networks { if let Some(ipv4) = network.ipv4.as_ref() { - self.execute_config_network_ipv4(name.clone(), ipv4)?; + self.execute_config_network_ipv4(name, ipv4)?; } if let Some(ipv6) = network.ipv6.as_ref() { - self.execute_config_network_ipv6(name.clone(), ipv6)?; + self.execute_config_network_ipv6(name, ipv6)?; } } // Process all network gateways - for (name, network) in cfg.networks { + for (name, network) in &cfg.networks { if let Some(ipv4) = network.ipv4.as_ref() { if let Some(ipv4gw) = ipv4.gateway.as_ref() { - self.execute_config_network_ipv4_gateway(name.clone(), ipv4gw)?; + self.execute_config_network_ipv4_gateway(name, ipv4gw)?; } } if let Some(ipv6) = network.ipv6.as_ref() { if let Some(ipv6gw) = ipv6.gateway.as_ref() { - self.execute_config_network_ipv6_gateway(name.clone(), ipv6gw)?; + self.execute_config_network_ipv6_gateway(name, ipv6gw)?; } } } // Create all blueprint states // Don't process gateways yet because they will depend on blueprints existing - for (name, blueprint) in cfg.blueprints.clone() { + for (name, blueprint) in &cfg.blueprints { self.execute_config_blueprint(name, blueprint)?; } // Process all ipv4 and ipv6 configurations - for (name, blueprint) in cfg.blueprints.clone() { + for (name, blueprint) in &cfg.blueprints { if let Some(ipv4) = blueprint.ipv4.as_ref() { - self.execute_config_blueprint_ipv4(name.clone(), ipv4)?; + self.execute_config_blueprint_ipv4(name, ipv4)?; } if let Some(ipv6) = blueprint.ipv6.as_ref() { - self.execute_config_blueprint_ipv6(name.clone(), ipv6)?; + self.execute_config_blueprint_ipv6(name, ipv6)?; } } // Process all blueprint gateways - for (name, blueprint) in cfg.blueprints { + for (name, blueprint) in &cfg.blueprints { if let Some(ipv4) = blueprint.ipv4.as_ref() { if let Some(ipv4gw) = ipv4.gateway.as_ref() { - self.execute_config_blueprint_ipv4_gateway(name.clone(), ipv4gw)?; + self.execute_config_blueprint_ipv4_gateway(name, ipv4gw)?; } } if let Some(ipv6) = blueprint.ipv6.as_ref() { if let Some(ipv6gw) = ipv6.gateway.as_ref() { - self.execute_config_blueprint_ipv6_gateway(name.clone(), ipv6gw)?; + self.execute_config_blueprint_ipv6_gateway(name, ipv6gw)?; } } } // Create all template states - for (name, template) in cfg.templates { + for (name, template) in &cfg.templates { self.execute_config_template(name, template)?; } // Create all machine states - for (name, machine) in cfg.machines { + for (name, machine) in &cfg.machines { self.execute_config_machine(name, machine)?; } @@ -332,81 +333,86 @@ impl GlobalStateManagerInner { &mut self.blueprint_state_registry } + #[instrument(level = "debug", skip_all, err)] fn execute_config_allocations( &mut self, config_allocations: &HashMap, ) -> GlobalStateManagerResult<()> { - for (allocation_name, allocation_config) in config_allocations { - if self.allocations.contains_key(allocation_name) { - return Err(GlobalStateManagerError::DuplicateName( - allocation_name.clone(), - )); + for (name, allocation_config) in config_allocations { + if self.allocations.contains_key(name) { + return Err(GlobalStateManagerError::DuplicateName(name.clone())); } - let address_pool = - self.resolve_address_pool(allocation_name.clone(), config_allocations)?; - self.allocations.insert( - allocation_name.clone(), - Arc::new(Allocation { - config: allocation_config.clone(), - address_pool, - }), - ); + let address_pool = self.resolve_address_pool(name.clone(), config_allocations)?; + + let allocation = Arc::new(Allocation { + config: allocation_config.clone(), + address_pool, + }); + debug!("Added allocation: {}: {:?}", name, allocation); + self.allocations.insert(name.clone(), allocation); } Ok(()) } + #[instrument(level = "debug", skip(self, model), err)] fn execute_config_model( &mut self, - name: String, - model: config::Model, + name: &str, + model: &config::Model, ) -> GlobalStateManagerResult<()> { - if self.models.contains_key(&name) { - return Err(GlobalStateManagerError::DuplicateName(name)); + if self.models.contains_key(name) { + return Err(GlobalStateManagerError::DuplicateName(name.to_owned())); } - self.models.insert(name, model); + debug!("Added model: {}: {:?}", name, model); + self.models.insert(name.to_owned(), model.to_owned()); Ok(()) } + #[instrument(level = "debug", skip(self, profile), err)] fn execute_config_profile( &mut self, - name: String, - profile: config::Profile, + name: &str, + profile: &config::Profile, ) -> GlobalStateManagerResult<()> { if self .profile_state_registry - .get_state_id_by_name(&name) + .get_state_id_by_name(name) .is_some() { - return Err(GlobalStateManagerError::DuplicateName(name)); + return Err(GlobalStateManagerError::DuplicateName(name.to_owned())); } let id = self.profile_state_registry.allocate_id(); - let state = ProfileState::new(id, name, profile); + let state = ProfileState::new(id, name.to_owned(), profile.clone()); self.profile_state_registry .attach_state(state) .expect("must attach"); + debug!("Added profile: {}: {:?}", name, profile); + Ok(()) } + #[instrument(level = "debug", skip(self, network), err)] fn execute_config_network( &mut self, - name: String, - network: config::Network, + name: &str, + network: &config::Network, ) -> GlobalStateManagerResult<()> { if self .network_state_registry - .get_state_id_by_name(&name) + .get_state_id_by_name(name) .is_some() { - return Err(GlobalStateManagerError::DuplicateName(name)); + return Err(GlobalStateManagerError::DuplicateName(name.to_owned())); } let id = self.network_state_registry.allocate_id(); let state = { - let mut network_state = NetworkState::new(id, Some(name), NetworkOrigin::Direct); + let mut network_state = + NetworkState::new(id, Some(name.to_owned()), NetworkOrigin::Direct); // Set model - let model_name = self.or_default_model(network.model)?; + let model_name = self.or_default_model(network.model.to_owned())?; let model = self .models .get(&model_name) @@ -427,17 +433,20 @@ impl GlobalStateManagerInner { self.network_state_registry .attach_state(state) .expect("must attach"); + + debug!("Added network: {}: {:?}", name, network); Ok(()) } + #[instrument(level = "debug", skip(self, ipv4), err)] fn execute_config_network_ipv4( &mut self, - name: String, + name: &str, ipv4: &config::NetworkIpv4, ) -> GlobalStateManagerResult<()> { let network_state_id = self .network_state_registry - .get_state_id_by_name(&name) + .get_state_id_by_name(name) .expect("must exist"); let mut network_state = self .network_state_registry @@ -470,14 +479,15 @@ impl GlobalStateManagerInner { Ok(()) } + #[instrument(level = "debug", skip(self, ipv6), err)] fn execute_config_network_ipv6( &mut self, - name: String, + name: &str, ipv6: &config::NetworkIpv6, ) -> GlobalStateManagerResult<()> { let network_state_id = self .network_state_registry - .get_state_id_by_name(&name) + .get_state_id_by_name(name) .expect("must exist"); let mut network_state = self .network_state_registry @@ -510,14 +520,15 @@ impl GlobalStateManagerInner { Ok(()) } + #[instrument(level = "debug", skip(self, ipv4gw), err)] fn execute_config_network_ipv4_gateway( &mut self, - name: String, + name: &str, ipv4gw: &config::NetworkGateway, ) -> GlobalStateManagerResult<()> { let network_state_id = self .network_state_registry - .get_state_id_by_name(&name) + .get_state_id_by_name(name) .expect("must exist"); let mut network_state = self .network_state_registry @@ -550,14 +561,15 @@ impl GlobalStateManagerInner { Ok(()) } + #[instrument(level = "debug", skip(self, ipv6gw), err)] fn execute_config_network_ipv6_gateway( &mut self, - name: String, + name: &str, ipv6gw: &config::NetworkGateway, ) -> GlobalStateManagerResult<()> { let network_state_id = self .network_state_registry - .get_state_id_by_name(&name) + .get_state_id_by_name(name) .expect("must exist"); let mut network_state = self .network_state_registry @@ -590,25 +602,26 @@ impl GlobalStateManagerInner { Ok(()) } + #[instrument(level = "debug", skip(self, blueprint), err)] fn execute_config_blueprint( &mut self, - name: String, - blueprint: config::Blueprint, + name: &str, + blueprint: &config::Blueprint, ) -> GlobalStateManagerResult<()> { if self .blueprint_state_registry - .get_state_id_by_name(&name) + .get_state_id_by_name(name) .is_some() { - return Err(GlobalStateManagerError::DuplicateName(name)); + return Err(GlobalStateManagerError::DuplicateName(name.to_owned())); } let id = self.blueprint_state_registry.allocate_id(); let state = { - let mut blueprint_state = BlueprintState::new(id, name); + let mut blueprint_state = BlueprintState::new(id, name.to_owned()); // Set model - let model = match blueprint.model { + let model = match blueprint.model.to_owned() { Some(x) => x, None => WeightedList::Single( self.default_model @@ -620,6 +633,7 @@ impl GlobalStateManagerInner { blueprint_state.set_limit_network_count( blueprint .limits + .to_owned() .network_count .map(|wl| self.srng().weighted_choice(wl)), ); @@ -634,17 +648,21 @@ impl GlobalStateManagerInner { self.blueprint_state_registry .attach_state(state) .expect("must attach"); + + debug!("Added blueprint: {}: {:?}", name, blueprint); + Ok(()) } + #[instrument(level = "debug", skip(self, ipv4), err)] fn execute_config_blueprint_ipv4( &mut self, - name: String, + name: &str, ipv4: &config::BlueprintIpv4, ) -> GlobalStateManagerResult<()> { let blueprint_state_id = self .blueprint_state_registry - .get_state_id_by_name(&name) + .get_state_id_by_name(name) .expect("must exist"); let mut blueprint_state = self .blueprint_state_registry @@ -697,14 +715,15 @@ impl GlobalStateManagerInner { Ok(()) } + #[instrument(level = "debug", skip(self, ipv4gw), err)] fn execute_config_blueprint_ipv4_gateway( &mut self, - name: String, + name: &str, ipv4gw: &config::BlueprintGateway, ) -> GlobalStateManagerResult<()> { let blueprint_state_id = self .blueprint_state_registry - .get_state_id_by_name(&name) + .get_state_id_by_name(name) .expect("must exist"); let mut blueprint_state = self .blueprint_state_registry @@ -747,14 +766,15 @@ impl GlobalStateManagerInner { Ok(()) } + #[instrument(level = "debug", skip(self, ipv6), err)] fn execute_config_blueprint_ipv6( &mut self, - name: String, + name: &str, ipv6: &config::BlueprintIpv6, ) -> GlobalStateManagerResult<()> { let blueprint_state_id = self .blueprint_state_registry - .get_state_id_by_name(&name) + .get_state_id_by_name(name) .expect("must exist"); let mut blueprint_state = self .blueprint_state_registry @@ -807,14 +827,15 @@ impl GlobalStateManagerInner { Ok(()) } + #[instrument(level = "debug", skip(self, ipv6gw), err)] fn execute_config_blueprint_ipv6_gateway( &mut self, - name: String, + name: &str, ipv6gw: &config::BlueprintGateway, ) -> GlobalStateManagerResult<()> { let blueprint_state_id = self .blueprint_state_registry - .get_state_id_by_name(&name) + .get_state_id_by_name(name) .expect("must exist"); let mut blueprint_state = self .blueprint_state_registry @@ -857,25 +878,26 @@ impl GlobalStateManagerInner { Ok(()) } + #[instrument(level = "debug", skip(self, template), err)] fn execute_config_template( &mut self, - name: String, - template: config::Template, + name: &str, + template: &config::Template, ) -> GlobalStateManagerResult<()> { if self .template_state_registry - .get_state_id_by_name(&name) + .get_state_id_by_name(name) .is_some() { - return Err(GlobalStateManagerError::DuplicateName(name)); + return Err(GlobalStateManagerError::DuplicateName(name.to_owned())); } let id = self.template_state_registry.allocate_id(); let state = { - let mut template_state = TemplateState::new(id, name); + let mut template_state = TemplateState::new(id, name.to_owned()); - template_state.set_disable_capabilities(template.disable_capabilities); - if let Some(wl) = template.limits.machine_count { + template_state.set_disable_capabilities(template.disable_capabilities.to_owned()); + if let Some(wl) = template.limits.to_owned().machine_count { template_state.set_limit_machine_count(Some(self.srng().weighted_choice(wl))); } template_state @@ -912,33 +934,37 @@ impl GlobalStateManagerInner { self.template_state_registry .attach_state(state) .expect("must attach"); + + debug!("Added template: {}: {:?}", name, template); Ok(()) } + #[instrument(level = "debug", skip(self, machine), err)] fn execute_config_machine( &mut self, - name: String, - machine: config::Machine, + name: &str, + machine: &config::Machine, ) -> GlobalStateManagerResult<()> { if self .machine_state_registry - .get_state_id_by_name(&name) + .get_state_id_by_name(name) .is_some() { - return Err(GlobalStateManagerError::DuplicateName(name)); + return Err(GlobalStateManagerError::DuplicateName(name.to_owned())); } let id = self.machine_state_registry.allocate_id(); let state = { - let mut machine_state = MachineState::new(id, Some(name), MachineOrigin::Config); + let mut machine_state = + MachineState::new(id, Some(name.to_owned()), MachineOrigin::Config); - machine_state.set_disable_capabilities(machine.disable_capabilities); + machine_state.set_disable_capabilities(machine.disable_capabilities.to_owned()); machine_state.set_bootstrap(machine.bootstrap); // Create primary interface let interface_name = machine_state.allocate_interface(None, None)?; - match machine.location { + match machine.location.to_owned() { config::MachineLocation::Network { network, address4, @@ -980,9 +1006,11 @@ impl GlobalStateManagerInner { self.machine_state_registry .attach_state(state) .expect("must attach"); + debug!("Added machine: {}: {:?}", name, machine); Ok(()) } + #[instrument(level = "debug", skip(self, config_allocations), err)] fn resolve_address_pool( &self, allocation_name: String, @@ -1009,6 +1037,9 @@ impl GlobalStateManagerInner { } // Reserve out any allocations that used this as their pool + let mut scope4_allocs: Vec = Vec::new(); + let mut scope6_allocs: Vec = Vec::new(); + for (k, v) in config_allocations { // Exclude our own allocation if *k == allocation_name { @@ -1018,7 +1049,8 @@ impl GlobalStateManagerInner { let pool = self.or_default_pool(scope4.pool4.clone())?; if pool == allocation_name { for s in &scope4.scope4 { - address_pool.reserve_allocation_v4(*s, None)?; + scope4_allocs.push(*s); + scope4_allocs = Ipv4Net::aggregate(&scope4_allocs); } } } @@ -1026,12 +1058,20 @@ impl GlobalStateManagerInner { let pool = self.or_default_pool(scope6.pool6.clone())?; if pool == allocation_name { for s in &scope6.scope6 { - address_pool.reserve_allocation_v6(*s, None)?; + scope6_allocs.push(*s); + scope6_allocs = Ipv6Net::aggregate(&scope6_allocs); } } } } + for s in scope4_allocs { + address_pool.reserve_allocation_v4(s, None)?; + } + for s in scope6_allocs { + address_pool.reserve_allocation_v6(s, None)?; + } + Ok(address_pool) } } diff --git a/veilid-tools/src/virtual_network/router_server/global_state_manager/state/blueprint_locations_list.rs b/veilid-tools/src/virtual_network/router_server/global_state_manager/state/blueprint_locations_list.rs new file mode 100644 index 00000000..469c8b9b --- /dev/null +++ b/veilid-tools/src/virtual_network/router_server/global_state_manager/state/blueprint_locations_list.rs @@ -0,0 +1,245 @@ +use super::*; + +/// Locations where a network can be instantiated when a blueprint is generated +#[derive(Debug, Clone)] +pub enum BlueprintLocationsList { + /// Network will be a new allocation + Allocations { allocations: WeightedList }, + /// Network will be allocated as a subnet of an existing network + Networks { + networks: WeightedList, + }, +} + +#[derive(Debug, Clone)] +pub struct NetworkLocation { + pub scope: Vec, + pub reserve: Vec, + pub super_net: Option, +} + +impl BlueprintLocationsList { + #[instrument(level = "debug", skip_all, err)] + pub fn pick_v4( + &self, + gsm_inner: &mut GlobalStateManagerInner, + prefix: &WeightedList, + ) -> GlobalStateManagerResult>> { + // Get maximum prefix + let max_prefix = prefix + .iter() + .max() + .copied() + .expect("must have at least one element"); + + // Get addresses for network + match self { + BlueprintLocationsList::Allocations { allocations } => { + // Get allocations which have subnets that would fit + // our maximum requested prefix + let Some(address_pools) = allocations.try_filter_map(|allocation_name| { + let allocation = gsm_inner + .allocations() + .get(allocation_name) + .expect("must exist"); + if allocation.address_pool.can_allocate_v4(max_prefix)? { + Ok(Some(allocation.address_pool.clone())) + } else { + Ok(None) + } + })? + else { + return Ok(None); + }; + + // Pick an address pool + let mut address_pool = gsm_inner.srng().weighted_choice(address_pools); + + // Pick a prefix length that would fit in the subnet + let opt_subnet = prefix + .try_filter(|p| address_pool.can_allocate_v4(*p))? + .as_ref() + .map(|wl| { + let subnet_prefix = *gsm_inner.srng().weighted_choice_ref(wl); + + address_pool.allocate_random_v4(gsm_inner.srng(), subnet_prefix, ()) + }) + .transpose()? + .flatten(); + let Some(subnet) = opt_subnet else { + return Ok(None); + }; + Ok(Some(NetworkLocation { + scope: vec![subnet], + reserve: Vec::new(), + super_net: None, + })) + } + BlueprintLocationsList::Networks { networks } => { + // Get networks which have subnets that would fit + // our maximum requested prefix + let Some(available_networks) = networks.try_filter(|network_id| { + let super_network_state = gsm_inner + .network_states() + .get_state(*network_id) + .expect("must exist"); + + Ok(super_network_state.can_allocate_subnet_v4(None, max_prefix)) + })? + else { + return Ok(None); + }; + + // Pick a network + let super_network_id = *gsm_inner.srng().weighted_choice_ref(&available_networks); + let mut super_network_state = gsm_inner + .network_states() + .get_state(super_network_id) + .expect("must exist"); + + // Pick a prefix that fits in this network and allocate from it + let opt_subnet = prefix + .filter(|p| super_network_state.can_allocate_subnet_v4(None, *p)) + .as_ref() + .map(|wl| { + let subnet_prefix = *gsm_inner.srng().weighted_choice_ref(wl); + + // Allocate subnet from this network + super_network_state.allocate_subnet_v4( + gsm_inner, + OwnerTag::Network(super_network_state.id()), + None, + subnet_prefix, + ) + }) + .transpose()?; + let Some(subnet) = opt_subnet else { + return Ok(None); + }; + + // Update network state + gsm_inner + .network_states_mut() + .set_state(super_network_state); + + Ok(Some(NetworkLocation { + scope: vec![subnet], + reserve: Vec::new(), + super_net: Some(super_network_id), + })) + } + } + } + + #[instrument(level = "debug", skip_all, err)] + pub fn pick_v6( + &self, + gsm_inner: &mut GlobalStateManagerInner, + prefix: &WeightedList, + ) -> GlobalStateManagerResult>> { + // Get maximum prefix + let max_prefix = prefix + .iter() + .max() + .copied() + .expect("must have at least one element"); + + // Get addresses for network + match self { + BlueprintLocationsList::Allocations { allocations } => { + // Get allocations which have subnets that would fit + // our maximum requested prefix + let Some(address_pools) = allocations.try_filter_map(|allocation_name| { + let allocation = gsm_inner + .allocations() + .get(allocation_name) + .expect("must exist"); + if allocation.address_pool.can_allocate_v6(max_prefix)? { + Ok(Some(allocation.address_pool.clone())) + } else { + Ok(None) + } + })? + else { + return Ok(None); + }; + + // Pick an address pool + let mut address_pool = gsm_inner.srng().weighted_choice(address_pools); + + // Pick a prefix length that would fit in the subnet + let opt_subnet = prefix + .try_filter(|p| address_pool.can_allocate_v6(*p))? + .as_ref() + .map(|wl| { + let subnet_prefix = *gsm_inner.srng().weighted_choice_ref(wl); + + address_pool.allocate_random_v6(gsm_inner.srng(), subnet_prefix, ()) + }) + .transpose()? + .flatten(); + let Some(subnet) = opt_subnet else { + return Ok(None); + }; + Ok(Some(NetworkLocation { + scope: vec![subnet], + reserve: Vec::new(), + super_net: None, + })) + } + BlueprintLocationsList::Networks { networks } => { + // Get networks which have subnets that would fit + // our maximum requested prefix + let Some(available_networks) = networks.try_filter(|network_id| { + let super_network_state = gsm_inner + .network_states() + .get_state(*network_id) + .expect("must exist"); + + Ok(super_network_state.can_allocate_subnet_v6(None, max_prefix)) + })? + else { + return Ok(None); + }; + + // Pick a network + let super_network_id = *gsm_inner.srng().weighted_choice_ref(&available_networks); + let mut super_network_state = gsm_inner + .network_states() + .get_state(super_network_id) + .expect("must exist"); + + // Pick a prefix that fits in this network and allocate from it + let opt_subnet = prefix + .filter(|p| super_network_state.can_allocate_subnet_v6(None, *p)) + .as_ref() + .map(|wl| { + let subnet_prefix = *gsm_inner.srng().weighted_choice_ref(wl); + + // Allocate subnet from this network + super_network_state.allocate_subnet_v6( + gsm_inner, + OwnerTag::Network(super_network_state.id()), + None, + subnet_prefix, + ) + }) + .transpose()?; + let Some(subnet) = opt_subnet else { + return Ok(None); + }; + + // Update network state + gsm_inner + .network_states_mut() + .set_state(super_network_state); + + Ok(Some(NetworkLocation { + scope: vec![subnet], + reserve: Vec::new(), + super_net: Some(super_network_id), + })) + } + } + } +} diff --git a/veilid-tools/src/virtual_network/router_server/global_state_manager/state/blueprint_state.rs b/veilid-tools/src/virtual_network/router_server/global_state_manager/state/blueprint_state.rs index b76217f9..ec8092b2 100644 --- a/veilid-tools/src/virtual_network/router_server/global_state_manager/state/blueprint_state.rs +++ b/veilid-tools/src/virtual_network/router_server/global_state_manager/state/blueprint_state.rs @@ -82,6 +82,7 @@ impl BlueprintState { } } + #[instrument(level = "debug", skip(self))] pub fn set_limit_network_count(&mut self, limit_network_count: Option) { // Update fields self.fields = Arc::new(BlueprintStateFields { @@ -90,6 +91,7 @@ impl BlueprintState { }); } + #[instrument(level = "debug", skip(self))] pub fn set_model(&mut self, model: WeightedList) { let model = Some(model.map(|x| Arc::new(x.clone()))); // Update fields @@ -99,6 +101,7 @@ impl BlueprintState { }); } + #[instrument(level = "debug", skip(self, gsm_inner), err)] pub fn clear_ipv4( &mut self, gsm_inner: &mut GlobalStateManagerInner, @@ -118,6 +121,7 @@ impl BlueprintState { Ok(()) } + #[instrument(level = "debug", skip(self, _gsm_inner), err)] pub fn clear_ipv4_gateway( &mut self, _gsm_inner: &mut GlobalStateManagerInner, @@ -141,6 +145,7 @@ impl BlueprintState { Ok(()) } + #[instrument(level = "debug", skip(self, gsm_inner), err)] pub fn set_ipv4( &mut self, gsm_inner: &mut GlobalStateManagerInner, @@ -166,6 +171,7 @@ impl BlueprintState { Ok(()) } + #[instrument(level = "debug", skip(self, gsm_inner), err)] pub fn set_ipv4_gateway( &mut self, gsm_inner: &mut GlobalStateManagerInner, @@ -198,6 +204,7 @@ impl BlueprintState { Ok(()) } + #[instrument(level = "debug", skip(self, gsm_inner), err)] pub fn clear_ipv6( &mut self, gsm_inner: &mut GlobalStateManagerInner, @@ -217,6 +224,7 @@ impl BlueprintState { Ok(()) } + #[instrument(level = "debug", skip(self, _gsm_inner), err)] pub fn clear_ipv6_gateway( &mut self, _gsm_inner: &mut GlobalStateManagerInner, @@ -240,6 +248,7 @@ impl BlueprintState { Ok(()) } + #[instrument(level = "debug", skip(self, gsm_inner), err)] pub fn set_ipv6( &mut self, gsm_inner: &mut GlobalStateManagerInner, @@ -265,6 +274,7 @@ impl BlueprintState { Ok(()) } + #[instrument(level = "debug", skip(self, gsm_inner), err)] pub fn set_ipv6_gateway( &mut self, gsm_inner: &mut GlobalStateManagerInner, @@ -314,6 +324,7 @@ impl BlueprintState { ok } + #[instrument(level = "debug", skip(self, gsm_inner), err)] fn generate_model_inner( &mut self, gsm_inner: &mut GlobalStateManagerInner, @@ -338,6 +349,7 @@ impl BlueprintState { } /// Network filter that ensures we can allocate an ipv4 gateway address on a network + #[instrument(level = "debug", skip(self, gsm_inner), err)] fn gateway_network_filter_v4( &self, gsm_inner: &GlobalStateManagerInner, @@ -353,6 +365,7 @@ impl BlueprintState { } /// Network filter that ensures we can allocate an ipv4 gateway address on a network + #[instrument(level = "debug", skip(self, gsm_inner), err)] fn gateway_network_filter_v6( &self, gsm_inner: &GlobalStateManagerInner, @@ -367,6 +380,7 @@ impl BlueprintState { Ok(can_allocate) } + #[instrument(level = "debug", skip(self, gsm_inner), err)] fn generate_ipv4_inner( &mut self, gsm_inner: &mut GlobalStateManagerInner, @@ -464,6 +478,7 @@ impl BlueprintState { Ok(()) } + #[instrument(level = "debug", skip(self, gsm_inner), err)] fn generate_ipv6_inner( &mut self, gsm_inner: &mut GlobalStateManagerInner, @@ -561,6 +576,7 @@ impl BlueprintState { Ok(()) } + #[instrument(level = "debug", skip(self, gsm_inner), err)] pub fn generate( &mut self, gsm_inner: &mut GlobalStateManagerInner, @@ -612,6 +628,7 @@ impl BlueprintState { Ok(network_state_id) } + #[instrument(level = "debug", skip(self, callback), err)] pub fn for_each_network_id(&self, mut callback: F) -> GlobalStateManagerResult> where F: FnMut(NetworkStateId) -> GlobalStateManagerResult>, @@ -624,6 +641,7 @@ impl BlueprintState { Ok(None) } + #[instrument(level = "debug", skip(self))] pub fn on_network_released(&mut self, network_id: NetworkStateId) { // Remove network from list let pos = self diff --git a/veilid-tools/src/virtual_network/router_server/global_state_manager/state/machine_state.rs b/veilid-tools/src/virtual_network/router_server/global_state_manager/state/machine_state.rs index 394eaaa2..26baa15e 100644 --- a/veilid-tools/src/virtual_network/router_server/global_state_manager/state/machine_state.rs +++ b/veilid-tools/src/virtual_network/router_server/global_state_manager/state/machine_state.rs @@ -60,6 +60,7 @@ impl MachineState { } } + #[instrument(level = "debug", skip(self, gsm_inner))] pub fn release(mut self, gsm_inner: &mut GlobalStateManagerInner) { self.release_all_interfaces(gsm_inner) .expect("must succeed"); @@ -74,6 +75,7 @@ impl MachineState { } } + #[instrument(level = "debug", skip(self))] pub fn set_disable_capabilities(&mut self, disable_capabilities: Vec) { self.fields = Arc::new(MachineStateFields { disable_capabilities: disable_capabilities.into(), @@ -81,6 +83,7 @@ impl MachineState { }); } + #[instrument(level = "debug", skip(self))] pub fn set_bootstrap(&mut self, bootstrap: bool) { self.fields = Arc::new(MachineStateFields { bootstrap, @@ -99,6 +102,7 @@ impl MachineState { } } + #[instrument(level = "debug", skip(self), err)] pub fn allocate_interface( &mut self, interface_name: Option, @@ -144,6 +148,7 @@ impl MachineState { intfs } + #[instrument(level = "debug", skip(self, gsm_inner), err)] pub fn allocate_address_ipv4( &mut self, gsm_inner: &mut GlobalStateManagerInner, @@ -210,6 +215,7 @@ impl MachineState { Ok(ifv4_addr) } + #[instrument(level = "debug", skip(self, gsm_inner), err)] pub fn allocate_address_ipv6( &mut self, gsm_inner: &mut GlobalStateManagerInner, @@ -275,6 +281,7 @@ impl MachineState { Ok(ifv6_addr) } + #[instrument(level = "debug", skip(self, gsm_inner), err)] pub fn attach_network( &mut self, gsm_inner: &mut GlobalStateManagerInner, @@ -310,6 +317,7 @@ impl MachineState { Ok(()) } + #[instrument(level = "debug", skip(self, gsm_inner), err)] pub fn detach_network( &mut self, gsm_inner: &mut GlobalStateManagerInner, @@ -353,6 +361,7 @@ impl MachineState { Ok(out) } + #[instrument(level = "debug", skip(self, gsm_inner), err)] pub fn release_address( &mut self, gsm_inner: &mut GlobalStateManagerInner, @@ -415,6 +424,7 @@ impl MachineState { Ok(()) } + #[instrument(level = "debug", skip(self, gsm_inner), err)] pub fn release_all_addresses( &mut self, gsm_inner: &mut GlobalStateManagerInner, @@ -445,6 +455,39 @@ impl MachineState { Ok(()) } + #[instrument(level = "debug", skip(self, gsm_inner), err)] + pub fn release_all_interfaces( + &mut self, + gsm_inner: &mut GlobalStateManagerInner, + ) -> GlobalStateManagerResult<()> { + let interface_names: Vec = self + .fields + .interfaces + .keys() + .map(|x| (**x).clone()) + .collect(); + for interface_name in interface_names { + let interface_key = Arc::new(interface_name); + let Some(mut machine_state_interface) = + self.fields.interfaces.get(&interface_key).cloned() + else { + return Err(GlobalStateManagerError::InvalidName( + (*interface_key).clone(), + )); + }; + + Self::detach_network_inner(gsm_inner, &mut machine_state_interface)?; + } + + // Update fields + self.fields = Arc::new(MachineStateFields { + interfaces: imbl::HashMap::new(), + ..(*self.fields).clone() + }); + + Ok(()) + } + //////////////////////////////////////////////////////////////////////// fn detach_network_inner( @@ -514,38 +557,6 @@ impl MachineState { Ok(()) } - - pub fn release_all_interfaces( - &mut self, - gsm_inner: &mut GlobalStateManagerInner, - ) -> GlobalStateManagerResult<()> { - let interface_names: Vec = self - .fields - .interfaces - .keys() - .map(|x| (**x).clone()) - .collect(); - for interface_name in interface_names { - let interface_key = Arc::new(interface_name); - let Some(mut machine_state_interface) = - self.fields.interfaces.get(&interface_key).cloned() - else { - return Err(GlobalStateManagerError::InvalidName( - (*interface_key).clone(), - )); - }; - - Self::detach_network_inner(gsm_inner, &mut machine_state_interface)?; - } - - // Update fields - self.fields = Arc::new(MachineStateFields { - interfaces: imbl::HashMap::new(), - ..(*self.fields).clone() - }); - - Ok(()) - } } impl State for MachineState { diff --git a/veilid-tools/src/virtual_network/router_server/global_state_manager/state/network_state.rs b/veilid-tools/src/virtual_network/router_server/global_state_manager/state/network_state.rs index 57421fb4..5848d602 100644 --- a/veilid-tools/src/virtual_network/router_server/global_state_manager/state/network_state.rs +++ b/veilid-tools/src/virtual_network/router_server/global_state_manager/state/network_state.rs @@ -139,6 +139,7 @@ impl NetworkState { } } + #[instrument(level = "debug", skip(self, gsm_inner))] pub fn release(self, gsm_inner: &mut GlobalStateManagerInner) { if let NetworkOrigin::Blueprint(generating_blueprint) = self.immutable.origin { let mut blueprint_state = gsm_inner @@ -150,6 +151,7 @@ impl NetworkState { } } + #[instrument(level = "debug", skip(self))] pub fn set_model(&mut self, params: NetworkStateModelParams) { self.fields = Arc::new(NetworkStateFields { model: NetworkStateModel { params }, @@ -157,6 +159,7 @@ impl NetworkState { }); } + #[instrument(level = "debug", skip(self, gsm_inner), err)] pub fn clear_ipv4( &mut self, gsm_inner: &mut GlobalStateManagerInner, @@ -188,6 +191,7 @@ impl NetworkState { Ok(()) } + #[instrument(level = "debug", skip(self, gsm_inner), err)] pub fn clear_ipv4_gateway( &mut self, gsm_inner: &mut GlobalStateManagerInner, @@ -232,6 +236,7 @@ impl NetworkState { Ok(()) } + #[instrument(level = "debug", skip(self, gsm_inner), err)] pub fn set_ipv4( &mut self, gsm_inner: &mut GlobalStateManagerInner, @@ -262,6 +267,7 @@ impl NetworkState { Ok(()) } + #[instrument(level = "debug", skip(self, gsm_inner), err)] pub fn set_ipv4_gateway( &mut self, gsm_inner: &mut GlobalStateManagerInner, @@ -361,6 +367,7 @@ impl NetworkState { Ok(()) } + #[instrument(level = "debug", skip(self, gsm_inner), err)] pub fn clear_ipv6( &mut self, gsm_inner: &mut GlobalStateManagerInner, @@ -392,6 +399,7 @@ impl NetworkState { Ok(()) } + #[instrument(level = "debug", skip(self, gsm_inner), err)] pub fn clear_ipv6_gateway( &mut self, gsm_inner: &mut GlobalStateManagerInner, @@ -436,6 +444,7 @@ impl NetworkState { Ok(()) } + #[instrument(level = "debug", skip(self, gsm_inner), err)] pub fn set_ipv6( &mut self, gsm_inner: &mut GlobalStateManagerInner, @@ -465,6 +474,7 @@ impl NetworkState { Ok(()) } + #[instrument(level = "debug", skip(self, gsm_inner), err)] pub fn set_ipv6_gateway( &mut self, gsm_inner: &mut GlobalStateManagerInner, @@ -588,6 +598,7 @@ impl NetworkState { Ok(can_allocate) } + #[instrument(level = "debug", skip(self, gsm_inner), err)] pub fn allocate_address_v4( &mut self, gsm_inner: &mut GlobalStateManagerInner, @@ -617,6 +628,7 @@ impl NetworkState { self.can_allocate_subnet_v4(opt_address, 32) } + #[instrument(level = "debug", skip(self, gsm_inner), err)] pub fn allocate_subnet_v4( &mut self, gsm_inner: &mut GlobalStateManagerInner, @@ -674,6 +686,7 @@ impl NetworkState { } } + #[instrument(level = "debug", skip(self), err)] pub fn release_address_v4( &mut self, addr: Ipv4Addr, @@ -681,6 +694,7 @@ impl NetworkState { self.release_subnet_v4(Ipv4Net::new(addr, 32).expect("must succeed")) } + #[instrument(level = "debug", skip(self), err)] pub fn release_subnet_v4( &mut self, net: Ipv4Net, @@ -696,6 +710,7 @@ impl NetworkState { Ok(opt_tag) } + #[instrument(level = "debug", skip(self, gsm_inner), err)] pub fn allocate_address_v6( &mut self, gsm_inner: &mut GlobalStateManagerInner, @@ -726,6 +741,7 @@ impl NetworkState { self.can_allocate_subnet_v6(opt_address, 128) } + #[instrument(level = "debug", skip(self, gsm_inner), err)] pub fn allocate_subnet_v6( &mut self, gsm_inner: &mut GlobalStateManagerInner, @@ -783,6 +799,7 @@ impl NetworkState { } } + #[instrument(level = "debug", skip(self), err)] pub fn release_address_v6( &mut self, addr: Ipv6Addr, @@ -790,6 +807,7 @@ impl NetworkState { self.release_subnet_v6(Ipv6Net::new(addr, 128).expect("must succeed")) } + #[instrument(level = "debug", skip(self), err)] pub fn release_subnet_v6( &mut self, net: Ipv6Net, diff --git a/veilid-tools/src/virtual_network/router_server/global_state_manager/state/profile_state.rs b/veilid-tools/src/virtual_network/router_server/global_state_manager/state/profile_state.rs index 1bea01c1..1142e05a 100644 --- a/veilid-tools/src/virtual_network/router_server/global_state_manager/state/profile_state.rs +++ b/veilid-tools/src/virtual_network/router_server/global_state_manager/state/profile_state.rs @@ -30,6 +30,7 @@ impl ProfileState { } } + #[instrument(level = "debug", skip(self))] pub fn next_instance(&mut self) -> Option { let instance_index = { let instance_index = self.fields.next_instance_index; diff --git a/veilid-tools/src/virtual_network/router_server/global_state_manager/state/resolves_to.rs b/veilid-tools/src/virtual_network/router_server/global_state_manager/state/resolves_to.rs deleted file mode 100644 index 70b177de..00000000 --- a/veilid-tools/src/virtual_network/router_server/global_state_manager/state/resolves_to.rs +++ /dev/null @@ -1,82 +0,0 @@ -use super::*; - -#[derive(Debug)] -pub enum ResolveToError { - MissingSymbol, - AlreadyResolved, -} - -pub type ResolveToResult = Result; - -#[derive(Clone, Debug)] -pub struct ResolvesTo -where - I: Clone + fmt::Debug, -{ - value: Arc>>, -} - -impl ResolvesTo -where - I: Clone + fmt::Debug, -{ - pub fn get(&self) -> Option { - self.value.lock().clone() - } -} - -#[derive(Debug)] -pub struct ResolveToManager -where - T: PartialEq + Eq + PartialOrd + Ord + fmt::Debug, - I: Clone + fmt::Debug, -{ - symbols: BTreeMap, Arc>>>, -} - -impl ResolveToManager -where - T: PartialEq + Eq + PartialOrd + Ord + fmt::Debug, - I: Clone + fmt::Debug, -{ - pub fn new() -> Self { - Self { - symbols: BTreeMap::new(), - } - } - - pub fn add(&mut self, symbol: T) -> ResolvesTo { - let symbol = Arc::new(symbol); - let value = self - .symbols - .entry(symbol.clone()) - .or_insert_with(|| Arc::new(Mutex::new(None))) - .clone(); - - ResolvesTo { value } - } - - pub fn resolve(&mut self, symbol: &T, value: I) -> ResolveToResult<()> { - match self.symbols.get_mut(symbol) { - Some(s) => { - let mut inner = s.lock(); - if inner.is_some() { - return Err(ResolveToError::AlreadyResolved); - } - *inner = Some(value); - Ok(()) - } - 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/global_state_manager/state/state_registry.rs b/veilid-tools/src/virtual_network/router_server/global_state_manager/state/state_registry.rs index d1fc23e0..b5d4e157 100644 --- a/veilid-tools/src/virtual_network/router_server/global_state_manager/state/state_registry.rs +++ b/veilid-tools/src/virtual_network/router_server/global_state_manager/state/state_registry.rs @@ -49,7 +49,7 @@ impl core::hash::Hash for StateId { #[derive(Debug, Clone)] pub struct StateRegistry { - state_id_by_name: imbl::HashMap, StateIdInternal>, + state_id_by_name: imbl::HashMap, state_by_id: imbl::HashMap>, next_state_id: StateIdInternal, free_state_ids: imbl::Vector, @@ -122,9 +122,10 @@ impl StateRegistry { return Err(GlobalStateManagerError::DuplicateName(name)); } // Register the named state - self.state_id_by_name - .insert(Arc::new(name), id.0) - .expect("should not have a duplicated name here"); + assert!( + self.state_id_by_name.insert(name, id.0).is_none(), + "should not have a duplicated name here" + ); } // Attach the state to the state slot @@ -171,7 +172,7 @@ impl StateRegistry { self.state_by_id.insert(state.id().0, Some(state)); } - pub fn get_state_id_by_name(&self, name: &String) -> Option> { + pub fn get_state_id_by_name(&self, name: &str) -> Option> { // Get the id associated with this name let id = self.state_id_by_name.get(name)?; Some(StateId::new(*id)) diff --git a/veilid-tools/src/virtual_network/router_server/global_state_manager/state/template_locations_list.rs b/veilid-tools/src/virtual_network/router_server/global_state_manager/state/template_locations_list.rs new file mode 100644 index 00000000..d43862c1 --- /dev/null +++ b/veilid-tools/src/virtual_network/router_server/global_state_manager/state/template_locations_list.rs @@ -0,0 +1,185 @@ +use super::*; + +#[derive(Debug, Clone)] +enum BlueprintAvailability { + Existing(NetworkState), + Generate(BlueprintState), +} + +/// Locations where a machine can be instantiated when a template is generated +#[derive(Debug, Clone)] +pub enum TemplateLocationsList { + Networks { + networks: WeightedList, + }, + Blueprints { + blueprints: WeightedList, + }, +} + +impl TemplateLocationsList { + #[instrument(level = "debug", skip_all, err)] + pub fn can_pick( + &self, + gsm_inner: &mut GlobalStateManagerInner, + mut network_filter: F, + ) -> GlobalStateManagerResult + where + F: FnMut(&GlobalStateManagerInner, NetworkStateId) -> GlobalStateManagerResult, + { + match self { + TemplateLocationsList::Networks { networks } => { + // Filter the weighted list of networks to those that are still active and or not yet started + if networks + .try_filter(|id| { + let network_state = gsm_inner.network_states().get_state(*id)?; + self.is_network_available(gsm_inner, network_state, &mut network_filter) + })? + .is_none() + { + return Ok(false); + }; + } + TemplateLocationsList::Blueprints { blueprints } => { + // Filter the weighted list of blueprints to those that are still active or not yet started and can allocate + if blueprints + .try_filter(|id| { + let blueprint_state = gsm_inner.blueprint_states().get_state(*id)?; + + self.is_blueprint_available(gsm_inner, blueprint_state, &mut network_filter) + .map(|x| x.is_some()) + })? + .is_none() + { + return Ok(false); + }; + } + }; + Ok(true) + } + + #[instrument(level = "debug", skip_all, err)] + pub fn pick( + &self, + gsm_inner: &mut GlobalStateManagerInner, + mut network_filter: F, + ) -> GlobalStateManagerResult> + where + F: FnMut(&GlobalStateManagerInner, NetworkStateId) -> GlobalStateManagerResult, + { + // Get a network to generate the machine on + let network_state = match self { + TemplateLocationsList::Networks { networks } => { + // Filter the weighted list of networks to those that are still active and or not yet started + let Some(available_networks) = networks.try_filter_map(|id| { + let network_state = gsm_inner.network_states().get_state(*id)?; + if self.is_network_available( + gsm_inner, + network_state.clone(), + &mut network_filter, + )? { + Ok(Some(network_state)) + } else { + Ok(None) + } + })? + else { + return Ok(None); + }; + + // Weighted choice of network now that we have a candidate list + let network_state = gsm_inner.srng().weighted_choice(available_networks); + + // Return network state to use + network_state + } + TemplateLocationsList::Blueprints { blueprints } => { + // Filter the weighted list of blueprints to those that are still active or not yet started and can allocate + let Some(available_blueprints) = blueprints.try_filter_map(|id| { + let blueprint_state = gsm_inner.blueprint_states().get_state(*id)?; + + self.is_blueprint_available(gsm_inner, blueprint_state, &mut network_filter) + })? + else { + return Ok(None); + }; + + // Weighted choice of blueprint now that we have a candidate list + match gsm_inner.srng().weighted_choice(available_blueprints) { + BlueprintAvailability::Existing(network_state) => network_state, + BlueprintAvailability::Generate(mut blueprint_state) => { + // Generate network state from blueprint state + let network_state_id = blueprint_state.generate(gsm_inner)?; + + // Update blueprint state + gsm_inner.blueprint_states_mut().set_state(blueprint_state); + + // Return network state + gsm_inner.network_states().get_state(network_state_id)? + } + } + } + }; + + Ok(Some(network_state)) + } + + #[instrument(level = "debug", skip_all, err)] + fn is_network_available( + &self, + gsm_inner: &GlobalStateManagerInner, + network_state: NetworkState, + mut network_filter: F, + ) -> GlobalStateManagerResult + where + F: FnMut(&GlobalStateManagerInner, NetworkStateId) -> GlobalStateManagerResult, + { + // If the network is not active, it is not available + if !network_state.is_active()? { + return Ok(false); + } + + // Check the network filter + if !network_filter(gsm_inner, network_state.id())? { + return Ok(false); + } + + Ok(true) + } + + #[instrument(level = "debug", skip_all, err)] + fn is_blueprint_available( + &self, + gsm_inner: &mut GlobalStateManagerInner, + blueprint_state: BlueprintState, + mut network_filter: F, + ) -> GlobalStateManagerResult> + where + F: FnMut(&GlobalStateManagerInner, NetworkStateId) -> GlobalStateManagerResult, + { + // See if the networks generated from this blueprint so far have availability + // in this template + if let Some(available_network_state) = blueprint_state.for_each_network_id(|id| { + // Check the network's availability + let network_state = gsm_inner.network_states().get_state(id)?; + if self.is_network_available(gsm_inner, network_state.clone(), &mut network_filter)? { + // We found one + return Ok(Some(network_state)); + } + // Try next network + Ok(None) + })? { + // We found a usable network + return Ok(Some(BlueprintAvailability::Existing( + available_network_state, + ))); + } + + // If the blueprint is active, it is available because it can make a new network + if blueprint_state.is_active(gsm_inner) { + return Ok(Some(BlueprintAvailability::Generate(blueprint_state))); + } + + Ok(None) + } +} diff --git a/veilid-tools/src/virtual_network/router_server/global_state_manager/state/template_state.rs b/veilid-tools/src/virtual_network/router_server/global_state_manager/state/template_state.rs index 0f49d19e..08014cca 100644 --- a/veilid-tools/src/virtual_network/router_server/global_state_manager/state/template_state.rs +++ b/veilid-tools/src/virtual_network/router_server/global_state_manager/state/template_state.rs @@ -47,6 +47,7 @@ impl TemplateState { } } + #[instrument(level = "debug", skip(self))] pub fn set_disable_capabilities(&mut self, disable_capabilities: Vec) { let disable_capabilities = imbl::Vector::from_iter(disable_capabilities.into_iter().map(Arc::new)); @@ -57,6 +58,7 @@ impl TemplateState { }); } + #[instrument(level = "debug", skip(self))] pub fn set_networks_list(&mut self, networks: WeightedList) { let locations_list = Some(TemplateLocationsList::Networks { networks }); @@ -67,6 +69,7 @@ impl TemplateState { }); } + #[instrument(level = "debug", skip(self))] pub fn set_blueprints_list(&mut self, blueprints: WeightedList) { let locations_list = Some(TemplateLocationsList::Blueprints { blueprints }); @@ -77,6 +80,7 @@ impl TemplateState { }); } + #[instrument(level = "debug", skip(self))] pub fn clear_locations_list(&mut self) { let locations_list = None; @@ -87,6 +91,7 @@ impl TemplateState { }); } + #[instrument(level = "debug", skip(self))] pub fn set_limit_machine_count(&mut self, limit_machine_count: Option) { // Update fields self.fields = Arc::new(TemplateStateFields { @@ -95,6 +100,7 @@ impl TemplateState { }); } + #[instrument(level = "debug", skip(self))] pub fn set_limit_machines_per_network( &mut self, limit_machines_per_network: Option>, @@ -142,6 +148,7 @@ impl TemplateState { Ok(true) } + #[instrument(level = "debug", skip(self, gsm_inner), err)] pub fn generate( &mut self, gsm_inner: &mut GlobalStateManagerInner, @@ -237,6 +244,7 @@ impl TemplateState { Ok(machine_state_id) } + #[instrument(level = "debug", skip(self))] pub fn on_machine_released(&mut self, machine_state_id: MachineStateId) { let machines = self.fields.machines.without(&machine_state_id); let mut machines_per_network = self.fields.machines_per_network.clone(); diff --git a/veilid-tools/src/virtual_network/router_server/predefined_config.yml b/veilid-tools/src/virtual_network/router_server/predefined_config.yml index 543df918..b4dd02a1 100644 --- a/veilid-tools/src/virtual_network/router_server/predefined_config.yml +++ b/veilid-tools/src/virtual_network/router_server/predefined_config.yml @@ -28,7 +28,10 @@ default_pool: "$internet" networks: # Predefined networks $internet: - allocation: "$internet" + ipv4: + allocation: "$internet" + ipv6: + allocation: "$internet" model: "$internet" #################################################################