validation and start of generation

This commit is contained in:
Christien Rioux 2024-11-20 21:29:54 -05:00
parent 981674b870
commit 2cab1d75c1
5 changed files with 469 additions and 69 deletions

View File

@ -52,6 +52,7 @@ pub mod socket_tools;
pub mod spawn; pub mod spawn;
pub mod split_url; pub mod split_url;
pub mod startup_lock; pub mod startup_lock;
pub mod static_string_table;
pub mod tick_task; pub mod tick_task;
pub mod timeout; pub mod timeout;
pub mod timeout_or; pub mod timeout_or;
@ -241,6 +242,8 @@ pub use split_url::*;
#[doc(inline)] #[doc(inline)]
pub use startup_lock::*; pub use startup_lock::*;
#[doc(inline)] #[doc(inline)]
pub use static_string_table::*;
#[doc(inline)]
pub use tick_task::*; pub use tick_task::*;
#[doc(inline)] #[doc(inline)]
pub use timeout::*; pub use timeout::*;

View File

@ -0,0 +1,21 @@
use super::*;
static STRING_TABLE: std::sync::LazyLock<Mutex<BTreeSet<&'static str>>> =
std::sync::LazyLock::new(|| Mutex::new(BTreeSet::new()));
pub trait ToStaticStr {
fn to_static_str(&self) -> &'static str;
}
impl<T: AsRef<str>> ToStaticStr for T {
fn to_static_str(&self) -> &'static str {
let s = self.as_ref();
let mut string_table = STRING_TABLE.lock();
if let Some(v) = string_table.get(s) {
return v;
}
let ss = Box::leak(s.to_owned().into_boxed_str());
string_table.insert(ss);
ss
}
}

View File

