[skip ci] blueprint generation and cleanup

This commit is contained in:
Christien Rioux 2025-01-05 17:18:59 -05:00
parent 464700d9ba
commit 7e9ae74e0c
13 changed files with 732 additions and 457 deletions

View File

@ -64,8 +64,8 @@ fn main() -> Result<(), String> {
let args = CmdlineArgs::parse();
let config =
Config::new(args.config_file).map_err(|e| format!("Error loading config: {}", e))?;
let config = config::Config::new(args.config_file)
.map_err(|e| format!("Error loading config: {}", e))?;
if args.dump_config {
let cfg_yaml = serde_yaml::to_string(&config)

View File

@ -8,9 +8,11 @@ use validator::{Validate, ValidateArgs, ValidationError, ValidationErrors};
const PREDEFINED_CONFIG: &str = include_str!("predefined_config.yml");
const DEFAULT_CONFIG: &str = include_str!("default_config.yml");
#[derive(Debug)]
#[derive(Debug, ThisError)]
pub enum ConfigError {
#[error("parse error")]
ParseError(::config::ConfigError),
#[error("validate error")]
ValidateError(validator::ValidationErrors),
}
@ -515,7 +517,7 @@ pub struct Allocation {
pub subnets: Subnets,
}
struct ValidateContext<'a> {
pub struct ValidateContext<'a> {
config: &'a Config,
}

View File

@ -397,7 +397,7 @@ impl<T: fmt::Debug + Clone> AddressPool<T> {
let mut current_scope_end_subnet_index =
scope_start_subnet_index + scope_ranges[scope_index].2;
let opt_allocation = loop {
loop {
// Get the net at this current subnet index
let netbits = u32::from(scope_ranges[current_scope_index].0.network());
let subnetbits = if prefix == 32 {
@ -437,9 +437,7 @@ impl<T: fmt::Debug + Clone> AddressPool<T> {
current_scope_end_subnet_index =
current_scope_start_subnet_index + scope_ranges[current_scope_index].2;
}
};
opt_allocation
}
}
fn find_random_allocation_v6(&self, srng: &mut StableRng, prefix: u8) -> Option<Ipv6Net> {
@ -501,7 +499,7 @@ impl<T: fmt::Debug + Clone> AddressPool<T> {
let mut current_scope_end_subnet_index =
scope_start_subnet_index + scope_ranges[scope_index].2;
let opt_allocation = loop {
loop {
// Get the net at this current subnet index
let netbits = u128::from(scope_ranges[current_scope_index].0.network());
let subnetbits = if prefix == 128 {
@ -541,8 +539,6 @@ impl<T: fmt::Debug + Clone> AddressPool<T> {
current_scope_end_subnet_index =
current_scope_start_subnet_index + scope_ranges[current_scope_index].2;
}
};
opt_allocation
}
}
}

View File

@ -115,7 +115,7 @@ impl MachineRegistryInner {
.template_states()
.get_state(template_state_id)
.expect("must exist");
if !template_state.is_active(self)? {
if !template_state.is_active(self) {
Ok(None)
} else {
Ok(Some(template_state))

View File

@ -1,120 +1,117 @@
use super::*;
#[derive(Debug)]
struct BlueprintStateUnlockedInner {
/// The global random number generator
srng: StableRng,
struct BlueprintStateImmutable {
/// The unique id of this blueprint
id: BlueprintStateId,
/// The name of this blueprint state
name: String,
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct BlueprintStateIpv4Params {
pub locations: NetworkLocationsList,
pub prefix: WeightedList<u8>,
pub gateway: Option<BlueprintStateGatewayParams>,
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct BlueprintStateIpv6Params {
pub locations: NetworkLocationsList,
pub prefix: WeightedList<u8>,
pub gateway: Option<BlueprintStateGatewayParams>,
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct BlueprintStateGatewayParams {
pub translation: WeightedList<config::Translation>,
pub upnp: Probability,
pub locations: Option<MachineLocationsList>,
}
#[derive(Debug)]
#[derive(Debug, Clone)]
struct BlueprintStateIpv4 {
params: BlueprintStateIpv4Params,
gateway: Option<BlueprintStateIpv4Gateway>,
}
#[derive(Debug)]
#[derive(Debug, Clone)]
struct BlueprintStateIpv6 {
params: BlueprintStateIpv6Params,
gateway: Option<BlueprintStateIpv6Gateway>,
}
#[derive(Debug)]
#[derive(Debug, Clone)]
struct BlueprintStateIpv4Gateway {
params: BlueprintStateGatewayParams,
}
#[derive(Debug)]
#[derive(Debug, Clone)]
struct BlueprintStateIpv6Gateway {
params: BlueprintStateGatewayParams,
}
#[derive(Debug)]
struct BlueprintStateInner {
#[derive(Debug, Clone)]
struct BlueprintStateFields {
limit_network_count: Option<usize>,
networks: Vec<NetworkStateId>,
model: Option<WeightedList<String>>,
networks: imbl::Vector<NetworkStateId>,
model: Option<WeightedList<Arc<String>>>,
ipv4: Option<BlueprintStateIpv4>,
ipv6: Option<BlueprintStateIpv6>,
}
#[derive(Debug, Clone)]
pub struct BlueprintState {
unlocked_inner: Arc<BlueprintStateUnlockedInner>,
inner: Arc<Mutex<BlueprintStateInner>>,
immutable: Arc<BlueprintStateImmutable>,
fields: Arc<BlueprintStateFields>,
}
pub type BlueprintStateId = StateId<BlueprintState>;
impl BlueprintState {
pub fn new(
srng: StableRng,
id: BlueprintStateId,
name: String,
) -> MachineRegistryResult<BlueprintState> {
pub fn new(id: BlueprintStateId, name: String) -> MachineRegistryResult<BlueprintState> {
Ok(Self {
unlocked_inner: Arc::new(BlueprintStateUnlockedInner { srng, id, name }),
inner: Arc::new(Mutex::new(BlueprintStateInner {
immutable: Arc::new(BlueprintStateImmutable { id, name }),
fields: Arc::new(BlueprintStateFields {
limit_network_count: None,
networks: Vec::new(),
networks: imbl::Vector::new(),
model: None,
ipv4: None,
ipv6: None,
})),
}),
})
}
pub fn set_limit_network_count(&self, limit_network_count: Option<usize>) {
let mut inner = self.inner.lock();
inner.limit_network_count = limit_network_count;
pub fn set_limit_network_count(&mut self, limit_network_count: Option<usize>) {
// Update fields
self.fields = Arc::new(BlueprintStateFields {
limit_network_count,
..(*self.fields).clone()
});
}
pub fn set_model(&self, model: WeightedList<String>) {
let mut inner = self.inner.lock();
inner.model = Some(model);
pub fn set_model(&mut self, model: WeightedList<String>) {
let model = Some(model.map(|x| Arc::new(x.clone())));
// Update fields
self.fields = Arc::new(BlueprintStateFields {
model,
..(*self.fields).clone()
});
}
pub fn set_ipv4(
&self,
&mut self,
params: BlueprintStateIpv4Params,
gateway_params: Option<BlueprintStateGatewayParams>,
) {
let mut inner = self.inner.lock();
if inner.ipv4.is_none() {
inner.ipv4 = Some(BlueprintStateIpv4 {
let mut ipv4 = if let Some(ipv4) = self.fields.ipv4.clone() {
BlueprintStateIpv4 { params, ..ipv4 }
} else {
BlueprintStateIpv4 {
params,
gateway: None,
});
} else {
inner.ipv4.as_mut().map(|ipv4| {
ipv4.params = params;
});
}
let ipv4 = inner.ipv4.as_mut().expect("must exist");
}
};
if ipv4.gateway.is_some() {
if let Some(gateway_params) = gateway_params {
@ -127,25 +124,27 @@ impl BlueprintState {
params: gateway_params,
})
}
// Update fields
self.fields = Arc::new(BlueprintStateFields {
ipv4: Some(ipv4),
..(*self.fields).clone()
});
}
pub fn set_ipv6(
&self,
&mut self,
params: BlueprintStateIpv6Params,
gateway_params: Option<BlueprintStateGatewayParams>,
) {
let mut inner = self.inner.lock();
if inner.ipv6.is_none() {
inner.ipv6 = Some(BlueprintStateIpv6 {
let mut ipv6 = if let Some(ipv6) = self.fields.ipv6.clone() {
BlueprintStateIpv6 { params, ..ipv6 }
} else {
BlueprintStateIpv6 {
params,
gateway: None,
});
} else {
inner.ipv6.as_mut().map(|ipv6| {
ipv6.params = params;
});
}
let ipv6 = inner.ipv6.as_mut().expect("must exist");
}
};
if ipv6.gateway.is_some() {
if let Some(gateway_params) = gateway_params {
@ -158,33 +157,38 @@ impl BlueprintState {
params: gateway_params,
})
}
// Update fields
self.fields = Arc::new(BlueprintStateFields {
ipv6: Some(ipv6),
..(*self.fields).clone()
});
}
pub fn is_active(&self) -> MachineRegistryResult<bool> {
let inner = self.inner.lock();
pub fn is_active(&self, machine_registry_inner: &mut MachineRegistryInner) -> bool {
// Save a backup of the entire state
let backup = machine_registry_inner.clone();
// See if there's room for another network
if let Some(limit_network_count) = inner.limit_network_count {
if inner.networks.len() >= limit_network_count {
return Ok(false);
}
}
// Make a copy of this blueprint state
let mut current_state = self.clone();
todo!("needs better implementation");
// See what would happen if we try to generate this blueprint
let ok = current_state.generate(machine_registry_inner).is_ok();
Ok(true)
// Restore the backup
*machine_registry_inner = backup;
// Return if this worked or not
ok
}
fn generate_model_inner(
inner: &mut BlueprintStateInner,
&mut self,
machine_registry_inner: &mut MachineRegistryInner,
network_state: NetworkState,
network_state: &mut NetworkState,
) -> MachineRegistryResult<()> {
let model_name = match inner.model.clone() {
Some(models) => machine_registry_inner
.srng()
.weighted_choice_ref(&models)
.clone(),
let model_name = match self.fields.model.as_ref() {
Some(models) => (**machine_registry_inner.srng().weighted_choice_ref(models)).clone(),
None => machine_registry_inner.config().default_model.clone(),
};
let Some(model) = machine_registry_inner.config().models.get(&model_name) else {
@ -196,17 +200,51 @@ impl BlueprintState {
distance: model.distance.clone(),
loss: model.loss,
};
network_state.with_model(params);
network_state.set_model(params);
Ok(())
}
/// Network filter that ensures we can allocate an ipv4 gateway address on a network
fn gateway_network_filter_v4(
&self,
machine_registry_inner: &MachineRegistryInner,
network_state_id: NetworkStateId,
) -> MachineRegistryResult<bool> {
// Get the network state
let network_state = machine_registry_inner
.network_states()
.get_state(network_state_id)?;
// See if we can allocate on this network
let can_allocate = network_state.can_allocate_address_v4(None);
Ok(can_allocate)
}
/// Network filter that ensures we can allocate an ipv4 gateway address on a network
fn gateway_network_filter_v6(
&self,
machine_registry_inner: &MachineRegistryInner,
network_state_id: NetworkStateId,
) -> MachineRegistryResult<bool> {
// Get the network state
let network_state = machine_registry_inner
.network_states()
.get_state(network_state_id)?;
// See if we can allocate on this network
let can_allocate = network_state.can_allocate_address_v6(None);
Ok(can_allocate)
}
fn generate_ipv4_inner(
inner: &mut BlueprintStateInner,
&mut self,
machine_registry_inner: &mut MachineRegistryInner,
network_state: NetworkState,
network_state: &mut NetworkState,
) -> MachineRegistryResult<()> {
network_state.clear_ipv4(machine_registry_inner);
let Some(ipv4) = inner.ipv4.as_ref() else {
network_state.clear_ipv4(machine_registry_inner)?;
let Some(ipv4) = self.fields.ipv4.as_ref() else {
return Ok(());
};
@ -234,8 +272,7 @@ impl BlueprintState {
.subnets
.subnet4
.as_ref()
.map(|subnet| subnet.filter(|p| p.prefix_len() <= max_prefix))
.flatten())
.and_then(|subnet| subnet.filter(|p| p.prefix_len() <= max_prefix)))
})?
else {
return Err(MachineRegistryError::NoAllocation);
@ -256,14 +293,16 @@ impl BlueprintState {
.filter(|p| *p >= net.prefix_len())
.as_ref()
.map(|wl| {
let subnet_prefix =
machine_registry_inner.srng().weighted_choice_ref(wl).clone();
let subnet_prefix = *machine_registry_inner.srng().weighted_choice_ref(wl);
// Use an address pool temporarily to pick a subnet
let mut address_pool =
AddressPool::<()>::new();
let mut address_pool = AddressPool::<()>::new();
address_pool.add_scope_v4(net);
address_pool.allocate_random_v4(machine_registry_inner.srng(), subnet_prefix, ())
address_pool.allocate_random_v4(
machine_registry_inner.srng(),
subnet_prefix,
(),
)
})
.transpose()?
.flatten();
@ -276,40 +315,39 @@ impl BlueprintState {
// Get networks which have subnets that would fit
// our maximum requested prefix
let Some(available_networks) = networks.try_filter(|network_id| {
let network_state = machine_registry_inner
let super_network_state = machine_registry_inner
.network_states()
.get_state(*network_id)
.expect("must exist");
Ok(network_state.can_allocate_subnet_v4(None, max_prefix))
Ok(super_network_state.can_allocate_subnet_v4(None, max_prefix))
})?
else {
return Err(MachineRegistryError::NoAllocation);
};
// Pick a network
let network_id = *machine_registry_inner
let super_network_id = *machine_registry_inner
.srng()
.weighted_choice_ref(&available_networks);
let network_state = machine_registry_inner
let mut super_network_state = machine_registry_inner
.network_states()
.get_state(network_id)
.get_state(super_network_id)
.expect("must exist");
// Pick a prefix that fits in this network and allocate from it
let opt_subnet = ipv4
.params
.prefix
.filter(|p| network_state.can_allocate_subnet_v4(None, *p))
.filter(|p| super_network_state.can_allocate_subnet_v4(None, *p))
.as_ref()
.map(|wl| {
let subnet_prefix =
machine_registry_inner.srng().weighted_choice_ref(wl).clone();
let subnet_prefix = *machine_registry_inner.srng().weighted_choice_ref(wl);
// Allocate subnet from this network
network_state.allocate_subnet_v4(
super_network_state.allocate_subnet_v4(
machine_registry_inner,
OwnerTag::Network(network_state.id()),
OwnerTag::Network(super_network_state.id()),
None,
subnet_prefix,
)
@ -319,7 +357,12 @@ impl BlueprintState {
return Err(MachineRegistryError::NoAllocation);
};
(subnet, Some(network_id))
// Update network state
machine_registry_inner
.network_states_mut()
.set_state(super_network_state);
(subnet, Some(super_network_id))
}
};
@ -330,53 +373,266 @@ impl BlueprintState {
let gateway_params = match ipv4.gateway.as_ref() {
Some(v4gw) => {
let translation = machine_registry_inner
let translation = *machine_registry_inner
.srng()
.weighted_choice_ref(&v4gw.params.translation)
.clone();
.weighted_choice_ref(&v4gw.params.translation);
let upnp = machine_registry_inner
.srng()
.probability_test(v4gw.params.upnp);
let location = match v4gw.params.locations {
Some(locations) => todo!(),
None => todo!(),
};
let (external_network, external_address) = match v4gw.params.locations.as_ref() {
Some(locations_list) => {
// A external network location was specified, pick one
// Get a network to generate the machine on
let mut gateway_network_state = locations_list.pick(
machine_registry_inner,
|machine_registry_inner, id| {
self.gateway_network_filter_v4(machine_registry_inner, id)
},
)?;
let gateway_network_state_id = gateway_network_state.id();
xxx instantiate and clean up on failure
// Allocate an external address on this network
let external_interface_address = gateway_network_state
.allocate_address_v4(
machine_registry_inner,
OwnerTag::Gateway(network_state.id()),
None,
)?;
// Update the network state
machine_registry_inner
.network_states_mut()
.set_state(gateway_network_state);
(
gateway_network_state_id,
Some(external_interface_address.ip),
)
}
None => {
// No external network specified for gateway machine
// So use the same network as ourselves
(network_state.id(), None)
}
};
Some(NetworkStateIpv4GatewayParams {
translation,
upnp,
external_network: todo!(),
external_network,
internal_address: None,
external_address: None,
external_address,
})
}
None => None,
};
network_state.set_ipv4(machine_registry_inner, params, gateway_params);
network_state.set_ipv4(machine_registry_inner, params, gateway_params)?;
Ok(())
}
fn generate_ipv6_inner(
inner: &mut BlueprintStateInner,
&mut self,
machine_registry_inner: &mut MachineRegistryInner,
network_state: NetworkState,
network_state: &mut NetworkState,
) -> MachineRegistryResult<()> {
//
network_state.clear_ipv6(machine_registry_inner)?;
let Some(ipv6) = self.fields.ipv6.as_ref() else {
return Ok(());
};
// Get maximum prefix
let max_prefix = ipv6
.params
.prefix
.iter()
.max()
.copied()
.expect("must have at least one element");
// Get addresses for network
let (subnet, super_net) = match &ipv6.params.locations {
NetworkLocationsList::Allocations { allocations } => {
// Get allocations which have subnets that would fit
// our maximum requested prefix
let Some(alloc_subnets) = allocations.try_filter_map(|allocation_name| {
let allocation = machine_registry_inner
.config()
.allocations
.get(allocation_name)
.expect("must exist");
Ok(allocation
.subnets
.subnet6
.as_ref()
.and_then(|subnet| subnet.filter(|p| p.prefix_len() <= max_prefix)))
})?
else {
return Err(MachineRegistryError::NoAllocation);
};
// Pick an allocation
let subnets = machine_registry_inner
.srng()
.weighted_choice_ref(&alloc_subnets);
// Pick a subnet
let net = *machine_registry_inner.srng().weighted_choice_ref(subnets);
// Pick a prefix length that would fit in the subnet
let opt_subnet = ipv6
.params
.prefix
.filter(|p| *p >= net.prefix_len())
.as_ref()
.map(|wl| {
let subnet_prefix = *machine_registry_inner.srng().weighted_choice_ref(wl);
// Use an address pool temporarily to pick a subnet
let mut address_pool = AddressPool::<()>::new();
address_pool.add_scope_v6(net);
address_pool.allocate_random_v6(
machine_registry_inner.srng(),
subnet_prefix,
(),
)
})
.transpose()?
.flatten();
let Some(subnet) = opt_subnet else {
return Err(MachineRegistryError::NoAllocation);
};
(subnet, None)
}
NetworkLocationsList::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 = machine_registry_inner
.network_states()
.get_state(*network_id)
.expect("must exist");
Ok(super_network_state.can_allocate_subnet_v6(None, max_prefix))
})?
else {
return Err(MachineRegistryError::NoAllocation);
};
// Pick a network
let super_network_id = *machine_registry_inner
.srng()
.weighted_choice_ref(&available_networks);
let mut super_network_state = machine_registry_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 = ipv6
.params
.prefix
.filter(|p| super_network_state.can_allocate_subnet_v6(None, *p))
.as_ref()
.map(|wl| {
let subnet_prefix = *machine_registry_inner.srng().weighted_choice_ref(wl);
// Allocate subnet from this network
super_network_state.allocate_subnet_v6(
machine_registry_inner,
OwnerTag::Network(super_network_state.id()),
None,
subnet_prefix,
)
})
.transpose()?;
let Some(subnet) = opt_subnet else {
return Err(MachineRegistryError::NoAllocation);
};
// Update network state
machine_registry_inner
.network_states_mut()
.set_state(super_network_state);
(subnet, Some(super_network_id))
}
};
let params = NetworkStateIpv6Params {
allocation: subnet,
super_net,
};
let gateway_params = match ipv6.gateway.as_ref() {
Some(v6gw) => {
let translation = *machine_registry_inner
.srng()
.weighted_choice_ref(&v6gw.params.translation);
let upnp = machine_registry_inner
.srng()
.probability_test(v6gw.params.upnp);
let (external_network, external_address) = match v6gw.params.locations.as_ref() {
Some(locations_list) => {
// A external network location was specified, pick one
// Get a network to generate the machine on
let mut gateway_network_state = locations_list.pick(
machine_registry_inner,
|machine_registry_inner, id| {
self.gateway_network_filter_v6(machine_registry_inner, id)
},
)?;
let gateway_network_state_id = gateway_network_state.id();
// Allocate an external address on this network
let external_interface_address = gateway_network_state
.allocate_address_v6(
machine_registry_inner,
OwnerTag::Gateway(network_state.id()),
None,
)?;
// Update the network state
machine_registry_inner
.network_states_mut()
.set_state(gateway_network_state);
(
gateway_network_state_id,
Some(external_interface_address.ip),
)
}
None => {
// No external network specified for gateway machine
// So use the same network as ourselves
(network_state.id(), None)
}
};
Some(NetworkStateIpv6GatewayParams {
translation,
upnp,
external_network,
internal_address: None,
external_address,
})
}
None => None,
};
network_state.set_ipv6(machine_registry_inner, params, gateway_params)?;
Ok(())
}
pub fn generate(
&self,
&mut self,
machine_registry_inner: &mut MachineRegistryInner,
) -> MachineRegistryResult<NetworkState> {
let mut inner = self.inner.lock();
) -> MachineRegistryResult<NetworkStateId> {
// See if there's room for another network
if let Some(limit_network_count) = inner.limit_network_count {
if inner.networks.len() >= limit_network_count {
if let Some(limit_network_count) = self.fields.limit_network_count {
if self.fields.networks.len() >= limit_network_count {
return Err(MachineRegistryError::BlueprintComplete);
}
}
@ -385,39 +641,47 @@ xxx instantiate and clean up on failure
let network_state_id = machine_registry_inner.network_states_mut().allocate_id();
// Create an anonymous network state
let network_state = NetworkState::new(network_state_id, None);
let mut network_state =
NetworkState::new(network_state_id, None, NetworkOrigin::Blueprint(self.id()));
if let Err(e) = (|| {
Self::generate_model_inner(&mut *inner, machine_registry_inner, network_state.clone())?;
Self::generate_ipv4_inner(&mut *inner, machine_registry_inner, network_state.clone())?;
Self::generate_ipv6_inner(&mut *inner, machine_registry_inner, network_state.clone())?;
self.generate_model_inner(machine_registry_inner, &mut network_state)?;
self.generate_ipv4_inner(machine_registry_inner, &mut network_state)?;
self.generate_ipv6_inner(machine_registry_inner, &mut network_state)?;
Ok(())
})() {
// Release the network state and id if things failed to allocate
network_state.release(machine_registry_inner);
machine_registry_inner
.network_states_mut()
.release_id(network_state_id);
.release_id(network_state_id)
.expect("must succeed");
return Err(e);
}
// Attach the state to the id
machine_registry_inner
.network_states_mut()
.attach_state(network_state.clone());
.attach_state(network_state)?;
// Record the newly instantiated network
inner.networks.push(network_state_id);
let mut networks = self.fields.networks.clone();
networks.push_back(network_state_id);
Ok(network_state)
// Update fields
self.fields = Arc::new(BlueprintStateFields {
networks,
..(*self.fields).clone()
});
Ok(network_state_id)
}
pub fn for_each_network_id<F, R>(&self, callback: F) -> MachineRegistryResult<Option<R>>
pub fn for_each_network_id<F, R>(&self, mut callback: F) -> MachineRegistryResult<Option<R>>
where
F: Fn(NetworkStateId) -> MachineRegistryResult<Option<R>>,
F: FnMut(NetworkStateId) -> MachineRegistryResult<Option<R>>,
{
let inner = self.inner.lock();
for network_id in &inner.networks {
for network_id in &self.fields.networks {
if let Some(res) = callback(*network_id)? {
return Ok(Some(res));
}
@ -425,23 +689,31 @@ xxx instantiate and clean up on failure
Ok(None)
}
pub fn on_network_released(&self, network_id: NetworkStateId) {
let mut inner = self.inner.lock();
let pos = inner
pub fn on_network_released(&mut self, network_id: NetworkStateId) {
// Remove network from list
let pos = self
.fields
.networks
.iter()
.position(|id| *id == network_id)
.expect("must exist");
inner.networks.remove(pos);
let mut networks = self.fields.networks.clone();
networks.remove(pos);
// Update fields
self.fields = Arc::new(BlueprintStateFields {
networks,
..(*self.fields).clone()
});
}
}
impl State for BlueprintState {
fn id(&self) -> StateId<Self> {
self.unlocked_inner.id.clone()
self.immutable.id
}
fn name(&self) -> Option<String> {
Some(self.unlocked_inner.name.clone())
Some(self.immutable.name.clone())
}
}

View File

@ -1,5 +1,11 @@
use super::*;
#[derive(Debug, Clone)]
enum BlueprintAvailability {
Existing(NetworkState),
Generate(BlueprintState),
}
/// Locations where a machine can be instantiated
#[derive(Debug, Clone)]
pub enum MachineLocationsList {
@ -10,3 +16,194 @@ pub enum MachineLocationsList {
blueprints: WeightedList<BlueprintStateId>,
},
}
impl MachineLocationsList {
pub fn can_pick<F>(
&self,
machine_registry_inner: &mut MachineRegistryInner,
mut network_filter: F,
) -> MachineRegistryResult<bool>
where
F: FnMut(&MachineRegistryInner, NetworkStateId) -> MachineRegistryResult<bool>,
{
match self {
MachineLocationsList::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 =
machine_registry_inner.network_states().get_state(*id)?;
self.is_network_available(
machine_registry_inner,
network_state,
&mut network_filter,
)
})?
.is_none()
{
return Ok(false);
};
}
MachineLocationsList::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 =
machine_registry_inner.blueprint_states().get_state(*id)?;
self.is_blueprint_available(
machine_registry_inner,
blueprint_state,
&mut network_filter,
)
.map(|x| x.is_some())
})?
.is_none()
{
return Ok(false);
};
}
};
Ok(true)
}
pub fn pick<F>(
&self,
machine_registry_inner: &mut MachineRegistryInner,
mut network_filter: F,
) -> MachineRegistryResult<NetworkState>
where
F: FnMut(&MachineRegistryInner, NetworkStateId) -> MachineRegistryResult<bool>,
{
// Get a network to generate the machine on
let network_state = match self {
MachineLocationsList::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 = machine_registry_inner.network_states().get_state(*id)?;
if self.is_network_available(
machine_registry_inner,
network_state.clone(),
&mut network_filter,
)? {
Ok(Some(network_state))
} else {
Ok(None)
}
})?
else {
return Err(MachineRegistryError::NetworkComplete);
};
// Weighted choice of network now that we have a candidate list
let network_state = machine_registry_inner
.srng()
.weighted_choice(available_networks);
// Return network state to use
network_state
}
MachineLocationsList::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 =
machine_registry_inner.blueprint_states().get_state(*id)?;
self.is_blueprint_available(
machine_registry_inner,
blueprint_state,
&mut network_filter,
)
})?
else {
return Err(MachineRegistryError::BlueprintComplete);
};
// Weighted choice of blueprint now that we have a candidate list
match machine_registry_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(machine_registry_inner)?;
// Update blueprint state
machine_registry_inner
.blueprint_states_mut()
.set_state(blueprint_state);
// Return network state
machine_registry_inner
.network_states()
.get_state(network_state_id)?
}
}
}
};
Ok(network_state)
}
fn is_network_available<F>(
&self,
machine_registry_inner: &MachineRegistryInner,
network_state: NetworkState,
mut network_filter: F,
) -> MachineRegistryResult<bool>
where
F: FnMut(&MachineRegistryInner, NetworkStateId) -> MachineRegistryResult<bool>,
{
// 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(machine_registry_inner, network_state.id())? {
return Ok(false);
}
Ok(true)
}
fn is_blueprint_available<F>(
&self,
machine_registry_inner: &mut MachineRegistryInner,
blueprint_state: BlueprintState,
mut network_filter: F,
) -> MachineRegistryResult<Option<BlueprintAvailability>>
where
F: FnMut(&MachineRegistryInner, NetworkStateId) -> MachineRegistryResult<bool>,
{
// 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 = machine_registry_inner.network_states().get_state(id)?;
if self.is_network_available(
machine_registry_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(machine_registry_inner) {
return Ok(Some(BlueprintAvailability::Generate(blueprint_state)));
}
Ok(None)
}
}

View File

@ -61,14 +61,18 @@ impl MachineState {
}
pub fn release(mut self, machine_registry_inner: &mut MachineRegistryInner) {
self.release_all_interfaces(machine_registry_inner);
self.release_all_interfaces(machine_registry_inner)
.expect("must succeed");
if let MachineOrigin::Template(generating_template) = self.immutable.origin {
let template_state = machine_registry_inner
.template_states_mut()
let mut template_state = machine_registry_inner
.template_states()
.get_state(generating_template)
.expect("must exist");
template_state.on_machine_released(self.id());
machine_registry_inner
.template_states_mut()
.set_state(template_state);
}
}
@ -108,7 +112,7 @@ impl MachineState {
if self.fields.interfaces.contains_key(&interface_key) {
return Err(MachineRegistryError::DuplicateName);
}
let flags = opt_interface_flags.unwrap_or_else(|| InterfaceFlags {
let flags = opt_interface_flags.unwrap_or(InterfaceFlags {
is_loopback: false,
is_running: true,
is_point_to_point: false,
@ -175,7 +179,7 @@ impl MachineState {
.set_state(network_state);
// Get address flags
let flags = opt_address_flags.unwrap_or_else(|| AddressFlags {
let flags = opt_address_flags.unwrap_or(AddressFlags {
is_dynamic,
is_temporary: false,
is_preferred: true,
@ -240,7 +244,7 @@ impl MachineState {
.set_state(network_state);
// Get address flags
let flags = opt_address_flags.unwrap_or_else(|| AddressFlags {
let flags = opt_address_flags.unwrap_or(AddressFlags {
is_dynamic,
is_temporary: false,
is_preferred: true,
@ -536,103 +540,11 @@ impl MachineState {
Ok(())
}
// let network_state = match params {
// MachineParameters::Direct {
// network_id,
// disable_capabilities: _,
// bootstrap: _,
// } => todo!(),
// MachineParameters::Config { name, def } => {
// machine_registry_inner
// .get_or_create_network_state_from_machine_location(&def.location)?;
// }
// };
// let srng = machine_registry_inner.unlocked_inner.srng.clone();
// // Build list of default route interface addresses
// let mut addrs = Vec::<InterfaceAddress>::new();
// // Make the default route interface
// let machine_location = machine_def.location.clone();
// let (allocate_v4, opt_address4, allocate_v6, opt_address6) = match machine_location {
// config::MachineLocation::Network {
// network: _,
// address4,
// address6,
// } => (
// network_state.is_ipv4() && address4.is_some(),
// address4,
// network_state.is_ipv6() && address6.is_some(),
// address6,
// ),
// config::MachineLocation::Blueprint { blueprint: _ } => {
// (network_state.is_ipv4(), None, network_state.is_ipv6(), None)
// }
// };
// if allocate_v4 {
// let if_addr4 = match network_state.allocate_address_v4(srng.clone(), id, opt_address4) {
// Ok(v) => v,
// Err(e) => {
// network_state
// .release_all_addresses(addrs.iter().map(|x| x.if_addr.ip()))
// .expect("must succeed");
// return Err(e);
// }
// };
// addrs.push(InterfaceAddress {
// if_addr: IfAddr::V4(if_addr4),
// flags: AddressFlags {
// is_dynamic: false,
// is_temporary: false,
// is_preferred: true,
// },
// });
// }
// if allocate_v6 {
// let if_addr6 = match network_state.allocate_address_v6(srng.clone(), id, opt_address6) {
// Ok(v) => v,
// Err(e) => {
// network_state
// .release_all_addresses(addrs.iter().map(|x| x.if_addr.ip()))
// .expect("must succeed");
// return Err(e);
// }
// };
// addrs.push(InterfaceAddress {
// if_addr: IfAddr::V6(if_addr6),
// flags: AddressFlags {
// is_dynamic: false,
// is_temporary: false,
// is_preferred: true,
// },
// });
// }
// // Allocate an address on the network and make an veilid-style interface record for it
// let network_interface = NetworkInterface {
// name: "vin0".to_owned(),
// flags: InterfaceFlags {
// is_loopback: false,
// is_running: true,
// is_point_to_point: false,
// has_default_route: true,
// },
// addrs,
// };
// interfaces.push(MachineStateInterface {
// network_id: network_state.id(),
// network_interface,
// });
// }
}
impl State for MachineState {
fn id(&self) -> StateId<Self> {
self.immutable.id.clone()
self.immutable.id
}
fn name(&self) -> Option<String> {

View File

@ -1,7 +1,7 @@
use super::*;
/// Locations where a network can be instantiated
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum NetworkLocationsList {
/// Network will be a new allocation
Allocations { allocations: WeightedList<String> },

View File

@ -140,11 +140,14 @@ impl NetworkState {
pub fn release(self, machine_registry_inner: &mut MachineRegistryInner) {
if let NetworkOrigin::Blueprint(generating_blueprint) = self.immutable.origin {
let blueprint_state = machine_registry_inner
.blueprint_states_mut()
let mut blueprint_state = machine_registry_inner
.blueprint_states()
.get_state(generating_blueprint)
.expect("must exist");
blueprint_state.on_network_released(self.id());
machine_registry_inner
.blueprint_states_mut()
.set_state(blueprint_state)
}
}
@ -673,7 +676,7 @@ impl NetworkState {
impl State for NetworkState {
fn id(&self) -> StateId<Self> {
self.immutable.id.clone()
self.immutable.id
}
fn name(&self) -> Option<String> {

View File

@ -47,7 +47,7 @@ impl ProfileState {
impl State for ProfileState {
fn id(&self) -> StateId<Self> {
self.immutable.id.clone()
self.immutable.id
}
fn name(&self) -> Option<String> {

View File

@ -181,45 +181,9 @@ impl<S: State> StateAllocator<S> {
self.state_by_id.insert(state.id().0, Some(state));
}
// pub fn update_state<
// R,
// F: FnOnce(&mut StateAllocator<S>, S) -> MachineRegistryResult<(R, Option<S>)>,
// >(
// &mut self,
// id: StateId<S>,
// callback: F,
// ) -> MachineRegistryResult<R> {
// // Get state to update
// let state = {
// // Get the allocator slot
// let Some(opt_state) = self.state_by_id.get(&id.0) else {
// return Err(MachineRegistryError::InvalidId);
// };
// let Some(state) = opt_state else {
// return Err(MachineRegistryError::NotAttached);
// };
// // Make copy of state to update
// state.clone()
// };
// // Call the callback
// let id = state.id();
// let (res, opt_new_state) = callback(self, state)?;
// if let Some(new_state) = opt_new_state {
// assert_eq!(id, new_state.id(), "state id must not change");
// self.state_by_id.insert(id.0, Some(new_state));
// }
// Ok(res)
// }
pub fn get_state_id_by_name(&self, name: &String) -> Option<StateId<S>> {
// Get the id associated with this name
let Some(id) = self.state_id_by_name.get(name) else {
return None;
};
let id = self.state_id_by_name.get(name)?;
Some(StateId::new(*id))
}
}

View File

@ -2,7 +2,9 @@ use super::*;
#[derive(Debug)]
struct TemplateStateImmutable {
/// The unique id of this template
id: TemplateStateId,
/// The name of this template state
name: String,
}
@ -12,12 +14,6 @@ struct PerNetworkInfo {
machines: imbl::HashSet<MachineStateId>,
}
#[derive(Debug, Clone)]
enum BlueprintAvailability {
Existing(NetworkState),
Generate(BlueprintState),
}
#[derive(Debug, Clone)]
struct TemplateStateFields {
limit_machine_count: Option<usize>,
@ -110,14 +106,27 @@ impl TemplateState {
});
}
fn is_network_available(&self, network_state: NetworkState) -> MachineRegistryResult<bool> {
// If the network is not active, it is not available
if !network_state.is_active()? {
return Ok(false);
}
pub fn is_active(&self, machine_registry_inner: &mut MachineRegistryInner) -> bool {
// Save a backup of the entire state
let backup = machine_registry_inner.clone();
// Make a copy of this template state
let mut current_state = self.clone();
// See what would happen if we try to generate this template
let ok = current_state.generate(machine_registry_inner).is_ok();
// Restore the backup
*machine_registry_inner = backup;
// Return if this worked or not
ok
}
/// Network filter that keeps this template generation within per-network limits
fn network_filter(&self, network_state_id: NetworkStateId) -> MachineRegistryResult<bool> {
// Get the per network info
let Some(pni) = self.fields.machines_per_network.get(&network_state.id()) else {
let Some(pni) = self.fields.machines_per_network.get(&network_state_id) else {
// If we haven't allocated anything in the network yet it is
// by definition available
return Ok(true);
@ -130,87 +139,6 @@ impl TemplateState {
return Ok(false);
}
}
Ok(true)
}
fn is_blueprint_available(
&self,
machine_registry_inner: &MachineRegistryInner,
blueprint_state: BlueprintState,
) -> MachineRegistryResult<Option<BlueprintAvailability>> {
// 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 = machine_registry_inner.network_states().get_state(id)?;
if self.is_network_available(network_state.clone())? {
// 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()? {
return Ok(Some(BlueprintAvailability::Generate(blueprint_state)));
}
Ok(None)
}
pub fn is_active(
&self,
machine_registry_inner: &MachineRegistryInner,
) -> MachineRegistryResult<bool> {
// See if we have reached our machine limit
if let Some(limit_machine_count) = self.fields.limit_machine_count {
if self.fields.machines.len() >= limit_machine_count {
return Ok(false);
}
}
let Some(locations_list) = self.fields.locations_list.as_ref() else {
return Ok(false);
};
match locations_list {
MachineLocationsList::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 =
machine_registry_inner.network_states().get_state(*id)?;
self.is_network_available(network_state)
})?
.is_none()
{
return Ok(false);
};
}
MachineLocationsList::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 =
machine_registry_inner.blueprint_states().get_state(*id)?;
self.is_blueprint_available(machine_registry_inner, blueprint_state)
.map(|x| x.is_some())
})?
.is_none()
{
return Ok(false);
};
}
};
Ok(true)
}
@ -220,7 +148,7 @@ impl TemplateState {
) -> MachineRegistryResult<MachineStateId> {
// See if we have reached our machine limit
if let Some(limit_machine_count) = self.fields.limit_machine_count {
if self.fields.machines.len() < limit_machine_count.try_into().unwrap_or(usize::MAX) {
if self.fields.machines.len() < limit_machine_count {
return Err(MachineRegistryError::TemplateComplete);
}
}
@ -231,65 +159,26 @@ impl TemplateState {
};
// Get a network to generate the machine on
let network_state = match locations_list {
MachineLocationsList::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 = machine_registry_inner.network_states().get_state(*id)?;
if self.is_network_available(network_state.clone())? {
Ok(Some(network_state))
} else {
Ok(None)
}
})?
else {
return Err(MachineRegistryError::NetworkComplete);
};
// Weighted choice of network now that we have a candidate list
let network_state = machine_registry_inner
.srng()
.weighted_choice_ref(&available_networks);
// Return network state to use
network_state.clone()
}
MachineLocationsList::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 =
machine_registry_inner.blueprint_states().get_state(*id)?;
self.is_blueprint_available(machine_registry_inner, blueprint_state)
})?
else {
return Err(MachineRegistryError::BlueprintComplete);
};
// Weighted choice of blueprint now that we have a candidate list
match machine_registry_inner
.srng()
.weighted_choice_ref(&available_blueprints)
{
BlueprintAvailability::Existing(network_state) => network_state.clone(),
BlueprintAvailability::Generate(blueprint_state) => {
blueprint_state.generate(machine_registry_inner)?
}
}
}
};
let network_state =
locations_list.pick(machine_registry_inner, |_, x| self.network_filter(x))?;
// Allocate a machine id
let machine_state_id = machine_registry_inner.machine_states_mut().allocate_id();
xxx continue here
// Create an anonymous machine state
let machine_state = MachineState::new(machine_state_id, None);
let mut machine_state =
MachineState::new(machine_state_id, None, MachineOrigin::Template(self.id()));
// Scope to release state on error
if let Err(e) = (|| {
// Build out the machine state from the template
machine_state.set_disable_capabilities(inner.disable_capabilities.clone());
machine_state.set_disable_capabilities(
self.fields
.disable_capabilities
.iter()
.map(|x| (**x).clone())
.collect(),
);
machine_state.set_bootstrap(false);
// Make the default route interface
@ -307,53 +196,67 @@ xxx continue here
machine_state.release(machine_registry_inner);
machine_registry_inner
.machine_states_mut()
.release_id(machine_state_id);
.release_id(machine_state_id)
.expect("must succeed");
return Err(e);
}
// Attach the state to the id
machine_registry_inner
.machine_states_mut()
.attach_state(machine_state.clone());
.attach_state(machine_state)
.expect("must succeed");
// Record the newly instantiated machine
inner.machines.insert(machine_state_id);
let limit_machines_per_network = inner.limit_machines_per_network.clone();
let per_network_info = inner
.machines_per_network
let machines = self.fields.machines.update(machine_state_id);
let mut machines_per_network = self.fields.machines_per_network.clone();
let per_network_info = machines_per_network
.entry(network_state.id())
.or_insert_with(|| {
let limit_machine_count = limit_machines_per_network.map(|wl| {
machine_registry_inner
.srng()
.weighted_choice_ref(&wl)
.clone()
});
let limit_machine_count = self
.fields
.limit_machines_per_network
.as_ref()
.map(|wl| *machine_registry_inner.srng().weighted_choice_ref(wl));
PerNetworkInfo {
limit_machine_count,
machines: HashSet::new(),
machines: imbl::HashSet::new(),
}
});
per_network_info.machines.insert(machine_state_id);
Ok(machine_state)
// Update fields
self.fields = Arc::new(TemplateStateFields {
machines,
machines_per_network,
..(*self.fields).clone()
});
Ok(machine_state_id)
}
pub fn on_machine_released(&self, machine_state_id: MachineStateId) {
let mut inner = self.inner.lock();
inner.machines.remove(&machine_state_id);
for (_network_id, pni) in &mut inner.machines_per_network {
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();
for (_network_id, pni) in machines_per_network.iter_mut() {
pni.machines.remove(&machine_state_id);
}
// Update fields
self.fields = Arc::new(TemplateStateFields {
machines,
machines_per_network,
..(*self.fields).clone()
});
}
}
impl State for TemplateState {
fn id(&self) -> StateId<Self> {
self.unlocked_inner.id.clone()
self.immutable.id
}
fn name(&self) -> Option<String> {
Some(self.unlocked_inner.name.clone())
Some(self.immutable.name.clone())
}
}

View File

@ -56,6 +56,29 @@ impl<T: fmt::Debug + Clone> WeightedList<T> {
}
}
pub fn map<F, S>(&self, mut map: F) -> WeightedList<S>
where
F: FnMut(&T) -> S,
S: fmt::Debug + Clone,
{
match self {
WeightedList::Single(v) => WeightedList::Single(map(v)),
WeightedList::List(vec) => {
let mut out = Vec::<Weighted<S>>::with_capacity(vec.len());
for v in vec {
out.push(match v {
Weighted::Weighted { item, weight } => Weighted::Weighted {
item: map(item),
weight: *weight,
},
Weighted::Unweighted(item) => Weighted::Unweighted(map(item)),
});
}
WeightedList::List(out)
}
}
}
pub fn filter<F>(&self, mut filter: F) -> Option<WeightedList<T>>
where
F: FnMut(&T) -> bool,
@ -63,9 +86,10 @@ impl<T: fmt::Debug + Clone> WeightedList<T> {
match self {
WeightedList::Single(v) => {
if filter(v) {
return Some(self.clone());
Some(self.clone())
} else {
None
}
return None;
}
WeightedList::List(vec) => {
let mut out = Vec::<Weighted<T>>::with_capacity(vec.len());
@ -90,9 +114,10 @@ impl<T: fmt::Debug + Clone> WeightedList<T> {
match self {
WeightedList::Single(v) => {
if filter(v)? {
return Ok(Some(self.clone()));
Ok(Some(self.clone()))
} else {
Ok(None)
}
return Ok(None);
}
WeightedList::List(vec) => {
let mut out = Vec::<Weighted<T>>::with_capacity(vec.len());
@ -117,9 +142,10 @@ impl<T: fmt::Debug + Clone> WeightedList<T> {
match self {
WeightedList::Single(v) => {
if let Some(item) = filter(v)? {
return Ok(Some(WeightedList::Single(item)));
Ok(Some(WeightedList::Single(item)))
} else {
Ok(None)
}
return Ok(None);
}
WeightedList::List(vec) => {
let mut out = Vec::<Weighted<S>>::with_capacity(vec.len());
@ -159,7 +185,7 @@ impl<T: fmt::Debug + Clone> core::ops::Index<usize> for WeightedList<T> {
fn index(&self, index: usize) -> &Self::Output {
match self {
WeightedList::Single(s) => &s,
WeightedList::Single(s) => s,
WeightedList::List(vec) => vec[index].item(),
}
}