2025-12-10 07:41:14 +01:00
|
|
|
use std::collections::HashSet;
|
|
|
|
|
|
2025-12-18 22:23:02 +01:00
|
|
|
use thiserror::Error;
|
2025-12-10 07:41:14 +01:00
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
|
nodes::{
|
2025-12-18 22:23:02 +01:00
|
|
|
common::node::SpawnNodeError,
|
2026-01-27 13:12:45 +01:00
|
|
|
node::{Node, apply_node_config_patch, create_node_config},
|
2025-12-10 07:41:14 +01:00
|
|
|
},
|
2026-01-27 13:12:45 +01:00
|
|
|
scenario,
|
2025-12-10 07:41:14 +01:00
|
|
|
topology::{
|
2025-12-18 22:48:45 +01:00
|
|
|
config::{TopologyBuildError, TopologyBuilder, TopologyConfig},
|
2026-01-27 13:12:45 +01:00
|
|
|
generation::{GeneratedNodeConfig, find_expected_peer_counts},
|
2026-01-25 10:11:16 +02:00
|
|
|
readiness::{NetworkReadiness, ReadinessCheck, ReadinessError},
|
2025-12-10 07:41:14 +01:00
|
|
|
utils::multiaddr_port,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// Runtime representation of a spawned topology with running nodes.
|
|
|
|
|
pub struct Topology {
|
2026-01-26 08:26:15 +01:00
|
|
|
pub(crate) nodes: Vec<Node>,
|
2025-12-10 07:41:14 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-26 08:26:15 +01:00
|
|
|
pub type DeployedNodes = Vec<Node>;
|
2025-12-16 03:03:54 +01:00
|
|
|
|
2025-12-18 22:23:02 +01:00
|
|
|
#[derive(Debug, Error)]
|
|
|
|
|
pub enum SpawnTopologyError {
|
2025-12-18 22:48:45 +01:00
|
|
|
#[error(transparent)]
|
|
|
|
|
Build(#[from] TopologyBuildError),
|
2025-12-18 22:23:02 +01:00
|
|
|
#[error(transparent)]
|
|
|
|
|
Node(#[from] SpawnNodeError),
|
2026-01-27 13:12:45 +01:00
|
|
|
#[error("node config patch failed for node-{index}: {source}")]
|
|
|
|
|
ConfigPatch {
|
|
|
|
|
index: usize,
|
|
|
|
|
source: scenario::DynError,
|
|
|
|
|
},
|
2025-12-18 22:23:02 +01:00
|
|
|
}
|
|
|
|
|
|
2025-12-10 07:41:14 +01:00
|
|
|
impl Topology {
|
2025-12-18 22:23:02 +01:00
|
|
|
pub async fn spawn(config: TopologyConfig) -> Result<Self, SpawnTopologyError> {
|
2025-12-18 22:48:45 +01:00
|
|
|
let generated = TopologyBuilder::new(config.clone()).build()?;
|
2026-01-27 13:12:45 +01:00
|
|
|
let nodes = Self::spawn_nodes(generated.nodes()).await?;
|
2025-12-10 07:41:14 +01:00
|
|
|
|
2026-01-26 08:26:15 +01:00
|
|
|
Ok(Self { nodes })
|
2025-12-10 07:41:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn spawn_with_empty_membership(
|
|
|
|
|
config: TopologyConfig,
|
|
|
|
|
ids: &[[u8; 32]],
|
|
|
|
|
blend_ports: &[u16],
|
2025-12-18 22:23:02 +01:00
|
|
|
) -> Result<Self, SpawnTopologyError> {
|
2025-12-10 07:41:14 +01:00
|
|
|
let generated = TopologyBuilder::new(config.clone())
|
|
|
|
|
.with_ids(ids.to_vec())
|
|
|
|
|
.with_blend_ports(blend_ports.to_vec())
|
2025-12-18 22:48:45 +01:00
|
|
|
.build()?;
|
2025-12-10 07:41:14 +01:00
|
|
|
|
2026-01-27 13:12:45 +01:00
|
|
|
let nodes = Self::spawn_nodes(generated.nodes()).await?;
|
2025-12-10 07:41:14 +01:00
|
|
|
|
2026-01-26 08:26:15 +01:00
|
|
|
Ok(Self { nodes })
|
2025-12-10 07:41:14 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-26 08:26:15 +01:00
|
|
|
pub(crate) async fn spawn_nodes(
|
2026-01-27 13:12:45 +01:00
|
|
|
nodes: &[GeneratedNodeConfig],
|
2025-12-18 22:23:02 +01:00
|
|
|
) -> Result<DeployedNodes, SpawnTopologyError> {
|
2026-01-27 13:12:45 +01:00
|
|
|
let mut spawned = Vec::new();
|
|
|
|
|
for node in nodes {
|
|
|
|
|
let mut config = create_node_config(node.general.clone());
|
|
|
|
|
|
|
|
|
|
if let Some(patch) = node.config_patch.as_ref() {
|
|
|
|
|
config = apply_node_config_patch(config, patch).map_err(|source| {
|
|
|
|
|
SpawnTopologyError::ConfigPatch {
|
|
|
|
|
index: node.index,
|
|
|
|
|
source,
|
|
|
|
|
}
|
|
|
|
|
})?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let label = format!("node-{}", node.index);
|
|
|
|
|
spawned.push(Node::spawn(config, &label).await?);
|
2025-12-10 07:41:14 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-27 13:12:45 +01:00
|
|
|
Ok(spawned)
|
2025-12-10 07:41:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[must_use]
|
2026-01-26 08:26:15 +01:00
|
|
|
pub fn nodes(&self) -> &[Node] {
|
|
|
|
|
&self.nodes
|
2025-12-10 07:41:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn wait_network_ready(&self) -> Result<(), ReadinessError> {
|
|
|
|
|
let listen_ports = self.node_listen_ports();
|
|
|
|
|
if listen_ports.len() <= 1 {
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let initial_peer_ports = self.node_initial_peer_ports();
|
|
|
|
|
let expected_peer_counts = find_expected_peer_counts(&listen_ports, &initial_peer_ports);
|
|
|
|
|
let labels = self.node_labels();
|
|
|
|
|
|
|
|
|
|
let check = NetworkReadiness {
|
|
|
|
|
topology: self,
|
|
|
|
|
expected_peer_counts: &expected_peer_counts,
|
|
|
|
|
labels: &labels,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
check.wait().await?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn node_listen_ports(&self) -> Vec<u16> {
|
2026-01-26 08:26:15 +01:00
|
|
|
self.nodes
|
2025-12-10 07:41:14 +01:00
|
|
|
.iter()
|
2026-01-29 09:33:25 +02:00
|
|
|
.map(|node| node.config().user.network.backend.swarm.port)
|
2025-12-10 07:41:14 +01:00
|
|
|
.collect()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn node_initial_peer_ports(&self) -> Vec<HashSet<u16>> {
|
2026-01-26 08:26:15 +01:00
|
|
|
self.nodes
|
2025-12-10 07:41:14 +01:00
|
|
|
.iter()
|
|
|
|
|
.map(|node| {
|
|
|
|
|
node.config()
|
2026-01-29 09:33:25 +02:00
|
|
|
.user
|
2025-12-10 07:41:14 +01:00
|
|
|
.network
|
|
|
|
|
.backend
|
|
|
|
|
.initial_peers
|
|
|
|
|
.iter()
|
|
|
|
|
.filter_map(multiaddr_port)
|
|
|
|
|
.collect::<HashSet<u16>>()
|
|
|
|
|
})
|
|
|
|
|
.collect()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn node_labels(&self) -> Vec<String> {
|
2026-01-26 08:26:15 +01:00
|
|
|
self.nodes
|
2025-12-10 07:41:14 +01:00
|
|
|
.iter()
|
|
|
|
|
.enumerate()
|
2026-01-29 09:33:25 +02:00
|
|
|
.map(|(idx, node)| {
|
|
|
|
|
format!(
|
|
|
|
|
"node#{idx}@{}",
|
|
|
|
|
node.config().user.network.backend.swarm.port
|
|
|
|
|
)
|
|
|
|
|
})
|
2025-12-10 07:41:14 +01:00
|
|
|
.collect()
|
|
|
|
|
}
|
|
|
|
|
}
|