@ -28,14 +28,18 @@ impl<T: fmt::Debug + Clone> Default for WeightedList<T> {
impl<T: fmt::Debug + Clone> Validate for WeightedList<T> { impl<T: fmt::Debug + Clone> Validate for WeightedList<T> {
fn validate(&self) -> Result<(), ValidationErrors> { fn validate(&self) -> Result<(), ValidationErrors> {
let mut errors = ValidationErrors::new(); let mut errors = ValidationErrors::new();
if let Self::List(v) = self { match self {
if v.is_empty() { Self::List(v) => {
errors.add( if v.is_empty() {
"List", errors.add(
ValidationError::new("len") "List",
.with_message("weighted list must not be empty".into()), 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() { if errors.is_empty() {
@ -46,6 +50,34 @@ impl<T: fmt::Debug + Clone> Validate for WeightedList<T> {
} }
} }
impl<T: fmt::Debug + Clone> WeightedList<T> {
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<E, F: FnMut(&T) -> 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 type Probability = f32; pub type Probability = f32;
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@ -76,6 +108,21 @@ impl<T: fmt::Debug + Clone> Validate for Weighted<T> {
} }
} }
impl<T: fmt::Debug + Clone> Weighted<T> {
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)] #[derive(Debug, Clone, Serialize, Deserialize, Validate)]
#[validate(context = "ValidateContext<'v_a>")] #[validate(context = "ValidateContext<'v_a>")]
pub struct Profile { pub struct Profile {
@ -94,7 +141,10 @@ pub enum Instance {
} }
#[derive(Debug, Clone, Serialize, Deserialize, Validate)] #[derive(Debug, Clone, Serialize, Deserialize, Validate)]
#[validate(context = "ValidateContext<'v_a>")] #[validate(
context = "ValidateContext<'v_a>",
schema(function = "validate_machine", use_context)
)]
pub struct Machine { pub struct Machine {
#[serde(flatten)] #[serde(flatten)]
#[validate(custom(function = "validate_location_exists", use_context))] #[validate(custom(function = "validate_location_exists", use_context))]
@ -109,23 +159,64 @@ pub struct Machine {
pub bootstrap: bool, pub bootstrap: bool,
} }
fn validate_machine(machine: &Machine, _context: &ValidateContext) -> Result<(), ValidationError> {
if machine.address4.is_none() && machine.address6.is_none() {
return Err(ValidationError::new("badaddr")
.with_message("machine must have at least one address".into()));
}
if machine.disable_capabilities.contains(&("".to_string())) {
return Err(ValidationError::new("badcap")
.with_message("machine has empty disabled capability".into()));
}
Ok(())
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)] #[derive(Debug, Clone, Serialize, Deserialize, Validate)]
#[validate(context = "ValidateContext<'v_a>")] #[validate(
context = "ValidateContext<'v_a>",
schema(function = "validate_template", use_context)
)]
pub struct Template { pub struct Template {
#[serde(flatten)] #[serde(flatten)]
#[validate(custom(function = "validate_location_exists", use_context))] #[validate(custom(function = "validate_location_exists", use_context))]
pub location: Location, pub location: Location,
#[serde(flatten)] #[serde(flatten)]
#[validate(nested)]
pub limits: Limits, pub limits: Limits,
#[serde(default)] #[serde(default)]
pub disable_capabilities: Vec<String>, pub disable_capabilities: Vec<String>,
} }
fn validate_template(
template: &Template,
_context: &ValidateContext,
) -> Result<(), ValidationError> {
if template.disable_capabilities.contains(&("".to_string())) {
return Err(ValidationError::new("badcap")
.with_message("template has empty disabled capability".into()));
}
Ok(())
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)] #[derive(Debug, Clone, Serialize, Deserialize, Validate)]
#[validate(schema(function = "validate_limits"))]
pub struct Limits { pub struct Limits {
#[validate(nested)]
pub machine_count: WeightedList<u32>, pub machine_count: WeightedList<u32>,
} }
fn validate_limits(limits: &Limits) -> Result<(), ValidationError> {
limits.machine_count.try_for_each(|x| {
if *x == 0 {
return Err(ValidationError::new("badcount")
.with_message("limits has zero machine count".into()));
}
Ok(())
})?;
Ok(())
}
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)] #[serde(untagged)]
pub enum Location { pub enum Location {
@ -136,28 +227,66 @@ pub enum Location {
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
#[derive(Debug, Clone, Serialize, Deserialize, Validate)] #[derive(Debug, Clone, Serialize, Deserialize, Validate)]
#[validate(
context = "ValidateContext<'v_a>",
schema(function = "validate_network", use_context)
)]
pub struct Network { pub struct Network {
#[serde(default)] #[serde(default)]
#[validate(custom(function = "validate_model_exists", use_context))]
pub model: Option<String>, pub model: Option<String>,
#[serde(default)] #[serde(default)]
#[validate(custom(function = "validate_network_ipv4", use_context))]
pub ipv4: Option<NetworkIpv4>, pub ipv4: Option<NetworkIpv4>,
#[serde(default)] #[serde(default)]
#[validate(custom(function = "validate_network_ipv6", use_context))]
pub ipv6: Option<NetworkIpv6>, pub ipv6: Option<NetworkIpv6>,
} }
#[derive(Debug, Clone, Serialize, Deserialize, Validate)] fn validate_network(network: &Network, _context: &ValidateContext) -> Result<(), ValidationError> {
if network.ipv4.is_none() && network.ipv6.is_none() {
return Err(ValidationError::new("badaddr")
.with_message("network must support at least one address type".into()));
}
Ok(())
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetworkIpv4 { pub struct NetworkIpv4 {
pub allocation: String, pub allocation: String,
#[serde(default)] #[serde(default)]
pub gateway: Option<NetworkGateway>, pub gateway: Option<NetworkGateway>,
} }
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
fn validate_network_ipv4(
network_ipv4: &NetworkIpv4,
context: &ValidateContext,
) -> Result<(), ValidationError> {
validate_allocation_exists(&network_ipv4.allocation, context)?;
if let Some(gateway) = &network_ipv4.gateway {
validate_network_gateway(gateway, context)?;
}
Ok(())
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetworkIpv6 { pub struct NetworkIpv6 {
pub allocation: String, pub allocation: String,
#[serde(default)] #[serde(default)]
pub gateway: Option<NetworkGateway>, pub gateway: Option<NetworkGateway>,
} }
fn validate_network_ipv6(
network_ipv6: &NetworkIpv6,
context: &ValidateContext,
) -> Result<(), ValidationError> {
validate_allocation_exists(&network_ipv6.allocation, context)?;
if let Some(gateway) = &network_ipv6.gateway {
validate_network_gateway(gateway, context)?;
}
Ok(())
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)] #[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct NetworkGateway { pub struct NetworkGateway {
pub translation: Translation, pub translation: Translation,
@ -165,19 +294,46 @@ pub struct NetworkGateway {
pub network: Option<String>, pub network: Option<String>,
} }
fn validate_network_gateway(
gateway: &NetworkGateway,
context: &ValidateContext,
) -> Result<(), ValidationError> {
if let Some(network) = &gateway.network {
validate_network_exists(network, context)?;
}
Ok(())
}
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
#[derive(Debug, Clone, Serialize, Deserialize, Validate)] #[derive(Debug, Clone, Serialize, Deserialize, Validate)]
#[validate(
context = "ValidateContext<'v_a>",
schema(function = "validate_blueprint", use_context)
)]
pub struct Blueprint { pub struct Blueprint {
#[serde(default)] #[serde(default)]
#[validate(custom(function = "validate_models_exist", use_context))]
pub model: WeightedList<String>, pub model: WeightedList<String>,
#[serde(default)] #[serde(default)]
#[validate(custom(function = "validate_blueprint_ipv4", use_context))]
pub ipv4: Option<BlueprintIpv4>, pub ipv4: Option<BlueprintIpv4>,
#[serde(default)] #[serde(default)]
#[validate(custom(function = "validate_blueprint_ipv6", use_context))]
pub ipv6: Option<BlueprintIpv6>, pub ipv6: Option<BlueprintIpv6>,
} }
#[derive(Debug, Clone, Serialize, Deserialize, Validate)] fn validate_blueprint(
blueprint: &Blueprint,
_context: &ValidateContext,
) -> Result<(), ValidationError> {
if blueprint.ipv4.is_none() && blueprint.ipv6.is_none() {
return Err(ValidationError::new("badaddr")
.with_message("blueprint must support at least one address type".into()));
}
Ok(())
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlueprintIpv4 { pub struct BlueprintIpv4 {
#[serde(default)] #[serde(default)]
pub allocation: Option<String>, pub allocation: Option<String>,
@ -185,7 +341,28 @@ pub struct BlueprintIpv4 {
#[serde(default)] #[serde(default)]
pub gateway: Option<BlueprintGateway>, pub gateway: Option<BlueprintGateway>,
} }
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
fn validate_blueprint_ipv4(
blueprint_ipv4: &BlueprintIpv4,
context: &ValidateContext,
) -> Result<(), ValidationError> {
if let Some(allocation) = &blueprint_ipv4.allocation {
validate_allocation_exists(allocation, context)?;
}
if blueprint_ipv4.prefix > 32 {
return Err(
ValidationError::new("badprefix").with_message("ipv4 blueprint prefix too long".into())
);
}
if let Some(gateway) = &blueprint_ipv4.gateway {
validate_blueprint_gateway(gateway, context)?;
}
Ok(())
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlueprintIpv6 { pub struct BlueprintIpv6 {
#[serde(default)] #[serde(default)]
pub allocation: Option<String>, pub allocation: Option<String>,
@ -194,16 +371,47 @@ pub struct BlueprintIpv6 {
pub gateway: Option<BlueprintGateway>, pub gateway: Option<BlueprintGateway>,
} }
#[derive(Debug, Clone, Serialize, Deserialize, Validate)] fn validate_blueprint_ipv6(
blueprint_ipv6: &BlueprintIpv6,
context: &ValidateContext,
) -> Result<(), ValidationError> {
if let Some(allocation) = &blueprint_ipv6.allocation {
validate_allocation_exists(allocation, context)?;
}
if blueprint_ipv6.prefix > 128 {
return Err(
ValidationError::new("badprefix").with_message("ipv6 blueprint prefix too long".into())
);
}
if let Some(gateway) = &blueprint_ipv6.gateway {
validate_blueprint_gateway(gateway, context)?;
}
Ok(())
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlueprintGateway { pub struct BlueprintGateway {
pub translation: WeightedList<Translation>, pub translation: WeightedList<Translation>,
pub upnp: Probability, pub upnp: Probability,
pub network: Option<String>, pub network: Option<String>,
} }
fn validate_blueprint_gateway(
gateway: &BlueprintGateway,
context: &ValidateContext,
) -> Result<(), ValidationError> {
gateway.translation.validate_once()?;
if let Some(network) = &gateway.network {
validate_network_exists(network, context)?;
}
Ok(())
}
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
#[derive(Debug, Clone, Serialize, Deserialize, Validate)] #[derive(Debug, Clone, Serialize, Deserialize, Validate)]
#[validate(schema(function = "validate_subnets"))]
pub struct Subnets { pub struct Subnets {
#[serde(default)] #[serde(default)]
pub subnet4: Vec<Ipv4Net>, pub subnet4: Vec<Ipv4Net>,
@ -211,13 +419,35 @@ pub struct Subnets {
pub subnet6: Vec<Ipv6Net>, pub subnet6: Vec<Ipv6Net>,
} }
fn validate_subnets(subnets: &Subnets) -> Result<(), ValidationError> {
if subnets.subnet4.is_empty() && subnets.subnet6.is_empty() {
return Err(ValidationError::new("badsub")
.with_message("subnets must support at least one address type".into()));
}
Ok(())
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)] #[derive(Debug, Clone, Serialize, Deserialize, Validate)]
#[validate(schema(function = "validate_distance"))]
pub struct Distance { pub struct Distance {
pub min: f32, pub min: f32,
pub max: f32, pub max: f32,
} }
fn validate_distance(distance: &Distance) -> Result<(), ValidationError> {
if distance.min < 0.0 {
return Err(ValidationError::new("baddist")
.with_message("distance minimum must not be negative".into()));
}
if distance.max < distance.min {
return Err(ValidationError::new("baddist")
.with_message("distance maximum must not be less than the minimum".into()));
}
Ok(())
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)] #[derive(Debug, Clone, Serialize, Deserialize, Validate)]
#[validate(schema(function = "validate_distribution"))]
pub struct Distribution { pub struct Distribution {
pub mean: f32, pub mean: f32,
pub sigma: f32, pub sigma: f32,
@ -226,6 +456,22 @@ pub struct Distribution {
pub max: f32, pub max: f32,
} }
fn validate_distribution(distribution: &Distribution) -> Result<(), ValidationError> {
if distribution.mean < 0.0 {
return Err(ValidationError::new("baddistrib")
.with_message("distribution mean must not be negative".into()));
}
if distribution.sigma < distribution.mean {
return Err(ValidationError::new("baddistrib")
.with_message("distribution sigma must not be less than the mean".into()));
}
if distribution.max < distribution.min {
return Err(ValidationError::new("baddistrib")
.with_message("distribution maximum must not be less than the minimum".into()));
}
Ok(())
}
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum Translation { pub enum Translation {
@ -242,17 +488,23 @@ impl Default for Translation {
} }
#[derive(Debug, Clone, Serialize, Deserialize, Validate)] #[derive(Debug, Clone, Serialize, Deserialize, Validate)]
#[validate(context = "ValidateContext<'v_a>")]
pub struct Model { pub struct Model {
#[validate(nested)]
pub latency: Distribution, pub latency: Distribution,
#[serde(default)] #[serde(default)]
#[validate(nested)]
pub distance: Option<Distance>, pub distance: Option<Distance>,
#[serde(default)] #[serde(default)]
#[validate(range(min = 0.0, max = 1.0))]
pub loss: Probability, pub loss: Probability,
} }
#[derive(Debug, Clone, Serialize, Deserialize, Validate)] #[derive(Debug, Clone, Serialize, Deserialize, Validate)]
#[validate(context = "ValidateContext<'v_a>")]
pub struct Allocation { pub struct Allocation {
#[serde(flatten)] #[serde(flatten)]
#[validate(nested)]
pub subnets: Subnets, pub subnets: Subnets,
} }
@ -306,22 +558,22 @@ impl Config {
errors = e; errors = e;
} }
errors.merge_self("profiles", validate_all_profiles(&out.profiles, &context)); errors.merge_self("profiles", validate_all_with_args(&out.profiles, &context));
errors.merge_self("machines", validate_all_machines(&out.machines, &context)); errors.merge_self("machines", validate_all_with_args(&out.machines, &context));
errors.merge_self( errors.merge_self(
"templates", "templates",
validate_all_templates(&out.templates, &context), validate_all_with_args(&out.templates, &context),
); );
errors.merge_self("networks", validate_all_networks(&out.networks, &context)); errors.merge_self("networks", validate_all_with_args(&out.networks, &context));
errors.merge_self( errors.merge_self(
"blueprints", "blueprints",
validate_all_blueprints(&out.blueprints, &context), validate_all_with_args(&out.blueprints, &context),
); );
errors.merge_self( errors.merge_self(
"allocation", "allocation",
validate_all_allocations(&out.allocations, &context), validate_all_with_args(&out.allocations, &context),
); );
errors.merge_self("models", validate_all_models(&out.models, &context)); errors.merge_self("models", validate_all_with_args(&out.models, &context));
if !errors.is_empty() { if !errors.is_empty() {
return Err(ConfigError::ValidateError(errors)); return Err(ConfigError::ValidateError(errors));
@ -335,6 +587,12 @@ fn validate_instances_exist(
value: &Vec<Instance>, value: &Vec<Instance>,
context: &ValidateContext, context: &ValidateContext,
) -> Result<(), ValidationError> { ) -> Result<(), ValidationError> {
for v in value {
match v {
Instance::Machine { machine } => validate_machines_exist(machine, context)?,
Instance::Template { template } => validate_templates_exist(template, context)?,
}
}
Ok(()) Ok(())
} }
@ -342,61 +600,103 @@ fn validate_location_exists(
value: &Location, value: &Location,
context: &ValidateContext, context: &ValidateContext,
) -> Result<(), ValidationError> { ) -> Result<(), ValidationError> {
match value {
Location::Network { network } => {
network.try_for_each(|m| validate_network_exists(m, context))?;
}
Location::Blueprint { blueprint } => {
blueprint.try_for_each(|t| validate_blueprint_exists(t, context))?;
}
}
Ok(()) Ok(())
} }
fn validate_network_exists(value: &str, context: &ValidateContext) -> Result<(), ValidationError> { fn validate_network_exists(value: &str, context: &ValidateContext) -> Result<(), ValidationError> {
Ok(()) if !context.config.networks.contains_key(value) {
} return Err(ValidationError::new("noexist").with_message("network does not exist".into()));
fn validate_model_exists(value: &str, context: &ValidateContext) -> Result<(), ValidationError> {
Ok(())
}
fn validate_all_profiles(
value: &HashMap<String, Profile>,
context: &ValidateContext,
) -> Result<(), ValidationErrors> {
for x in value.values() {
x.validate_with_args(context)?
} }
Ok(()) Ok(())
} }
fn validate_all_machines( fn validate_blueprint_exists(
value: &HashMap<String, Machine>, value: &str,
context: &ValidateContext, context: &ValidateContext,
) -> Result<(), ValidationErrors> { ) -> Result<(), ValidationError> {
if !context.config.blueprints.contains_key(value) {
return Err(ValidationError::new("noexist").with_message("blueprint does not exist".into()));
}
Ok(()) Ok(())
} }
fn validate_all_templates(
value: &HashMap<String, Template>, fn validate_allocation_exists(
value: &str,
context: &ValidateContext, context: &ValidateContext,
) -> Result<(), ValidationErrors> { ) -> Result<(), ValidationError> {
if !context.config.allocations.contains_key(value) {
return Err(
ValidationError::new("noexist").with_message("allocation does not exist".into())
);
}
Ok(()) Ok(())
} }
fn validate_all_networks(
value: &HashMap<String, Network>, fn validate_model_exists(value: &str, context: &ValidateContext) -> Result<(), ValidationError> {
context: &ValidateContext, if !context.config.networks.contains_key(value) {
) -> Result<(), ValidationErrors> { return Err(ValidationError::new("noexist").with_message("model does not exist".into()));
}
Ok(()) Ok(())
} }
fn validate_all_blueprints(
value: &HashMap<String, Blueprint>, fn validate_models_exist(
value: &WeightedList<String>,
context: &ValidateContext, context: &ValidateContext,
) -> Result<(), ValidationErrors> { ) -> Result<(), ValidationError> {
value.try_for_each(|x| validate_model_exists(x, context))
}
fn validate_machine_exists(value: &str, context: &ValidateContext) -> Result<(), ValidationError> {
if !context.config.machines.contains_key(value) {
return Err(ValidationError::new("noexist").with_message("machine does not exist".into()));
}
Ok(()) Ok(())
} }
fn validate_all_allocations(
value: &HashMap<String, Allocation>, fn validate_machines_exist(
value: &WeightedList<String>,
context: &ValidateContext, context: &ValidateContext,
) -> Result<(), ValidationErrors> { ) -> Result<(), ValidationError> {
value.try_for_each(|x| validate_machine_exists(x, context))
}
fn validate_template_exists(value: &str, context: &ValidateContext) -> Result<(), ValidationError> {
if !context.config.templates.contains_key(value) {
return Err(ValidationError::new("noexist").with_message("template does not exist".into()));
}
Ok(()) Ok(())
} }
fn validate_all_models(
value: &HashMap<String, Model>, fn validate_templates_exist(
value: &WeightedList<String>,
context: &ValidateContext, context: &ValidateContext,
) -> Result<(), ValidationError> {
value.try_for_each(|x| validate_template_exists(x, context))
}
fn validate_all_with_args<'v_a, T: ValidateArgs<'v_a, Args = &'v_a ValidateContext<'v_a>>>(
value: &HashMap<String, T>,
context: &'v_a ValidateContext,
) -> Result<(), ValidationErrors> { ) -> Result<(), ValidationErrors> {
let mut errors = ValidationErrors::new();
for (n, x) in value.values().enumerate() {
errors.merge_self(
format!("[{n}]").to_static_str(),
x.validate_with_args(context),
);
}
if !errors.is_empty() {
return Err(errors);
}
Ok(()) Ok(())
} }

View File

@ -1,4 +1,5 @@
use super::*; use super::*;
use rand::Rng;
#[derive(Debug)] #[derive(Debug)]
struct Machine {} struct Machine {}
@ -71,12 +72,26 @@ impl MachineRegistry {
match instance_def { match instance_def {
config::Instance::Machine { machine } => { config::Instance::Machine { machine } => {
self.create_machine(machine); let machine = self.weighted_choice(machine);
let machine_def = self
.unlocked_inner
.config
.machines
.get(machine)
.expect("config validation is broken");
self.create_machine(machine_def).await
}
config::Instance::Template { template } => {
let template = self.weighted_choice(template);
let template_def = self
.unlocked_inner
.config
.templates
.get(template)
.expect("config validation is broken");
self.create_machine_from_template(template_def).await
} }
config::Instance::Template { template } => todo!(),
} }
Ok(machine_id)
} }
pub async fn release(&self, machine_id: MachineId) -> MachineRegistryResult<()> {} pub async fn release(&self, machine_id: MachineId) -> MachineRegistryResult<()> {}
@ -86,25 +101,88 @@ impl MachineRegistry {
async fn create_machine( async fn create_machine(
&self, &self,
machine_def: config::Machine, machine_def: &config::Machine,
) -> MachineRegistryResult<MachineId> { ) -> MachineRegistryResult<MachineId> {
// // Get network from location
Ok(0)
} }
fn weighted_choice<T: fmt::Debug + Clone>( async fn create_machine_from_template(
&self, &self,
weighted_list: &config::WeightedList<T>, template_def: &config::Template,
) -> &T { ) -> MachineRegistryResult<MachineId> {
Ok(0)
}
async fn get_or_create_network_from_location(
&self,
location_def: &config::Location,
) -> MachineRegistryResult<NetworkId> {
match location_def {
config::Location::Network { network } => {
let network = self.weighted_choice(network);
let network_def = self
.unlocked_inner
.config
.networks
.get(network)
.expect("config validation is broken");
self.get_or_create_network(network, network_def).await
}
config::Location::Blueprint { blueprint } => {
let blueprint = self.weighted_choice(blueprint);
let blueprint_def = self
.unlocked_inner
.config
.blueprints
.get(blueprint)
.expect("config validation is broken");
self.get_or_create_network_from_blueprint(blueprint, blueprint_def)
.await
}
}
}
async fn get_or_create_network(
&self,
network: &String,
network_def: &config::Network,
) -> MachineRegistryResult<NetworkId> {
Ok(0)
}
async fn get_or_create_network_from_blueprint(
&self,
blueprint: &String,
blueprint_def: &config::Blueprint,
) -> MachineRegistryResult<NetworkId> {
Ok(0)
}
fn weighted_choice<'a, T: fmt::Debug + Clone>(
&self,
weighted_list: &'a config::WeightedList<T>,
) -> &'a T {
match weighted_list { match weighted_list {
config::WeightedList::Single(x) => x, config::WeightedList::Single(x) => x,
config::WeightedList::List(vec) => { config::WeightedList::List(vec) => {
let total_weight = vec let total_weight = vec
.iter() .iter()
.map(|x| match x { .map(|x| x.weight())
config::Weighted::Weighted { item, weight } => weight, .reduce(|acc, x| acc + x)
config::Weighted::Unweighted(item) => 1.0, .expect("config validation broken");
})
.reduce(|acc, x| acc + x); let r = rand::thread_rng().gen_range(0.0..=total_weight);
let mut current_weight = 0.0f32;
for x in vec {
current_weight += x.weight();
if r < current_weight {
return x.item();
}
}
// Catch f32 imprecision
vec.last().expect("config validation broken").item()
} }
} }
} }

View File

@ -42,9 +42,7 @@ struct RouterServerUnlockedInner {
} }
#[derive(Debug)] #[derive(Debug)]
struct RouterServerInner { struct RouterServerInner {}
//tcp_connections: HashMap<
}
/// Router server for virtual networking /// Router server for virtual networking
/// ///