mirror of
https://github.com/logos-blockchain/logos-blockchain-testing.git
synced 2026-03-31 16:23:08 +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::{
|
||||
Application, ClusterControlProfile, ClusterMode, DeploymentPolicy, DynError, ExistingCluster,
|
||||
ExternalNodeSource, HttpReadinessRequirement, NodeControlCapability, ObservabilityCapability,
|
||||
RequiresNodeControl,
|
||||
builder_ops::CoreBuilderAccess,
|
||||
expectation::Expectation,
|
||||
runtime::{
|
||||
@ -614,11 +615,17 @@ impl<E: Application, Caps> Builder<E, Caps> {
|
||||
#[must_use]
|
||||
/// Finalize the scenario, computing run metrics and initializing
|
||||
/// 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 descriptors = parts.resolve_deployment()?;
|
||||
let run_plan = parts.run_plan();
|
||||
let run_metrics = RunMetrics::new(run_plan.duration);
|
||||
|
||||
validate_source_contract::<Caps>(parts.sources())?;
|
||||
|
||||
let source_orchestration_plan = build_source_orchestration_plan(parts.sources())?;
|
||||
|
||||
initialize_components(
|
||||
@ -721,6 +728,17 @@ fn build_source_orchestration_plan(
|
||||
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 {
|
||||
match error {
|
||||
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, ()> {
|
||||
#[must_use]
|
||||
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 {
|
||||
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,
|
||||
}
|
||||
|
||||
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.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum ClusterControlProfile {
|
||||
@ -137,6 +148,16 @@ pub enum 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]
|
||||
pub const fn framework_owns_lifecycle(self) -> bool {
|
||||
matches!(self, Self::FrameworkManaged)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user