mirror of
https://gitlab.com/veilid/veilid.git
synced 2025-03-06 05:05:57 -05:00
validation and start of generation
This commit is contained in:
parent
981674b870
commit
2cab1d75c1
@ -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::*;
|
||||||
|
21
veilid-tools/src/static_string_table.rs
Normal file
21
veilid-tools/src/static_string_table.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
///
|
///
|
||||||
|
Loading…
x
Reference in New Issue
Block a user