integration: add run ID isolation for concurrent test execution

Add run ID-based isolation to container naming and network setup to
enable multiple integration tests to run concurrently on the same
Docker daemon without conflicts.

Changes:
- hsic: Add run ID prefix to headscale container names and use dynamic
  port allocation for metrics endpoint (port 0 lets kernel assign)
- tsic: Add run ID prefix to tailscale container names
- dsic: Add run ID prefix to DERP container names
- scenario: Use run ID-aware test suite container name for network setup

Container naming now follows: {type}-{runIDShort}-{identifier}-{hash}
Example: ts-mdjtzx-1-74-fgdyls, hs-mdjtzx-pingallbyip-abc123

The run ID is obtained from HEADSCALE_INTEGRATION_RUN_ID environment
variable via dockertestutil.GetIntegrationRunID().
This commit is contained in:
Kristoffer Dalby
2026-01-09 11:18:24 +00:00
parent 84c092a9f9
commit 87c230d251
4 changed files with 60 additions and 9 deletions

View File

@@ -147,7 +147,18 @@ func New(
return nil, err
}
hostname := fmt.Sprintf("derp-%s-%s", strings.ReplaceAll(version, ".", "-"), hash)
// Include run ID in hostname for easier identification of which test run owns this container
runID := dockertestutil.GetIntegrationRunID()
var hostname string
if runID != "" {
// Use last 6 chars of run ID (the random hash part) for brevity
runIDShort := runID[len(runID)-6:]
hostname = fmt.Sprintf("derp-%s-%s-%s", runIDShort, strings.ReplaceAll(version, ".", "-"), hash)
} else {
hostname = fmt.Sprintf("derp-%s-%s", strings.ReplaceAll(version, ".", "-"), hash)
}
tlsCert, tlsKey, err := integrationutil.CreateCertificate(hostname)
if err != nil {
return nil, fmt.Errorf("failed to create certificates for headscale test: %w", err)

View File

@@ -74,6 +74,7 @@ type HeadscaleInContainer struct {
// optional config
port int
extraPorts []string
hostMetricsPort string // Dynamically assigned host port for metrics/pprof access
caCerts [][]byte
hostPortBindings map[string][]string
aclPolicy *policyv2.Policy
@@ -330,7 +331,18 @@ func New(
return nil, err
}
hostname := "hs-" + hash
// Include run ID in hostname for easier identification of which test run owns this container
runID := dockertestutil.GetIntegrationRunID()
var hostname string
if runID != "" {
// Use last 6 chars of run ID (the random hash part) for brevity
runIDShort := runID[len(runID)-6:]
hostname = fmt.Sprintf("hs-%s-%s", runIDShort, hash)
} else {
hostname = "hs-" + hash
}
hsic := &HeadscaleInContainer{
hostname: hostname,
@@ -438,13 +450,13 @@ func New(
Env: env,
}
// Bind metrics port to predictable host port
// Bind metrics port to dynamic host port (kernel assigns free port)
if runOptions.PortBindings == nil {
runOptions.PortBindings = map[docker.Port][]docker.PortBinding{}
}
runOptions.PortBindings["9090/tcp"] = []docker.PortBinding{
{HostPort: "49090"},
{HostPort: "0"}, // Let kernel assign a free port
}
if len(hsic.hostPortBindings) > 0 {
@@ -540,9 +552,14 @@ func New(
hsic.container = container
// Get the dynamically assigned host port for metrics/pprof
hsic.hostMetricsPort = container.GetHostPort("9090/tcp")
log.Printf(
"Ports for %s: metrics/pprof=49090\n",
"Headscale %s metrics available at http://localhost:%s/metrics (debug at http://localhost:%s/debug/)\n",
hsic.hostname,
hsic.hostMetricsPort,
hsic.hostMetricsPort,
)
// Write the CA certificates to the container
@@ -932,6 +949,13 @@ func (t *HeadscaleInContainer) GetPort() string {
return strconv.Itoa(t.port)
}
// GetHostMetricsPort returns the dynamically assigned host port for metrics/pprof access.
// This port can be used by operators to access metrics at http://localhost:{port}/metrics
// and debug endpoints at http://localhost:{port}/debug/ while tests are running.
func (t *HeadscaleInContainer) GetHostMetricsPort() string {
return t.hostMetricsPort
}
// GetHealthEndpoint returns a health endpoint for the HeadscaleInContainer
// instance.
func (t *HeadscaleInContainer) GetHealthEndpoint() string {

View File

@@ -247,9 +247,14 @@ func (s *Scenario) AddNetwork(name string) (*dockertest.Network, error) {
// We run the test suite in a docker container that calls a couple of endpoints for
// readiness checks, this ensures that we can run the tests with individual networks
// and have the client reach the different containers
// TODO(kradalby): Can the test-suite be renamed so we can have multiple?
err = dockertestutil.AddContainerToNetwork(s.pool, network, "headscale-test-suite")
// and have the client reach the different containers.
// The container name includes the run ID to support multiple concurrent test runs.
testSuiteName := "headscale-test-suite"
if runID := dockertestutil.GetIntegrationRunID(); runID != "" {
testSuiteName = "headscale-test-suite-" + runID
}
err = dockertestutil.AddContainerToNetwork(s.pool, network, testSuiteName)
if err != nil {
return nil, fmt.Errorf("failed to add test suite container to network: %w", err)
}

View File

@@ -307,7 +307,18 @@ func New(
return nil, err
}
hostname := fmt.Sprintf("ts-%s-%s", strings.ReplaceAll(version, ".", "-"), hash)
// Include run ID in hostname for easier identification of which test run owns this container
runID := dockertestutil.GetIntegrationRunID()
var hostname string
if runID != "" {
// Use last 6 chars of run ID (the random hash part) for brevity
runIDShort := runID[len(runID)-6:]
hostname = fmt.Sprintf("ts-%s-%s-%s", runIDShort, strings.ReplaceAll(version, ".", "-"), hash)
} else {
hostname = fmt.Sprintf("ts-%s-%s", strings.ReplaceAll(version, ".", "-"), hash)
}
tsic := &TailscaleInContainer{
version: version,