140 lines
3.9 KiB
Rust
Raw Normal View History

use std::collections::HashSet;
2025-12-18 22:23:02 +01:00
use thiserror::Error;
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},
},
2026-01-27 13:12:45 +01:00
scenario,
topology::{
config::{TopologyBuildError, TopologyBuilder, TopologyConfig},
2026-01-27 13:12:45 +01:00
generation::{GeneratedNodeConfig, find_expected_peer_counts},
readiness::{NetworkReadiness, ReadinessCheck, ReadinessError},
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>,
}
2026-01-26 08:26:15 +01:00
pub type DeployedNodes = Vec<Node>;
2025-12-18 22:23:02 +01:00
#[derive(Debug, Error)]
pub enum SpawnTopologyError {
#[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
}
impl Topology {
2025-12-18 22:23:02 +01:00
pub async fn spawn(config: TopologyConfig) -> Result<Self, SpawnTopologyError> {
let generated = TopologyBuilder::new(config.clone()).build()?;
2026-01-27 13:12:45 +01:00
let nodes = Self::spawn_nodes(generated.nodes()).await?;
2026-01-26 08:26:15 +01:00
Ok(Self { nodes })
}
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> {
let generated = TopologyBuilder::new(config.clone())
.with_ids(ids.to_vec())
.with_blend_ports(blend_ports.to_vec())
.build()?;
2026-01-27 13:12:45 +01:00
let nodes = Self::spawn_nodes(generated.nodes()).await?;
2026-01-26 08:26:15 +01:00
Ok(Self { nodes })
}
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?);
}
2026-01-27 13:12:45 +01:00
Ok(spawned)
}
#[must_use]
2026-01-26 08:26:15 +01:00
pub fn nodes(&self) -> &[Node] {
&self.nodes
}
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
.iter()
.map(|node| node.config().network.backend.swarm.port)
.collect()
}
fn node_initial_peer_ports(&self) -> Vec<HashSet<u16>> {
2026-01-26 08:26:15 +01:00
self.nodes
.iter()
.map(|node| {
node.config()
.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
.iter()
.enumerate()
2026-01-26 08:26:15 +01:00
.map(|(idx, node)| format!("node#{idx}@{}", node.config().network.backend.swarm.port))
.collect()
}
}