diff --git a/src/canister/user_index/can.did b/src/canister/user_index/can.did index 93d4f153..77edc0fa 100644 --- a/src/canister/user_index/can.did +++ b/src/canister/user_index/can.did @@ -81,8 +81,8 @@ type RejectionCode = variant { SysFatal; CanisterReject; }; -type Result = variant { Ok : text; Err : text }; -type Result_1 = variant { Ok : principal; Err : text }; +type Result = variant { Ok : principal; Err : text }; +type Result_1 = variant { Ok : text; Err : text }; type Result_2 = variant { Ok : record { CanisterStatusResponse }; Err : record { RejectionCode; text }; @@ -117,8 +117,11 @@ type UserIndexInitArgs = record { access_control_map : opt vec record { principal; vec UserAccessRole }; }; service : (UserIndexInitArgs) -> { + allot_empty_canister : () -> (Result); are_signups_enabled : () -> (bool) query; - create_pool_of_individual_user_available_canisters : (text, blob) -> (Result); + create_pool_of_individual_user_available_canisters : (text, blob) -> ( + Result_1, + ); get_current_list_of_all_well_known_principal_values : () -> ( vec record { KnownPrincipalType; principal }, ) query; @@ -127,7 +130,7 @@ service : (UserIndexInitArgs) -> { get_last_broadcast_call_status : () -> (BroadcastCallStatus) query; get_list_of_available_canisters : () -> (vec principal) query; get_recycle_status : () -> (RecycleStatus) query; - get_requester_principals_canister_id_create_if_not_exists : () -> (Result_1); + get_requester_principals_canister_id_create_if_not_exists : () -> (Result); get_requester_principals_canister_id_create_if_not_exists_and_optionally_allow_referrer : () -> ( principal, ); @@ -149,15 +152,15 @@ service : (UserIndexInitArgs) -> { opt principal, ) query; http_request : (HttpRequest) -> (HttpResponse) query; - issue_rewards_for_referral : (principal, principal, principal) -> (Result); + issue_rewards_for_referral : (principal, principal, principal) -> (Result_1); make_individual_canister_logs_private : (principal) -> (Result_3); make_individual_canister_logs_public : (principal) -> (Result_3); provision_empty_canisters : (nat64) -> (); recharge_individual_user_canister : () -> (Result_3); reclaim_cycles_from_individual_canisters : () -> (); request_cycles : (nat) -> (Result_3); - reset_user_individual_canisters : (vec principal) -> (Result); - return_cycles_to_platform_orchestrator_canister : () -> (Result); + reset_user_individual_canisters : (vec principal) -> (Result_1); + return_cycles_to_platform_orchestrator_canister : () -> (Result_1); set_permission_to_upgrade_individual_canisters : (bool) -> (text); start_upgrades_for_individual_canisters : (text, blob) -> (text); toggle_signups_enabled : () -> (Result_3); @@ -174,5 +177,7 @@ service : (UserIndexInitArgs) -> { opt principal, opt CanisterInstallMode, ) -> (text); - validate_reset_user_individual_canisters : (vec principal) -> (Result) query; + validate_reset_user_individual_canisters : (vec principal) -> ( + Result_1, + ) query; } diff --git a/src/canister/user_index/src/api/canister_management/allot_empty_canister.rs b/src/canister/user_index/src/api/canister_management/allot_empty_canister.rs new file mode 100644 index 00000000..097bc0c7 --- /dev/null +++ b/src/canister/user_index/src/api/canister_management/allot_empty_canister.rs @@ -0,0 +1,39 @@ +use candid::Principal; +use ic_cdk::caller; +use ic_cdk_macros::update; +use shared_utils::constant::{ + get_backup_individual_user_canister_batch_size, get_backup_individual_user_canister_threshold, +}; + +use crate::{ + util::{ + canister_management::provision_number_of_empty_canisters, + types::individual_user_canister::IndividualUserCanister, + }, + CANISTER_DATA, +}; + +#[update] +fn allot_empty_canister() -> Result { + let registered_individual_canister = IndividualUserCanister::new(caller())?; + let result = registered_individual_canister.allot_empty_canister(); + + let backup_canister_count = + CANISTER_DATA.with_borrow(|canister_data| canister_data.backup_canister_pool.len() as u64); + + if backup_canister_count < get_backup_individual_user_canister_threshold() { + let number_of_canisters = get_backup_individual_user_canister_batch_size(); + let breaking_condition = || { + CANISTER_DATA.with_borrow_mut(|canister_data| { + canister_data.backup_canister_pool.len() as u64 + > get_backup_individual_user_canister_batch_size() + }) + }; + ic_cdk::spawn(provision_number_of_empty_canisters( + number_of_canisters, + breaking_condition, + )); + } + + result +} diff --git a/src/canister/user_index/src/api/canister_management/mod.rs b/src/canister/user_index/src/api/canister_management/mod.rs index dd3cc224..870753ae 100644 --- a/src/canister/user_index/src/api/canister_management/mod.rs +++ b/src/canister/user_index/src/api/canister_management/mod.rs @@ -36,6 +36,7 @@ use crate::{ CANISTER_DATA, }; +pub mod allot_empty_canister; pub mod create_pool_of_available_canisters; pub mod get_last_broadcast_call_status; pub mod get_subnet_available_capacity; diff --git a/src/canister/user_index/src/util/types/individual_user_canister.rs b/src/canister/user_index/src/util/types/individual_user_canister.rs index 8bae1001..d020f790 100644 --- a/src/canister/user_index/src/util/types/individual_user_canister.rs +++ b/src/canister/user_index/src/util/types/individual_user_canister.rs @@ -93,4 +93,17 @@ impl IndividualUserCanister { .await .map_err(|e| e.1) } + + pub fn allot_empty_canister(&self) -> Result { + CANISTER_DATA.with_borrow_mut(|canister_data| { + let Some(new_canister_id) = canister_data.backup_canister_pool.iter().next().copied() + else { + return Err("No Backup Canisters Available".into()); + }; + + canister_data.backup_canister_pool.remove(&new_canister_id); + + Ok(new_canister_id) + }) + } } diff --git a/src/lib/integration_tests/tests/subnet_orchestrator/main.rs b/src/lib/integration_tests/tests/subnet_orchestrator/main.rs index 59022f8d..167ecffe 100644 --- a/src/lib/integration_tests/tests/subnet_orchestrator/main.rs +++ b/src/lib/integration_tests/tests/subnet_orchestrator/main.rs @@ -1,2 +1,3 @@ pub mod provision_new_available_and_backup_canister_on_signups_if_required; pub mod recharge_individual_canister_when_requested; +pub mod test_allot_empty_canisters_to_individual_canister; diff --git a/src/lib/integration_tests/tests/subnet_orchestrator/test_allot_empty_canisters_to_individual_canister.rs b/src/lib/integration_tests/tests/subnet_orchestrator/test_allot_empty_canisters_to_individual_canister.rs new file mode 100644 index 00000000..c8de58dc --- /dev/null +++ b/src/lib/integration_tests/tests/subnet_orchestrator/test_allot_empty_canisters_to_individual_canister.rs @@ -0,0 +1,127 @@ +use candid::Principal; +use pocket_ic::WasmResult; +use shared_utils::common::types::known_principal::KnownPrincipalType; +use test_utils::setup::{ + env::pocket_ic_env::get_new_pocket_ic_env, + test_constants::{get_mock_user_alice_principal_id, get_mock_user_charlie_principal_id}, +}; + +#[test] +fn test_allot_empty_canisters_to_individual_canister() { + let (pocket_ic, known_principal) = get_new_pocket_ic_env(); + let platform_canister_id = known_principal + .get(&KnownPrincipalType::CanisterIdPlatformOrchestrator) + .cloned() + .unwrap(); + + let super_admin = known_principal + .get(&KnownPrincipalType::UserIdGlobalSuperAdmin) + .cloned() + .unwrap(); + + let application_subnets = pocket_ic.topology().get_app_subnets(); + + let charlie_global_admin = get_mock_user_charlie_principal_id(); + + pocket_ic + .update_call( + platform_canister_id, + super_admin, + "add_principal_as_global_admin", + candid::encode_one(charlie_global_admin).unwrap(), + ) + .unwrap(); + + let subnet_orchestrator_canister_id: Principal = pocket_ic + .update_call( + platform_canister_id, + charlie_global_admin, + "provision_subnet_orchestrator_canister", + candid::encode_one(application_subnets[1]).unwrap(), + ) + .map(|res| { + let canister_id_result: Result = match res { + WasmResult::Reply(payload) => candid::decode_one(&payload).unwrap(), + _ => panic!("Canister call failed"), + }; + canister_id_result.unwrap() + }) + .unwrap(); + + for i in 0..120 { + pocket_ic.tick(); + } + + let alice_yral_principal_id = get_mock_user_alice_principal_id(); + let alice_yral_canister_id = pocket_ic + .update_call( + subnet_orchestrator_canister_id, + alice_yral_principal_id, + "get_requester_principals_canister_id_create_if_not_exists", + candid::encode_one(()).unwrap(), + ) + .map(|res| { + let canister_id: Result = match res { + WasmResult::Reply(payload) => candid::decode_one(&payload).unwrap(), + _ => panic!("Canister call failed"), + }; + canister_id + }) + .unwrap() + .unwrap(); + + let mut intial_subnet_backup_capacity = pocket_ic + .query_call( + subnet_orchestrator_canister_id, + super_admin, + "get_subnet_backup_capacity", + candid::encode_one(()).unwrap(), + ) + .map(|reply_payload| { + let subnet_capacity: u64 = match reply_payload { + WasmResult::Reply(payload) => candid::decode_one(&payload).unwrap(), + _ => panic!("\nšŸ›‘ get_subnet_backup_capacity failed\n"), + }; + subnet_capacity + }) + .unwrap(); + + for _ in 0..10 { + pocket_ic + .update_call( + subnet_orchestrator_canister_id, + alice_yral_canister_id, + "allot_empty_canister", + candid::encode_one(()).unwrap(), + ) + .map(|res| { + let canister_id: Result = match res { + WasmResult::Reply(payload) => candid::decode_one(&payload).unwrap(), + _ => panic!("Canister call failed"), + }; + canister_id + }) + .unwrap() + .unwrap(); + + intial_subnet_backup_capacity -= 1; + } + + let final_backup_capacity = pocket_ic + .query_call( + subnet_orchestrator_canister_id, + super_admin, + "get_subnet_backup_capacity", + candid::encode_one(()).unwrap(), + ) + .map(|reply_payload| { + let subnet_capacity: u64 = match reply_payload { + WasmResult::Reply(payload) => candid::decode_one(&payload).unwrap(), + _ => panic!("\nšŸ›‘ get_subnet_backup_capacity failed\n"), + }; + subnet_capacity + }) + .unwrap(); + + assert!(final_backup_capacity > intial_subnet_backup_capacity); +}