Home > Enterprise >  Testcontainers: communication between containers mapped outside port
Testcontainers: communication between containers mapped outside port

Time:04-13

I have such test setup:

  1. MyService connects to PostgtreSQL
  2. MyService endpoint is being called from test suite

Both MyService and PostgreSQL are being run with Testcontainers.

Here is the network schema I want to achieve.

Network Schema

At first I tried to arrange communication by exposing ports.

static final PostgreSQLContainer<?> postgres =
            new PostgreSQLContainer<>(DockerImageName.parse(POSTGRES_VERSION));

static final GenericContainer<?> myService = new GenericContainer<>(DockerImageName.parse(MY_SERVICE_IMAGE))
                .withEnv(
                    Map.of(
                        "SPRING_DATASOURCE_URL", postgres.getJdbcUrl(),
                        "SPRING_DATASOURCE_USERNAME", postgres.getUsername(),
                        "SPRING_DATASOURCE_PASSWORD", postgres.getPassword()
                    )
                )
                .withExposedPorts(8080)
                .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("MyService")))

According to logs MyService couldn't establish connection to PostgreSQL.

Caused by: java.net.ConnectException: Connection refused

Then I configured both services to share the same network.

static final Network SHARED_NETWORK = Network.newNetwork();

static final PostgreSQLContainer<?> postgres =
            new PostgreSQLContainer<>(DockerImageName.parse(POSTGRES_VERSION))
                 .withNetwork(SHARED_NETWORK)
                 .withNetworkAliases("postgres");

static final GenericContainer<?> myService = new GenericContainer<>(DockerImageName.parse(MY_SERVICE_IMAGE))
                .withEnv(
                    Map.of(
                        "SPRING_DATASOURCE_URL", "jdbc:postgresql://postgres:5432/"   postgres.getDatabaseName(),
                        "SPRING_DATASOURCE_USERNAME", postgres.getUsername(),
                        "SPRING_DATASOURCE_PASSWORD", postgres.getPassword()
                    )
                )
                .withExposedPorts(8080)
                .withNetwork(SHARED_NETWORK)
                .withNetworkAliases("MyService")
                .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("MyService")))

Now MyService has established connection with PostgreSQL successfully. But when I perform HTTP request to MyService from the test suite, I get the same error.

restTemplate.getForObject("http://"   myService.getHost()   ":"   myService.getMappedPort(8080)  "/api/endpoint", Void.class)
Caused by: java.net.ConnectException: Connection refused

My question is how can I setup the containers network to make this architecture work?

CodePudding user response:

You need to specify port bindings to expose a port to the "outside world".

Example similar to what you want:

  Network network = Network.newNetwork();
  GenericContainer mariaDbServer = getMariaDbContainer(network);
  GenericContainer flywayRunner = getFlywayContainer(network);
  ...
  @SuppressWarnings("rawtypes")
  private GenericContainer getMariaDbContainer(Network network) {

    return new GenericContainer<>("mariadb:10.4.21-focal")
        .withEnv(Map.of("MYSQL_ROOT_PASSWORD", "password", "MYSQL_DATABASE", "somedatabase"))
        .withCommand(
            "mysqld", "--default-authentication-plugin=mysql_native_password", "--character-set-server=utf8mb4",
            "--collation-server=utf8mb4_unicode_ci").withNetwork(network).withNetworkAliases("somedatabasedb")
        .withNetworkMode(network.getId())
        .withExposedPorts(3306).withCreateContainerCmdModifier(
            cmd -> cmd.withNetworkMode(network.getId()).withHostConfig(
                    new HostConfig()
                        .withPortBindings(new PortBinding(Ports.Binding.bindPort(20306), new ExposedPort(3306))))
                .withNetworkMode(network.getId())).withStartupTimeout(Duration.ofMinutes(2L));
  }

  @SuppressWarnings("rawtypes")
  private GenericContainer getFlywayContainer(Network network) {

    return new GenericContainer<>("flyway/flyway:7.15.0-alpine")
        .withEnv(Map.of("MYSQL_ROOT_PASSWORD", "password", "MYSQL_DATABASE", "somedatabase"))
        .withCommand(
            "-url=jdbc:mariadb://somedatabasedb -schemas=somedatabase-user=root -password=password -connectRetries=300 migrate")
        .withFileSystemBind(Paths.get(".", "infrastructure/database/schema").toAbsolutePath().toString(),
            "/flyway/sql", BindMode.READ_ONLY).withNetwork(network).waitingFor(
            Wait.forLogMessage(".*Successfully applied.*", 1)
        ).withStartupTimeout(Duration.of(60, ChronoUnit.SECONDS));
  }

Container two communicates with container one using "internal" port.

Container one exposes 20306 (that redirects to 3306) port to the "outside world".

  • Related