Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,26 @@ List<Container> listChildContainers() {
void startAmbassadorContainer() {
if (!this.ambassadorPortMappings.isEmpty()) {
this.ambassadorContainer.start();
connectAmbassadorToComposeNetworks();
}
}

private void connectAmbassadorToComposeNetworks() {
Set<String> composeNetworkIds = listChildContainers()
.stream()
.filter(container -> {
return container.getNetworkSettings() != null && container.getNetworkSettings().getNetworks() != null;
})
.flatMap(container -> container.getNetworkSettings().getNetworks().entrySet().stream())
.filter(entry -> !Arrays.asList("bridge", "host", "none").contains(entry.getKey()))
.map(entry -> entry.getValue().getNetworkID())
.collect(Collectors.toSet());

for (String composeNetworkId : composeNetworkIds) {
this.dockerClient.connectToNetworkCmd()
.withContainerId(this.ambassadorContainer.getContainerId())
.withNetworkId(composeNetworkId)
.exec();
}
}

Expand All @@ -282,27 +302,25 @@ public void withExposedService(String serviceName, int servicePort, @NonNull Wai
/*
* For every service/port pair that needs to be exposed, we register a target on an 'ambassador container'.
*
* The ambassador container's role is to link (within the Docker network) to one of the
* compose services, and proxy TCP network I/O out to a port that the ambassador container
* exposes.
* The ambassador container's role is to proxy TCP network I/O from one of the compose
* services out to a port that the ambassador container exposes.
*
* After Docker Compose starts, the ambassador container is connected to the compose
* network so it can resolve service names via Docker's built-in DNS.
*
* This avoids the need for the docker compose file to explicitly expose ports on all the
* services.
*
* {@link GenericContainer} should ensure that the ambassador container is on the same network
* as the rest of the compose environment.
*/

// Ambassador container will be started together after docker compose has started
int ambassadorPort = nextAmbassadorPort.getAndIncrement();
ambassadorPortMappings
.computeIfAbsent(serviceInstanceName, __ -> new ConcurrentHashMap<>())
.put(servicePort, ambassadorPort);
ambassadorContainer.withTarget(ambassadorPort, serviceInstanceName, servicePort);
ambassadorContainer.addLink(
new FutureContainer(this.project + this.composeSeparator + serviceInstanceName),
serviceInstanceName
);
// Use the full container name as the socat target so Docker DNS can resolve it
// on the compose network (e.g., "project-redis-1" instead of just "redis-1")
String containerName = this.project + this.composeSeparator + serviceInstanceName;
ambassadorContainer.withTarget(ambassadorPort, containerName, servicePort);
addWaitStrategy(serviceInstanceName, waitStrategy);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package org.testcontainers.junit;

import org.junit.jupiter.api.AutoClose;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.ComposeContainer;
import org.testcontainers.utility.DockerImageName;
import redis.clients.jedis.Jedis;

import java.io.File;

import static org.assertj.core.api.Assertions.assertThat;

class ComposeWithMultipleNetworkTest extends BaseComposeTest {

@AutoClose
public ComposeContainer environment = new ComposeContainer(
DockerImageName.parse("docker:25.0.5"),
new File("src/test/resources/v2-compose-test-with-multiple-networks.yml")
)
.withExposedService("redis-1", REDIS_PORT)
.withExposedService("another-redis-1", REDIS_PORT);

ComposeWithMultipleNetworkTest() {
environment.start();
}

@Override
protected ComposeContainer getEnvironment() {
return environment;
}

@Test
void redisInstanceInDifferentNetworkCanBeUsed() {
Jedis jedis = new Jedis(
getEnvironment().getServiceHost("another-redis-1", REDIS_PORT),
getEnvironment().getServicePort("another-redis-1", REDIS_PORT)
);

jedis.incr("test");
jedis.incr("test");
jedis.incr("test");

assertThat(jedis.get("test")).as("A redis instance defined in compose can be used in isolation").isEqualTo("3");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version: '2.1'
services:
redis:
image: redis
networks:
- redis-net
another-redis:
image: redis
networks:
- another-redis-net

networks:
redis-net:
another-redis-net:
Loading