refactor(compose): redesign runtime around stack access cfgsync

This commit is contained in:
andrussal 2026-04-10 16:59:40 +02:00
parent e5406badd2
commit 29637acadf
12 changed files with 819 additions and 233 deletions

View File

@ -13,7 +13,7 @@ use crate::{
discover_attachable_services, discover_service_container_id,
inspect_api_container_port_label, inspect_mapped_tcp_ports,
},
env::ComposeDeployEnv,
env::{ComposeDeployEnv, readiness_http_path},
};
pub(super) struct ComposeAttachProvider<E: ComposeDeployEnv> {
@ -198,7 +198,7 @@ async fn collect_readiness_endpoints<E: ComposeDeployEnv>(
let container_id = discover_service_container_id(project, service).await?;
let api_port = discover_api_port(&container_id).await?;
let mut endpoint = build_service_endpoint(host, api_port)?;
endpoint.set_path(<E as ComposeDeployEnv>::node_readiness_path());
endpoint.set_path(readiness_http_path::<E>());
endpoints.push(endpoint);
}

View File

@ -14,11 +14,7 @@ use testing_framework_core::scenario::{
internal::{CleanupGuard, FeedHandle},
};
use crate::{
env::{ComposeCfgsyncEnv, ComposeDeployEnv},
errors::ComposeRunnerError,
lifecycle::cleanup::RunnerCleanup,
};
use crate::{env::ComposeDeployEnv, errors::ComposeRunnerError, lifecycle::cleanup::RunnerCleanup};
/// Docker Compose-based deployer for test scenarios.
#[derive(Clone, Copy)]
@ -145,7 +141,7 @@ impl<E: ComposeDeployEnv> ComposeDeployer<E> {
scenario: &Scenario<E, Caps>,
) -> Result<(Runner<E>, ComposeDeploymentMetadata), ComposeRunnerError>
where
E: ComposeCfgsyncEnv,
E: ComposeDeployEnv,
Caps: RequiresNodeControl + ObservabilityCapabilityProvider + Send + Sync,
{
let deployer = Self {
@ -163,7 +159,7 @@ impl<E: ComposeDeployEnv> ComposeDeployer<E> {
impl<E, Caps> Deployer<E, Caps> for ComposeDeployer<E>
where
Caps: RequiresNodeControl + ObservabilityCapabilityProvider + Send + Sync,
E: ComposeCfgsyncEnv,
E: ComposeDeployEnv,
{
type Error = ComposeRunnerError;

View File

@ -28,7 +28,7 @@ use super::{
};
use crate::{
docker::control::{ComposeAttachedNodeControl, ComposeNodeControl},
env::{ComposeCfgsyncEnv, ComposeDeployEnv},
env::ComposeDeployEnv,
errors::ComposeRunnerError,
infrastructure::{
environment::StackEnvironment,
@ -41,14 +41,14 @@ const PRINT_ENDPOINTS_ENV: &str = "TESTNET_PRINT_ENDPOINTS";
pub struct DeploymentOrchestrator<E>
where
E: ComposeCfgsyncEnv,
E: ComposeDeployEnv,
{
deployer: ComposeDeployer<E>,
}
impl<E> DeploymentOrchestrator<E>
where
E: ComposeCfgsyncEnv,
E: ComposeDeployEnv,
{
pub const fn new(deployer: ComposeDeployer<E>) -> Self {
Self { deployer }
@ -656,7 +656,7 @@ fn profiling_url(host: &str, api_port: u16) -> String {
struct PreparedDeployment<E>
where
E: ComposeCfgsyncEnv,
E: ComposeDeployEnv,
{
environment: StackEnvironment,
descriptors: <E as Application>::Deployment,
@ -667,7 +667,7 @@ async fn prepare_deployment<E>(
observability: &ObservabilityInputs,
) -> Result<PreparedDeployment<E>, ComposeRunnerError>
where
E: ComposeCfgsyncEnv,
E: ComposeDeployEnv,
{
let DeploymentContext {
environment,

View File

@ -3,7 +3,7 @@ use std::marker::PhantomData;
use tracing::{debug, info, warn};
use crate::{
env::ComposeDeployEnv,
env::{ComposeDeployEnv, node_container_ports},
errors::ComposeRunnerError,
infrastructure::{
environment::StackEnvironment,
@ -20,7 +20,7 @@ impl<E: ComposeDeployEnv> PortManager<E> {
environment: &mut StackEnvironment,
descriptors: &E::Deployment,
) -> Result<HostPortMapping, ComposeRunnerError> {
let nodes = E::node_container_ports(descriptors).map_err(|source| {
let nodes = node_container_ports::<E>(descriptors).map_err(|source| {
ComposeRunnerError::Config(crate::errors::ConfigError::Descriptor { source })
})?;
debug!(

View File

@ -4,7 +4,7 @@ use testing_framework_core::scenario::HttpReadinessRequirement;
use tracing::{info, warn};
use crate::{
env::ComposeDeployEnv,
env::{ComposeDeployEnv, wait_remote_readiness as remote_readiness_future},
errors::ComposeRunnerError,
infrastructure::{environment::StackEnvironment, ports::HostPortMapping},
lifecycle::readiness::ensure_nodes_ready_with_ports,
@ -55,7 +55,10 @@ async fn wait_remote_readiness<E: ComposeDeployEnv>(
run_readiness_check(
environment,
"remote readiness probe failed",
E::wait_remote_readiness(descriptors, host_ports, requirement)
remote_readiness_future::<E>(descriptors, host_ports, requirement)
.map_err(|source| {
ComposeRunnerError::Readiness(crate::errors::StackReadinessError::Remote { source })
})?
.await
.map_err(|source| {
ComposeRunnerError::Readiness(crate::errors::StackReadinessError::Remote { source })

View File

@ -6,7 +6,7 @@ use tracing::info;
use crate::{
docker::ensure_docker_available,
env::ComposeCfgsyncEnv,
env::ComposeDeployEnv,
errors::ComposeRunnerError,
infrastructure::environment::{
StackEnvironment, ensure_supported_topology, prepare_environment,
@ -15,14 +15,14 @@ use crate::{
pub struct DeploymentSetup<'a, E>
where
E: ComposeCfgsyncEnv,
E: ComposeDeployEnv,
{
descriptors: &'a <E as Application>::Deployment,
}
pub struct DeploymentContext<'a, E>
where
E: ComposeCfgsyncEnv,
E: ComposeDeployEnv,
{
pub descriptors: &'a <E as Application>::Deployment,
pub environment: StackEnvironment,
@ -30,7 +30,7 @@ where
impl<'a, E> DeploymentSetup<'a, E>
where
E: ComposeCfgsyncEnv,
E: ComposeDeployEnv,
{
pub fn new(descriptors: &'a <E as Application>::Deployment) -> Self {
Self { descriptors }

View File

@ -5,7 +5,7 @@ mod node;
pub use node::{
BinaryConfigNodeSpec, EnvEntry, LoopbackNodeRuntimeSpec, NodeDescriptor,
binary_config_node_runtime_spec, build_binary_config_node_descriptors,
build_loopback_node_descriptors,
build_binary_config_node_descriptors_with_file_name, build_loopback_node_descriptors,
};
/// Top-level docker-compose descriptor built from an environment-specific

View File

@ -223,6 +223,35 @@ pub fn build_binary_config_node_descriptors(
})
}
pub fn build_binary_config_node_descriptors_with_file_name(
node_count: usize,
spec: &BinaryConfigNodeSpec,
file_name_for_index: impl Fn(usize) -> String,
) -> Vec<NodeDescriptor> {
(0..node_count)
.map(|index| {
let file_name = file_name_for_index(index);
NodeDescriptor::with_loopback_ports(
node_identifier(index),
env::var(&spec.image_env_var).unwrap_or_else(|_| spec.default_image.clone()),
vec![
spec.binary_path.clone(),
"--config".to_owned(),
spec.config_container_path.clone(),
],
vec![format!(
"./stack/configs/{file_name}:{}:ro",
spec.config_container_path
)],
vec![],
spec.container_ports.clone(),
vec![EnvEntry::new("RUST_LOG", &spec.rust_log)],
env::var(&spec.platform_env_var).ok(),
)
})
.collect()
}
pub fn binary_config_node_runtime_spec(
index: usize,
spec: &BinaryConfigNodeSpec,

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,6 @@ use anyhow::anyhow;
use reqwest::Url;
use testing_framework_core::{
adjust_timeout,
cfgsync::StaticArtifactRenderer,
scenario::{Application, internal::CleanupGuard},
topology::DeploymentDescriptor,
};
@ -25,7 +24,10 @@ use crate::{
ensure_image_present,
workspace::ComposeWorkspace,
},
env::{ComposeCfgsyncEnv, ComposeConfigServerMode, ComposeDeployEnv, ConfigServerHandle},
env::{
ComposeConfigServerMode, ComposeDeployEnv, ConfigServerHandle, cfgsync_container_spec,
cfgsync_server_mode, cfgsync_start_timeout, compose_descriptor, prepare_compose_configs,
},
errors::{ComposeRunnerError, ConfigError, WorkspaceError},
infrastructure::template::write_compose_file,
lifecycle::cleanup::RunnerCleanup,
@ -197,7 +199,7 @@ pub fn update_cfgsync_logged<E>(
metrics_otlp_ingest_url: Option<&Url>,
) -> Result<(), ComposeRunnerError>
where
E: ComposeCfgsyncEnv,
E: ComposeDeployEnv,
{
info!(cfgsync_port, "updating cfgsync configuration");
@ -217,24 +219,26 @@ pub async fn start_cfgsync_stage<E: ComposeDeployEnv>(
cfgsync_port: u16,
project_name: &str,
) -> Result<Option<Box<dyn ConfigServerHandle>>, ComposeRunnerError> {
if matches!(E::cfgsync_server_mode(), ComposeConfigServerMode::Disabled) {
if matches!(
cfgsync_server_mode::<E>(),
ComposeConfigServerMode::Disabled
) {
return Ok(None);
}
info!(cfgsync_port = cfgsync_port, "launching cfgsync server");
let network = compose_network_name(project_name);
let spec = E::cfgsync_container_spec(&workspace.cfgsync_path, cfgsync_port, &network).map_err(
|source| {
let spec = cfgsync_container_spec::<E>(&workspace.cfgsync_path, cfgsync_port, &network)
.map_err(|source| {
ComposeRunnerError::Config(ConfigError::CfgsyncStart {
port: cfgsync_port,
source,
})
},
)?;
})?;
let handle = start_docker_config_server(
&spec,
adjust_timeout(E::cfgsync_start_timeout()),
adjust_timeout(cfgsync_start_timeout::<E>()),
"docker run cfgsync server",
)
.await
@ -259,9 +263,9 @@ pub fn configure_cfgsync<E>(
metrics_otlp_ingest_url: Option<&Url>,
) -> Result<(), ConfigError>
where
E: ComposeCfgsyncEnv,
E: ComposeDeployEnv,
{
E::write_cfgsync_config(
prepare_compose_configs::<E>(
&workspace.cfgsync_path,
descriptors,
cfgsync_port,
@ -303,7 +307,7 @@ pub fn write_compose_artifacts<E: ComposeDeployEnv>(
workspace_root = %workspace.root.display(),
"building compose descriptor"
);
let descriptor = E::compose_descriptor(descriptors, cfgsync_port)
let descriptor = compose_descriptor::<E>(descriptors, cfgsync_port)
.map_err(|source| ConfigError::Descriptor { source })?;
let compose_path = workspace.root.join("compose.generated.yml");
@ -360,7 +364,7 @@ pub async fn prepare_environment<E>(
metrics_otlp_ingest_url: Option<&Url>,
) -> Result<StackEnvironment, ComposeRunnerError>
where
E: ComposeCfgsyncEnv,
E: ComposeDeployEnv,
{
let prepared = prepare_stack_artifacts::<E>(descriptors, metrics_otlp_ingest_url).await?;
let mut cfgsync_handle = start_cfgsync_for_prepared::<E>(&prepared).await?;
@ -376,7 +380,7 @@ pub async fn prepare_environment_manual<E>(
metrics_otlp_ingest_url: Option<&Url>,
) -> Result<StackEnvironment, ComposeRunnerError>
where
E: ComposeCfgsyncEnv,
E: ComposeDeployEnv,
{
let prepared = prepare_stack_artifacts::<E>(descriptors, metrics_otlp_ingest_url).await?;
let cfgsync_handle = start_cfgsync_for_prepared::<E>(&prepared).await?;
@ -391,7 +395,7 @@ async fn prepare_stack_artifacts<E>(
metrics_otlp_ingest_url: Option<&Url>,
) -> Result<PreparedEnvironment, ComposeRunnerError>
where
E: ComposeDeployEnv + StaticArtifactRenderer<Deployment = <E as Application>::Deployment>,
E: ComposeDeployEnv,
{
let workspace = prepare_workspace_logged()?;
let cfgsync_port = allocate_cfgsync_port()?;
@ -419,15 +423,15 @@ async fn ensure_compose_images_present<E: ComposeDeployEnv>(
descriptors: &E::Deployment,
cfgsync_port: u16,
) -> Result<(), ComposeRunnerError> {
let descriptor = E::compose_descriptor(descriptors, 0)
let descriptor = compose_descriptor::<E>(descriptors, 0)
.map_err(|source| ComposeRunnerError::Config(ConfigError::Descriptor { source }))?;
let mut images = descriptor
.nodes()
.iter()
.map(|node| (node.image().to_owned(), node.platform().map(str::to_owned)))
.collect::<BTreeSet<_>>();
if matches!(E::cfgsync_server_mode(), ComposeConfigServerMode::Docker) {
let cfgsync_spec = E::cfgsync_container_spec(
if matches!(cfgsync_server_mode::<E>(), ComposeConfigServerMode::Docker) {
let cfgsync_spec = cfgsync_container_spec::<E>(
&workspace.cfgsync_path,
cfgsync_port,
&compose_network_name("compose-image-check"),

View File

@ -10,7 +10,7 @@ pub use deployer::{ComposeDeployer, ComposeDeploymentMetadata};
pub use descriptor::{
BinaryConfigNodeSpec, ComposeDescriptor, EnvEntry, LoopbackNodeRuntimeSpec, NodeDescriptor,
binary_config_node_runtime_spec, build_binary_config_node_descriptors,
build_loopback_node_descriptors,
build_binary_config_node_descriptors_with_file_name, build_loopback_node_descriptors,
};
pub use docker::{
commands::{ComposeCommandError, compose_down, compose_up, dump_compose_logs},
@ -21,8 +21,9 @@ pub use docker::{
platform::host_gateway_entry,
};
pub use env::{
ComposeConfigServerMode, ComposeDeployEnv, ComposeReadinessProbe, ConfigServerHandle,
discovered_node_access,
ComposeAccess, ComposeCfgsync, ComposeConfigContext, ComposeConfigServerMode, ComposeConfigs,
ComposeDeployEnv, ComposeNodeConfigFileName, ComposeNodes, ComposeReadinessProbe,
ComposeRuntime, ComposeStack, ConfigServerHandle, discovered_node_access,
};
pub use errors::ComposeRunnerError;
pub use infrastructure::{

View File

@ -4,7 +4,7 @@ use testing_framework_core::scenario::{HttpReadinessRequirement, NodeClients};
use tokio::time::sleep;
use crate::{
env::ComposeDeployEnv,
env::{ComposeDeployEnv, build_node_clients, wait_for_nodes},
errors::{NodeClientError, StackReadinessError},
infrastructure::ports::{HostPortMapping, compose_runner_host},
};
@ -21,7 +21,8 @@ pub async fn ensure_nodes_ready_with_ports<E: ComposeDeployEnv>(
}
let host = compose_runner_host();
E::wait_for_nodes(ports, &host, requirement)
wait_for_nodes::<E>(ports, &host, requirement)
.map_err(|source| StackReadinessError::Remote { source })?
.await
.map_err(|source| StackReadinessError::Remote { source })
}
@ -41,6 +42,6 @@ pub fn build_node_clients_with_ports<E: ComposeDeployEnv>(
mapping: &HostPortMapping,
host: &str,
) -> Result<NodeClients<E>, NodeClientError> {
E::build_node_clients(descriptors, mapping, host)
build_node_clients::<E>(descriptors, mapping, host)
.map_err(|source| NodeClientError::Build { source })
}