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 testing_framework_core::{
scenario::{PeerSelection, StartNodeOptions},
topology::config::TopologyConfig,
};
use testing_framework_runner_local::ManualCluster;
use testing_framework_runner_local::LocalDeployer;
use tokio::time::sleep;
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`"]
async fn manual_cluster_two_clusters_merge() -> Result<()> {
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):
// - `POL_PROOF_DEV_MODE=true`
// - `RUST_LOG=info` (optional)
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.
println!("starting validator a");

View File

@ -1,4 +1,5 @@
pub mod kzg;
pub mod manual;
pub mod nodes;
pub mod scenario;
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
[dependencies]
async-trait = "0.1"
async-trait = "0.1"
logos-blockchain-executor = { workspace = true }
nomos-libp2p = { workspace = true }
nomos-network = { workspace = true }
nomos-node = { workspace = true }
nomos-utils = { workspace = true }
rand = { workspace = true }
testing-framework-config = { workspace = true }
testing-framework-core = { path = "../../core" }
thiserror = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }
nomos-libp2p = { workspace = true }
nomos-network = { workspace = true }
nomos-node = { workspace = true }
nomos-utils = { workspace = true }
rand = { workspace = true }
testing-framework-config = { workspace = true }
testing-framework-core = { path = "../../core" }
thiserror = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }

View File

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

View File

@ -1,6 +1,7 @@
use testing_framework_core::{
manual::ManualClusterHandle,
nodes::ApiClient,
scenario::{StartNodeOptions, StartedNode},
scenario::{DynError, StartNodeOptions, StartedNode},
topology::{
config::{TopologyBuildError, TopologyBuilder, TopologyConfig},
readiness::{ReadinessCheck, ReadinessError},
@ -26,12 +27,12 @@ pub enum ManualClusterError {
}
/// Imperative, in-process cluster that can start nodes on demand.
pub struct ManualCluster {
pub struct LocalManualCluster {
nodes: LocalDynamicNodes,
}
impl ManualCluster {
pub fn from_config(config: TopologyConfig) -> Result<Self, ManualClusterError> {
impl LocalManualCluster {
pub(crate) fn from_config(config: TopologyConfig) -> Result<Self, ManualClusterError> {
let builder = TopologyBuilder::new(config);
let descriptors = builder
.build()
@ -100,8 +101,35 @@ impl ManualCluster {
}
}
impl Drop for ManualCluster {
impl Drop for LocalManualCluster {
fn drop(&mut self) {
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,
},
topology::{
config::TopologyConfig,
deployment::{SpawnTopologyError, Topology},
readiness::ReadinessError,
},
@ -14,7 +15,10 @@ use testing_framework_core::{
use thiserror::Error;
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
/// integration harness.
#[derive(Clone)]
@ -130,13 +134,25 @@ impl LocalDeployer {
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();
info!(
validators = descriptors.validators().len(),
executors = descriptors.executors().len(),
"spawning local validators/executors"
);
let topology = descriptors
.clone()
.spawn_local()
@ -149,6 +165,7 @@ impl LocalDeployer {
})?;
info!("local nodes are ready");
Ok(topology)
}
}
@ -161,6 +178,7 @@ impl Default for LocalDeployer {
async fn wait_for_readiness(topology: &Topology) -> Result<(), ReadinessError> {
info!("waiting for local network readiness");
topology.wait_network_ready().await?;
Ok(())
}