use super::super::model::{
ConfiguredPoolExpectation, DEFAULT_INITIAL_CYCLES, LOCAL_ROOT_MIN_READY_CYCLES,
};
use super::parse_projection_config;
use crate::release_set::config::FleetConfigError;
use canic_core::{
bootstrap::compiled::{ConfigModel, SubnetConfig},
ids::CanisterRole,
};
use std::collections::{BTreeMap, BTreeSet};
#[derive(Clone, Copy)]
enum RootSubnetRoleScope {
Release,
Deployable,
}
impl RootSubnetRoleScope {
const fn includes_root(self) -> bool {
matches!(self, Self::Deployable)
}
}
pub(in crate::release_set) fn configured_local_root_create_cycles_from_source(
config_source: &str,
) -> Result<u128, FleetConfigError> {
let config = parse_projection_config(config_source)?;
let subnet = root_subnet(&config);
let mut cycles = subnet
.get_canister(&CanisterRole::WASM_STORE)
.map_or(DEFAULT_INITIAL_CYCLES, |cfg| cfg.initial_cycles.to_u128());
for role in subnet.auto_create_roles() {
if let Some(cfg) = subnet.get_canister(&role) {
cycles = cycles.saturating_add(cfg.initial_cycles.to_u128());
}
}
cycles = cycles.saturating_add(
u128::from(subnet.pool.minimum_size).saturating_mul(DEFAULT_INITIAL_CYCLES),
);
Ok(cycles.saturating_add(LOCAL_ROOT_MIN_READY_CYCLES))
}
pub(in crate::release_set) fn configured_pool_expectations_from_source(
config_source: &str,
) -> Result<Vec<ConfiguredPoolExpectation>, FleetConfigError> {
let config = parse_projection_config(config_source)?;
let subnet = root_subnet(&config);
let mut pools = BTreeMap::<String, ConfiguredPoolExpectation>::new();
for canister in subnet.canisters.values() {
if let Some(scaling) = &canister.scaling {
for (pool_name, pool) in &scaling.pools {
pools.insert(
format!("scaling:{pool_name}:{}", pool.canister_role.as_str()),
ConfiguredPoolExpectation {
pool: pool_name.clone(),
canister_role: pool.canister_role.as_str().to_string(),
},
);
}
}
if let Some(sharding) = &canister.sharding {
for (pool_name, pool) in &sharding.pools {
pools.insert(
format!("sharding:{pool_name}:{}", pool.canister_role.as_str()),
ConfiguredPoolExpectation {
pool: pool_name.clone(),
canister_role: pool.canister_role.as_str().to_string(),
},
);
}
}
if let Some(directory) = &canister.directory {
for (pool_name, pool) in &directory.pools {
pools.insert(
format!("directory:{pool_name}:{}", pool.canister_role.as_str()),
ConfiguredPoolExpectation {
pool: pool_name.clone(),
canister_role: pool.canister_role.as_str().to_string(),
},
);
}
}
}
Ok(pools.into_values().collect())
}
pub fn configured_release_roles_from_config(config: &ConfigModel) -> Vec<String> {
configured_root_subnet_roles(config, RootSubnetRoleScope::Release)
}
pub(in crate::release_set) fn configured_deployable_roles_from_source(
config_source: &str,
) -> Result<Vec<String>, FleetConfigError> {
configured_root_subnet_roles_from_source(config_source, RootSubnetRoleScope::Deployable)
}
pub(in crate::release_set) fn configured_bootstrap_roles_from_source(
config_source: &str,
) -> Result<Vec<String>, FleetConfigError> {
let config = parse_projection_config(config_source)?;
let subnet = root_subnet(&config);
let mut roles = BTreeSet::<String>::new();
roles.insert(CanisterRole::ROOT.as_str().to_string());
roles.extend(
subnet
.auto_create_roles()
.iter()
.map(|role| role.as_str().to_string()),
);
for role in subnet.auto_create_roles() {
let Some(canister) = subnet.get_canister(&role) else {
continue;
};
if let Some(sharding) = &canister.sharding {
for pool in sharding.pools.values() {
if pool.policy.initial_shards > 0 {
roles.insert(pool.canister_role.as_str().to_string());
}
}
}
if let Some(scaling) = &canister.scaling {
for pool in scaling.pools.values() {
if pool.policy.initial_workers > 0 {
roles.insert(pool.canister_role.as_str().to_string());
}
}
}
}
Ok(sort_root_subnet_roles(roles.into_iter().collect()))
}
fn configured_root_subnet_roles_from_source(
config_source: &str,
scope: RootSubnetRoleScope,
) -> Result<Vec<String>, FleetConfigError> {
let config = parse_projection_config(config_source)?;
Ok(configured_root_subnet_roles(&config, scope))
}
fn configured_root_subnet_roles(config: &ConfigModel, scope: RootSubnetRoleScope) -> Vec<String> {
let subnet = root_subnet(config);
let root_subnet_roles = subnet
.canisters
.keys()
.filter(|role| !role.is_wasm_store())
.filter(|role| scope.includes_root() || !role.is_root())
.map(|role| role.as_str().to_string())
.collect::<Vec<_>>();
sort_root_subnet_roles(root_subnet_roles)
}
fn root_subnet(config: &ConfigModel) -> &SubnetConfig {
config
.subnets
.values()
.find(|subnet| subnet.canisters.keys().any(CanisterRole::is_root))
.expect("validated config must contain exactly one root subnet")
}
fn sort_root_subnet_roles(mut roles: Vec<String>) -> Vec<String> {
roles.sort_by(|left, right| {
match (
left == CanisterRole::ROOT.as_str(),
right == CanisterRole::ROOT.as_str(),
) {
(true, false) => std::cmp::Ordering::Less,
(false, true) => std::cmp::Ordering::Greater,
_ => left.cmp(right),
}
});
roles
}