Drive runtime stabilization from cluster control semantics

This commit is contained in:
andrussal 2026-03-08 14:59:59 +01:00
parent 7e3531a4b2
commit 898eadf976
5 changed files with 45 additions and 21 deletions

View File

@ -1,7 +1,9 @@
use std::{sync::Arc, time::Duration};
use super::{metrics::Metrics, node_clients::ClusterClient};
use crate::scenario::{Application, ClusterWaitHandle, DynError, NodeClients, NodeControlHandle};
use crate::scenario::{
Application, ClusterControlProfile, ClusterWaitHandle, DynError, NodeClients, NodeControlHandle,
};
#[derive(Debug, thiserror::Error)]
enum RunContextCapabilityError {
@ -15,6 +17,7 @@ pub struct RunContext<E: Application> {
node_clients: NodeClients<E>,
metrics: RunMetrics,
expectation_cooldown: Duration,
cluster_control_profile: ClusterControlProfile,
telemetry: Metrics,
feed: <E::FeedRuntime as super::FeedRuntime>::Feed,
node_control: Option<Arc<dyn NodeControlHandle<E>>>,
@ -28,6 +31,7 @@ pub struct RuntimeAssembly<E: Application> {
node_clients: NodeClients<E>,
run_duration: Duration,
expectation_cooldown: Duration,
cluster_control_profile: ClusterControlProfile,
telemetry: Metrics,
feed: <E::FeedRuntime as super::FeedRuntime>::Feed,
node_control: Option<Arc<dyn NodeControlHandle<E>>>,
@ -42,6 +46,7 @@ impl<E: Application> RunContext<E> {
node_clients: NodeClients<E>,
run_duration: Duration,
expectation_cooldown: Duration,
cluster_control_profile: ClusterControlProfile,
telemetry: Metrics,
feed: <E::FeedRuntime as super::FeedRuntime>::Feed,
node_control: Option<Arc<dyn NodeControlHandle<E>>>,
@ -53,6 +58,7 @@ impl<E: Application> RunContext<E> {
node_clients,
metrics,
expectation_cooldown,
cluster_control_profile,
telemetry,
feed,
node_control,
@ -102,13 +108,13 @@ impl<E: Application> RunContext<E> {
}
#[must_use]
pub fn node_control(&self) -> Option<Arc<dyn NodeControlHandle<E>>> {
self.node_control.clone()
pub const fn cluster_control_profile(&self) -> ClusterControlProfile {
self.cluster_control_profile
}
#[must_use]
pub(crate) const fn controls_nodes(&self) -> bool {
self.node_control.is_some()
pub fn node_control(&self) -> Option<Arc<dyn NodeControlHandle<E>>> {
self.node_control.clone()
}
pub(crate) async fn wait_network_ready(&self) -> Result<(), DynError> {
@ -135,6 +141,7 @@ impl<E: Application> RuntimeAssembly<E> {
node_clients: NodeClients<E>,
run_duration: Duration,
expectation_cooldown: Duration,
cluster_control_profile: ClusterControlProfile,
telemetry: Metrics,
feed: <E::FeedRuntime as super::FeedRuntime>::Feed,
) -> Self {
@ -143,6 +150,7 @@ impl<E: Application> RuntimeAssembly<E> {
node_clients,
run_duration,
expectation_cooldown,
cluster_control_profile,
telemetry,
feed,
node_control: None,
@ -169,6 +177,7 @@ impl<E: Application> RuntimeAssembly<E> {
self.node_clients,
self.run_duration,
self.expectation_cooldown,
self.cluster_control_profile,
self.telemetry,
self.feed,
self.node_control,
@ -193,6 +202,7 @@ impl<E: Application> From<RunContext<E>> for RuntimeAssembly<E> {
node_clients: context.node_clients,
run_duration: context.metrics.run_duration(),
expectation_cooldown: context.expectation_cooldown,
cluster_control_profile: context.cluster_control_profile,
telemetry: context.telemetry,
feed: context.feed,
node_control: context.node_control,

View File

@ -192,10 +192,10 @@ impl<E: Application> Runner<E> {
}
fn settle_wait_duration(context: &RunContext<E>) -> Option<Duration> {
let has_node_control = context.controls_nodes();
let control_profile = context.cluster_control_profile();
let configured_wait = context.expectation_cooldown();
if configured_wait.is_zero() && !has_node_control {
if configured_wait.is_zero() && !control_profile.supports_node_control() {
return None;
}
@ -233,7 +233,7 @@ impl<E: Application> Runner<E> {
fn cooldown_duration(context: &RunContext<E>) -> Option<Duration> {
// Managed environments need a minimum cooldown so feed and expectations
// observe stabilized state.
let needs_stabilization = context.controls_nodes();
let needs_stabilization = context.cluster_control_profile().framework_owns_lifecycle();
let mut wait = context.expectation_cooldown();

View File

@ -3,12 +3,12 @@ use std::{env, sync::Arc, time::Duration};
use reqwest::Url;
use testing_framework_core::{
scenario::{
ApplicationExternalProvider, CleanupGuard, ClusterMode, ClusterWaitHandle,
DeploymentPolicy, FeedHandle, FeedRuntime, HttpReadinessRequirement, Metrics, NodeClients,
NodeControlHandle, ObservabilityCapabilityProvider, ObservabilityInputs,
RequiresNodeControl, Runner, RuntimeAssembly, Scenario, SourceOrchestrationPlan,
SourceProviders, StaticManagedProvider, build_source_orchestration_plan,
orchestrate_sources_with_providers,
ApplicationExternalProvider, CleanupGuard, ClusterControlProfile, ClusterMode,
ClusterWaitHandle, DeploymentPolicy, FeedHandle, FeedRuntime, HttpReadinessRequirement,
Metrics, NodeClients, NodeControlHandle, ObservabilityCapabilityProvider,
ObservabilityInputs, RequiresNodeControl, Runner, RuntimeAssembly, Scenario,
SourceOrchestrationPlan, SourceProviders, StaticManagedProvider,
build_source_orchestration_plan, orchestrate_sources_with_providers,
},
topology::DeploymentDescriptor,
};
@ -163,6 +163,7 @@ impl<E: ComposeDeployEnv> DeploymentOrchestrator<E> {
node_clients,
scenario.duration(),
scenario.expectation_cooldown(),
scenario.cluster_control_profile(),
observability.telemetry_handle()?,
feed,
node_control,
@ -269,6 +270,7 @@ impl<E: ComposeDeployEnv> DeploymentOrchestrator<E> {
descriptors: prepared.descriptors.clone(),
duration: scenario.duration(),
expectation_cooldown: scenario.expectation_cooldown(),
cluster_control_profile: scenario.cluster_control_profile(),
telemetry,
environment: &mut prepared.environment,
node_control,
@ -389,6 +391,7 @@ struct RuntimeBuildInput<'a, E: ComposeDeployEnv> {
descriptors: E::Deployment,
duration: Duration,
expectation_cooldown: Duration,
cluster_control_profile: ClusterControlProfile,
telemetry: Metrics,
environment: &'a mut StackEnvironment,
node_control: Option<Arc<dyn NodeControlHandle<E>>>,
@ -414,6 +417,7 @@ async fn build_compose_runtime<E: ComposeDeployEnv>(
node_clients,
input.duration,
input.expectation_cooldown,
input.cluster_control_profile,
input.telemetry,
feed,
input.node_control,
@ -461,6 +465,7 @@ fn build_runtime_assembly<E: ComposeDeployEnv>(
node_clients: NodeClients<E>,
run_duration: Duration,
expectation_cooldown: Duration,
cluster_control_profile: ClusterControlProfile,
telemetry: Metrics,
feed: <E::FeedRuntime as FeedRuntime>::Feed,
node_control: Option<Arc<dyn NodeControlHandle<E>>>,
@ -471,6 +476,7 @@ fn build_runtime_assembly<E: ComposeDeployEnv>(
node_clients,
run_duration,
expectation_cooldown,
cluster_control_profile,
telemetry,
feed,
)

View File

@ -5,9 +5,9 @@ use kube::Client;
use reqwest::Url;
use testing_framework_core::{
scenario::{
Application, ApplicationExternalProvider, CleanupGuard, ClusterMode, ClusterWaitHandle,
Deployer, DynError, FeedHandle, FeedRuntime, HttpReadinessRequirement, Metrics,
MetricsError, NodeClients, ObservabilityCapabilityProvider, ObservabilityInputs,
Application, ApplicationExternalProvider, CleanupGuard, ClusterControlProfile, ClusterMode,
ClusterWaitHandle, Deployer, DynError, FeedHandle, FeedRuntime, HttpReadinessRequirement,
Metrics, MetricsError, NodeClients, ObservabilityCapabilityProvider, ObservabilityInputs,
RequiresNodeControl, Runner, RuntimeAssembly, Scenario, SourceOrchestrationPlan,
SourceProviders, StaticManagedProvider, build_source_orchestration_plan,
orchestrate_sources_with_providers,
@ -236,6 +236,7 @@ where
node_clients,
scenario.duration(),
scenario.expectation_cooldown(),
scenario.cluster_control_profile(),
telemetry,
feed,
)
@ -503,6 +504,7 @@ fn build_runner_parts<E: K8sDeployEnv, Caps>(
runtime.node_clients,
scenario.duration(),
scenario.expectation_cooldown(),
scenario.cluster_control_profile(),
runtime.telemetry,
runtime.feed,
cluster_wait,
@ -643,6 +645,7 @@ fn build_k8s_runtime_assembly<E: K8sDeployEnv>(
node_clients: NodeClients<E>,
duration: Duration,
expectation_cooldown: Duration,
cluster_control_profile: ClusterControlProfile,
telemetry: Metrics,
feed: Feed<E>,
cluster_wait: Arc<dyn ClusterWaitHandle<E>>,
@ -652,6 +655,7 @@ fn build_k8s_runtime_assembly<E: K8sDeployEnv>(
node_clients,
duration,
expectation_cooldown,
cluster_control_profile,
telemetry,
feed,
)

View File

@ -10,10 +10,10 @@ use std::{
use async_trait::async_trait;
use testing_framework_core::{
scenario::{
Application, CleanupGuard, Deployer, DeploymentPolicy, DynError, FeedHandle, FeedRuntime,
HttpReadinessRequirement, Metrics, NodeClients, NodeControlCapability, NodeControlHandle,
RetryPolicy, Runner, RuntimeAssembly, Scenario, ScenarioError, SourceOrchestrationPlan,
build_source_orchestration_plan, spawn_feed,
Application, CleanupGuard, ClusterControlProfile, Deployer, DeploymentPolicy, DynError,
FeedHandle, FeedRuntime, HttpReadinessRequirement, Metrics, NodeClients,
NodeControlCapability, NodeControlHandle, RetryPolicy, Runner, RuntimeAssembly, Scenario,
ScenarioError, SourceOrchestrationPlan, build_source_orchestration_plan, spawn_feed,
},
topology::DeploymentDescriptor,
};
@ -211,6 +211,7 @@ impl<E: LocalDeployerEnv> ProcessDeployer<E> {
node_clients,
scenario.duration(),
scenario.expectation_cooldown(),
scenario.cluster_control_profile(),
None,
)
.await?;
@ -248,6 +249,7 @@ impl<E: LocalDeployerEnv> ProcessDeployer<E> {
node_clients,
scenario.duration(),
scenario.expectation_cooldown(),
scenario.cluster_control_profile(),
Some(node_control),
)
.await?;
@ -483,6 +485,7 @@ async fn run_context_for<E: Application>(
node_clients: NodeClients<E>,
duration: Duration,
expectation_cooldown: Duration,
cluster_control_profile: ClusterControlProfile,
node_control: Option<Arc<dyn NodeControlHandle<E>>>,
) -> Result<RuntimeContext<E>, ProcessDeployerError> {
if node_clients.is_empty() {
@ -495,6 +498,7 @@ async fn run_context_for<E: Application>(
node_clients,
duration,
expectation_cooldown,
cluster_control_profile,
Metrics::empty(),
feed,
);