Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port mapping for ipv4 & ipv6 can cause different ports to be mapped and getMappedPort function will return wrong port #750

Open
vsamofal opened this issue Apr 5, 2024 · 5 comments
Labels
triage Investigation required

Comments

@vsamofal
Copy link

vsamofal commented Apr 5, 2024

Expected Behaviour

I want to get a predictable port number and host,

the problem seems to be in this file - https://github.com/testcontainers/testcontainers-node/blob/main/packages/testcontainers/src/utils/bound-ports.ts

method - resolveHostPortBinding
line - return hostPortBindings[0].hostPort;

Actual Behaviour

We have tests that are running in parallel and it was quite hard to identify that issue, because locally on mac os everything is fine, but on latest ubuntu the problem auto assigning port appear

testcontainers/testcontainers-dotnet#825 - same issue for .net

So what happening:

  1. Starting a redis container (doesn't matter which one, it just an example)
  2. withExposedPorts(6389) - let docker to auto assign host port
  3. calling getMappedPort function -> may return ipv4 or ipv6 port, depends on your luck

Testcontainer Logs
...

Steps to Reproduce

  1. In this environment ubuntu 22
  2. With this config.
  3. Run '...'
  const container = await new GenericContainer(
    `${options.imageName}:${options.imageTag}`,
  )
    .withExposedPorts(6379)
    .start();

  // eslint-disable-next-line no-console
  console.timeEnd(`start redis`);

  const redisConfig = {
    port: container.getMappedPort(6379),
    host: 'localhost',
  } satisfies RedisStartedConfig;
  1. Run docker ps, and see that there are 2 ports actually

Environment Information

  • Operating System: ubuntu
  • Docker Version: 24+
  • Node version: 20+
  • Testcontainers version: 10.8.1

we temporary fixed it by retrieving ipv4 port from the docker inspect info:

import { StartedTestContainer } from 'testcontainers/build/test-container';

export function retrievePortFromBinding(
  container: StartedTestContainer,
  sourcePort: number,
): number {
  return Number.parseInt(
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    (container as never as any).inspectResult.NetworkSettings.Ports[
      `${sourcePort}/tcp`
    ].find((p: { HostIp: string; HostPort: string }) => p.HostIp === '0.0.0.0')
      .HostPort,
  );
}
@cristianrgreco cristianrgreco added the triage Investigation required label Apr 5, 2024
@cristianrgreco
Copy link
Collaborator

Thanks for raising, I'll need to look into it. Testcontainers resolves a list of host IPs, the order of which is determined by several factors like node version and OS. When we get the mapped port we probably need to find the port where the host matches the order, similar as you've done

@vsamofal
Copy link
Author

vsamofal commented Apr 5, 2024

The order actually is quite random, even in one test execution, we do start redis & postgres, we have around 20 files with tests, and for each file we start dbs, and some files are passing and some not, seems like it's super random from docker side.

@cristianrgreco
Copy link
Collaborator

Yep I think docker returns the ports in random order, but we should be able to find the one we want by host

@jarpoole
Copy link

jarpoole commented Jul 22, 2024

Pretty sure we are also seeing the same issue but I'm having a really hard time consistently reproducing it to confirm....

It does seem like there was an attempt to fix the issue in #483 though?

@cristianrgreco
Copy link
Collaborator

cristianrgreco commented Jul 23, 2024

It does seem like there was an attempt to fix the issue in #483 though?

That's correct. The behaviour right now is this:

  1. Resolve the host:
    export const resolveHost = async (
    dockerode: Dockerode,
    strategyResult: ContainerRuntimeClientStrategyResult,
    indexServerAddress: string,
    env: NodeJS.ProcessEnv = process.env
    ): Promise<string> => {
    if (strategyResult.allowUserOverrides) {
    if (env.TESTCONTAINERS_HOST_OVERRIDE !== undefined) {
    return env.TESTCONTAINERS_HOST_OVERRIDE;
    }
    }
    const { protocol, hostname } = new URL(strategyResult.uri);
    switch (protocol) {
    case "http:":
    case "https:":
    case "tcp:":
    return hostname;
    case "unix:":
    case "npipe:": {
    if (isInContainer()) {
    const networkName = strategyResult.uri.includes("podman.sock") ? "podman" : "bridge";
    const gateway = await findGateway(dockerode, networkName);
    if (gateway !== undefined) {
    return gateway;
    }
    const defaultGateway = await findDefaultGateway(dockerode, indexServerAddress);
    if (defaultGateway !== undefined) {
    return defaultGateway;
    }
    }
    return "localhost";
    }
    default:
    throw new Error(`Unsupported protocol: ${protocol}`);
    }
    };
  2. Query local DNS for this host. If the host is localhost, this may return 0.0.0.0/::1. The order is important and is what will be used to determine if we use IPv4 or IPv6 ports respectively.

Could you please upgrade to version 10.10.4 of Testcontainers, reproduce the issue and share the logs (DEBUG=testcontainers*)? There's a useful diagnostic that shows the runtime information. That'll help me debug what's going on in your cases

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
triage Investigation required
Projects
None yet
Development

No branches or pull requests

3 participants