use crate::{
InternalError, cdk,
ids::SystemMetricKind,
infra::{
InfraError,
ic::mgmt::{
InfraCanisterInstallMode, InfraCanisterSettings, InfraCanisterSnapshot,
InfraCanisterStatusResult, InfraCanisterStatusType, InfraDefiniteCanisterSettings,
InfraEnvironmentVariable, InfraLogVisibility, InfraMemoryMetrics, InfraQueryStats,
InfraUpdateSettingsArgs, InfraUpgradeFlags, MgmtInfra,
},
},
ops::{
ic::IcOpsError,
prelude::*,
runtime::metrics::{
canister_ops::{
CanisterOpsMetricOperation, CanisterOpsMetricOutcome, CanisterOpsMetricReason,
CanisterOpsMetrics,
},
platform_call::{
PlatformCallMetricMode, PlatformCallMetricOutcome, PlatformCallMetricReason,
PlatformCallMetricSurface, PlatformCallMetrics,
},
system::SystemMetrics,
},
},
};
use candid::{Nat, utils::ArgumentEncoder};
use std::future::Future;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CanisterInstallMode {
Install,
Reinstall,
Upgrade(Option<UpgradeFlags>),
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct UpgradeFlags {
pub skip_pre_upgrade: Option<bool>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum LogVisibility {
Controllers,
Public,
AllowedViewers(Vec<Principal>),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct EnvironmentVariable {
pub name: String,
pub value: String,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct CanisterSettings {
pub controllers: Option<Vec<Principal>>,
pub compute_allocation: Option<Nat>,
pub memory_allocation: Option<Nat>,
pub freezing_threshold: Option<Nat>,
pub reserved_cycles_limit: Option<Nat>,
pub log_visibility: Option<LogVisibility>,
pub log_memory_limit: Option<Nat>,
pub wasm_memory_limit: Option<Nat>,
pub wasm_memory_threshold: Option<Nat>,
pub environment_variables: Option<Vec<EnvironmentVariable>>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct UpdateSettingsArgs {
pub canister_id: Principal,
pub settings: CanisterSettings,
pub sender_canister_version: Option<u64>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CanisterSnapshot {
pub id: Vec<u8>,
pub taken_at_timestamp: u64,
pub total_size: u64,
}
#[derive(Clone, Debug)]
pub struct CanisterStatus {
pub status: CanisterStatusType,
pub settings: CanisterSettingsSnapshot,
pub module_hash: Option<Vec<u8>>,
pub memory_size: Nat,
pub memory_metrics: MemoryMetricsSnapshot,
pub cycles: Nat,
pub reserved_cycles: Nat,
pub idle_cycles_burned_per_day: Nat,
pub query_stats: QueryStatsSnapshot,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CanisterStatusType {
Running,
Stopping,
Stopped,
}
#[derive(Clone, Debug)]
pub struct CanisterSettingsSnapshot {
pub controllers: Vec<Principal>,
pub compute_allocation: Nat,
pub memory_allocation: Nat,
pub freezing_threshold: Nat,
pub reserved_cycles_limit: Nat,
pub log_visibility: LogVisibility,
pub log_memory_limit: Nat,
pub wasm_memory_limit: Nat,
pub wasm_memory_threshold: Nat,
pub environment_variables: Vec<EnvironmentVariable>,
}
#[derive(Clone, Debug)]
pub struct MemoryMetricsSnapshot {
pub wasm_memory_size: Nat,
pub stable_memory_size: Nat,
pub global_memory_size: Nat,
pub wasm_binary_size: Nat,
pub custom_sections_size: Nat,
pub canister_history_size: Nat,
pub wasm_chunk_store_size: Nat,
pub snapshots_size: Nat,
}
#[derive(Clone, Debug)]
pub struct QueryStatsSnapshot {
pub num_calls_total: Nat,
pub num_instructions_total: Nat,
pub request_payload_bytes_total: Nat,
pub response_payload_bytes_total: Nat,
}
pub struct MgmtOps;
impl MgmtOps {
pub async fn create_canister(
controllers: Vec<Principal>,
cycles: Cycles,
) -> Result<Principal, InternalError> {
let pid = management_call(MgmtInfra::create_canister(controllers, cycles)).await?;
SystemMetrics::increment(SystemMetricKind::CreateCanister);
Ok(pid)
}
pub async fn canister_status(canister_pid: Principal) -> Result<CanisterStatus, InternalError> {
let status = management_call(MgmtInfra::canister_status(canister_pid)).await?;
SystemMetrics::increment(SystemMetricKind::CanisterStatus);
Ok(canister_status_from_infra(status))
}
pub async fn take_canister_snapshot(
canister_pid: Principal,
replace_snapshot: Option<Vec<u8>>,
uninstall_code: Option<bool>,
) -> Result<CanisterSnapshot, InternalError> {
record_unscoped_canister_op(
CanisterOpsMetricOperation::Snapshot,
CanisterOpsMetricOutcome::Started,
CanisterOpsMetricReason::Ok,
);
match management_call(MgmtInfra::take_canister_snapshot(
canister_pid,
replace_snapshot,
uninstall_code,
))
.await
{
Ok(snapshot) => {
record_unscoped_canister_op(
CanisterOpsMetricOperation::Snapshot,
CanisterOpsMetricOutcome::Completed,
CanisterOpsMetricReason::Ok,
);
log!(
Topic::CanisterLifecycle,
Ok,
"take_canister_snapshot: {canister_pid} total_size={}",
snapshot.total_size
);
Ok(canister_snapshot_from_infra(snapshot))
}
Err(err) => {
record_unscoped_canister_op(
CanisterOpsMetricOperation::Snapshot,
CanisterOpsMetricOutcome::Failed,
CanisterOpsMetricReason::from_error(&err),
);
Err(err)
}
}
}
pub async fn load_canister_snapshot(
canister_pid: Principal,
snapshot_id: Vec<u8>,
) -> Result<(), InternalError> {
record_unscoped_canister_op(
CanisterOpsMetricOperation::Restore,
CanisterOpsMetricOutcome::Started,
CanisterOpsMetricReason::Ok,
);
match management_call(MgmtInfra::load_canister_snapshot(canister_pid, snapshot_id)).await {
Ok(()) => {
record_unscoped_canister_op(
CanisterOpsMetricOperation::Restore,
CanisterOpsMetricOutcome::Completed,
CanisterOpsMetricReason::Ok,
);
log!(
Topic::CanisterLifecycle,
Ok,
"load_canister_snapshot: {canister_pid}"
);
Ok(())
}
Err(err) => {
record_unscoped_canister_op(
CanisterOpsMetricOperation::Restore,
CanisterOpsMetricOutcome::Failed,
CanisterOpsMetricReason::from_error(&err),
);
Err(err)
}
}
}
#[must_use]
pub fn canister_cycle_balance() -> Cycles {
MgmtInfra::canister_cycle_balance()
}
pub async fn deposit_cycles(
canister_pid: Principal,
cycles: u128,
) -> Result<(), InternalError> {
management_call(MgmtInfra::deposit_cycles(canister_pid, cycles)).await?;
SystemMetrics::increment(SystemMetricKind::DepositCycles);
Ok(())
}
pub async fn get_cycles(canister_pid: Principal) -> Result<Cycles, InternalError> {
let cycles = management_call(MgmtInfra::get_cycles(canister_pid)).await?;
Ok(cycles)
}
pub async fn install_chunked_code<T: ArgumentEncoder>(
mode: CanisterInstallMode,
target_canister: Principal,
store_canister: Principal,
chunk_hashes_list: Vec<Vec<u8>>,
wasm_module_hash: Vec<u8>,
args: T,
) -> Result<(), InternalError> {
let chunk_count = chunk_hashes_list.len();
management_call(MgmtInfra::install_chunked_code(
install_mode_to_infra(mode),
target_canister,
store_canister,
chunk_hashes_list,
wasm_module_hash,
args,
))
.await?;
let metric_kind = match mode {
CanisterInstallMode::Install => SystemMetricKind::InstallCode,
CanisterInstallMode::Reinstall => SystemMetricKind::ReinstallCode,
CanisterInstallMode::Upgrade(_) => SystemMetricKind::UpgradeCode,
};
SystemMetrics::increment(metric_kind);
log!(
Topic::CanisterLifecycle,
Ok,
"install_chunked_code: {target_canister} mode={mode:?} store={store_canister} chunks={chunk_count}"
);
Ok(())
}
pub async fn install_code<T: ArgumentEncoder>(
mode: CanisterInstallMode,
target_canister: Principal,
wasm_module: Vec<u8>,
args: T,
) -> Result<(), InternalError> {
let payload_size_bytes = wasm_module.len();
management_call(MgmtInfra::install_code(
install_mode_to_infra(mode),
target_canister,
wasm_module,
args,
))
.await?;
let metric_kind = match mode {
CanisterInstallMode::Install => SystemMetricKind::InstallCode,
CanisterInstallMode::Reinstall => SystemMetricKind::ReinstallCode,
CanisterInstallMode::Upgrade(_) => SystemMetricKind::UpgradeCode,
};
SystemMetrics::increment(metric_kind);
log!(
Topic::CanisterLifecycle,
Ok,
"install_code: {target_canister} mode={mode:?} embedded_bytes={payload_size_bytes}"
);
Ok(())
}
pub async fn install_chunked_canister_with_payload<P: CandidType>(
mode: CanisterInstallMode,
target_canister: Principal,
store_canister: Principal,
chunk_hashes_list: Vec<Vec<u8>>,
wasm_module_hash: Vec<u8>,
payload: P,
extra_arg: Option<Vec<u8>>,
) -> Result<(), InternalError> {
Self::install_chunked_code(
mode,
target_canister,
store_canister,
chunk_hashes_list,
wasm_module_hash,
(payload, extra_arg),
)
.await
}
pub async fn install_embedded_canister_with_payload<P: CandidType>(
mode: CanisterInstallMode,
target_canister: Principal,
wasm_module: Vec<u8>,
payload: P,
extra_arg: Option<Vec<u8>>,
) -> Result<(), InternalError> {
Self::install_code(mode, target_canister, wasm_module, (payload, extra_arg)).await
}
pub async fn upload_chunk(
canister_pid: Principal,
chunk: Vec<u8>,
) -> Result<Vec<u8>, InternalError> {
let chunk_len = chunk.len();
let hash = management_call(MgmtInfra::upload_chunk(canister_pid, chunk)).await?;
#[expect(clippy::cast_precision_loss)]
let bytes_kb = chunk_len as f64 / 1_000.0;
log!(
Topic::CanisterLifecycle,
Ok,
"upload_chunk: {canister_pid} ({bytes_kb} KB)"
);
Ok(hash)
}
pub async fn stored_chunks(canister_pid: Principal) -> Result<Vec<Vec<u8>>, InternalError> {
management_call(MgmtInfra::stored_chunks(canister_pid)).await
}
pub async fn clear_chunk_store(canister_pid: Principal) -> Result<(), InternalError> {
management_call(MgmtInfra::clear_chunk_store(canister_pid)).await?;
log!(
Topic::CanisterLifecycle,
Ok,
"clear_chunk_store: {canister_pid}"
);
Ok(())
}
pub async fn uninstall_code(canister_pid: Principal) -> Result<(), InternalError> {
management_call(MgmtInfra::uninstall_code(canister_pid)).await?;
SystemMetrics::increment(SystemMetricKind::UninstallCode);
log!(
Topic::CanisterLifecycle,
Ok,
"🗑️ uninstall_code: {canister_pid}"
);
Ok(())
}
pub async fn stop_canister(canister_pid: Principal) -> Result<(), InternalError> {
management_call(MgmtInfra::stop_canister(canister_pid)).await?;
log!(
Topic::CanisterLifecycle,
Ok,
"stop_canister: {canister_pid}"
);
Ok(())
}
pub async fn delete_canister(canister_pid: Principal) -> Result<(), InternalError> {
management_call(MgmtInfra::delete_canister(canister_pid)).await?;
SystemMetrics::increment(SystemMetricKind::DeleteCanister);
Ok(())
}
pub async fn raw_rand() -> Result<[u8; 32], InternalError> {
let seed = management_call(MgmtInfra::raw_rand()).await?;
SystemMetrics::increment(SystemMetricKind::RawRand);
Ok(seed)
}
pub async fn update_settings(args: &UpdateSettingsArgs) -> Result<(), InternalError> {
let infra_args = update_settings_to_infra(args);
management_call(MgmtInfra::update_settings(&infra_args)).await?;
SystemMetrics::increment(SystemMetricKind::UpdateSettings);
Ok(())
}
}
async fn management_call<T>(
fut: impl Future<Output = Result<T, InfraError>>,
) -> Result<T, InternalError> {
record_management_call(
PlatformCallMetricOutcome::Started,
PlatformCallMetricReason::Ok,
);
match fut.await {
Ok(value) => {
record_management_call(
PlatformCallMetricOutcome::Completed,
PlatformCallMetricReason::Ok,
);
Ok(value)
}
Err(err) => {
record_management_call(
PlatformCallMetricOutcome::Failed,
PlatformCallMetricReason::Infra,
);
Err(IcOpsError::from(err).into())
}
}
}
fn record_management_call(outcome: PlatformCallMetricOutcome, reason: PlatformCallMetricReason) {
PlatformCallMetrics::record(
PlatformCallMetricSurface::Management,
PlatformCallMetricMode::Update,
outcome,
reason,
);
}
fn canister_status_from_infra(status: InfraCanisterStatusResult) -> CanisterStatus {
CanisterStatus {
status: status_type_from_infra(status.status),
settings: settings_from_infra(status.settings),
module_hash: status.module_hash,
memory_size: status.memory_size,
memory_metrics: memory_metrics_from_infra(status.memory_metrics),
cycles: status.cycles,
reserved_cycles: status.reserved_cycles,
idle_cycles_burned_per_day: status.idle_cycles_burned_per_day,
query_stats: query_stats_from_infra(status.query_stats),
}
}
fn canister_snapshot_from_infra(snapshot: InfraCanisterSnapshot) -> CanisterSnapshot {
CanisterSnapshot {
id: snapshot.id,
taken_at_timestamp: snapshot.taken_at_timestamp,
total_size: snapshot.total_size,
}
}
fn record_unscoped_canister_op(
operation: CanisterOpsMetricOperation,
outcome: CanisterOpsMetricOutcome,
reason: CanisterOpsMetricReason,
) {
CanisterOpsMetrics::record_unscoped(operation, outcome, reason);
}
const fn status_type_from_infra(status: InfraCanisterStatusType) -> CanisterStatusType {
match status {
InfraCanisterStatusType::Running => CanisterStatusType::Running,
InfraCanisterStatusType::Stopping => CanisterStatusType::Stopping,
InfraCanisterStatusType::Stopped => CanisterStatusType::Stopped,
}
}
fn settings_from_infra(settings: InfraDefiniteCanisterSettings) -> CanisterSettingsSnapshot {
CanisterSettingsSnapshot {
controllers: settings.controllers,
compute_allocation: settings.compute_allocation,
memory_allocation: settings.memory_allocation,
freezing_threshold: settings.freezing_threshold,
reserved_cycles_limit: settings.reserved_cycles_limit,
log_visibility: log_visibility_from_infra(settings.log_visibility),
log_memory_limit: settings.log_memory_limit,
wasm_memory_limit: settings.wasm_memory_limit,
wasm_memory_threshold: settings.wasm_memory_threshold,
environment_variables: settings
.environment_variables
.into_iter()
.map(environment_variable_from_infra)
.collect(),
}
}
fn log_visibility_from_infra(log_visibility: InfraLogVisibility) -> LogVisibility {
match log_visibility {
InfraLogVisibility::Controllers => LogVisibility::Controllers,
InfraLogVisibility::Public => LogVisibility::Public,
InfraLogVisibility::AllowedViewers(viewers) => LogVisibility::AllowedViewers(viewers),
}
}
fn environment_variable_from_infra(variable: InfraEnvironmentVariable) -> EnvironmentVariable {
EnvironmentVariable {
name: variable.name,
value: variable.value,
}
}
fn memory_metrics_from_infra(metrics: InfraMemoryMetrics) -> MemoryMetricsSnapshot {
MemoryMetricsSnapshot {
wasm_memory_size: metrics.wasm_memory_size,
stable_memory_size: metrics.stable_memory_size,
global_memory_size: metrics.global_memory_size,
wasm_binary_size: metrics.wasm_binary_size,
custom_sections_size: metrics.custom_sections_size,
canister_history_size: metrics.canister_history_size,
wasm_chunk_store_size: metrics.wasm_chunk_store_size,
snapshots_size: metrics.snapshots_size,
}
}
fn query_stats_from_infra(stats: InfraQueryStats) -> QueryStatsSnapshot {
QueryStatsSnapshot {
num_calls_total: stats.num_calls_total,
num_instructions_total: stats.num_instructions_total,
request_payload_bytes_total: stats.request_payload_bytes_total,
response_payload_bytes_total: stats.response_payload_bytes_total,
}
}
fn install_mode_to_infra(mode: CanisterInstallMode) -> InfraCanisterInstallMode {
match mode {
CanisterInstallMode::Install => InfraCanisterInstallMode::Install,
CanisterInstallMode::Reinstall => InfraCanisterInstallMode::Reinstall,
CanisterInstallMode::Upgrade(flags) => {
InfraCanisterInstallMode::Upgrade(flags.map(upgrade_flags_to_infra))
}
}
}
const fn upgrade_flags_to_infra(flags: UpgradeFlags) -> InfraUpgradeFlags {
InfraUpgradeFlags {
skip_pre_upgrade: flags.skip_pre_upgrade,
wasm_memory_persistence: None,
}
}
fn settings_to_infra(settings: &CanisterSettings) -> InfraCanisterSettings {
InfraCanisterSettings {
controllers: settings.controllers.clone(),
compute_allocation: settings.compute_allocation.clone(),
memory_allocation: settings.memory_allocation.clone(),
freezing_threshold: settings.freezing_threshold.clone(),
reserved_cycles_limit: settings.reserved_cycles_limit.clone(),
log_visibility: settings.log_visibility.clone().map(log_visibility_to_infra),
log_memory_limit: settings.log_memory_limit.clone(),
wasm_memory_limit: settings.wasm_memory_limit.clone(),
wasm_memory_threshold: settings.wasm_memory_threshold.clone(),
environment_variables: settings.environment_variables.clone().map(|vars| {
vars.into_iter()
.map(environment_variable_to_infra)
.collect()
}),
}
}
fn log_visibility_to_infra(setting: LogVisibility) -> InfraLogVisibility {
match setting {
LogVisibility::Controllers => InfraLogVisibility::Controllers,
LogVisibility::Public => InfraLogVisibility::Public,
LogVisibility::AllowedViewers(viewers) => InfraLogVisibility::AllowedViewers(viewers),
}
}
fn environment_variable_to_infra(variable: EnvironmentVariable) -> InfraEnvironmentVariable {
InfraEnvironmentVariable {
name: variable.name,
value: variable.value,
}
}
fn update_settings_to_infra(args: &UpdateSettingsArgs) -> InfraUpdateSettingsArgs {
InfraUpdateSettingsArgs {
canister_id: args.canister_id,
settings: settings_to_infra(&args.settings),
sender_canister_version: Some(cdk::api::canister_version()),
}
}