diff --git a/testing-framework/core/src/runtime/manual.rs b/testing-framework/core/src/runtime/manual.rs index 4e8af53..ac17826 100644 --- a/testing-framework/core/src/runtime/manual.rs +++ b/testing-framework/core/src/runtime/manual.rs @@ -1,7 +1,11 @@ use async_trait::async_trait; -use crate::scenario::{Application, ClusterWaitHandle, NodeControlHandle}; +use crate::scenario::{Application, ClusterControlProfile, ClusterWaitHandle, NodeControlHandle}; /// Interface for imperative, deployer-backed manual clusters. #[async_trait] -pub trait ManualClusterHandle: NodeControlHandle + ClusterWaitHandle {} +pub trait ManualClusterHandle: NodeControlHandle + ClusterWaitHandle { + fn cluster_control_profile(&self) -> ClusterControlProfile { + ClusterControlProfile::ManualControlled + } +} diff --git a/testing-framework/core/src/scenario/definition.rs b/testing-framework/core/src/scenario/definition.rs index 4bc70d5..6824cf3 100644 --- a/testing-framework/core/src/scenario/definition.rs +++ b/testing-framework/core/src/scenario/definition.rs @@ -4,8 +4,8 @@ use thiserror::Error; use tracing::{debug, info}; use super::{ - Application, ClusterMode, DeploymentPolicy, DynError, ExistingCluster, ExternalNodeSource, - HttpReadinessRequirement, NodeControlCapability, ObservabilityCapability, + Application, ClusterControlProfile, ClusterMode, DeploymentPolicy, DynError, ExistingCluster, + ExternalNodeSource, HttpReadinessRequirement, NodeControlCapability, ObservabilityCapability, builder_ops::CoreBuilderAccess, expectation::Expectation, runtime::{ @@ -123,6 +123,11 @@ impl Scenario { self.sources.cluster_mode() } + #[must_use] + pub const fn cluster_control_profile(&self) -> ClusterControlProfile { + self.sources.control_profile() + } + #[must_use] #[doc(hidden)] pub fn attached_source(&self) -> Option<&ExistingCluster> { diff --git a/testing-framework/core/src/scenario/mod.rs b/testing-framework/core/src/scenario/mod.rs index 431ee7c..dc464dc 100644 --- a/testing-framework/core/src/scenario/mod.rs +++ b/testing-framework/core/src/scenario/mod.rs @@ -54,7 +54,7 @@ pub use runtime::{ wait_for_http_ports_with_host_and_requirement, wait_for_http_ports_with_requirement, wait_http_readiness, wait_until_stable, }; -pub use sources::{ClusterMode, ExistingCluster, ExternalNodeSource}; +pub use sources::{ClusterControlProfile, ClusterMode, ExistingCluster, ExternalNodeSource}; pub use workload::Workload; pub use crate::env::Application; diff --git a/testing-framework/core/src/scenario/sources/mod.rs b/testing-framework/core/src/scenario/sources/mod.rs index 7f63800..01e8807 100644 --- a/testing-framework/core/src/scenario/sources/mod.rs +++ b/testing-framework/core/src/scenario/sources/mod.rs @@ -2,4 +2,4 @@ mod model; pub(crate) use model::ScenarioSources; #[doc(hidden)] -pub use model::{ClusterMode, ExistingCluster, ExternalNodeSource}; +pub use model::{ClusterControlProfile, ClusterMode, ExistingCluster, ExternalNodeSource}; diff --git a/testing-framework/core/src/scenario/sources/model.rs b/testing-framework/core/src/scenario/sources/model.rs index 63c6c1b..599584c 100644 --- a/testing-framework/core/src/scenario/sources/model.rs +++ b/testing-framework/core/src/scenario/sources/model.rs @@ -127,6 +127,27 @@ pub enum ClusterMode { ExternalOnly, } +/// High-level control/lifecycle expectation for a cluster surface. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ClusterControlProfile { + FrameworkManaged, + ExistingClusterControlled, + ExternalUncontrolled, + ManualControlled, +} + +impl ClusterControlProfile { + #[must_use] + pub const fn framework_owns_lifecycle(self) -> bool { + matches!(self, Self::FrameworkManaged) + } + + #[must_use] + pub const fn supports_node_control(self) -> bool { + !matches!(self, Self::ExternalUncontrolled) + } +} + /// Source model that makes invalid managed+attached combinations /// unrepresentable by type. #[derive(Clone, Debug, Eq, PartialEq)] @@ -202,4 +223,52 @@ impl ScenarioSources { Self::ExternalOnly { .. } => ClusterMode::ExternalOnly, } } + + #[must_use] + pub(crate) const fn control_profile(&self) -> ClusterControlProfile { + match self.cluster_mode() { + ClusterMode::Managed => ClusterControlProfile::FrameworkManaged, + ClusterMode::ExistingCluster => ClusterControlProfile::ExistingClusterControlled, + ClusterMode::ExternalOnly => ClusterControlProfile::ExternalUncontrolled, + } + } +} + +#[cfg(test)] +mod tests { + use super::{ClusterControlProfile, ExistingCluster, ExternalNodeSource, ScenarioSources}; + + #[test] + fn managed_sources_map_to_framework_managed_control() { + assert_eq!( + ScenarioSources::default().control_profile(), + ClusterControlProfile::FrameworkManaged, + ); + } + + #[test] + fn attached_sources_map_to_existing_cluster_control() { + let sources = ScenarioSources::default() + .with_attach(ExistingCluster::for_compose_project("project".to_owned())); + + assert_eq!( + sources.control_profile(), + ClusterControlProfile::ExistingClusterControlled, + ); + } + + #[test] + fn external_only_sources_map_to_uncontrolled_profile() { + let sources = ScenarioSources::default() + .with_external_node(ExternalNodeSource::new( + "node".to_owned(), + "http://node".to_owned(), + )) + .into_external_only(); + + assert_eq!( + sources.control_profile(), + ClusterControlProfile::ExternalUncontrolled, + ); + } }