mirror of
https://github.com/logos-blockchain/logos-blockchain-testing.git
synced 2026-04-01 08:43:06 +00:00
824 lines
24 KiB
Rust
824 lines
24 KiB
Rust
use std::{sync::Arc, time::Duration};
|
|
|
|
use thiserror::Error;
|
|
use tracing::{debug, info};
|
|
|
|
use super::{
|
|
Application, AttachSource, DeploymentPolicy, DynError, ExternalNodeSource,
|
|
HttpReadinessRequirement, NodeControlCapability, ObservabilityCapability, ScenarioSources,
|
|
SourceReadinessPolicy,
|
|
builder_ops::CoreBuilderAccess,
|
|
expectation::Expectation,
|
|
runtime::{
|
|
context::RunMetrics,
|
|
orchestration::{SourceModeName, SourceOrchestrationPlan, SourceOrchestrationPlanError},
|
|
},
|
|
workload::Workload,
|
|
};
|
|
use crate::topology::{DeploymentDescriptor, DeploymentProvider, DeploymentSeed, DynTopologyError};
|
|
|
|
const MIN_EXPECTATION_FALLBACK_SECS: u64 = 10;
|
|
const MIN_RUN_DURATION_SECS: u64 = 10;
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum ScenarioBuildError {
|
|
#[error("topology build failed: {0}")]
|
|
Topology(#[source] DynTopologyError),
|
|
#[error("workload '{name}' failed to initialize")]
|
|
WorkloadInit { name: String, source: DynError },
|
|
#[error("expectation '{name}' failed to initialize")]
|
|
ExpectationInit { name: String, source: DynError },
|
|
#[error("invalid scenario source configuration: {message}")]
|
|
SourceConfiguration { message: String },
|
|
#[error("scenario source mode '{mode}' is not wired into deployers yet")]
|
|
SourceModeNotWiredYet { mode: &'static str },
|
|
}
|
|
|
|
/// Immutable scenario definition used by the runner, workloads, and
|
|
/// expectations.
|
|
pub struct Scenario<E: Application, Caps = ()> {
|
|
deployment: E::Deployment,
|
|
workloads: Vec<Arc<dyn Workload<E>>>,
|
|
expectations: Vec<Box<dyn Expectation<E>>>,
|
|
duration: Duration,
|
|
expectation_cooldown: Duration,
|
|
deployment_policy: DeploymentPolicy,
|
|
sources: ScenarioSources,
|
|
source_readiness_policy: SourceReadinessPolicy,
|
|
source_orchestration_plan: SourceOrchestrationPlan,
|
|
capabilities: Caps,
|
|
}
|
|
|
|
impl<E: Application, Caps> Scenario<E, Caps> {
|
|
fn new(
|
|
deployment: E::Deployment,
|
|
workloads: Vec<Arc<dyn Workload<E>>>,
|
|
expectations: Vec<Box<dyn Expectation<E>>>,
|
|
duration: Duration,
|
|
expectation_cooldown: Duration,
|
|
deployment_policy: DeploymentPolicy,
|
|
sources: ScenarioSources,
|
|
source_readiness_policy: SourceReadinessPolicy,
|
|
source_orchestration_plan: SourceOrchestrationPlan,
|
|
capabilities: Caps,
|
|
) -> Self {
|
|
Self {
|
|
deployment,
|
|
workloads,
|
|
expectations,
|
|
duration,
|
|
expectation_cooldown,
|
|
deployment_policy,
|
|
sources,
|
|
source_readiness_policy,
|
|
source_orchestration_plan,
|
|
capabilities,
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn deployment(&self) -> &E::Deployment {
|
|
&self.deployment
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn workloads(&self) -> &[Arc<dyn Workload<E>>] {
|
|
&self.workloads
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn expectations(&self) -> &[Box<dyn Expectation<E>>] {
|
|
&self.expectations
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn expectations_mut(&mut self) -> &mut [Box<dyn Expectation<E>>] {
|
|
&mut self.expectations
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn duration(&self) -> Duration {
|
|
self.duration
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn expectation_cooldown(&self) -> Duration {
|
|
self.expectation_cooldown
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn http_readiness_requirement(&self) -> HttpReadinessRequirement {
|
|
self.deployment_policy.readiness_requirement
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn deployment_policy(&self) -> DeploymentPolicy {
|
|
self.deployment_policy
|
|
}
|
|
|
|
#[must_use]
|
|
/// Selected source readiness policy.
|
|
///
|
|
/// This is currently reserved for future mixed-source orchestration and
|
|
/// does not change runtime behavior yet.
|
|
pub const fn source_readiness_policy(&self) -> SourceReadinessPolicy {
|
|
self.source_readiness_policy
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn sources(&self) -> &ScenarioSources {
|
|
&self.sources
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn source_orchestration_plan(&self) -> &SourceOrchestrationPlan {
|
|
&self.source_orchestration_plan
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn capabilities(&self) -> &Caps {
|
|
&self.capabilities
|
|
}
|
|
}
|
|
|
|
/// Scenario builder entry point.
|
|
pub struct Builder<E: Application, Caps = ()> {
|
|
deployment_provider: Box<dyn DeploymentProvider<E::Deployment>>,
|
|
topology_seed: Option<DeploymentSeed>,
|
|
workloads: Vec<Box<dyn Workload<E>>>,
|
|
expectations: Vec<Box<dyn Expectation<E>>>,
|
|
duration: Duration,
|
|
expectation_cooldown: Option<Duration>,
|
|
deployment_policy: DeploymentPolicy,
|
|
sources: ScenarioSources,
|
|
source_readiness_policy: SourceReadinessPolicy,
|
|
capabilities: Caps,
|
|
}
|
|
|
|
pub struct ScenarioBuilder<E: Application> {
|
|
inner: Builder<E, ()>,
|
|
}
|
|
|
|
pub struct NodeControlScenarioBuilder<E: Application> {
|
|
inner: Builder<E, NodeControlCapability>,
|
|
}
|
|
|
|
pub struct ObservabilityScenarioBuilder<E: Application> {
|
|
inner: Builder<E, ObservabilityCapability>,
|
|
}
|
|
|
|
macro_rules! impl_common_builder_methods {
|
|
($builder:ident) => {
|
|
impl<E: Application> $builder<E> {
|
|
#[must_use]
|
|
pub fn map_deployment_provider(
|
|
self,
|
|
f: impl FnOnce(
|
|
Box<dyn DeploymentProvider<E::Deployment>>,
|
|
) -> Box<dyn DeploymentProvider<E::Deployment>>,
|
|
) -> Self {
|
|
self.map_core_builder(|builder| builder.map_deployment_provider(f))
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn with_deployment_provider(
|
|
self,
|
|
deployment_provider: Box<dyn DeploymentProvider<E::Deployment>>,
|
|
) -> Self {
|
|
self.map_core_builder(|builder| {
|
|
builder.with_deployment_provider(deployment_provider)
|
|
})
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn with_deployment_seed(self, seed: DeploymentSeed) -> Self {
|
|
self.map_core_builder(|builder| builder.with_deployment_seed(seed))
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn with_workload<W>(self, workload: W) -> Self
|
|
where
|
|
W: Workload<E> + 'static,
|
|
{
|
|
self.map_core_builder(|builder| builder.with_workload(workload))
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn with_workload_boxed(self, workload: Box<dyn Workload<E>>) -> Self {
|
|
self.map_core_builder(|builder| builder.with_workload_boxed(workload))
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn with_expectation<Exp>(self, expectation: Exp) -> Self
|
|
where
|
|
Exp: Expectation<E> + 'static,
|
|
{
|
|
self.map_core_builder(|builder| builder.with_expectation(expectation))
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn with_expectation_boxed(self, expectation: Box<dyn Expectation<E>>) -> Self {
|
|
self.map_core_builder(|builder| builder.with_expectation_boxed(expectation))
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn with_run_duration(self, duration: Duration) -> Self {
|
|
self.map_core_builder(|builder| builder.with_run_duration(duration))
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn with_expectation_cooldown(self, cooldown: Duration) -> Self {
|
|
self.map_core_builder(|builder| builder.with_expectation_cooldown(cooldown))
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn with_http_readiness_requirement(
|
|
self,
|
|
requirement: HttpReadinessRequirement,
|
|
) -> Self {
|
|
self.map_core_builder(|builder| {
|
|
builder.with_http_readiness_requirement(requirement)
|
|
})
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn with_deployment_policy(self, policy: DeploymentPolicy) -> Self {
|
|
self.map_core_builder(|builder| builder.with_deployment_policy(policy))
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn with_attach_source(self, attach: AttachSource) -> Self {
|
|
self.map_core_builder(|builder| builder.with_attach_source(attach))
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn with_external_node(self, node: ExternalNodeSource) -> Self {
|
|
self.map_core_builder(|builder| builder.with_external_node(node))
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn with_source_readiness_policy(self, policy: SourceReadinessPolicy) -> Self {
|
|
self.map_core_builder(|builder| builder.with_source_readiness_policy(policy))
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn with_external_only_sources(self) -> Self {
|
|
self.map_core_builder(|builder| builder.with_external_only_sources())
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn run_duration(&self) -> Duration {
|
|
self.core_builder_ref().run_duration()
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
impl<E: Application> CoreBuilderAccess for ScenarioBuilder<E> {
|
|
type Env = E;
|
|
type Caps = ();
|
|
|
|
fn map_core_builder(
|
|
mut self,
|
|
f: impl FnOnce(Builder<Self::Env, Self::Caps>) -> Builder<Self::Env, Self::Caps>,
|
|
) -> Self {
|
|
self.inner = f(self.inner);
|
|
self
|
|
}
|
|
|
|
fn core_builder_ref(&self) -> &Builder<Self::Env, Self::Caps> {
|
|
&self.inner
|
|
}
|
|
|
|
fn core_builder_mut(&mut self) -> &mut Builder<Self::Env, Self::Caps> {
|
|
&mut self.inner
|
|
}
|
|
}
|
|
|
|
impl<E: Application> CoreBuilderAccess for NodeControlScenarioBuilder<E> {
|
|
type Env = E;
|
|
type Caps = NodeControlCapability;
|
|
|
|
fn map_core_builder(
|
|
mut self,
|
|
f: impl FnOnce(Builder<Self::Env, Self::Caps>) -> Builder<Self::Env, Self::Caps>,
|
|
) -> Self {
|
|
self.inner = f(self.inner);
|
|
self
|
|
}
|
|
|
|
fn core_builder_ref(&self) -> &Builder<Self::Env, Self::Caps> {
|
|
&self.inner
|
|
}
|
|
|
|
fn core_builder_mut(&mut self) -> &mut Builder<Self::Env, Self::Caps> {
|
|
&mut self.inner
|
|
}
|
|
}
|
|
|
|
impl<E: Application> CoreBuilderAccess for ObservabilityScenarioBuilder<E> {
|
|
type Env = E;
|
|
type Caps = ObservabilityCapability;
|
|
|
|
fn map_core_builder(
|
|
mut self,
|
|
f: impl FnOnce(Builder<Self::Env, Self::Caps>) -> Builder<Self::Env, Self::Caps>,
|
|
) -> Self {
|
|
self.inner = f(self.inner);
|
|
self
|
|
}
|
|
|
|
fn core_builder_ref(&self) -> &Builder<Self::Env, Self::Caps> {
|
|
&self.inner
|
|
}
|
|
|
|
fn core_builder_mut(&mut self) -> &mut Builder<Self::Env, Self::Caps> {
|
|
&mut self.inner
|
|
}
|
|
}
|
|
|
|
impl<E: Application, Caps: Default> Builder<E, Caps> {
|
|
#[must_use]
|
|
/// Start a builder from a topology provider.
|
|
pub fn new(deployment_provider: Box<dyn DeploymentProvider<E::Deployment>>) -> Self {
|
|
Self {
|
|
deployment_provider,
|
|
topology_seed: None,
|
|
workloads: Vec::new(),
|
|
expectations: Vec::new(),
|
|
duration: Duration::ZERO,
|
|
expectation_cooldown: None,
|
|
deployment_policy: DeploymentPolicy::default(),
|
|
sources: ScenarioSources::default(),
|
|
source_readiness_policy: SourceReadinessPolicy::default(),
|
|
capabilities: Caps::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<E: Application> ScenarioBuilder<E> {
|
|
#[must_use]
|
|
pub fn new(deployment_provider: Box<dyn DeploymentProvider<E::Deployment>>) -> Self {
|
|
Self {
|
|
inner: Builder::new(deployment_provider),
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn enable_node_control(self) -> NodeControlScenarioBuilder<E> {
|
|
NodeControlScenarioBuilder {
|
|
inner: self.inner.with_capabilities(NodeControlCapability),
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn enable_observability(self) -> ObservabilityScenarioBuilder<E> {
|
|
ObservabilityScenarioBuilder {
|
|
inner: self
|
|
.inner
|
|
.with_capabilities(ObservabilityCapability::default()),
|
|
}
|
|
}
|
|
|
|
pub fn build(self) -> Result<Scenario<E>, ScenarioBuildError> {
|
|
self.inner.build()
|
|
}
|
|
|
|
pub(crate) fn with_observability(
|
|
self,
|
|
observability: ObservabilityCapability,
|
|
) -> ObservabilityScenarioBuilder<E> {
|
|
ObservabilityScenarioBuilder {
|
|
inner: self.inner.with_capabilities(observability),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl_common_builder_methods!(ScenarioBuilder);
|
|
|
|
impl<E: Application> NodeControlScenarioBuilder<E> {
|
|
pub fn build(self) -> Result<Scenario<E, NodeControlCapability>, ScenarioBuildError> {
|
|
self.inner.build()
|
|
}
|
|
}
|
|
|
|
impl_common_builder_methods!(NodeControlScenarioBuilder);
|
|
|
|
impl<E: Application> ObservabilityScenarioBuilder<E> {
|
|
pub fn build(self) -> Result<Scenario<E, ObservabilityCapability>, ScenarioBuildError> {
|
|
self.inner.build()
|
|
}
|
|
|
|
pub(crate) fn capabilities_mut(&mut self) -> &mut ObservabilityCapability {
|
|
self.inner.capabilities_mut()
|
|
}
|
|
}
|
|
|
|
impl_common_builder_methods!(ObservabilityScenarioBuilder);
|
|
|
|
impl<E: Application, Caps> Builder<E, Caps> {
|
|
#[must_use]
|
|
/// Transform the existing deployment provider while preserving all
|
|
/// accumulated builder state.
|
|
pub fn map_deployment_provider(
|
|
mut self,
|
|
f: impl FnOnce(
|
|
Box<dyn DeploymentProvider<E::Deployment>>,
|
|
) -> Box<dyn DeploymentProvider<E::Deployment>>,
|
|
) -> Self {
|
|
self.deployment_provider = f(self.deployment_provider);
|
|
self
|
|
}
|
|
|
|
#[must_use]
|
|
/// Replace the topology provider while preserving all accumulated builder
|
|
/// state.
|
|
pub fn with_deployment_provider(
|
|
mut self,
|
|
deployment_provider: Box<dyn DeploymentProvider<E::Deployment>>,
|
|
) -> Self {
|
|
self.deployment_provider = deployment_provider;
|
|
self
|
|
}
|
|
|
|
#[must_use]
|
|
/// Internal capability transition helper.
|
|
pub(crate) fn with_capabilities<NewCaps>(self, capabilities: NewCaps) -> Builder<E, NewCaps> {
|
|
let Self {
|
|
deployment_provider,
|
|
topology_seed,
|
|
workloads,
|
|
expectations,
|
|
duration,
|
|
expectation_cooldown,
|
|
deployment_policy,
|
|
sources,
|
|
source_readiness_policy,
|
|
..
|
|
} = self;
|
|
|
|
Builder {
|
|
deployment_provider,
|
|
topology_seed,
|
|
workloads,
|
|
expectations,
|
|
duration,
|
|
expectation_cooldown,
|
|
deployment_policy,
|
|
sources,
|
|
source_readiness_policy,
|
|
capabilities,
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn capabilities(&self) -> &Caps {
|
|
&self.capabilities
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn capabilities_mut(&mut self) -> &mut Caps {
|
|
&mut self.capabilities
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn run_duration(&self) -> Duration {
|
|
self.duration
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn expectation_cooldown_override(&self) -> Option<Duration> {
|
|
self.expectation_cooldown
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn http_readiness_requirement(&self) -> HttpReadinessRequirement {
|
|
self.deployment_policy.readiness_requirement
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn deployment_policy(&self) -> DeploymentPolicy {
|
|
self.deployment_policy
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn with_deployment_seed(mut self, seed: DeploymentSeed) -> Self {
|
|
self.topology_seed = Some(seed);
|
|
self
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn with_workload<W>(mut self, workload: W) -> Self
|
|
where
|
|
W: Workload<E> + 'static,
|
|
{
|
|
self.add_workload(Box::new(workload));
|
|
self
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn with_workload_boxed(mut self, workload: Box<dyn Workload<E>>) -> Self {
|
|
self.add_workload(workload);
|
|
self
|
|
}
|
|
|
|
#[must_use]
|
|
/// Add a standalone expectation not tied to a workload.
|
|
pub fn with_expectation<Exp>(mut self, expectation: Exp) -> Self
|
|
where
|
|
Exp: Expectation<E> + 'static,
|
|
{
|
|
self.add_expectation(Box::new(expectation));
|
|
self
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn with_expectation_boxed(mut self, expectation: Box<dyn Expectation<E>>) -> Self {
|
|
self.add_expectation(expectation);
|
|
self
|
|
}
|
|
|
|
#[must_use]
|
|
/// Configure the intended run duration.
|
|
pub const fn with_run_duration(mut self, duration: Duration) -> Self {
|
|
self.duration = duration;
|
|
self
|
|
}
|
|
|
|
#[must_use]
|
|
/// Override the expectation cooldown used by the runner.
|
|
pub const fn with_expectation_cooldown(mut self, cooldown: Duration) -> Self {
|
|
self.expectation_cooldown = Some(cooldown);
|
|
self
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn with_http_readiness_requirement(
|
|
mut self,
|
|
requirement: HttpReadinessRequirement,
|
|
) -> Self {
|
|
self.deployment_policy.readiness_requirement = requirement;
|
|
self
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn with_deployment_policy(mut self, policy: DeploymentPolicy) -> Self {
|
|
self.deployment_policy = policy;
|
|
self
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn with_attach_source(mut self, attach: AttachSource) -> Self {
|
|
self.sources.set_attach(attach);
|
|
self
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn with_external_node(mut self, node: ExternalNodeSource) -> Self {
|
|
self.sources.add_external_node(node);
|
|
self
|
|
}
|
|
|
|
#[must_use]
|
|
/// Configure source readiness policy metadata.
|
|
///
|
|
/// This is currently reserved for future mixed-source orchestration and
|
|
/// does not change runtime behavior yet.
|
|
pub fn with_source_readiness_policy(mut self, policy: SourceReadinessPolicy) -> Self {
|
|
self.source_readiness_policy = policy;
|
|
self
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn with_external_only_sources(mut self) -> Self {
|
|
self.sources.set_external_only();
|
|
self
|
|
}
|
|
|
|
fn add_workload(&mut self, workload: Box<dyn Workload<E>>) {
|
|
self.expectations.extend(workload.expectations());
|
|
self.workloads.push(workload);
|
|
}
|
|
|
|
fn add_expectation(&mut self, expectation: Box<dyn Expectation<E>>) {
|
|
self.expectations.push(expectation);
|
|
}
|
|
|
|
#[must_use]
|
|
/// Finalize the scenario, computing run metrics and initializing
|
|
/// components.
|
|
pub fn build(self) -> Result<Scenario<E, Caps>, ScenarioBuildError> {
|
|
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);
|
|
let source_orchestration_plan =
|
|
build_source_orchestration_plan(parts.sources(), parts.source_readiness_policy)?;
|
|
|
|
initialize_components(
|
|
&descriptors,
|
|
&run_metrics,
|
|
&mut parts.workloads,
|
|
&mut parts.expectations,
|
|
)?;
|
|
let workloads: Vec<Arc<dyn Workload<E>>> =
|
|
parts.workloads.into_iter().map(Arc::from).collect();
|
|
|
|
info!(
|
|
nodes = descriptors.node_count(),
|
|
duration_secs = run_plan.duration.as_secs(),
|
|
workloads = workloads.len(),
|
|
expectations = parts.expectations.len(),
|
|
"scenario built"
|
|
);
|
|
|
|
Ok(Scenario::new(
|
|
descriptors,
|
|
workloads,
|
|
parts.expectations,
|
|
run_plan.duration,
|
|
run_plan.expectation_cooldown,
|
|
parts.deployment_policy,
|
|
parts.sources,
|
|
parts.source_readiness_policy,
|
|
source_orchestration_plan,
|
|
parts.capabilities,
|
|
))
|
|
}
|
|
}
|
|
|
|
struct RunPlan {
|
|
duration: Duration,
|
|
expectation_cooldown: Duration,
|
|
}
|
|
|
|
struct BuilderParts<E: Application, Caps> {
|
|
deployment_provider: Box<dyn DeploymentProvider<E::Deployment>>,
|
|
topology_seed: Option<DeploymentSeed>,
|
|
workloads: Vec<Box<dyn Workload<E>>>,
|
|
expectations: Vec<Box<dyn Expectation<E>>>,
|
|
duration: Duration,
|
|
expectation_cooldown: Option<Duration>,
|
|
deployment_policy: DeploymentPolicy,
|
|
sources: ScenarioSources,
|
|
source_readiness_policy: SourceReadinessPolicy,
|
|
capabilities: Caps,
|
|
}
|
|
|
|
impl<E: Application, Caps> BuilderParts<E, Caps> {
|
|
fn from_builder(builder: Builder<E, Caps>) -> Self {
|
|
let Builder {
|
|
deployment_provider,
|
|
topology_seed,
|
|
workloads,
|
|
expectations,
|
|
duration,
|
|
expectation_cooldown,
|
|
deployment_policy,
|
|
sources,
|
|
source_readiness_policy,
|
|
capabilities,
|
|
..
|
|
} = builder;
|
|
|
|
Self {
|
|
deployment_provider,
|
|
topology_seed,
|
|
workloads,
|
|
expectations,
|
|
duration,
|
|
expectation_cooldown,
|
|
deployment_policy,
|
|
sources,
|
|
source_readiness_policy,
|
|
capabilities,
|
|
}
|
|
}
|
|
|
|
fn resolve_deployment(&self) -> Result<E::Deployment, ScenarioBuildError> {
|
|
self.deployment_provider
|
|
.build(self.topology_seed.as_ref())
|
|
.map_err(ScenarioBuildError::Topology)
|
|
}
|
|
|
|
fn run_plan(&self) -> RunPlan {
|
|
RunPlan {
|
|
duration: enforce_min_duration(self.duration),
|
|
expectation_cooldown: expectation_cooldown_for(self.expectation_cooldown),
|
|
}
|
|
}
|
|
|
|
fn sources(&self) -> &ScenarioSources {
|
|
&self.sources
|
|
}
|
|
}
|
|
|
|
fn build_source_orchestration_plan(
|
|
sources: &ScenarioSources,
|
|
readiness_policy: SourceReadinessPolicy,
|
|
) -> Result<SourceOrchestrationPlan, ScenarioBuildError> {
|
|
SourceOrchestrationPlan::try_from_sources(sources, readiness_policy)
|
|
.map_err(source_plan_error_to_build_error)
|
|
}
|
|
|
|
fn source_plan_error_to_build_error(error: SourceOrchestrationPlanError) -> ScenarioBuildError {
|
|
match error {
|
|
SourceOrchestrationPlanError::SourceModeNotWiredYet { mode } => {
|
|
ScenarioBuildError::SourceModeNotWiredYet {
|
|
mode: source_mode_name(mode),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const fn source_mode_name(mode: SourceModeName) -> &'static str {
|
|
match mode {
|
|
SourceModeName::Attached => "Attached",
|
|
}
|
|
}
|
|
|
|
impl<E: Application> Builder<E, ()> {
|
|
#[must_use]
|
|
pub fn enable_node_control(self) -> Builder<E, NodeControlCapability> {
|
|
self.with_capabilities(NodeControlCapability)
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn enable_observability(self) -> Builder<E, ObservabilityCapability> {
|
|
self.with_capabilities(ObservabilityCapability::default())
|
|
}
|
|
}
|
|
|
|
fn initialize_components<E: Application>(
|
|
descriptors: &E::Deployment,
|
|
run_metrics: &RunMetrics,
|
|
workloads: &mut [Box<dyn Workload<E>>],
|
|
expectations: &mut [Box<dyn Expectation<E>>],
|
|
) -> Result<(), ScenarioBuildError> {
|
|
initialize_workloads(descriptors, run_metrics, workloads)?;
|
|
|
|
initialize_expectations(descriptors, run_metrics, expectations)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn initialize_workloads<E: Application>(
|
|
descriptors: &E::Deployment,
|
|
run_metrics: &RunMetrics,
|
|
workloads: &mut [Box<dyn Workload<E>>],
|
|
) -> Result<(), ScenarioBuildError> {
|
|
for workload in workloads {
|
|
debug!(workload = workload.name(), "initializing workload");
|
|
let name = workload.name().to_owned();
|
|
|
|
workload
|
|
.init(descriptors, run_metrics)
|
|
.map_err(|source| workload_init_error(name, source))?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn initialize_expectations<E: Application>(
|
|
descriptors: &E::Deployment,
|
|
run_metrics: &RunMetrics,
|
|
expectations: &mut [Box<dyn Expectation<E>>],
|
|
) -> Result<(), ScenarioBuildError> {
|
|
for expectation in expectations {
|
|
debug!(expectation = expectation.name(), "initializing expectation");
|
|
let name = expectation.name().to_owned();
|
|
|
|
expectation
|
|
.init(descriptors, run_metrics)
|
|
.map_err(|source| expectation_init_error(name, source))?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn workload_init_error(name: String, source: DynError) -> ScenarioBuildError {
|
|
ScenarioBuildError::WorkloadInit { name, source }
|
|
}
|
|
|
|
fn expectation_init_error(name: String, source: DynError) -> ScenarioBuildError {
|
|
ScenarioBuildError::ExpectationInit { name, source }
|
|
}
|
|
|
|
fn enforce_min_duration(requested: Duration) -> Duration {
|
|
let min_duration = min_run_duration();
|
|
|
|
requested.max(min_duration)
|
|
}
|
|
|
|
fn default_expectation_cooldown() -> Duration {
|
|
Duration::from_secs(MIN_EXPECTATION_FALLBACK_SECS)
|
|
}
|
|
|
|
fn expectation_cooldown_for(override_value: Option<Duration>) -> Duration {
|
|
override_value.unwrap_or_else(default_expectation_cooldown)
|
|
}
|
|
|
|
fn min_run_duration() -> Duration {
|
|
Duration::from_secs(MIN_RUN_DURATION_SECS)
|
|
}
|