Constrain manual cluster creation to deployers

This commit is contained in:
andrussal 2026-01-19 13:27:01 +01:00
parent 90816f9e02
commit df5641f573
7 changed files with 91 additions and 29 deletions

View File

@ -1,11 +1,11 @@
use std::{env, time::Duration}; use std::time::Duration;
use anyhow::Result; use anyhow::Result;
use testing_framework_core::{ use testing_framework_core::{
scenario::{PeerSelection, StartNodeOptions}, scenario::{PeerSelection, StartNodeOptions},
topology::config::TopologyConfig, topology::config::TopologyConfig,
}; };
use testing_framework_runner_local::ManualCluster; use testing_framework_runner_local::LocalDeployer;
use tokio::time::sleep; use tokio::time::sleep;
use tracing_subscriber::fmt::try_init; use tracing_subscriber::fmt::try_init;
@ -15,18 +15,12 @@ const MAX_HEIGHT_DIFF: u64 = 5;
#[ignore = "run manually with `cargo test -p runner-examples -- --ignored manual_cluster_two_clusters_merge`"] #[ignore = "run manually with `cargo test -p runner-examples -- --ignored manual_cluster_two_clusters_merge`"]
async fn manual_cluster_two_clusters_merge() -> Result<()> { async fn manual_cluster_two_clusters_merge() -> Result<()> {
let _ = try_init(); let _ = try_init();
let workspace_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("examples crate should live under the workspace root");
let circuits_dir = workspace_root.join("testing-framework/assets/stack/circuits");
unsafe {
env::set_var("LOGOS_BLOCKCHAIN_CIRCUITS", circuits_dir);
}
// Required env vars (set on the command line when running this test): // Required env vars (set on the command line when running this test):
// - `POL_PROOF_DEV_MODE=true` // - `POL_PROOF_DEV_MODE=true`
// - `RUST_LOG=info` (optional) // - `RUST_LOG=info` (optional)
let config = TopologyConfig::with_node_numbers(2, 0); let config = TopologyConfig::with_node_numbers(2, 0);
let cluster = ManualCluster::from_config(config)?; let deployer = LocalDeployer::new();
let cluster = deployer.manual_cluster(config)?;
// Nodes are stopped automatically when the cluster is dropped. // Nodes are stopped automatically when the cluster is dropped.
println!("starting validator a"); println!("starting validator a");

View File

@ -1,4 +1,5 @@
pub mod kzg; pub mod kzg;
pub mod manual;
pub mod nodes; pub mod nodes;
pub mod scenario; pub mod scenario;
pub mod topology; pub mod topology;

View File

@ -0,0 +1,21 @@
use async_trait::async_trait;
use crate::scenario::{DynError, StartNodeOptions, StartedNode};
/// Interface for imperative, deployer-backed manual clusters.
#[async_trait]
pub trait ManualClusterHandle: Send + Sync {
async fn start_validator_with(
&self,
name: &str,
options: StartNodeOptions,
) -> Result<StartedNode, DynError>;
async fn start_executor_with(
&self,
name: &str,
options: StartNodeOptions,
) -> Result<StartedNode, DynError>;
async fn wait_network_ready(&self) -> Result<(), DynError>;
}

View File

@ -13,15 +13,15 @@ version = "0.1.0"
workspace = true workspace = true
[dependencies] [dependencies]
async-trait = "0.1" async-trait = "0.1"
logos-blockchain-executor = { workspace = true } logos-blockchain-executor = { workspace = true }
nomos-libp2p = { workspace = true } nomos-libp2p = { workspace = true }
nomos-network = { workspace = true } nomos-network = { workspace = true }
nomos-node = { workspace = true } nomos-node = { workspace = true }
nomos-utils = { workspace = true } nomos-utils = { workspace = true }
rand = { workspace = true } rand = { workspace = true }
testing-framework-config = { workspace = true } testing-framework-config = { workspace = true }
testing-framework-core = { path = "../../core" } testing-framework-core = { path = "../../core" }
thiserror = { workspace = true } thiserror = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }

View File

@ -2,6 +2,6 @@ mod manual;
mod node_control; mod node_control;
mod runner; mod runner;
pub use manual::{ManualCluster, ManualClusterError}; pub use manual::{LocalManualCluster, ManualClusterError};
pub use node_control::{LocalDynamicError, LocalDynamicNodes, LocalDynamicSeed}; pub use node_control::{LocalDynamicError, LocalDynamicNodes, LocalDynamicSeed};
pub use runner::{LocalDeployer, LocalDeployerError}; pub use runner::{LocalDeployer, LocalDeployerError};

View File

@ -1,6 +1,7 @@
use testing_framework_core::{ use testing_framework_core::{
manual::ManualClusterHandle,
nodes::ApiClient, nodes::ApiClient,
scenario::{StartNodeOptions, StartedNode}, scenario::{DynError, StartNodeOptions, StartedNode},
topology::{ topology::{
config::{TopologyBuildError, TopologyBuilder, TopologyConfig}, config::{TopologyBuildError, TopologyBuilder, TopologyConfig},
readiness::{ReadinessCheck, ReadinessError}, readiness::{ReadinessCheck, ReadinessError},
@ -26,12 +27,12 @@ pub enum ManualClusterError {
} }
/// Imperative, in-process cluster that can start nodes on demand. /// Imperative, in-process cluster that can start nodes on demand.
pub struct ManualCluster { pub struct LocalManualCluster {
nodes: LocalDynamicNodes, nodes: LocalDynamicNodes,
} }
impl ManualCluster { impl LocalManualCluster {
pub fn from_config(config: TopologyConfig) -> Result<Self, ManualClusterError> { pub(crate) fn from_config(config: TopologyConfig) -> Result<Self, ManualClusterError> {
let builder = TopologyBuilder::new(config); let builder = TopologyBuilder::new(config);
let descriptors = builder let descriptors = builder
.build() .build()
@ -100,8 +101,35 @@ impl ManualCluster {
} }
} }
impl Drop for ManualCluster { impl Drop for LocalManualCluster {
fn drop(&mut self) { fn drop(&mut self) {
self.stop_all(); self.stop_all();
} }
} }
#[async_trait::async_trait]
impl ManualClusterHandle for LocalManualCluster {
async fn start_validator_with(
&self,
name: &str,
options: StartNodeOptions,
) -> Result<StartedNode, DynError> {
self.start_validator_with(name, options)
.await
.map_err(|err| err.into())
}
async fn start_executor_with(
&self,
name: &str,
options: StartNodeOptions,
) -> Result<StartedNode, DynError> {
self.start_executor_with(name, options)
.await
.map_err(|err| err.into())
}
async fn wait_network_ready(&self) -> Result<(), DynError> {
self.wait_network_ready().await.map_err(|err| err.into())
}
}

View File

@ -7,6 +7,7 @@ use testing_framework_core::{
RunContext, Runner, Scenario, ScenarioError, spawn_block_feed, RunContext, Runner, Scenario, ScenarioError, spawn_block_feed,
}, },
topology::{ topology::{
config::TopologyConfig,
deployment::{SpawnTopologyError, Topology}, deployment::{SpawnTopologyError, Topology},
readiness::ReadinessError, readiness::ReadinessError,
}, },
@ -14,7 +15,10 @@ use testing_framework_core::{
use thiserror::Error; use thiserror::Error;
use tracing::{debug, info}; use tracing::{debug, info};
use crate::node_control::{LocalDynamicNodes, LocalDynamicSeed}; use crate::{
manual::{LocalManualCluster, ManualClusterError},
node_control::{LocalDynamicNodes, LocalDynamicSeed},
};
/// Spawns validators and executors as local processes, reusing the existing /// Spawns validators and executors as local processes, reusing the existing
/// integration harness. /// integration harness.
#[derive(Clone)] #[derive(Clone)]
@ -130,13 +134,25 @@ impl LocalDeployer {
Self::default() Self::default()
} }
async fn prepare_topology<C>(scenario: &Scenario<C>) -> Result<Topology, LocalDeployerError> { /// Build a manual cluster using this deployer's local implementation.
pub fn manual_cluster(
&self,
config: TopologyConfig,
) -> Result<LocalManualCluster, ManualClusterError> {
LocalManualCluster::from_config(config)
}
async fn prepare_topology<Caps>(
scenario: &Scenario<Caps>,
) -> Result<Topology, LocalDeployerError> {
let descriptors = scenario.topology(); let descriptors = scenario.topology();
info!( info!(
validators = descriptors.validators().len(), validators = descriptors.validators().len(),
executors = descriptors.executors().len(), executors = descriptors.executors().len(),
"spawning local validators/executors" "spawning local validators/executors"
); );
let topology = descriptors let topology = descriptors
.clone() .clone()
.spawn_local() .spawn_local()
@ -149,6 +165,7 @@ impl LocalDeployer {
})?; })?;
info!("local nodes are ready"); info!("local nodes are ready");
Ok(topology) Ok(topology)
} }
} }
@ -161,6 +178,7 @@ impl Default for LocalDeployer {
async fn wait_for_readiness(topology: &Topology) -> Result<(), ReadinessError> { async fn wait_for_readiness(topology: &Topology) -> Result<(), ReadinessError> {
info!("waiting for local network readiness"); info!("waiting for local network readiness");
topology.wait_network_ready().await?; topology.wait_network_ready().await?;
Ok(()) Ok(())
} }