mirror of
https://gitlab.com/veilid/veilid.git
synced 2024-12-26 15:59:24 -05:00
[skip ci] template work
This commit is contained in:
parent
29f1e2da11
commit
65629f03e9
@ -217,6 +217,45 @@ fn validate_machine(machine: &Machine, _context: &ValidateContext) -> Result<(),
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum MachineLocation {
|
||||
Network {
|
||||
network: String,
|
||||
#[serde(default)]
|
||||
address4: Option<Ipv4Addr>,
|
||||
#[serde(default)]
|
||||
address6: Option<Ipv6Addr>,
|
||||
},
|
||||
Blueprint {
|
||||
blueprint: String,
|
||||
},
|
||||
}
|
||||
|
||||
fn validate_machine_location(
|
||||
value: &MachineLocation,
|
||||
context: &ValidateContext,
|
||||
) -> Result<(), ValidationError> {
|
||||
match value {
|
||||
MachineLocation::Network {
|
||||
network,
|
||||
address4,
|
||||
address6,
|
||||
} => {
|
||||
if address4.is_none() && address6.is_none() {
|
||||
return Err(ValidationError::new("badaddr")
|
||||
.with_message("machine must have at least one address".into()));
|
||||
}
|
||||
validate_network_exists(network, context)?;
|
||||
}
|
||||
MachineLocation::Blueprint { blueprint } => {
|
||||
validate_blueprint_exists(blueprint, context)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
|
||||
#[validate(
|
||||
context = "ValidateContext<'v_a>",
|
||||
@ -247,15 +286,28 @@ fn validate_template(
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
|
||||
#[validate(schema(function = "validate_template_limits"))]
|
||||
pub struct TemplateLimits {
|
||||
/// maximum number of machines this template will generate
|
||||
#[validate(nested)]
|
||||
pub machine_count: WeightedList<u32>,
|
||||
#[serde(default)]
|
||||
pub machine_count: Option<WeightedList<u32>>,
|
||||
#[validate(nested)]
|
||||
pub machines_per_network: WeightedList<u32>,
|
||||
}
|
||||
|
||||
fn validate_template_limits(limits: &TemplateLimits) -> Result<(), ValidationError> {
|
||||
limits.machine_count.try_for_each(|x| {
|
||||
if let Some(machine_count) = &limits.machine_count {
|
||||
machine_count.try_for_each(|x| {
|
||||
if *x == 0 {
|
||||
return Err(ValidationError::new("badcount")
|
||||
.with_message("template limits has zero machine count".into()));
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
limits.machines_per_network.try_for_each(|x| {
|
||||
if *x == 0 {
|
||||
return Err(ValidationError::new("badcount")
|
||||
.with_message("template limits has zero machine count".into()));
|
||||
.with_message("template limits has zero machines per network count".into()));
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
@ -263,39 +315,6 @@ fn validate_template_limits(limits: &TemplateLimits) -> Result<(), ValidationErr
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum MachineLocation {
|
||||
Specific {
|
||||
network: String,
|
||||
#[serde(default)]
|
||||
address4: Option<Ipv4Addr>,
|
||||
#[serde(default)]
|
||||
address6: Option<Ipv6Addr>,
|
||||
},
|
||||
}
|
||||
|
||||
fn validate_machine_location(
|
||||
value: &MachineLocation,
|
||||
context: &ValidateContext,
|
||||
) -> Result<(), ValidationError> {
|
||||
match value {
|
||||
MachineLocation::Specific {
|
||||
network,
|
||||
address4,
|
||||
address6,
|
||||
} => {
|
||||
if address4.is_none() && address6.is_none() {
|
||||
return Err(ValidationError::new("badaddr")
|
||||
.with_message("machine must have at least one address".into()));
|
||||
}
|
||||
validate_network_exists(network, context)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum TemplateLocation {
|
||||
@ -433,18 +452,22 @@ fn validate_blueprint(
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
|
||||
#[validate(schema(function = "validate_blueprint_limits"))]
|
||||
pub struct BlueprintLimits {
|
||||
/// maximum number of networks this blueprint will generate
|
||||
#[validate(nested)]
|
||||
pub network_count: WeightedList<u32>,
|
||||
#[serde(default)]
|
||||
pub network_count: Option<WeightedList<u32>>,
|
||||
}
|
||||
|
||||
fn validate_blueprint_limits(limits: &BlueprintLimits) -> Result<(), ValidationError> {
|
||||
limits.network_count.try_for_each(|x| {
|
||||
if *x == 0 {
|
||||
return Err(ValidationError::new("badcount")
|
||||
.with_message("blueprint limits has zero machine count".into()));
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
if let Some(network_count) = &limits.network_count {
|
||||
network_count.try_for_each(|x| {
|
||||
if *x == 0 {
|
||||
return Err(ValidationError::new("badcount")
|
||||
.with_message("blueprint limits has zero network count".into()));
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -64,9 +64,11 @@ machines:
|
||||
#################################################################
|
||||
# Templates
|
||||
#
|
||||
# Templates are used to generate Machines that are all on a single
|
||||
# network. A maximum number of machines are allocated on the
|
||||
# network within the limits specified.
|
||||
# Templates are used to generate Machines
|
||||
# * if networks are specified, then all machines are created on that
|
||||
# single network. A maximum number of machines are allocated on the
|
||||
# network within the limits specified.
|
||||
# * if a blueprint is spec
|
||||
|
||||
templates:
|
||||
# Default servers on the boot network
|
||||
@ -77,22 +79,23 @@ templates:
|
||||
bootrelay:
|
||||
network: "boot"
|
||||
machine_count: 4
|
||||
machines_per_network: 4
|
||||
# Servers on subnets within the 'internet' network
|
||||
relayserver:
|
||||
blueprint: "direct"
|
||||
machine_count: [1, 2, 3]
|
||||
machines_per_network: [1, 2, 3]
|
||||
ipv4server:
|
||||
blueprint: "direct_ipv4_no_ipv6"
|
||||
machine_count: [1, 2, 3]
|
||||
machines_per_network: [1, 2, 3]
|
||||
ipv6server:
|
||||
blueprint: "direct_ipv6_no_ipv4"
|
||||
machine_count: [1, 2, 3]
|
||||
machines_per_network: [1, 2, 3]
|
||||
nat4home:
|
||||
blueprint: "nat_ipv4_no_ipv6"
|
||||
machine_count: [1, 2, 3]
|
||||
machines_per_network: [1, 2, 3]
|
||||
nat4+6home:
|
||||
blueprint: "nat_ipv4_direct_ipv6"
|
||||
machine_count: [1, 2, 3]
|
||||
machines_per_network: [1, 2, 3]
|
||||
|
||||
#################################################################
|
||||
# Networks
|
||||
|
@ -264,51 +264,14 @@ impl MachineRegistryInner {
|
||||
let machine_def = {
|
||||
// Get the active template state
|
||||
let template_state = self.get_or_create_template_state(&name, template_def)?;
|
||||
if !template_state.is_active()? {
|
||||
if !template_state.is_active(self)? {
|
||||
return Err(MachineRegistryError::TemplateComplete);
|
||||
}
|
||||
|
||||
// Pick or instantiate an available network
|
||||
xxx add 'def()' selector to all types
|
||||
let active_networks = match template_state.template_def.location.clone() {
|
||||
config::TemplateLocation::Network { network } => {
|
||||
// Filter the weighted list of networks to those that are still active or not yet started and can allocate
|
||||
let Some(active_networks) = network.try_filter(|n| {
|
||||
self.get_network_state_by_name(&n)
|
||||
.map(|ns| ns.is_active())
|
||||
.unwrap_or(Ok(true))
|
||||
})?
|
||||
else {
|
||||
return Err(MachineRegistryError::NetworkComplete);
|
||||
};
|
||||
}
|
||||
config::TemplateLocation::Blueprint { blueprint } => {
|
||||
// Filter the weighted list of blueprints to those that are still active or not yet started and can allocate
|
||||
let Some(active_blueprints) = blueprint.try_filter(|b| {
|
||||
self.get_blueprint_state(&b)
|
||||
.map(|bs| bs.is_active(self))
|
||||
.unwrap_or(Ok(true))
|
||||
})?
|
||||
else {
|
||||
return Err(MachineRegistryError::BlueprintComplete);
|
||||
};
|
||||
template_state.instantiate(self)?
|
||||
|
||||
// Activate some blueprint and pick a network
|
||||
}
|
||||
};
|
||||
|
||||
// Weighted choice of network now that we have a candidate list
|
||||
//let network =
|
||||
|
||||
config::Machine {
|
||||
location: config::MachineLocation::Specific {
|
||||
network: todo!(),
|
||||
address4: None,
|
||||
address6: None,
|
||||
},
|
||||
disable_capabilities: template_state.template_def.disable_capabilities.clone(),
|
||||
bootstrap: false,
|
||||
}
|
||||
xxx how to pass through per-network limits
|
||||
};
|
||||
|
||||
// Allocate a machine id
|
||||
@ -321,9 +284,9 @@ impl MachineRegistryInner {
|
||||
// Create a new machine state
|
||||
let machine_state = match MachineState::try_new(
|
||||
self,
|
||||
machine_id,
|
||||
MachineStateName::Template(name.clone()),
|
||||
machine_def.clone(),
|
||||
machine_id,
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
@ -345,12 +308,10 @@ impl MachineRegistryInner {
|
||||
// Return the unique id
|
||||
Ok(machine_id)
|
||||
}
|
||||
pub(super) fn get_template_state(
|
||||
&mut self,
|
||||
name: &String,
|
||||
) -> MachineRegistryResult<&mut TemplateState> {
|
||||
pub(super) fn get_template_state(&self, name: &String) -> MachineRegistryResult<TemplateState> {
|
||||
self.template_state_by_name
|
||||
.get_mut(name)
|
||||
.get(name)
|
||||
.cloned()
|
||||
.ok_or_else(|| MachineRegistryError::TemplateNotFound)
|
||||
}
|
||||
|
||||
@ -359,7 +320,7 @@ impl MachineRegistryInner {
|
||||
machine_location: &config::MachineLocation,
|
||||
) -> MachineRegistryResult<NetworkState> {
|
||||
match machine_location {
|
||||
config::MachineLocation::Specific {
|
||||
config::MachineLocation::Network {
|
||||
network: name,
|
||||
address4: _,
|
||||
address6: _,
|
||||
@ -373,6 +334,17 @@ impl MachineRegistryInner {
|
||||
.expect("config validation is broken");
|
||||
self.get_or_create_network_state(name.clone(), network_def)
|
||||
}
|
||||
|
||||
config::MachineLocation::Blueprint { blueprint: name } => {
|
||||
let blueprint_def = self
|
||||
.unlocked_inner
|
||||
.config
|
||||
.blueprints
|
||||
.get(name)
|
||||
.cloned()
|
||||
.expect("config validation is broken");
|
||||
self.get_or_create_network_state_from_blueprint(name.clone(), blueprint_def)
|
||||
}
|
||||
}
|
||||
}
|
||||
pub(super) fn get_or_create_network_state_from_template_location(
|
||||
@ -406,11 +378,12 @@ impl MachineRegistryInner {
|
||||
}
|
||||
|
||||
pub(super) fn get_blueprint_state(
|
||||
&mut self,
|
||||
&self,
|
||||
name: &String,
|
||||
) -> MachineRegistryResult<&mut BlueprintState> {
|
||||
) -> MachineRegistryResult<BlueprintState> {
|
||||
self.blueprint_state_by_name
|
||||
.get_mut(name)
|
||||
.get(name)
|
||||
.cloned()
|
||||
.ok_or_else(|| MachineRegistryError::BlueprintNotFound)
|
||||
}
|
||||
|
||||
@ -533,26 +506,18 @@ impl MachineRegistryInner {
|
||||
&mut self,
|
||||
name: String,
|
||||
blueprint_def: config::Blueprint,
|
||||
) -> MachineRegistryResult<NetworkId> {
|
||||
) -> MachineRegistryResult<NetworkState> {
|
||||
// Get the active blueprint state
|
||||
let blueprint_state = self.get_or_create_blueprint_state(&name, blueprint_def)?;
|
||||
if !blueprint_state.is_active(self)? {
|
||||
return Err(MachineRegistryError::BlueprintComplete);
|
||||
}
|
||||
|
||||
//xxx
|
||||
// Make machine def from current template state
|
||||
let machine_def = config::Machine {
|
||||
location: match template_state.template_def.location.clone() {
|
||||
config::TemplateLocation::Network { network } => {
|
||||
config::MachineLocation::Network { network }
|
||||
}
|
||||
config::TemplateLocation::Blueprint { blueprint } => {
|
||||
config::MachineLocation::Blueprint { blueprint }
|
||||
}
|
||||
},
|
||||
disable_capabilities: template_state.template_def.disable_capabilities.clone(),
|
||||
bootstrap: false,
|
||||
// Make network def from current blueprint state
|
||||
let machine_def = config::Network {
|
||||
model: self.unlocked_inner.srng.weighted_choice(blueprint_state),
|
||||
ipv4: todo!(),
|
||||
ipv6: todo!(),
|
||||
};
|
||||
|
||||
// Allocate a machine id
|
||||
|
@ -8,7 +8,7 @@ struct BlueprintStateUnlockedInner {
|
||||
|
||||
#[derive(Debug)]
|
||||
struct BlueprintStateInner {
|
||||
limit_network_count: u32,
|
||||
limit_network_count: Option<u32>,
|
||||
networks: HashSet<NetworkId>,
|
||||
}
|
||||
|
||||
@ -24,10 +24,12 @@ impl BlueprintState {
|
||||
name: String,
|
||||
blueprint_def: config::Blueprint,
|
||||
) -> MachineRegistryResult<BlueprintState> {
|
||||
let limit_network_count = *machine_registry_inner
|
||||
.unlocked_inner
|
||||
.srng
|
||||
.weighted_choice(&blueprint_def.limits.network_count);
|
||||
let limit_network_count = blueprint_def.limits.network_count.as_ref().map(|nc| {
|
||||
*machine_registry_inner
|
||||
.unlocked_inner
|
||||
.srng
|
||||
.weighted_choice(nc)
|
||||
});
|
||||
|
||||
Ok(Self {
|
||||
unlocked_inner: Arc::new(BlueprintStateUnlockedInner {
|
||||
@ -41,6 +43,10 @@ impl BlueprintState {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn def(&self) -> &config::Blueprint {
|
||||
&self.unlocked_inner.blueprint_def
|
||||
}
|
||||
|
||||
pub fn is_active(
|
||||
&self,
|
||||
machine_registry_inner: &MachineRegistryInner,
|
||||
|
@ -60,7 +60,7 @@ impl MachineState {
|
||||
// Make the default route interface
|
||||
let machine_location = machine_def.location.clone();
|
||||
let (allocate_v4, opt_address4, allocate_v6, opt_address6) = match machine_location {
|
||||
config::MachineLocation::Specific {
|
||||
config::MachineLocation::Network {
|
||||
network: _,
|
||||
address4,
|
||||
address6,
|
||||
@ -70,6 +70,9 @@ impl MachineState {
|
||||
network_state.is_ipv6() && address6.is_some(),
|
||||
address6,
|
||||
),
|
||||
config::MachineLocation::Blueprint { blueprint: _ } => {
|
||||
(network_state.is_ipv4(), None, network_state.is_ipv6(), None)
|
||||
}
|
||||
};
|
||||
|
||||
if allocate_v4 {
|
||||
@ -163,6 +166,10 @@ impl MachineState {
|
||||
self.unlocked_inner.name.clone()
|
||||
}
|
||||
|
||||
pub fn def(&self) -> &config::Machine {
|
||||
&self.unlocked_inner.machine_def
|
||||
}
|
||||
|
||||
pub fn id(&self) -> MachineId {
|
||||
self.unlocked_inner.id
|
||||
}
|
||||
|
@ -143,6 +143,10 @@ impl NetworkState {
|
||||
self.unlocked_inner.name.clone()
|
||||
}
|
||||
|
||||
pub fn def(&self) -> &config::Network {
|
||||
&self.unlocked_inner.network_def
|
||||
}
|
||||
|
||||
pub fn id(&self) -> NetworkId {
|
||||
self.unlocked_inner.id
|
||||
}
|
||||
|
@ -8,7 +8,8 @@ struct TemplateStateUnlockedInner {
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TemplateStateInner {
|
||||
limit_machine_count: u32,
|
||||
limit_machine_count: Option<u32>,
|
||||
limit_machines_per_network: u32,
|
||||
machines: HashSet<MachineId>,
|
||||
}
|
||||
|
||||
@ -24,15 +25,22 @@ impl TemplateState {
|
||||
name: String,
|
||||
template_def: config::Template,
|
||||
) -> MachineRegistryResult<TemplateState> {
|
||||
let limit_machine_count = *machine_registry_inner
|
||||
let limit_machine_count = template_def.limits.machine_count.as_ref().map(|mc| {
|
||||
*machine_registry_inner
|
||||
.unlocked_inner
|
||||
.srng
|
||||
.weighted_choice(mc)
|
||||
});
|
||||
let limit_machines_per_network = *machine_registry_inner
|
||||
.unlocked_inner
|
||||
.srng
|
||||
.weighted_choice(&template_def.limits.machine_count);
|
||||
.weighted_choice(&template_def.limits.machines_per_network);
|
||||
|
||||
Ok(Self {
|
||||
unlocked_inner: Arc::new(TemplateStateUnlockedInner { name, template_def }),
|
||||
inner: Arc::new(Mutex::new(TemplateStateInner {
|
||||
limit_machine_count,
|
||||
limit_machines_per_network,
|
||||
machines: HashSet::new(),
|
||||
})),
|
||||
})
|
||||
@ -42,8 +50,116 @@ impl TemplateState {
|
||||
self.unlocked_inner.name.clone()
|
||||
}
|
||||
|
||||
pub fn is_active(&self) -> MachineRegistryResult<bool> {
|
||||
pub fn def(&self) -> &config::Template {
|
||||
&self.unlocked_inner.template_def
|
||||
}
|
||||
|
||||
pub fn is_active(
|
||||
&self,
|
||||
machine_registry_inner: &mut MachineRegistryInner,
|
||||
) -> MachineRegistryResult<bool> {
|
||||
let inner = self.inner.lock();
|
||||
Ok(inner.machines.len() < inner.limit_machine_count.try_into().unwrap_or(usize::MAX))
|
||||
if let Some(limit_machine_count) = inner.limit_machine_count {
|
||||
if inner.machines.len() < limit_machine_count.try_into().unwrap_or(usize::MAX) {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
match self.def().location.clone() {
|
||||
config::TemplateLocation::Network { network } => {
|
||||
// Filter the weighted list of networks to those that are still active or not yet started and can allocate
|
||||
if network
|
||||
.try_filter(|n| {
|
||||
machine_registry_inner
|
||||
.get_network_state_by_name(&n)
|
||||
.clone()
|
||||
.map(|ns| ns.is_active())
|
||||
.unwrap_or(Ok(true))
|
||||
})?
|
||||
.is_none()
|
||||
{
|
||||
return Ok(false);
|
||||
};
|
||||
}
|
||||
config::TemplateLocation::Blueprint { blueprint } => {
|
||||
// Filter the weighted list of blueprints to those that are still active or not yet started and can allocate
|
||||
if blueprint
|
||||
.try_filter(|b| {
|
||||
machine_registry_inner
|
||||
.get_blueprint_state(&b)
|
||||
.clone()
|
||||
.map(|bs| bs.is_active(machine_registry_inner))
|
||||
.unwrap_or(Ok(true))
|
||||
})?
|
||||
.is_none()
|
||||
{
|
||||
return Ok(false);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn instantiate(
|
||||
&self,
|
||||
machine_registry_inner: &mut MachineRegistryInner,
|
||||
) -> MachineRegistryResult<config::Machine> {
|
||||
// Pick or instantiate an available network
|
||||
let location = match self.def().location.clone() {
|
||||
config::TemplateLocation::Network { network } => {
|
||||
// Filter the weighted list of networks to those that are still active or not yet started and can allocate
|
||||
let Some(active_networks) = network.try_filter(|n| {
|
||||
machine_registry_inner
|
||||
.get_network_state_by_name(&n)
|
||||
.clone()
|
||||
.map(|ns| ns.is_active())
|
||||
.unwrap_or(Ok(true))
|
||||
})?
|
||||
else {
|
||||
return Err(MachineRegistryError::NetworkComplete);
|
||||
};
|
||||
|
||||
// Weighted choice of network now that we have a candidate list
|
||||
let network_name = machine_registry_inner
|
||||
.unlocked_inner
|
||||
.srng
|
||||
.weighted_choice(&active_networks);
|
||||
config::MachineLocation::Network {
|
||||
network: network_name.clone(),
|
||||
address4: None,
|
||||
address6: None,
|
||||
}
|
||||
}
|
||||
config::TemplateLocation::Blueprint { blueprint } => {
|
||||
// Filter the weighted list of blueprints to those that are still active or not yet started and can allocate
|
||||
let Some(active_blueprints) = blueprint.try_filter(|b| {
|
||||
machine_registry_inner
|
||||
.get_blueprint_state(&b)
|
||||
.clone()
|
||||
.map(|bs| bs.is_active(machine_registry_inner))
|
||||
.unwrap_or(Ok(true))
|
||||
})?
|
||||
else {
|
||||
return Err(MachineRegistryError::BlueprintComplete);
|
||||
};
|
||||
|
||||
// Weighted choice of blueprint now that we have a candidate list
|
||||
let blueprint_name = machine_registry_inner
|
||||
.unlocked_inner
|
||||
.srng
|
||||
.weighted_choice(&active_blueprints);
|
||||
|
||||
config::MachineLocation::Blueprint {
|
||||
blueprint: blueprint_name.clone(),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(config::Machine {
|
||||
location,
|
||||
disable_capabilities: self.def().disable_capabilities.clone(),
|
||||
bootstrap: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user