#[cfg(test)]
mod tests;
use crate::{
fleet_catalog::{
CommittedFleetCatalog, FleetCatalogEntryV1, FleetCatalogError, commit_fleet_catalog_entry,
},
fleet_install_plan::FleetInstallPlan,
};
use std::path::Path;
use candid::Principal;
use canic_core::{
control_plane_support::{
config::ComponentTopology, error::InternalError, ops::fleet_registry::FleetRegistryOps,
},
dto::{
fleet_registry::{
FleetRegistrySnapshotResponse, FleetRegistryVersion, FleetSubnetRootEntry,
FleetSubnetRootStatus,
},
fleet_subnet_root::FleetSubnetRootCanisterSummary,
},
ids::{FleetCoordinatorBinding, FleetName, FleetRegistryAuthority},
};
use thiserror::Error as ThisError;
pub(super) struct TerminalFleetCatalogPublicationRequest<'a> {
pub project_root: &'a Path,
pub fleet_name: FleetName,
pub environment: &'a str,
pub deployed_at_unix_secs: u64,
pub fleet_install_plan: &'a FleetInstallPlan,
pub component_topology: &'a ComponentTopology,
pub coordinator: Principal,
pub registry: &'a FleetRegistrySnapshotResponse,
pub root_summaries: &'a [FleetSubnetRootCanisterSummary],
}
#[derive(Debug, ThisError)]
pub(super) enum TerminalFleetCatalogPublicationError {
#[error("terminal Fleet catalog authority differs from the immutable install plan")]
AuthorityMismatch,
#[error(transparent)]
Catalog(#[from] FleetCatalogError),
#[error("terminal Fleet catalog publication time must be positive")]
NonPositiveDeploymentTime,
#[error("terminal Fleet Registry evidence is not exact")]
RegistryEvidenceMismatch,
#[error("terminal Fleet Registry validation failed: {0}")]
RegistryValidation(#[source] InternalError),
#[error("terminal Fleet root count or immutable root plan differs from the Registry")]
RootSetMismatch,
#[error("terminal evidence for Fleet Subnet Root {root} is incomplete or contradictory")]
RootSummaryMismatch { root: Principal },
}
pub(super) fn publish_terminal_fleet_catalog(
request: TerminalFleetCatalogPublicationRequest<'_>,
) -> Result<CommittedFleetCatalog, TerminalFleetCatalogPublicationError> {
validate_terminal_authority(&request)?;
let entry = FleetCatalogEntryV1 {
canonical_network_id: request.fleet_install_plan.fleet.fleet.canonical_network_id,
fleet_id: request.fleet_install_plan.fleet.fleet.fleet_id,
fleet_name: request.fleet_name,
app: request.fleet_install_plan.fleet.app.clone(),
environment: request.environment.to_string(),
deployed_at_unix_secs: request.deployed_at_unix_secs,
coordinator_principal: request.coordinator.to_text(),
};
commit_fleet_catalog_entry(request.project_root, entry).map_err(Into::into)
}
fn validate_terminal_authority(
request: &TerminalFleetCatalogPublicationRequest<'_>,
) -> Result<(), TerminalFleetCatalogPublicationError> {
if request.deployed_at_unix_secs == 0 {
return Err(TerminalFleetCatalogPublicationError::NonPositiveDeploymentTime);
}
let expected_authority = FleetRegistryAuthority {
binding: FleetCoordinatorBinding {
fleet: request.fleet_install_plan.fleet.clone(),
coordinator_subnet: request.fleet_install_plan.coordinator.coordinator_subnet,
coordinator: request.coordinator,
},
epoch: 1,
};
if request.coordinator == Principal::anonymous()
|| request.coordinator == Principal::management_canister()
|| request.registry.registry.authority != expected_authority
{
return Err(TerminalFleetCatalogPublicationError::AuthorityMismatch);
}
FleetRegistryOps::validate(
&expected_authority,
request.component_topology,
&request.registry.registry,
)
.map_err(TerminalFleetCatalogPublicationError::RegistryValidation)?;
let manifest = FleetRegistryOps::manifest(
&expected_authority,
request.component_topology,
&request.registry.registry,
)
.map_err(TerminalFleetCatalogPublicationError::RegistryValidation)?;
let version = FleetRegistryOps::version(
&expected_authority,
request.component_topology,
&request.registry.registry,
)
.map_err(TerminalFleetCatalogPublicationError::RegistryValidation)?;
if request.registry.manifest != manifest || request.registry.version != version {
return Err(TerminalFleetCatalogPublicationError::RegistryEvidenceMismatch);
}
validate_terminal_roots(request, &version)
}
fn validate_terminal_roots(
request: &TerminalFleetCatalogPublicationRequest<'_>,
registry_version: &FleetRegistryVersion,
) -> Result<(), TerminalFleetCatalogPublicationError> {
let planned_roots = &request.fleet_install_plan.fleet_subnet_roots;
let registry_roots = &request.registry.registry.fleet_subnet_roots;
if planned_roots.is_empty()
|| planned_roots.len() != registry_roots.len()
|| registry_roots.len() != request.root_summaries.len()
{
return Err(TerminalFleetCatalogPublicationError::RootSetMismatch);
}
for ((planned, registered), summary) in planned_roots
.iter()
.zip(registry_roots)
.zip(request.root_summaries)
{
if !root_matches_plan(registered, planned) {
return Err(TerminalFleetCatalogPublicationError::RootSetMismatch);
}
validate_root_summary(registered, registry_version, summary)?;
}
Ok(())
}
fn root_matches_plan(
registered: &FleetSubnetRootEntry,
planned: &crate::fleet_install_plan::PlannedFleetSubnetRoot,
) -> bool {
let expected_release_set = planned.initial_release_set;
registered.placement_subnet == planned.placement_subnet
&& registered.component_admissions == planned.component_admissions
&& registered.component_topology_digest == planned.component_topology_digest
&& registered.active_release_set == expected_release_set
&& registered.limits == planned.limits
&& registered.status == FleetSubnetRootStatus::Active
}
fn validate_root_summary(
registered: &FleetSubnetRootEntry,
registry_version: &FleetRegistryVersion,
summary: &FleetSubnetRootCanisterSummary,
) -> Result<(), TerminalFleetCatalogPublicationError> {
let total = summary
.infrastructure_canisters
.checked_add(summary.component_canisters);
let managed = 1_u32.checked_add(summary.component_canisters);
if summary.fleet_registry != *registry_version
|| summary.placement_subnet != registered.placement_subnet
|| summary.fleet_subnet_root != registered.fleet_subnet_root
|| summary.status != FleetSubnetRootStatus::Active
|| summary.infrastructure_canisters != 2
|| total != Some(summary.total_canisters)
|| managed.is_none_or(|count| count > registered.limits.maximum_managed_canisters)
{
return Err(TerminalFleetCatalogPublicationError::RootSummaryMismatch {
root: registered.fleet_subnet_root,
});
}
Ok(())
}