n8n/packages/testing/containers/services/load-balancer.ts
Declan Carroll 3a33a448b0
Some checks failed
CI: Master (Build, Test, Lint) / Build for Github Cache (push) Has been cancelled
CI: Master (Build, Test, Lint) / Unit tests (22.x) (push) Has been cancelled
CI: Master (Build, Test, Lint) / Unit tests (24.14.1) (push) Has been cancelled
CI: Master (Build, Test, Lint) / Unit tests (25.x) (push) Has been cancelled
CI: Master (Build, Test, Lint) / Lint (push) Has been cancelled
CI: Master (Build, Test, Lint) / Performance (push) Has been cancelled
CI: Master (Build, Test, Lint) / Notify Slack on failure (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Prepare Docker (coverage) (push) Has been cancelled
Util: Update Node Popularity / approve-and-automerge (push) Has been cancelled
Test: E2E Coverage Weekly / E2E (coverage) (push) Has been cancelled
Test: E2E Coverage Weekly / Aggregate Coverage (push) Has been cancelled
Release: Schedule Patch Release PRs / Create patch release PR (${{ matrix.track }}) (beta) (push) Has been cancelled
Release: Schedule Patch Release PRs / Create patch release PR (${{ matrix.track }}) (stable) (push) Has been cancelled
Release: Schedule Patch Release PRs / Create patch release PR (${{ matrix.track }}) (v1) (push) Has been cancelled
test(benchmark): Question-driven Playwright benchmark suite with tiered topology and rich diagnostics (#29024)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 21:14:08 +00:00

107 lines
2.6 KiB
TypeScript

import { GenericContainer, Wait } from 'testcontainers';
import { createSilentLogConsumer } from '../helpers/utils';
import { TEST_CONTAINER_IMAGES } from '../test-containers';
import type { LoadBalancerPolicy, Service, ServiceResult } from './types';
export interface LoadBalancerConfig {
mainCount: number;
hostPort?: number;
policy: LoadBalancerPolicy;
}
export interface LoadBalancerMeta {
hostPort: number;
baseUrl: string;
}
export type LoadBalancerResult = ServiceResult<LoadBalancerMeta>;
function buildCaddyConfig(upstreamServers: string[], policy: LoadBalancerPolicy): string {
const backends = upstreamServers.join(' ');
return `
:80 {
# Reverse proxy with load balancing
reverse_proxy ${backends} {
lb_policy ${policy}
# Health check
health_uri /healthz
health_interval 10s
# Timeouts
transport http {
dial_timeout 60s
read_timeout 60s
write_timeout 60s
}
}
# Set max request body size
request_body {
max_size 50MB
}
}`;
}
export const loadBalancer: Service<LoadBalancerResult> = {
description: 'Caddy load balancer',
shouldStart: (ctx) => ctx.needsLoadBalancer,
getOptions(ctx) {
return {
mainCount: ctx.mains,
hostPort: ctx.allocatedPorts.loadBalancer,
policy: ctx.config.lbPolicy ?? 'first',
} as LoadBalancerConfig;
},
env(result) {
return {
WEBHOOK_URL: result.meta.baseUrl,
N8N_PROXY_HOPS: '1',
};
},
async start(network, projectName, config?: unknown): Promise<LoadBalancerResult> {
const { mainCount, hostPort, policy } = config as LoadBalancerConfig;
const { consumer, throwWithLogs } = createSilentLogConsumer();
// Generate upstream server addresses
const upstreamServers = Array.from(
{ length: mainCount },
(_, index) => `${projectName}-n8n-main-${index + 1}:5678`,
);
const caddyConfig = buildCaddyConfig(upstreamServers, policy);
try {
const container = await new GenericContainer(TEST_CONTAINER_IMAGES.caddy)
.withNetwork(network)
.withExposedPorts(hostPort ? { container: 80, host: hostPort } : 80)
.withCopyContentToContainer([{ content: caddyConfig, target: '/etc/caddy/Caddyfile' }])
.withWaitStrategy(Wait.forListeningPorts())
.withLabels({
'com.docker.compose.project': projectName,
'com.docker.compose.service': 'caddy-lb',
})
.withName(`${projectName}-caddy-lb`)
.withReuse()
.withLogConsumer(consumer)
.start();
const actualHostPort = container.getMappedPort(80);
return {
container,
meta: {
hostPort: actualHostPort,
baseUrl: `http://localhost:${actualHostPort}`,
},
};
} catch (error) {
return throwWithLogs(error);
}
},
};