diff --git a/src/declarations/mission_control/mission_control.did.d.ts b/src/declarations/mission_control/mission_control.did.d.ts index 588108d405..85ece06682 100644 --- a/src/declarations/mission_control/mission_control.did.d.ts +++ b/src/declarations/mission_control/mission_control.did.d.ts @@ -52,6 +52,7 @@ export interface CyclesMonitoringStartConfig { orbiters_strategy: [] | [SegmentsMonitoringStrategy]; mission_control_strategy: [] | [CyclesMonitoringStrategy]; satellites_strategy: [] | [SegmentsMonitoringStrategy]; + ufos_strategy: [] | [SegmentsMonitoringStrategy]; } export interface CyclesMonitoringStatus { monitored_ids: Array; @@ -61,6 +62,7 @@ export interface CyclesMonitoringStopConfig { satellite_ids: [] | [Array]; try_mission_control: [] | [boolean]; orbiter_ids: [] | [Array]; + ufo_ids: [] | [Array]; } export type CyclesMonitoringStrategy = { BelowThreshold: CyclesThreshold }; export interface CyclesThreshold { @@ -200,6 +202,13 @@ export type TransferError_1 = | { CreatedInFuture: { ledger_time: bigint } } | { TooOld: null } | { InsufficientFunds: { balance: bigint } }; +export interface Ufo { + updated_at: bigint; + metadata: Array<[string, string]>; + created_at: bigint; + settings: [] | [Settings]; + ufo_id: Principal; +} export interface User { updated_at: bigint; metadata: Array<[string, string]>; @@ -233,6 +242,7 @@ export interface _SERVICE { list_mission_control_controllers: ActorMethod<[], Array<[Principal, AccessKey]>>; list_orbiters: ActorMethod<[], Array<[Principal, Orbiter]>>; list_satellites: ActorMethod<[], Array<[Principal, Satellite]>>; + list_ufos: ActorMethod<[], Array<[Principal, Ufo]>>; set_config: ActorMethod<[[] | [Config]], undefined>; set_metadata: ActorMethod<[Array<[string, string]>], undefined>; set_mission_control_controllers: ActorMethod<[Array, SetAccessKey], undefined>; @@ -248,11 +258,14 @@ export interface _SERVICE { [Array, Array, SetAccessKey], undefined >; + set_ufo: ActorMethod<[Principal, [] | [string]], Ufo>; + set_ufo_metadata: ActorMethod<[Principal, Array<[string, string]>], Ufo>; start_monitoring: ActorMethod<[], undefined>; stop_monitoring: ActorMethod<[], undefined>; top_up: ActorMethod<[Principal, Tokens], undefined>; unset_orbiter: ActorMethod<[Principal], undefined>; unset_satellite: ActorMethod<[Principal], undefined>; + unset_ufo: ActorMethod<[Principal], undefined>; update_and_start_monitoring: ActorMethod<[MonitoringStartConfig], undefined>; update_and_stop_monitoring: ActorMethod<[MonitoringStopConfig], undefined>; } diff --git a/src/declarations/mission_control/mission_control.factory.certified.did.js b/src/declarations/mission_control/mission_control.factory.certified.did.js index 5959ee89fb..ab08d403d3 100644 --- a/src/declarations/mission_control/mission_control.factory.certified.did.js +++ b/src/declarations/mission_control/mission_control.factory.certified.did.js @@ -180,6 +180,13 @@ export const idlFactory = ({ IDL }) => { scope: AccessKeyScope, expires_at: IDL.Opt(IDL.Nat64) }); + const Ufo = IDL.Record({ + updated_at: IDL.Nat64, + metadata: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), + created_at: IDL.Nat64, + settings: IDL.Opt(Settings), + ufo_id: IDL.Principal + }); const SetAccessKey = IDL.Record({ metadata: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), kind: IDL.Opt(AccessKeyKind), @@ -193,7 +200,8 @@ export const idlFactory = ({ IDL }) => { const CyclesMonitoringStartConfig = IDL.Record({ orbiters_strategy: IDL.Opt(SegmentsMonitoringStrategy), mission_control_strategy: IDL.Opt(CyclesMonitoringStrategy), - satellites_strategy: IDL.Opt(SegmentsMonitoringStrategy) + satellites_strategy: IDL.Opt(SegmentsMonitoringStrategy), + ufos_strategy: IDL.Opt(SegmentsMonitoringStrategy) }); const MonitoringStartConfig = IDL.Record({ cycles_config: IDL.Opt(CyclesMonitoringStartConfig) @@ -201,7 +209,8 @@ export const idlFactory = ({ IDL }) => { const CyclesMonitoringStopConfig = IDL.Record({ satellite_ids: IDL.Opt(IDL.Vec(IDL.Principal)), try_mission_control: IDL.Opt(IDL.Bool), - orbiter_ids: IDL.Opt(IDL.Vec(IDL.Principal)) + orbiter_ids: IDL.Opt(IDL.Vec(IDL.Principal)), + ufo_ids: IDL.Opt(IDL.Vec(IDL.Principal)) }); const MonitoringStopConfig = IDL.Record({ cycles_config: IDL.Opt(CyclesMonitoringStopConfig) @@ -238,6 +247,7 @@ export const idlFactory = ({ IDL }) => { ), list_orbiters: IDL.Func([], [IDL.Vec(IDL.Tuple(IDL.Principal, Orbiter))], []), list_satellites: IDL.Func([], [IDL.Vec(IDL.Tuple(IDL.Principal, Satellite))], []), + list_ufos: IDL.Func([], [IDL.Vec(IDL.Tuple(IDL.Principal, Ufo))], []), set_config: IDL.Func([IDL.Opt(Config)], [], []), set_metadata: IDL.Func([IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text))], [], []), set_mission_control_controllers: IDL.Func([IDL.Vec(IDL.Principal), SetAccessKey], [], []), @@ -263,11 +273,14 @@ export const idlFactory = ({ IDL }) => { [], [] ), + set_ufo: IDL.Func([IDL.Principal, IDL.Opt(IDL.Text)], [Ufo], []), + set_ufo_metadata: IDL.Func([IDL.Principal, IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text))], [Ufo], []), start_monitoring: IDL.Func([], [], []), stop_monitoring: IDL.Func([], [], []), top_up: IDL.Func([IDL.Principal, Tokens], [], []), unset_orbiter: IDL.Func([IDL.Principal], [], []), unset_satellite: IDL.Func([IDL.Principal], [], []), + unset_ufo: IDL.Func([IDL.Principal], [], []), update_and_start_monitoring: IDL.Func([MonitoringStartConfig], [], []), update_and_stop_monitoring: IDL.Func([MonitoringStopConfig], [], []) }); diff --git a/src/declarations/mission_control/mission_control.factory.did.js b/src/declarations/mission_control/mission_control.factory.did.js index 065e439c53..a2e0a9668e 100644 --- a/src/declarations/mission_control/mission_control.factory.did.js +++ b/src/declarations/mission_control/mission_control.factory.did.js @@ -180,6 +180,13 @@ export const idlFactory = ({ IDL }) => { scope: AccessKeyScope, expires_at: IDL.Opt(IDL.Nat64) }); + const Ufo = IDL.Record({ + updated_at: IDL.Nat64, + metadata: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), + created_at: IDL.Nat64, + settings: IDL.Opt(Settings), + ufo_id: IDL.Principal + }); const SetAccessKey = IDL.Record({ metadata: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), kind: IDL.Opt(AccessKeyKind), @@ -193,7 +200,8 @@ export const idlFactory = ({ IDL }) => { const CyclesMonitoringStartConfig = IDL.Record({ orbiters_strategy: IDL.Opt(SegmentsMonitoringStrategy), mission_control_strategy: IDL.Opt(CyclesMonitoringStrategy), - satellites_strategy: IDL.Opt(SegmentsMonitoringStrategy) + satellites_strategy: IDL.Opt(SegmentsMonitoringStrategy), + ufos_strategy: IDL.Opt(SegmentsMonitoringStrategy) }); const MonitoringStartConfig = IDL.Record({ cycles_config: IDL.Opt(CyclesMonitoringStartConfig) @@ -201,7 +209,8 @@ export const idlFactory = ({ IDL }) => { const CyclesMonitoringStopConfig = IDL.Record({ satellite_ids: IDL.Opt(IDL.Vec(IDL.Principal)), try_mission_control: IDL.Opt(IDL.Bool), - orbiter_ids: IDL.Opt(IDL.Vec(IDL.Principal)) + orbiter_ids: IDL.Opt(IDL.Vec(IDL.Principal)), + ufo_ids: IDL.Opt(IDL.Vec(IDL.Principal)) }); const MonitoringStopConfig = IDL.Record({ cycles_config: IDL.Opt(CyclesMonitoringStopConfig) @@ -238,6 +247,7 @@ export const idlFactory = ({ IDL }) => { ), list_orbiters: IDL.Func([], [IDL.Vec(IDL.Tuple(IDL.Principal, Orbiter))], ['query']), list_satellites: IDL.Func([], [IDL.Vec(IDL.Tuple(IDL.Principal, Satellite))], ['query']), + list_ufos: IDL.Func([], [IDL.Vec(IDL.Tuple(IDL.Principal, Ufo))], ['query']), set_config: IDL.Func([IDL.Opt(Config)], [], []), set_metadata: IDL.Func([IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text))], [], []), set_mission_control_controllers: IDL.Func([IDL.Vec(IDL.Principal), SetAccessKey], [], []), @@ -263,11 +273,14 @@ export const idlFactory = ({ IDL }) => { [], [] ), + set_ufo: IDL.Func([IDL.Principal, IDL.Opt(IDL.Text)], [Ufo], []), + set_ufo_metadata: IDL.Func([IDL.Principal, IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text))], [Ufo], []), start_monitoring: IDL.Func([], [], []), stop_monitoring: IDL.Func([], [], []), top_up: IDL.Func([IDL.Principal, Tokens], [], []), unset_orbiter: IDL.Func([IDL.Principal], [], []), unset_satellite: IDL.Func([IDL.Principal], [], []), + unset_ufo: IDL.Func([IDL.Principal], [], []), update_and_start_monitoring: IDL.Func([MonitoringStartConfig], [], []), update_and_stop_monitoring: IDL.Func([MonitoringStopConfig], [], []) }); diff --git a/src/frontend/src/lib/services/factory/factory.create.services.ts b/src/frontend/src/lib/services/factory/factory.create.services.ts index 67e382e566..88d4af1138 100644 --- a/src/frontend/src/lib/services/factory/factory.create.services.ts +++ b/src/frontend/src/lib/services/factory/factory.create.services.ts @@ -404,7 +404,8 @@ export const createSatelliteWizard = async ({ strategy: monitoringStrategy, ids: [canisterId] }), - orbiters_strategy: toNullable() + orbiters_strategy: toNullable(), + ufos_strategy: toNullable() }) } }); @@ -536,7 +537,8 @@ export const createOrbiterWizard = async ({ orbiters_strategy: toNullable({ strategy: monitoringStrategy, ids: [canisterId] - }) + }), + ufos_strategy: toNullable() }) } }); diff --git a/src/frontend/src/lib/services/mission-control/monitoring.services.ts b/src/frontend/src/lib/services/mission-control/monitoring.services.ts index da933642e8..d3a0eeb7b3 100644 --- a/src/frontend/src/lib/services/mission-control/monitoring.services.ts +++ b/src/frontend/src/lib/services/mission-control/monitoring.services.ts @@ -283,7 +283,9 @@ const setMonitoringCyclesStrategy = async ({ strategy: moduleStrategy } ] - : [] + : [], + // TODO: support for ufo + ufos_strategy: toNullable() }) } }); @@ -398,7 +400,9 @@ const stopMonitoringCycles = async ({ cycles_config: toNullable({ try_mission_control: toNullable(stopMissionControl), satellite_ids: satellites.length > 0 ? toNullable(satellites) : [], - orbiter_ids: orbiters.length > 0 ? toNullable(orbiters) : [] + orbiter_ids: orbiters.length > 0 ? toNullable(orbiters) : [], + // TODO: support for ufo + ufo_ids: toNullable() }) } }); diff --git a/src/libs/shared/src/types.rs b/src/libs/shared/src/types.rs index b5ad9983a1..7adfe6540d 100644 --- a/src/libs/shared/src/types.rs +++ b/src/libs/shared/src/types.rs @@ -15,6 +15,7 @@ pub mod state { pub type MissionControlId = SegmentId; pub type SatelliteId = SegmentId; pub type OrbiterId = SegmentId; + pub type UfoId = SegmentId; pub type Metadata = HashMap; diff --git a/src/mission_control/mission_control.did b/src/mission_control/mission_control.did index b2b8767c9a..4f9e0e91cd 100644 --- a/src/mission_control/mission_control.did +++ b/src/mission_control/mission_control.did @@ -32,6 +32,7 @@ type CyclesMonitoringStartConfig = record { orbiters_strategy : opt SegmentsMonitoringStrategy; mission_control_strategy : opt CyclesMonitoringStrategy; satellites_strategy : opt SegmentsMonitoringStrategy; + ufos_strategy : opt SegmentsMonitoringStrategy; }; type CyclesMonitoringStatus = record { monitored_ids : vec principal; @@ -41,6 +42,7 @@ type CyclesMonitoringStopConfig = record { satellite_ids : opt vec principal; try_mission_control : opt bool; orbiter_ids : opt vec principal; + ufo_ids : opt vec principal; }; type CyclesMonitoringStrategy = variant { BelowThreshold : CyclesThreshold }; type CyclesThreshold = record { fund_cycles : nat; min_cycles : nat }; @@ -155,6 +157,13 @@ type TransferError_1 = variant { TooOld; InsufficientFunds : record { balance : nat }; }; +type Ufo = record { + updated_at : nat64; + metadata : vec record { text; text }; + created_at : nat64; + settings : opt Settings; + ufo_id : principal; +}; type User = record { updated_at : nat64; metadata : vec record { text; text }; @@ -189,6 +198,7 @@ service : (InitMissionControlArgs) -> { ) query; list_orbiters : () -> (vec record { principal; Orbiter }) query; list_satellites : () -> (vec record { principal; Satellite }) query; + list_ufos : () -> (vec record { principal; Ufo }) query; set_config : (opt Config) -> (); set_metadata : (vec record { text; text }) -> (); set_mission_control_controllers : (vec principal, SetAccessKey) -> (); @@ -204,11 +214,14 @@ service : (InitMissionControlArgs) -> { vec principal, SetAccessKey, ) -> (); + set_ufo : (principal, opt text) -> (Ufo); + set_ufo_metadata : (principal, vec record { text; text }) -> (Ufo); start_monitoring : () -> (); stop_monitoring : () -> (); top_up : (principal, Tokens) -> (); unset_orbiter : (principal) -> (); unset_satellite : (principal) -> (); + unset_ufo : (principal) -> (); update_and_start_monitoring : (MonitoringStartConfig) -> (); update_and_stop_monitoring : (MonitoringStopConfig) -> (); } diff --git a/src/mission_control/src/api/mod.rs b/src/mission_control/src/api/mod.rs index f8e40c85a7..2005a271dc 100644 --- a/src/mission_control/src/api/mod.rs +++ b/src/mission_control/src/api/mod.rs @@ -4,4 +4,5 @@ mod mgmt; mod monitoring; mod orbiters; mod satellites; +mod ufos; mod wallet; diff --git a/src/mission_control/src/api/ufos.rs b/src/mission_control/src/api/ufos.rs new file mode 100644 index 0000000000..ca42c4d1f0 --- /dev/null +++ b/src/mission_control/src/api/ufos.rs @@ -0,0 +1,27 @@ +use crate::factory::store::{get_ufos, set_ufo_metadata as set_ufo_metadata_store}; +use crate::factory::ufo::{attach_ufo, detach_ufo}; +use crate::guards::caller_is_user_or_admin_controller; +use crate::types::state::{Ufo, Ufos}; +use ic_cdk_macros::{query, update}; +use junobuild_shared::ic::UnwrapOrTrap; +use junobuild_shared::types::state::{Metadata, UfoId}; + +#[query(guard = "caller_is_user_or_admin_controller")] +fn list_ufos() -> Ufos { + get_ufos() +} + +#[update(guard = "caller_is_user_or_admin_controller")] +fn set_ufo(ufo_id: UfoId, name: Option) -> Ufo { + attach_ufo(&ufo_id, &name).unwrap_or_trap() +} + +#[update(guard = "caller_is_user_or_admin_controller")] +fn unset_ufo(ufo_id: UfoId) { + detach_ufo(&ufo_id).unwrap_or_trap() +} + +#[update(guard = "caller_is_user_or_admin_controller")] +fn set_ufo_metadata(ufo_id: UfoId, metadata: Metadata) -> Ufo { + set_ufo_metadata_store(&ufo_id, &metadata).unwrap_or_trap() +} diff --git a/src/mission_control/src/factory/mod.rs b/src/mission_control/src/factory/mod.rs index 27921e5cfc..04c26f9214 100644 --- a/src/mission_control/src/factory/mod.rs +++ b/src/mission_control/src/factory/mod.rs @@ -3,3 +3,4 @@ mod msg; pub mod orbiter; pub mod satellite; pub mod store; +pub mod ufo; diff --git a/src/mission_control/src/factory/msg.rs b/src/mission_control/src/factory/msg.rs index 9f0a405ae4..34f5c9644d 100644 --- a/src/mission_control/src/factory/msg.rs +++ b/src/mission_control/src/factory/msg.rs @@ -1,2 +1,3 @@ pub const ORBITER_NOT_FOUND: &str = "Orbiter not found or not owned by this mission control."; pub const SATELLITE_NOT_FOUND: &str = "Satellite not found or not owned by this mission control."; +pub const UFO_NOT_FOUND: &str = "UFO not found or not owned by this mission control."; diff --git a/src/mission_control/src/factory/store.rs b/src/mission_control/src/factory/store.rs index 03df98202f..7765f0f39c 100644 --- a/src/mission_control/src/factory/store.rs +++ b/src/mission_control/src/factory/store.rs @@ -1,7 +1,7 @@ use crate::memory::manager::STATE; use crate::types::core::Segment; -use crate::types::state::{Orbiter, Orbiters, Satellite, Satellites}; -use junobuild_shared::types::state::{Metadata, OrbiterId, SatelliteId}; +use crate::types::state::{Orbiter, Orbiters, Satellite, Satellites, Ufo, Ufos}; +use junobuild_shared::types::state::{Metadata, OrbiterId, SatelliteId, UfoId}; use std::collections::HashMap; use std::hash::Hash; @@ -79,6 +79,66 @@ pub fn set_orbiter_metadata( }) } +// --------------------------------------------------------- +// UFOs +// --------------------------------------------------------- + +pub fn get_ufos() -> Ufos { + STATE.with(|state| state.borrow().heap.ufos.clone().unwrap_or_default()) +} + +pub fn get_ufo(ufo_id: &UfoId) -> Option { + STATE.with(|state| { + state + .borrow() + .heap + .ufos + .as_ref() + .and_then(|ufos| get_segment_impl(ufo_id, ufos)) + }) +} + +pub fn delete_ufo(ufo_id: &UfoId) -> Option { + STATE.with(|state| { + delete_segment_impl( + ufo_id, + state + .borrow_mut() + .heap + .ufos + .get_or_insert_with(HashMap::new), + ) + }) +} + +pub fn add_ufo(ufo_id: &UfoId, name: &Option) -> Ufo { + STATE.with(|state| { + add_segment_impl( + ufo_id, + &Ufo::from(ufo_id, name), + state + .borrow_mut() + .heap + .ufos + .get_or_insert_with(HashMap::new), + ) + }) +} + +pub fn set_ufo_metadata(ufo_id: &UfoId, metadata: &Metadata) -> Result { + STATE.with(|state| { + set_metadata_impl( + ufo_id, + metadata, + state + .borrow_mut() + .heap + .ufos + .get_or_insert_with(HashMap::new), + ) + }) +} + // --------------------------------------------------------- // Segments // --------------------------------------------------------- diff --git a/src/mission_control/src/factory/ufo.rs b/src/mission_control/src/factory/ufo.rs new file mode 100644 index 0000000000..9402636f78 --- /dev/null +++ b/src/mission_control/src/factory/ufo.rs @@ -0,0 +1,32 @@ +use crate::factory::msg::UFO_NOT_FOUND; +use crate::factory::store::{add_ufo, delete_ufo, get_ufo}; +use crate::types::state::Ufo; +use junobuild_shared::types::state::UfoId; + +pub fn attach_ufo(ufo_id: &UfoId, name: &Option) -> Result { + let ufo = get_ufo(ufo_id); + + match ufo { + Some(_) => Err("UFO already added to mission control.".to_string()), + None => { + // No assertion for Ufo + + let ufo = add_ufo(ufo_id, name); + + Ok(ufo) + } + } +} + +pub fn detach_ufo(ufo_id: &UfoId) -> Result<(), String> { + let ufo = get_ufo(ufo_id); + + match ufo { + None => Err(UFO_NOT_FOUND.to_string()), + Some(_ufo) => { + delete_ufo(ufo_id); + + Ok(()) + } + } +} diff --git a/src/mission_control/src/impls.rs b/src/mission_control/src/impls.rs index 065bd7ff22..11f4a116eb 100644 --- a/src/mission_control/src/impls.rs +++ b/src/mission_control/src/impls.rs @@ -5,7 +5,7 @@ use crate::types::state::CyclesMonitoringStrategy::BelowThreshold; use crate::types::state::{ Config, CyclesMonitoring, CyclesMonitoringStrategy, HeapState, MissionControlSettings, Monitoring, MonitoringHistory, MonitoringHistoryKey, Orbiter, Orbiters, Satellite, Settings, - State, User, + State, Ufo, User, }; use canfund::manager::options::{CyclesThreshold, FundStrategy}; use ic_cdk::api::time; @@ -14,7 +14,7 @@ use ic_stable_structures::Storable; use junobuild_shared::memory::serializers::{ deserialize_from_bytes, serialize_into_bytes, serialize_to_bytes, }; -use junobuild_shared::types::state::{Metadata, OrbiterId, SatelliteId, UserId}; +use junobuild_shared::types::state::{Metadata, OrbiterId, SatelliteId, UfoId, UserId}; use std::borrow::Cow; use std::collections::HashMap; @@ -35,6 +35,7 @@ impl From<&UserId> for HeapState { controllers: HashMap::new(), orbiters: Orbiters::new(), settings: None, + ufos: None, } } } @@ -166,6 +167,59 @@ impl Orbiter { } } +impl Ufo { + pub fn from(ufo_id: &UfoId, name: &Option) -> Self { + let now = time(); + + Ufo { + ufo_id: *ufo_id, + metadata: init_metadata(name), + settings: None, + created_at: now, + updated_at: now, + } + } + + pub fn clone_with_settings(&self, settings: &Settings) -> Self { + let now = time(); + + Ufo { + settings: Some(settings.clone()), + updated_at: now, + ..self.clone() + } + } + + pub fn toggle_cycles_monitoring(&self, enabled: bool) -> Result { + let settings = self + .settings + .clone() + .ok_or_else(|| "Settings not found.".to_string())?; + + let monitoring = settings + .monitoring + .clone() + .ok_or_else(|| "Monitoring configuration not found.".to_string())?; + + let cycles = monitoring + .cycles + .clone() + .ok_or_else(|| "Cycles monitoring configuration not found.".to_string())?; + + let now = time(); + + Ok(Ufo { + settings: Some(Settings { + monitoring: Some(Monitoring { + cycles: Some(CyclesMonitoring { enabled, ..cycles }), + }), + }), + updated_at: now, + ..self.clone() + }) + } +} + impl Settings { pub fn from(strategy: &CyclesMonitoringStrategy) -> Self { Settings { @@ -215,6 +269,18 @@ impl Segment for Orbiter { } } +impl Segment for Ufo { + fn set_metadata(&self, metadata: &Metadata) -> Self { + let now = time(); + + Ufo { + metadata: metadata.clone(), + updated_at: now, + ..self.clone() + } + } +} + impl CyclesMonitoringStrategy { pub fn to_fund_strategy(&self) -> Result { #[allow(unreachable_patterns)] diff --git a/src/mission_control/src/lib.rs b/src/mission_control/src/lib.rs index 308be995cc..a1559908bc 100644 --- a/src/mission_control/src/lib.rs +++ b/src/mission_control/src/lib.rs @@ -26,6 +26,8 @@ use crate::types::state::Orbiter; use crate::types::state::Orbiters; use crate::types::state::Satellite; use crate::types::state::Satellites; +use crate::types::state::Ufo; +use crate::types::state::Ufos; use crate::types::state::User; use candid::Principal; use ic_cdk_macros::export_candid; @@ -39,6 +41,7 @@ use junobuild_shared::types::interface::InitMissionControlArgs; use junobuild_shared::types::interface::SetAccessKey; use junobuild_shared::types::state::Metadata; use junobuild_shared::types::state::SatelliteId; +use junobuild_shared::types::state::UfoId; use junobuild_shared::types::state::UserId; use junobuild_shared::types::state::{AccessKeyId, AccessKeys, OrbiterId}; diff --git a/src/mission_control/src/monitoring/cycles/config.rs b/src/mission_control/src/monitoring/cycles/config.rs index b08d3587b5..70f2083027 100644 --- a/src/mission_control/src/monitoring/cycles/config.rs +++ b/src/mission_control/src/monitoring/cycles/config.rs @@ -6,8 +6,8 @@ use crate::monitoring::cycles::unregister::{ unregister_mission_control_monitoring, unregister_modules_monitoring, }; use crate::monitoring::store::heap::{ - disable_orbiter_monitoring, disable_satellite_monitoring, set_orbiter_strategy, - set_satellite_strategy, + disable_orbiter_monitoring, disable_satellite_monitoring, disable_ufo_monitoring, + set_orbiter_strategy, set_satellite_strategy, set_ufo_strategy, }; use crate::types::interface::{CyclesMonitoringStartConfig, CyclesMonitoringStopConfig}; @@ -26,6 +26,10 @@ pub fn register_and_start_cycles_monitoring( register_modules_monitoring(strategy, set_orbiter_strategy)?; } + if let Some(strategy) = &config.ufos_strategy { + register_modules_monitoring(strategy, set_ufo_strategy)?; + } + start_scheduler(); Ok(()) @@ -42,6 +46,10 @@ pub fn unregister_and_stop_cycles_monitoring( unregister_modules_monitoring(orbiter_ids, disable_orbiter_monitoring)?; } + if let Some(ufo_ids) = &config.ufo_ids { + unregister_modules_monitoring(ufo_ids, disable_ufo_monitoring)?; + } + if let Some(try_mission_control) = config.try_mission_control { if try_mission_control { unregister_mission_control_monitoring()?; diff --git a/src/mission_control/src/monitoring/cycles/start.rs b/src/mission_control/src/monitoring/cycles/start.rs index d6bfa6080f..9a110dc6b4 100644 --- a/src/mission_control/src/monitoring/cycles/start.rs +++ b/src/mission_control/src/monitoring/cycles/start.rs @@ -1,4 +1,4 @@ -use crate::factory::store::{get_orbiters, get_satellites}; +use crate::factory::store::{get_orbiters, get_satellites, get_ufos}; use crate::memory::manager::RUNTIME_STATE; use crate::monitoring::cycles::funding::init_funding_manager; use crate::monitoring::cycles::funding::register_cycles_monitoring; @@ -7,6 +7,7 @@ use crate::monitoring::cycles::scheduler::{ }; use crate::monitoring::store::heap::{ enable_mission_control_monitoring, enable_orbiter_monitoring, enable_satellite_monitoring, + enable_ufo_monitoring, }; use crate::types::core::SettingsMonitoring; use crate::types::runtime::RuntimeState; @@ -39,6 +40,7 @@ pub fn start_cycles_monitoring(enabled_only: bool) -> Result<(), String> { fn register_strategies(enabled_only: bool) -> Result<(), String> { let satellites = get_satellites(); let orbiters = get_orbiters(); + let ufos = get_ufos(); fn map_strategy( segment_id: &SegmentId, @@ -67,6 +69,10 @@ fn register_strategies(enabled_only: bool) -> Result<(), String> { .iter() .flat_map(|(orbiter_id, orbiter)| map_strategy(orbiter_id, &orbiter.settings, enabled_only)) .collect(); + let ufos_strategies: Vec = ufos + .iter() + .flat_map(|(ufo_id, ufo)| map_strategy(ufo_id, &ufo.settings, enabled_only)) + .collect(); if !satellites_strategies.is_empty() { register_cycles_monitoring_with_settings( @@ -90,6 +96,17 @@ fn register_strategies(enabled_only: bool) -> Result<(), String> { )?; } + if !ufos_strategies.is_empty() { + register_cycles_monitoring_with_settings( + &ufos_strategies, + if enabled_only { + None + } else { + Some(enable_ufo_monitoring) + }, + )?; + } + let mission_control_strategy = map_strategy(&id(), &get_settings(), enabled_only); if let Some(mission_control_strategy) = mission_control_strategy { diff --git a/src/mission_control/src/monitoring/cycles/stop.rs b/src/mission_control/src/monitoring/cycles/stop.rs index 847e476376..f7f249d058 100644 --- a/src/mission_control/src/monitoring/cycles/stop.rs +++ b/src/mission_control/src/monitoring/cycles/stop.rs @@ -1,9 +1,11 @@ -use crate::factory::store::{get_orbiters, get_satellites}; +use crate::factory::store::{get_orbiters, get_satellites, get_ufos}; use crate::monitoring::cycles::scheduler::{assert_scheduler_running, stop_scheduler}; use crate::monitoring::cycles::unregister::{ unregister_mission_control_monitoring, unregister_modules_monitoring, }; -use crate::monitoring::store::heap::{disable_orbiter_monitoring, disable_satellite_monitoring}; +use crate::monitoring::store::heap::{ + disable_orbiter_monitoring, disable_satellite_monitoring, disable_ufo_monitoring, +}; use crate::types::core::SettingsMonitoring; use junobuild_shared::types::state::SegmentId; @@ -20,6 +22,7 @@ pub fn stop_cycles_monitoring() -> Result<(), String> { fn unregister_strategies() -> Result<(), String> { let satellites = get_satellites(); let orbiters = get_orbiters(); + let ufos = get_ufos(); fn filter_enabled_strategy(segment_id: &SegmentId, settings: &Option) -> Option where @@ -43,6 +46,10 @@ fn unregister_strategies() -> Result<(), String> { .iter() .filter_map(|(orbiter_id, orbiter)| filter_enabled_strategy(orbiter_id, &orbiter.settings)) .collect(); + let ufo_ids: Vec = ufos + .iter() + .filter_map(|(ufo_id, ufo)| filter_enabled_strategy(ufo_id, &ufo.settings)) + .collect(); if !satellite_ids.is_empty() { unregister_modules_monitoring(&satellite_ids, disable_satellite_monitoring)?; @@ -52,6 +59,10 @@ fn unregister_strategies() -> Result<(), String> { unregister_modules_monitoring(&orbiter_ids, disable_orbiter_monitoring)?; } + if !ufo_ids.is_empty() { + unregister_modules_monitoring(&ufo_ids, disable_ufo_monitoring)?; + } + unregister_mission_control_monitoring()?; Ok(()) diff --git a/src/mission_control/src/monitoring/store/heap.rs b/src/mission_control/src/monitoring/store/heap.rs index 78de6e4474..3dce603fab 100644 --- a/src/mission_control/src/monitoring/store/heap.rs +++ b/src/mission_control/src/monitoring/store/heap.rs @@ -1,8 +1,10 @@ use crate::memory::manager::STATE; use crate::types::state::{ CyclesMonitoringStrategy, HeapState, MissionControlSettings, Orbiters, Satellites, Settings, + Ufos, }; -use junobuild_shared::types::state::{OrbiterId, SatelliteId}; +use junobuild_shared::types::state::{OrbiterId, SatelliteId, UfoId}; +use std::collections::HashMap; pub fn set_mission_control_strategy(strategy: &CyclesMonitoringStrategy) { STATE.with(|state| set_mission_control_strategy_impl(strategy, &mut state.borrow_mut().heap)) @@ -38,6 +40,20 @@ pub fn set_orbiter_strategy( }) } +pub fn set_ufo_strategy(ufo_id: &UfoId, strategy: &CyclesMonitoringStrategy) -> Result<(), String> { + STATE.with(|state| { + set_ufo_setting_impl( + ufo_id, + strategy, + state + .borrow_mut() + .heap + .ufos + .get_or_insert_with(HashMap::new), + ) + }) +} + pub fn enable_satellite_monitoring(satellite_id: &SatelliteId) -> Result<(), String> { STATE.with(|state| { toggle_satellite_monitoring_impl( @@ -54,6 +70,20 @@ pub fn enable_orbiter_monitoring(orbiter_id: &OrbiterId) -> Result<(), String> { }) } +pub fn enable_ufo_monitoring(ufo_id: &UfoId) -> Result<(), String> { + STATE.with(|state| { + toggle_ufo_monitoring_impl( + ufo_id, + true, + state + .borrow_mut() + .heap + .ufos + .get_or_insert_with(HashMap::new), + ) + }) +} + pub fn disable_satellite_monitoring(satellite_id: &SatelliteId) -> Result<(), String> { STATE.with(|state| { toggle_satellite_monitoring_impl( @@ -70,6 +100,20 @@ pub fn disable_orbiter_monitoring(orbiter_id: &OrbiterId) -> Result<(), String> }) } +pub fn disable_ufo_monitoring(ufo_id: &UfoId) -> Result<(), String> { + STATE.with(|state| { + toggle_ufo_monitoring_impl( + ufo_id, + false, + state + .borrow_mut() + .heap + .ufos + .get_or_insert_with(HashMap::new), + ) + }) +} + fn set_mission_control_strategy_impl(strategy: &CyclesMonitoringStrategy, state: &mut HeapState) { state.settings = Some( state @@ -153,6 +197,25 @@ fn set_orbiter_setting_impl( Ok(()) } +fn set_ufo_setting_impl( + ufo_id: &UfoId, + strategy: &CyclesMonitoringStrategy, + ufos: &mut Ufos, +) -> Result<(), String> { + let ufo = ufos.get(ufo_id).ok_or_else(|| { + format!( + "UFO {} not found. Strategy cannot be saved.", + ufo_id.to_text() + ) + })?; + + let update_ufo = ufo.clone_with_settings(&Settings::from(strategy)); + + ufos.insert(*ufo_id, update_ufo); + + Ok(()) +} + fn toggle_orbiter_monitoring_impl( orbiter_id: &OrbiterId, enabled: bool, @@ -171,3 +234,22 @@ fn toggle_orbiter_monitoring_impl( Ok(()) } + +fn toggle_ufo_monitoring_impl( + ufo_id: &UfoId, + enabled: bool, + ufos: &mut Ufos, +) -> Result<(), String> { + let ufo = ufos.get(ufo_id).ok_or_else(|| { + format!( + "UFO {} not found. Monitoring cannot be disabled.", + ufo_id.to_text() + ) + })?; + + let update_ufo = ufo.toggle_cycles_monitoring(enabled)?; + + ufos.insert(*ufo_id, update_ufo); + + Ok(()) +} diff --git a/src/mission_control/src/types.rs b/src/mission_control/src/types.rs index 80b7a30f34..24c5ae0226 100644 --- a/src/mission_control/src/types.rs +++ b/src/mission_control/src/types.rs @@ -4,13 +4,16 @@ pub mod state { use ic_stable_structures::StableBTreeMap; use junobuild_shared::types::memory::Memory; use junobuild_shared::types::monitoring::{CyclesBalance, FundingFailure}; - use junobuild_shared::types::state::{AccessKeys, Metadata, OrbiterId, SegmentId, Timestamp}; + use junobuild_shared::types::state::{ + AccessKeys, Metadata, OrbiterId, SegmentId, Timestamp, UfoId, + }; use junobuild_shared::types::state::{SatelliteId, UserId}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; pub type Satellites = HashMap; pub type Orbiters = HashMap; + pub type Ufos = HashMap; pub type MonitoringHistoryStable = StableBTreeMap; @@ -35,6 +38,7 @@ pub mod state { pub controllers: AccessKeys, pub orbiters: Orbiters, pub settings: Option, + pub ufos: Option, } #[derive(Default, CandidType, Serialize, Deserialize, Clone)] @@ -89,6 +93,15 @@ pub mod state { pub updated_at: Timestamp, } + #[derive(CandidType, Serialize, Deserialize, Clone)] + pub struct Ufo { + pub ufo_id: UfoId, + pub metadata: Metadata, + pub settings: Option, + pub created_at: Timestamp, + pub updated_at: Timestamp, + } + #[derive(Default, CandidType, Serialize, Deserialize, Clone)] pub struct Settings { pub monitoring: Option, @@ -171,7 +184,7 @@ pub mod interface { use candid::CandidType; use junobuild_shared::mgmt::types::cmc::SubnetId; use junobuild_shared::types::interface::InitStorageArgs; - use junobuild_shared::types::state::{OrbiterId, SatelliteId, SegmentId, Timestamp}; + use junobuild_shared::types::state::{OrbiterId, SatelliteId, SegmentId, Timestamp, UfoId}; use serde::{Deserialize, Serialize}; #[derive(CandidType, Serialize, Deserialize, Clone)] @@ -198,6 +211,7 @@ pub mod interface { pub mission_control_strategy: Option, pub satellites_strategy: Option, pub orbiters_strategy: Option, + pub ufos_strategy: Option, } #[derive(CandidType, Serialize, Deserialize, Clone)] @@ -205,6 +219,7 @@ pub mod interface { pub try_mission_control: Option, pub satellite_ids: Option>, pub orbiter_ids: Option>, + pub ufo_ids: Option>, } #[derive(CandidType, Serialize, Deserialize, Clone)] diff --git a/src/tests/specs/mission-control/mission-control.monitoring.history.spec.ts b/src/tests/specs/mission-control/mission-control.monitoring.history.spec.ts index c96af20b09..b407e7691b 100644 --- a/src/tests/specs/mission-control/mission-control.monitoring.history.spec.ts +++ b/src/tests/specs/mission-control/mission-control.monitoring.history.spec.ts @@ -23,6 +23,7 @@ describe('Mission Control > History', () => { let missionControlId: Principal; let orbiterId: Principal; let satelliteId: Principal; + let ufoId: Principal; const controller = Ed25519KeyIdentity.generate(); @@ -46,7 +47,11 @@ describe('Mission Control > History', () => { actor.setIdentity(controller); - const { orbiterId: oId, satelliteId: sId } = await setupMissionControlModules({ + const { + orbiterId: oId, + satelliteId: sId, + ufoId: uId + } = await setupMissionControlModules({ pic, controller, missionControlId @@ -54,11 +59,13 @@ describe('Mission Control > History', () => { orbiterId = oId; satelliteId = sId; + ufoId = uId; - const { set_orbiter, set_satellite } = actor; + const { set_orbiter, set_satellite, set_ufo } = actor; await set_orbiter(orbiterId, []); await set_satellite(satelliteId, []); + await set_ufo(ufoId, []); await tick(pic); }); @@ -93,6 +100,10 @@ describe('Mission Control > History', () => { it('should not have monitoring history for orbiter', async () => { await testEmptyHistory(orbiterId); }); + + it('should not have monitoring history for ufo', async () => { + await testEmptyHistory(ufoId); + }); }); describe('with history', () => { @@ -117,6 +128,10 @@ describe('Mission Control > History', () => { ids: [orbiterId], strategy }), + ufos_strategy: toNullable({ + ids: [ufoId], + strategy + }), mission_control_strategy: toNullable(strategy) } ] @@ -145,6 +160,10 @@ describe('Mission Control > History', () => { it('should have monitoring history for orbiter', async () => { await testMonitoringHistory({ segmentId: orbiterId, expectedLength: 1, actor }); }); + + it('should have monitoring history for ufo', async () => { + await testMonitoringHistory({ segmentId: ufoId, expectedLength: 1, actor }); + }); }); describe('collect entries over time', () => { @@ -166,6 +185,10 @@ describe('Mission Control > History', () => { it('should have monitoring history for orbiter', async () => { await testMonitoringHistory({ segmentId: orbiterId, expectedLength: 2, actor }); }); + + it('should have monitoring history for ufo', async () => { + await testMonitoringHistory({ segmentId: ufoId, expectedLength: 2, actor }); + }); }); describe('second round', () => { @@ -188,6 +211,10 @@ describe('Mission Control > History', () => { it('should have monitoring history for orbiter', async () => { await testMonitoringHistory({ segmentId: orbiterId, expectedLength: 3, actor }); }); + + it('should have monitoring history for ufo', async () => { + await testMonitoringHistory({ segmentId: ufoId, expectedLength: 3, actor }); + }); }); }); @@ -204,6 +231,10 @@ describe('Mission Control > History', () => { MissionControlDid.MonitoringHistoryKey, MissionControlDid.MonitoringHistory ][]; + let ufoHistory: [ + MissionControlDid.MonitoringHistoryKey, + MissionControlDid.MonitoringHistory + ][]; const testCleanedHistory = ({ before, @@ -251,6 +282,11 @@ describe('Mission Control > History', () => { expectedLength: 3, actor }); + ufoHistory = await testMonitoringHistory({ + segmentId: ufoId, + expectedLength: 3, + actor + }); const thirtyDays = 1000 * 60 * 60 * 24 * 30; @@ -289,6 +325,16 @@ describe('Mission Control > History', () => { testCleanedHistory({ before: orbiterHistory, after: updatedHistory }); }); + + it('should have monitoring history for ufo', async () => { + const updatedHistory = await testMonitoringHistory({ + segmentId: ufoId, + expectedLength: 3, + actor + }); + + testCleanedHistory({ before: ufoHistory, after: updatedHistory }); + }); }); }); }); diff --git a/src/tests/specs/mission-control/mission-control.monitoring.notifications.spec.ts b/src/tests/specs/mission-control/mission-control.monitoring.notifications.spec.ts index 79ae238d42..a00f455828 100644 --- a/src/tests/specs/mission-control/mission-control.monitoring.notifications.spec.ts +++ b/src/tests/specs/mission-control/mission-control.monitoring.notifications.spec.ts @@ -198,6 +198,7 @@ describe('Mission Control > Notifications', () => { strategy: satelliteStrategy }), orbiters_strategy: toNullable(), + ufos_strategy: toNullable(), mission_control_strategy: toNullable(missionControlStrategy) } ] @@ -212,6 +213,7 @@ describe('Mission Control > Notifications', () => { { satellite_ids: toNullable([satelliteId]), orbiter_ids: toNullable(), + ufo_ids: toNullable(), try_mission_control: toNullable(true) } ] @@ -347,6 +349,7 @@ describe('Mission Control > Notifications', () => { { satellites_strategy: toNullable(), orbiters_strategy: toNullable(), + ufos_strategy: toNullable(), mission_control_strategy: toNullable(missionControlStrategy) } ] @@ -361,6 +364,7 @@ describe('Mission Control > Notifications', () => { { satellite_ids: toNullable(), orbiter_ids: toNullable(), + ufo_ids: toNullable(), try_mission_control: toNullable(true) } ] diff --git a/src/tests/specs/mission-control/mission-control.monitoring.spec.ts b/src/tests/specs/mission-control/mission-control.monitoring.spec.ts index 40414056a7..ad238afc08 100644 --- a/src/tests/specs/mission-control/mission-control.monitoring.spec.ts +++ b/src/tests/specs/mission-control/mission-control.monitoring.spec.ts @@ -18,7 +18,8 @@ import { import { testMissionControlMonitoring, testOrbiterMonitoring, - testSatellitesMonitoring + testSatellitesMonitoring, + testUfoMonitoring } from '../../utils/monitoring-tests.utils'; import { MISSION_CONTROL_WASM_PATH } from '../../utils/setup-tests.utils'; @@ -29,6 +30,7 @@ describe('Mission Control > Monitoring', () => { let missionControlId: Principal; let orbiterId: Principal; let satelliteId: Principal; + let ufoId: Principal; const controller = Ed25519KeyIdentity.generate(); @@ -50,7 +52,11 @@ describe('Mission Control > Monitoring', () => { actor.setIdentity(controller); - const { orbiterId: oId, satelliteId: sId } = await setupMissionControlModules({ + const { + orbiterId: oId, + satelliteId: sId, + ufoId: uId + } = await setupMissionControlModules({ pic, controller, missionControlId @@ -58,11 +64,13 @@ describe('Mission Control > Monitoring', () => { orbiterId = oId; satelliteId = sId; + ufoId = uId; - const { set_orbiter, set_satellite } = actor; + const { set_orbiter, set_satellite, set_ufo } = actor; await set_orbiter(orbiterId, []); await set_satellite(satelliteId, []); + await set_ufo(ufoId, []); }); afterAll(async () => { @@ -204,6 +212,16 @@ describe('Mission Control > Monitoring', () => { ).toBeUndefined(); }); + it('should have no ufos settings', async () => { + const { list_ufos } = actor; + + const results = await list_ufos(); + + expect( + results.find(([_, { settings }]) => nonNullish(fromNullable(settings))) + ).toBeUndefined(); + }); + it('should fail at configuring monitoring if mission control is not already monitored', async () => { const { update_and_start_monitoring } = actor; @@ -215,6 +233,7 @@ describe('Mission Control > Monitoring', () => { strategy }), orbiters_strategy: toNullable(), + ufos_strategy: toNullable(), mission_control_strategy: toNullable() } ] @@ -233,6 +252,7 @@ describe('Mission Control > Monitoring', () => { { satellites_strategy: toNullable(), orbiters_strategy: toNullable(), + ufos_strategy: toNullable(), mission_control_strategy: toNullable(strategy) } ] @@ -266,6 +286,7 @@ describe('Mission Control > Monitoring', () => { strategy }), orbiters_strategy: toNullable(), + ufos_strategy: toNullable(), mission_control_strategy: toNullable() } ] @@ -287,6 +308,7 @@ describe('Mission Control > Monitoring', () => { ids: [orbiterId], strategy }), + ufos_strategy: toNullable(), mission_control_strategy: toNullable() } ] @@ -297,6 +319,28 @@ describe('Mission Control > Monitoring', () => { await testOrbiterMonitoring({ expectedEnabled: true, expectedStrategy: strategy, actor }); }); + it('should config and start monitoring for ufo', async () => { + const { update_and_start_monitoring } = actor; + + const config: MissionControlDid.MonitoringStartConfig = { + cycles_config: [ + { + satellites_strategy: toNullable(), + orbiters_strategy: toNullable(), + ufos_strategy: toNullable({ + ids: [ufoId], + strategy + }), + mission_control_strategy: toNullable() + } + ] + }; + + await update_and_start_monitoring(config); + + await testUfoMonitoring({ expectedEnabled: true, expectedStrategy: strategy, actor }); + }); + it('should fail at configuring monitoring for unknown satellite', async () => { const { update_and_start_monitoring } = actor; @@ -308,6 +352,7 @@ describe('Mission Control > Monitoring', () => { strategy }), orbiters_strategy: toNullable(), + ufos_strategy: toNullable(), mission_control_strategy: toNullable() } ] @@ -329,6 +374,7 @@ describe('Mission Control > Monitoring', () => { ids: [satelliteId], strategy }), + ufos_strategy: toNullable(), mission_control_strategy: toNullable() } ] @@ -339,6 +385,28 @@ describe('Mission Control > Monitoring', () => { ); }); + it('should fail at configuring monitoring for unknown ufo', async () => { + const { update_and_start_monitoring } = actor; + + const config: MissionControlDid.MonitoringStartConfig = { + cycles_config: [ + { + satellites_strategy: toNullable(), + orbiters_strategy: toNullable(), + ufos_strategy: toNullable({ + ids: [satelliteId], + strategy + }), + mission_control_strategy: toNullable() + } + ] + }; + + await expect(update_and_start_monitoring(config)).rejects.toThrow( + `UFO ${satelliteId.toText()} not found. Strategy cannot be saved.` + ); + }); + it('should fail at stopping monitoring for unknown satellite', async () => { const { update_and_stop_monitoring } = actor; @@ -347,6 +415,7 @@ describe('Mission Control > Monitoring', () => { { satellite_ids: toNullable([satelliteIdMock]), orbiter_ids: toNullable(), + ufo_ids: toNullable(), try_mission_control: toNullable() } ] @@ -365,6 +434,7 @@ describe('Mission Control > Monitoring', () => { { satellite_ids: toNullable(), orbiter_ids: toNullable([satelliteId]), + ufo_ids: toNullable(), try_mission_control: toNullable() } ] @@ -375,6 +445,25 @@ describe('Mission Control > Monitoring', () => { ); }); + it('should fail at stopping monitoring for unknown ufo', async () => { + const { update_and_stop_monitoring } = actor; + + const config: MissionControlDid.MonitoringStopConfig = { + cycles_config: [ + { + satellite_ids: toNullable(), + orbiter_ids: toNullable(), + ufo_ids: toNullable([satelliteId]), + try_mission_control: toNullable() + } + ] + }; + + await expect(update_and_stop_monitoring(config)).rejects.toThrow( + `UFO ${satelliteId.toText()} not found. Monitoring cannot be disabled.` + ); + }); + it('should fail at stopping monitoring for mission control if modules are still being monitored', async () => { const { update_and_stop_monitoring } = actor; @@ -383,6 +472,7 @@ describe('Mission Control > Monitoring', () => { { satellite_ids: toNullable(), orbiter_ids: toNullable(), + ufo_ids: toNullable(), try_mission_control: toNullable(true) } ] @@ -401,6 +491,7 @@ describe('Mission Control > Monitoring', () => { { satellite_ids: toNullable([satelliteId]), orbiter_ids: toNullable(), + ufo_ids: toNullable(), try_mission_control: toNullable() } ] @@ -420,6 +511,27 @@ describe('Mission Control > Monitoring', () => { { satellite_ids: toNullable(), orbiter_ids: toNullable([orbiterId]), + ufo_ids: toNullable(), + try_mission_control: toNullable() + } + ] + }; + + await update_and_stop_monitoring(config); + + await testSatellitesMonitoring({ expectedEnabled: false, expectedStrategy: strategy, actor }); + await testOrbiterMonitoring({ expectedEnabled: false, expectedStrategy: strategy, actor }); + }); + + it('should stop monitoring for ufo', async () => { + const { update_and_stop_monitoring } = actor; + + const config: MissionControlDid.MonitoringStopConfig = { + cycles_config: [ + { + satellite_ids: toNullable(), + orbiter_ids: toNullable(), + ufo_ids: toNullable([ufoId]), try_mission_control: toNullable() } ] @@ -439,6 +551,7 @@ describe('Mission Control > Monitoring', () => { { satellite_ids: toNullable(), orbiter_ids: toNullable(), + ufo_ids: toNullable(), try_mission_control: toNullable(true) } ] @@ -516,6 +629,7 @@ describe('Mission Control > Monitoring', () => { { satellites_strategy: toNullable(), orbiters_strategy: toNullable(), + ufos_strategy: toNullable(), mission_control_strategy: toNullable(updateStrategy) } ] @@ -541,6 +655,7 @@ describe('Mission Control > Monitoring', () => { strategy: updateStrategy }), orbiters_strategy: toNullable(), + ufos_strategy: toNullable(), mission_control_strategy: toNullable() } ] @@ -566,6 +681,33 @@ describe('Mission Control > Monitoring', () => { ids: [orbiterId], strategy: updateStrategy }), + ufos_strategy: toNullable(), + mission_control_strategy: toNullable() + } + ] + }; + + await update_and_start_monitoring(config); + + await testOrbiterMonitoring({ + expectedEnabled: true, + expectedStrategy: updateStrategy, + actor + }); + }); + + it('should update config for ufo', async () => { + const { update_and_start_monitoring } = actor; + + const config: MissionControlDid.MonitoringStartConfig = { + cycles_config: [ + { + satellites_strategy: toNullable(), + orbiters_strategy: toNullable(), + ufos_strategy: toNullable({ + ids: [ufoId], + strategy: updateStrategy + }), mission_control_strategy: toNullable() } ] diff --git a/src/tests/specs/mission-control/mission-control.monitoring.upgrade.spec.ts b/src/tests/specs/mission-control/mission-control.monitoring.upgrade.spec.ts index d8897bd62e..4b3a57ea34 100644 --- a/src/tests/specs/mission-control/mission-control.monitoring.upgrade.spec.ts +++ b/src/tests/specs/mission-control/mission-control.monitoring.upgrade.spec.ts @@ -23,6 +23,7 @@ describe.skip('Mission control > Upgrade > Monitoring', () => { let missionControlId: Principal; let orbiterId: Principal; let satelliteId: Principal; + let ufoId: Principal; const controller = Ed25519KeyIdentity.generate(); @@ -51,7 +52,11 @@ describe.skip('Mission control > Upgrade > Monitoring', () => { actor.setIdentity(controller); - const { orbiterId: oId, satelliteId: sId } = await setupMissionControlModules({ + const { + orbiterId: oId, + satelliteId: sId, + ufoId: uId + } = await setupMissionControlModules({ pic, controller, missionControlId @@ -59,11 +64,13 @@ describe.skip('Mission control > Upgrade > Monitoring', () => { orbiterId = oId; satelliteId = sId; + ufoId = uId; - const { set_orbiter, set_satellite } = actor; + const { set_orbiter, set_satellite, set_ufo } = actor; await set_orbiter(orbiterId, []); await set_satellite(satelliteId, []); + await set_ufo(ufoId, []); }); afterEach(async () => { @@ -86,6 +93,10 @@ describe.skip('Mission control > Upgrade > Monitoring', () => { ids: [orbiterId], strategy }), + ufos_strategy: toNullable({ + ids: [ufoId], + strategy + }), mission_control_strategy: toNullable(strategy) } ] @@ -104,6 +115,7 @@ describe.skip('Mission control > Upgrade > Monitoring', () => { { satellite_ids: toNullable(), orbiter_ids: toNullable([orbiterId]), + ufo_ids: toNullable(), try_mission_control: toNullable() } ] diff --git a/src/tests/specs/mission-control/mission-control.set-unset.spec.ts b/src/tests/specs/mission-control/mission-control.set-unset.spec.ts index 5466c3686d..60d0d6afeb 100644 --- a/src/tests/specs/mission-control/mission-control.set-unset.spec.ts +++ b/src/tests/specs/mission-control/mission-control.set-unset.spec.ts @@ -17,6 +17,7 @@ describe('Mission Control > Set / Unset', () => { let orbiterId: Principal; let satelliteId: Principal; + let ufoId: Principal; const controller = Ed25519KeyIdentity.generate(); @@ -38,7 +39,11 @@ describe('Mission Control > Set / Unset', () => { actor.setIdentity(controller); - const { orbiterId: oId, satelliteId: sId } = await setupMissionControlModules({ + const { + orbiterId: oId, + satelliteId: sId, + ufoId: uId + } = await setupMissionControlModules({ pic, controller, missionControlId @@ -46,6 +51,7 @@ describe('Mission Control > Set / Unset', () => { orbiterId = oId; satelliteId = sId; + ufoId = uId; }); afterAll(async () => { @@ -84,6 +90,18 @@ describe('Mission Control > Set / Unset', () => { MISSION_CONTROL_ADMIN_CONTROLLER_ERROR_MSG ); }); + + it('should throw errors on set ufo', async () => { + const { set_ufo } = actor; + + await expect(set_ufo(ufoId, [])).rejects.toThrow(MISSION_CONTROL_ADMIN_CONTROLLER_ERROR_MSG); + }); + + it('should throw errors on unset ufo', async () => { + const { unset_ufo } = actor; + + await expect(unset_ufo(ufoId)).rejects.toThrow(MISSION_CONTROL_ADMIN_CONTROLLER_ERROR_MSG); + }); }; describe('anonymous', () => { @@ -180,5 +198,42 @@ describe('Mission Control > Set / Unset', () => { expect(results).toHaveLength(0); }); + + it('should have no ufos set', async () => { + const { list_ufos } = actor; + + const results = await list_ufos(); + + expect(results).toHaveLength(0); + }); + + it('should set an ufo', async () => { + const { set_ufo, list_ufos } = actor; + + const ufo = await set_ufo(ufoId, ['Hello']); + + expect(ufo.ufo_id.toText()).toEqual(ufoId.toText()); + expect(ufo.created_at).toBeGreaterThan(0n); + expect(ufo.updated_at).toBeGreaterThan(0n); + + const results = await list_ufos(); + + expect(results).toHaveLength(1); + + expect(results[0][0].toText()).toEqual(ufoId.toText()); + expect(results[0][1].ufo_id.toText()).toEqual(ufoId.toText()); + expect(results[0][1].created_at).toBeGreaterThan(0n); + expect(results[0][1].updated_at).toBeGreaterThan(0n); + }); + + it('should unset an ufo', async () => { + const { unset_ufo, list_ufos } = actor; + + await unset_ufo(ufoId); + + const results = await list_ufos(); + + expect(results).toHaveLength(0); + }); }); }); diff --git a/src/tests/specs/mission-control/upgrade/mission-control.upgrade.spec.ts b/src/tests/specs/mission-control/upgrade/mission-control.upgrade.spec.ts index b4b6f501c7..9fc206abd2 100644 --- a/src/tests/specs/mission-control/upgrade/mission-control.upgrade.spec.ts +++ b/src/tests/specs/mission-control/upgrade/mission-control.upgrade.spec.ts @@ -194,7 +194,10 @@ describe('Mission control > Upgrade', () => { ids: [orbiterId], strategy }), - mission_control_strategy: toNullable(strategy) + mission_control_strategy: toNullable(strategy), + // ufos_strategy was technically not part of v0.1.0 but for simplicity reasons + // and to avoid duplicating did files just for this test, we set it to none + ufos_strategy: toNullable() } ] }; diff --git a/src/tests/utils/mission-control-tests.utils.ts b/src/tests/utils/mission-control-tests.utils.ts index 610b52d7ce..628ad1828a 100644 --- a/src/tests/utils/mission-control-tests.utils.ts +++ b/src/tests/utils/mission-control-tests.utils.ts @@ -28,7 +28,7 @@ export const setupMissionControlModules = async ({ pic: PocketIc; controller: Identity; missionControlId: Principal; -}): Promise<{ satelliteId: Principal; orbiterId: Principal }> => { +}): Promise<{ satelliteId: Principal; orbiterId: Principal; ufoId: Principal }> => { const { canisterId: orbiterId } = await pic.setupCanister({ idlFactory: idlFactoryOrbiter, wasm: ORBITER_WASM_PATH, @@ -55,5 +55,15 @@ export const setupMissionControlModules = async ({ sender: controller.getPrincipal() }); - return { satelliteId, orbiterId }; + const ufoId = await pic.createCanister({ + sender: controller.getPrincipal() + }); + + await pic.updateCanisterSettings({ + canisterId: ufoId, + controllers: [controller.getPrincipal(), missionControlId], + sender: controller.getPrincipal() + }); + + return { satelliteId, orbiterId, ufoId }; }; diff --git a/src/tests/utils/monitoring-tests.utils.ts b/src/tests/utils/monitoring-tests.utils.ts index 98a43bdef6..24d2ff6fbb 100644 --- a/src/tests/utils/monitoring-tests.utils.ts +++ b/src/tests/utils/monitoring-tests.utils.ts @@ -83,6 +83,25 @@ export const testOrbiterMonitoring = async ({ testMonitoring({ monitoring, expectedEnabled, expectedStrategy }); }; +export const testUfoMonitoring = async ({ + expectedEnabled, + expectedStrategy, + actor +}: { + expectedEnabled: boolean; + expectedStrategy: MissionControlDid.CyclesMonitoringStrategy; + actor: Actor; +}) => { + const { list_ufos } = actor; + + const [[_, ufo]] = await list_ufos(); + + const settings = fromNullable(ufo.settings); + const monitoring = fromNullable(settings?.monitoring ?? []); + + testMonitoring({ monitoring, expectedEnabled, expectedStrategy }); +}; + export const testMonitoringHistory = async ({ segmentId, expectedLength,