mirror of
https://github.com/logos-blockchain/logos-blockchain-testing.git
synced 2026-05-17 23:29:52 +00:00
Validate scenario mode guarantees early
This commit is contained in:
parent
cf1e6185fa
commit
7e3531a4b2
@ -6,6 +6,7 @@ use tracing::{debug, info};
|
|||||||
use super::{
|
use super::{
|
||||||
Application, ClusterControlProfile, ClusterMode, DeploymentPolicy, DynError, ExistingCluster,
|
Application, ClusterControlProfile, ClusterMode, DeploymentPolicy, DynError, ExistingCluster,
|
||||||
ExternalNodeSource, HttpReadinessRequirement, NodeControlCapability, ObservabilityCapability,
|
ExternalNodeSource, HttpReadinessRequirement, NodeControlCapability, ObservabilityCapability,
|
||||||
|
RequiresNodeControl,
|
||||||
builder_ops::CoreBuilderAccess,
|
builder_ops::CoreBuilderAccess,
|
||||||
expectation::Expectation,
|
expectation::Expectation,
|
||||||
runtime::{
|
runtime::{
|
||||||
@ -614,11 +615,17 @@ impl<E: Application, Caps> Builder<E, Caps> {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
/// Finalize the scenario, computing run metrics and initializing
|
/// Finalize the scenario, computing run metrics and initializing
|
||||||
/// components.
|
/// components.
|
||||||
pub fn build(self) -> Result<Scenario<E, Caps>, ScenarioBuildError> {
|
pub fn build(self) -> Result<Scenario<E, Caps>, ScenarioBuildError>
|
||||||
|
where
|
||||||
|
Caps: RequiresNodeControl,
|
||||||
|
{
|
||||||
let mut parts = BuilderParts::from_builder(self);
|
let mut parts = BuilderParts::from_builder(self);
|
||||||
let descriptors = parts.resolve_deployment()?;
|
let descriptors = parts.resolve_deployment()?;
|
||||||
let run_plan = parts.run_plan();
|
let run_plan = parts.run_plan();
|
||||||
let run_metrics = RunMetrics::new(run_plan.duration);
|
let run_metrics = RunMetrics::new(run_plan.duration);
|
||||||
|
|
||||||
|
validate_source_contract::<Caps>(parts.sources())?;
|
||||||
|
|
||||||
let source_orchestration_plan = build_source_orchestration_plan(parts.sources())?;
|
let source_orchestration_plan = build_source_orchestration_plan(parts.sources())?;
|
||||||
|
|
||||||
initialize_components(
|
initialize_components(
|
||||||
@ -721,6 +728,17 @@ fn build_source_orchestration_plan(
|
|||||||
SourceOrchestrationPlan::try_from_sources(sources).map_err(source_plan_error_to_build_error)
|
SourceOrchestrationPlan::try_from_sources(sources).map_err(source_plan_error_to_build_error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate_source_contract<Caps>(sources: &ScenarioSources) -> Result<(), ScenarioBuildError>
|
||||||
|
where
|
||||||
|
Caps: RequiresNodeControl,
|
||||||
|
{
|
||||||
|
validate_external_only_sources(sources)?;
|
||||||
|
|
||||||
|
validate_node_control_profile::<Caps>(sources)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn source_plan_error_to_build_error(error: SourceOrchestrationPlanError) -> ScenarioBuildError {
|
fn source_plan_error_to_build_error(error: SourceOrchestrationPlanError) -> ScenarioBuildError {
|
||||||
match error {
|
match error {
|
||||||
SourceOrchestrationPlanError::SourceModeNotWiredYet { mode } => {
|
SourceOrchestrationPlanError::SourceModeNotWiredYet { mode } => {
|
||||||
@ -729,6 +747,37 @@ fn source_plan_error_to_build_error(error: SourceOrchestrationPlanError) -> Scen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate_external_only_sources(sources: &ScenarioSources) -> Result<(), ScenarioBuildError> {
|
||||||
|
if matches!(sources.cluster_mode(), ClusterMode::ExternalOnly)
|
||||||
|
&& sources.external_nodes().is_empty()
|
||||||
|
{
|
||||||
|
return Err(ScenarioBuildError::SourceConfiguration {
|
||||||
|
message: "external-only scenarios require at least one external node".to_owned(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_node_control_profile<Caps>(sources: &ScenarioSources) -> Result<(), ScenarioBuildError>
|
||||||
|
where
|
||||||
|
Caps: RequiresNodeControl,
|
||||||
|
{
|
||||||
|
let profile = sources.control_profile();
|
||||||
|
|
||||||
|
if Caps::REQUIRED && !profile.supports_node_control() {
|
||||||
|
return Err(ScenarioBuildError::SourceConfiguration {
|
||||||
|
message: format!(
|
||||||
|
"node control requires a controllable cluster surface, but cluster mode '{}' uses control profile '{}'",
|
||||||
|
sources.cluster_mode().as_str(),
|
||||||
|
profile.as_str(),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
impl<E: Application> Builder<E, ()> {
|
impl<E: Application> Builder<E, ()> {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn enable_node_control(self) -> Builder<E, NodeControlCapability> {
|
pub fn enable_node_control(self) -> Builder<E, NodeControlCapability> {
|
||||||
@ -813,3 +862,59 @@ fn expectation_cooldown_for(override_value: Option<Duration>) -> Duration {
|
|||||||
fn min_run_duration() -> Duration {
|
fn min_run_duration() -> Duration {
|
||||||
Duration::from_secs(MIN_RUN_DURATION_SECS)
|
Duration::from_secs(MIN_RUN_DURATION_SECS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{
|
||||||
|
ScenarioBuildError, validate_external_only_sources, validate_node_control_profile,
|
||||||
|
};
|
||||||
|
use crate::scenario::{
|
||||||
|
ExistingCluster, ExternalNodeSource, NodeControlCapability, sources::ScenarioSources,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn external_only_requires_external_nodes() {
|
||||||
|
let error =
|
||||||
|
validate_external_only_sources(&ScenarioSources::default().into_external_only())
|
||||||
|
.expect_err("external-only without nodes should fail");
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
error,
|
||||||
|
ScenarioBuildError::SourceConfiguration { .. }
|
||||||
|
));
|
||||||
|
assert_eq!(
|
||||||
|
error.to_string(),
|
||||||
|
"invalid scenario source configuration: external-only scenarios require at least one external node"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn external_only_rejects_node_control_requirement() {
|
||||||
|
let sources = ScenarioSources::default()
|
||||||
|
.with_external_node(ExternalNodeSource::new(
|
||||||
|
"node-0".to_owned(),
|
||||||
|
"http://127.0.0.1:1".to_owned(),
|
||||||
|
))
|
||||||
|
.into_external_only();
|
||||||
|
let error = validate_node_control_profile::<NodeControlCapability>(&sources)
|
||||||
|
.expect_err("external-only should reject node control");
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
error,
|
||||||
|
ScenarioBuildError::SourceConfiguration { .. }
|
||||||
|
));
|
||||||
|
assert_eq!(
|
||||||
|
error.to_string(),
|
||||||
|
"invalid scenario source configuration: node control requires a controllable cluster surface, but cluster mode 'external-only' uses control profile 'external-uncontrolled'"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn existing_cluster_accepts_node_control_requirement() {
|
||||||
|
let sources = ScenarioSources::default()
|
||||||
|
.with_attach(ExistingCluster::for_compose_project("project".to_owned()));
|
||||||
|
|
||||||
|
validate_node_control_profile::<NodeControlCapability>(&sources)
|
||||||
|
.expect("existing cluster should be considered controllable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -127,6 +127,17 @@ pub enum ClusterMode {
|
|||||||
ExternalOnly,
|
ExternalOnly,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ClusterMode {
|
||||||
|
#[must_use]
|
||||||
|
pub const fn as_str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Managed => "managed",
|
||||||
|
Self::ExistingCluster => "existing-cluster",
|
||||||
|
Self::ExternalOnly => "external-only",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// High-level control/lifecycle expectation for a cluster surface.
|
/// High-level control/lifecycle expectation for a cluster surface.
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub enum ClusterControlProfile {
|
pub enum ClusterControlProfile {
|
||||||
@ -137,6 +148,16 @@ pub enum ClusterControlProfile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ClusterControlProfile {
|
impl ClusterControlProfile {
|
||||||
|
#[must_use]
|
||||||
|
pub const fn as_str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::FrameworkManaged => "framework-managed",
|
||||||
|
Self::ExistingClusterControlled => "existing-cluster-controlled",
|
||||||
|
Self::ExternalUncontrolled => "external-uncontrolled",
|
||||||
|
Self::ManualControlled => "manual-controlled",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn framework_owns_lifecycle(self) -> bool {
|
pub const fn framework_owns_lifecycle(self) -> bool {
|
||||||
matches!(self, Self::FrameworkManaged)
|
matches!(self, Self::FrameworkManaged)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user