use super::*;
use crate::dto::canister::{
CanisterHistoryResponse, CanisterInspectionOutcome, CanisterInspectionReserveResponse,
};
impl MgmtOps {
pub fn canister_inspection_reserve(
canister_pid: Principal,
) -> Result<CanisterInspectionReserveResponse, InternalError> {
let required_liquid_cycles =
MgmtInfra::canister_status_call_cost(canister_pid).map_err(OpsError::from)?;
Ok(CanisterInspectionReserveResponse {
caller: crate::ops::ic::IcOps::canister_self(),
canister_id: canister_pid,
native_cycles: ic_cdk::api::canister_cycle_balance(),
available_liquid_cycles: ic_cdk::api::canister_liquid_cycle_balance(),
required_liquid_cycles,
})
}
pub async fn canister_history(
canister_pid: Principal,
) -> Result<CanisterHistoryResponse, InternalError> {
let response = management_call(
ManagementCallMetricOperation::CanisterInfo,
MgmtInfra::canister_history(canister_pid),
)
.await?;
Ok(CanisterHistoryResponse {
canister_id: canister_pid,
history_candid: response.into_bytes(),
})
}
pub async fn canister_inspection(
canister_pid: Principal,
) -> Result<CanisterInspectionOutcome, InternalError> {
let native_cycles = crate::ops::ic::IcOps::canister_cycle_balance().to_u128();
match management_call_infra(
ManagementCallMetricOperation::CanisterStatus,
MgmtInfra::canister_status(canister_pid),
)
.await
{
Ok(status) => {
SystemMetrics::increment(SystemMetricKind::CanisterStatus);
Ok(CanisterInspectionOutcome::Status(Box::new(
Self::canister_status_to_dto(canister_status_from_infra(status)),
)))
}
Err(error) => inspection_failure(
canister_pid,
crate::ops::ic::IcOps::canister_self(),
native_cycles,
error,
),
}
}
pub async fn canister_history_total_changes(
canister_pid: Principal,
) -> Result<u64, InternalError> {
management_call(
ManagementCallMetricOperation::CanisterInfo,
MgmtInfra::canister_history_total_changes(canister_pid),
)
.await
}
#[must_use]
pub fn canister_status_to_dto(status: CanisterStatus) -> CanisterStatusResponse {
CanisterStatusResponse {
status: status.status,
settings: settings_to_dto(status.settings),
module_hash: status.module_hash,
memory_size: status.memory_size,
memory_metrics: memory_metrics_to_dto(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_to_dto(status.query_stats),
}
}
pub async fn canister_status(canister_pid: Principal) -> Result<CanisterStatus, InternalError> {
let status = management_call(
ManagementCallMetricOperation::CanisterStatus,
MgmtInfra::canister_status(canister_pid),
)
.await?;
SystemMetrics::increment(SystemMetricKind::CanisterStatus);
Ok(canister_status_from_infra(status))
}
pub async fn observe_canister_status(
canister_pid: Principal,
) -> Result<CanisterStatusObservation, InternalError> {
match management_call_infra(
ManagementCallMetricOperation::CanisterStatus,
MgmtInfra::canister_status(canister_pid),
)
.await
{
Ok(status) => {
SystemMetrics::increment(SystemMetricKind::CanisterStatus);
Ok(CanisterStatusObservation::Present(Box::new(
canister_status_from_infra(status),
)))
}
Err(error) if error.is_canister_not_found() => {
SystemMetrics::increment(SystemMetricKind::CanisterStatus);
Ok(CanisterStatusObservation::Absent)
}
Err(error) => Err(OpsError::from(error).into()),
}
}
pub async fn update_settings(args: &UpdateSettingsArgs) -> Result<(), InternalError> {
let infra_args = update_settings_to_infra(args);
management_call(
ManagementCallMetricOperation::UpdateSettings,
MgmtInfra::update_settings(&infra_args),
)
.await?;
SystemMetrics::increment(SystemMetricKind::UpdateSettings);
Ok(())
}
}
fn settings_to_dto(settings: CanisterSettingsSnapshot) -> CanisterSettingsDto {
CanisterSettingsDto {
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: 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_to_dto)
.collect(),
}
}
fn environment_variable_to_dto(variable: EnvironmentVariable) -> EnvironmentVariableDto {
EnvironmentVariableDto {
name: variable.name,
value: variable.value,
}
}
fn memory_metrics_to_dto(metrics: MemoryMetricsSnapshot) -> MemoryMetrics {
MemoryMetrics {
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_to_dto(stats: QueryStatsSnapshot) -> QueryStats {
QueryStats {
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 inspection_failure(
canister_id: Principal,
caller: Principal,
native_cycles: u128,
error: IcInfraError,
) -> Result<CanisterInspectionOutcome, InternalError> {
match error {
IcInfraError::CallFailed(ic_cdk::call::CallFailed::InsufficientLiquidCycleBalance(
error,
)) => Ok(CanisterInspectionOutcome::ReserveRequired(
CanisterInspectionReserveResponse {
caller,
canister_id,
native_cycles,
available_liquid_cycles: error.available,
required_liquid_cycles: error.required,
},
)),
error => Err(OpsError::from(error).into()),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::diagnostics::codes;
use ic_cdk::call::{CallFailed, CallPerformFailed, InsufficientLiquidCycleBalance};
#[test]
fn protected_inspection_retains_exact_reserve_numbers_and_target() {
let caller = Principal::from_slice(&[1]);
let target = Principal::from_slice(&[2]);
let response = inspection_failure(
target,
caller,
1000,
CallFailed::InsufficientLiquidCycleBalance(InsufficientLiquidCycleBalance {
available: 50,
required: 100,
})
.into(),
)
.unwrap();
let bytes = candid::encode_one(response).unwrap();
let decoded: CanisterInspectionOutcome = candid::decode_one(&bytes).unwrap();
let CanisterInspectionOutcome::ReserveRequired(evidence) = decoded else {
panic!("expected protected reserve evidence");
};
assert_eq!(
evidence,
CanisterInspectionReserveResponse {
caller,
canister_id: target,
native_cycles: 1000,
available_liquid_cycles: 50,
required_liquid_cycles: 100,
}
);
let error = inspection_failure(
target,
caller,
1000,
CallFailed::CallPerformFailed(CallPerformFailed).into(),
)
.err()
.unwrap();
assert_eq!(error.public_code(), Some(codes::PLATFORM_UNAVAILABLE));
}
}