2

I have this very simple code for running my integration test: (using Testcontainer artifact version 1.16.2) Note: I have tested whether this port in test is available before running the test case.

@Testcontainers
public class SftpServerTestContainer {
    private static final int PORT = 2222;
    private static final String DOCKER_IMAGE_NAME = "atmoz/sftp:latest";
    private static final String USER_PASS = "foo:pass:::folder";

    @Container
    private static final GenericContainer sftpContainer =
            new GenericContainer<>(DockerImageName.parse(DOCKER_IMAGE_NAME));

    @Bean
    @Profile("test")
    public GenericContainer sftpContainerAsABean(){
        sftpContainer
            .withAccessToHost(true)  
                .withExposedPorts(PORT)
                .withCommand(USER_PASS);
        sftpContainer.start();
        return sftpContainer;
    }
}

It has a very simple startup process of a GenericContainer with a given name. But i could never start it up and end up in the following failure:

[DEBUG] 2022-03-25 09:06:43.078 [docker-java-stream-164976604] wire - http-outgoing-4 >> "Content-Length: 26[\r][\n]"
[DEBUG] 2022-03-25 09:06:43.078 [docker-java-stream-164976604] wire - http-outgoing-4 >> "Host: localhost:2375[\r][\n]"
[DEBUG] 2022-03-25 09:06:43.078 [docker-java-stream-164976604] wire - http-outgoing-4 >> "Connection: keep-alive[\r][\n]"
[DEBUG] 2022-03-25 09:06:43.078 [docker-java-stream-164976604] wire - http-outgoing-4 >> "User-Agent: Apache-HttpClient/5.0.3 (Java/17)[\r][\n]"
[DEBUG] 2022-03-25 09:06:43.078 [docker-java-stream-164976604] wire - http-outgoing-4 >> "[\r][\n]"
[DEBUG] 2022-03-25 09:06:43.078 [docker-java-stream-164976604] wire - http-outgoing-4 >> "{"Detach":null,"Tty":null}"
[DEBUG] 2022-03-25 09:06:43.078 [docker-java-stream-164976604] wire - http-outgoing-4 << "HTTP/1.1 200 OK[\r][\n]"
[DEBUG] 2022-03-25 09:06:43.078 [docker-java-stream-164976604] wire - http-outgoing-4 << "Content-Type: application/vnd.docker.raw-stream[\r][\n]"
[DEBUG] 2022-03-25 09:06:43.078 [docker-java-stream-164976604] wire - http-outgoing-4 << "Api-Version"
[DEBUG] 2022-03-25 09:06:43.078 [docker-java-stream-164976604] wire - http-outgoing-4 << ": 1.41[\r][\n]"
[DEBUG] 2022-03-25 09:06:43.078 [docker-java-stream-164976604] wire - http-outgoing-4 << "Docker-Experimental: false[\r][\n]"
[DEBUG] 2022-03-25 09:06:43.078 [docker-java-stream-164976604] wire - http-outgoing-4 << "Ostype: linux[\r][\n]"
[DEBUG] 2022-03-25 09:06:43.078 [docker-java-stream-164976604] wire - http-outgoing-4 << "Server: Docker/20.10.13 (linux)[\r][\n]"
[DEBUG] 2022-03-25 09:06:43.078 [docker-java-stream-164976604] wire - http-outgoing-4 << "[\r][\n]"
[DEBUG] 2022-03-25 09:06:43.078 [docker-java-stream-164976604] headers - http-outgoing-4 << HTTP/1.1 200 OK
[DEBUG] 2022-03-25 09:06:43.078 [docker-java-stream-164976604] headers - http-outgoing-4 << Content-Type: application/vnd.docker.raw-stream
[DEBUG] 2022-03-25 09:06:43.078 [docker-java-stream-164976604] headers - http-outgoing-4 << Api-Version: 1.41
[DEBUG] 2022-03-25 09:06:43.078 [docker-java-stream-164976604] headers - http-outgoing-4 << Docker-Experimental: false
[DEBUG] 2022-03-25 09:06:43.078 [docker-java-stream-164976604] headers - http-outgoing-4 << Ostype: linux
[DEBUG] 2022-03-25 09:06:43.078 [docker-java-stream-164976604] headers - http-outgoing-4 << Server: Docker/20.10.13 (linux)
[DEBUG] 2022-03-25 09:06:43.137 [docker-java-stream-164976604] wire - http-outgoing-4 << "[0x2][0x0][0x0][0x0][0x0][0x0][0x0][0x1a]/bin/sh: 1: nc: not found[\n]"
[DEBUG] 2022-03-25 09:06:43.138 [docker-java-stream-164976604] wire - http-outgoing-4 << "[0x2][0x0][0x0][0x0][0x0][0x0][0x0]x/bin/bash: connect: Cannot assign requested address[\n]"
[DEBUG] 2022-03-25 09:06:43.138 [docker-java-stream-164976604] wire - http-outgoing-4 << "/bin/bash: /dev/tcp/localhost/2222: Cannot assign requested address[\n]"
[DEBUG] 2022-03-25 09:06:43.242 [docker-java-stream-164976604] wire - http-outgoing-4 << "[0x2][0x0][0x0][0x0][0x0][0x0][0x0][0x1a]/bin/sh: 1: nc: not found[\n]"
[DEBUG] 2022-03-25 09:06:43.243 [docker-java-stream-164976604] wire - http-outgoing-4 << "[0x2][0x0][0x0][0x0][0x0][0x0][0x0]x/bin/bash: connect: Cannot assign requested address[\n]"
[DEBUG] 2022-03-25 09:06:43.243 [docker-java-stream-164976604] wire - http-outgoing-4 << "/bin/bash: /dev/tcp/localhost/2222: Cannot assign requested address[\n]"
[DEBUG] 2022-03-25 09:06:43.346 [docker-java-stream-164976604] wire - http-outgoing-4 << "[0x2][0x0][0x0][0x0][0x0][0x0][0x0][0x1a]/bin/sh: 1: nc: not found[\n]"
[DEBUG] 2022-03-25 09:06:43.347 [docker-java-stream-164976604] wire - http-outgoing-4 << "[0x2][0x0][0x0][0x0][0x0][0x0][0x0]x/bin/bash: connect: Cannot assign requested address[\n]"
[DEBUG] 2022-03-25 09:06:43.347 [docker-java-stream-164976604] wire - http-outgoing-4 << "/bin/bash: /dev/tcp/localhost/2222: Cannot assign requested address[\n]"
[DEBUG] 2022-03-25 09:06:43.451 [docker-java-stream-164976604] wire - http-outgoing-4 << "[0x2][0x0][0x0][0x0][0x0][0x0][0x0][0x1a]/bin/sh: 1: nc: not found[\n]"
[DEBUG] 2022-03-25 09:06:43.452 [docker-java-stream-164976604] wire - http-outgoing-4 << "[0x2][0x0][0x0][0x0][0x0][0x0][0x0]x/bin/bash: connect: Cannot assign requested address[\n]"
[DEBUG] 2022-03-25 09:06:43.452 [docker-java-stream-164976604] wire - http-outgoing-4 << "/bin/bash: /dev/tcp/localhost/2222: Cannot assign requested address[\n]"
[DEBUG] 2022-03-25 09:06:43.555 [docker-java-stream-164976604] wire - http-outgoing-4 << "[0x2][0x0][0x0][0x0][0x0][0x0][0x0][0x1a]/bin/sh: 1: nc: not found[\n]"
[DEBUG] 2022-03-25 09:06:43.556 [docker-java-stream-164976604] wire - http-outgoing-4 << "[0x2][0x0][0x0][0x0][0x0][0x0][0x0]x/bin/bash: connect: Cannot assign requested address[\n]"
[DEBUG] 2022-03-25 09:06:43.556 [docker-java-stream-164976604] wire - http-outgoing-4 << "/bin/bash: /dev/tcp/localhost/2222: Cannot assign requested address[\n]"
[DEBUG] 2022-03-25 09:06:43.665 [docker-java-stream-164976604] wire - http-outgoing-4 << "[0x2][0x0][0x0][0x0][0x0][0x0][0x0][0x1a]/bin/sh: 1: nc: not found[\n]"
[DEBUG] 2022-03-25 09:06:43.668 [docker-java-stream-164976604] wire - http-outgoing-4 << "[0x2][0x0][0x0][0x0][0x0][0x0][0x0]x/bin/bash: connect: Cannot assign requested address[\n]"
[DEBUG] 2022-03-25 09:06:43.668 [docker-java-stream-164976604] wire - http-outgoing-4 << "/bin/bash: /dev/tcp/localhost/2222: Cannot assign requested address[\n]"
[DEBUG] 2022-03-25 09:06:43.778 [docker-java-stream-164976604] wire - http-outgoing-4 << "[0x2][0x0][0x0][0x0][0x0][0x0][0x0][0x1a]/bin/sh: 1: nc: not found[\n]"
[DEBUG] 2022-03-25 09:06:43.780 [docker-java-stream-164976604] wire - http-outgoing-4 << "[0x2][0x0][0x0][0x0][0x0][0x0][0x0]x/bin/bash: connect: Cannot assign requested address[\n]"
[DEBUG] 2022-03-25 09:06:43.780 [docker-java-stream-164976604] wire - http-outgoing-4 << "/bin/bash: /dev/tcp/localhost/2222: Cannot assign requested address[\n]"
[DEBUG] 2022-03-25 09:06:43.890 [docker-java-stream-164976604] wire - http-outgoing-4 << "[0x2][0x0][0

For some reason, I think docker-java lib that TestContainers is using nc to do some networking stuff. But if it is because of that, i quickly changed the image to atmoz/sftp:alpine and I received the following error (similar but not the same):

[DEBUG] 2022-03-25 09:11:36.145 [docker-java-stream--355075128] wire - http-outgoing-4 << "/bin/bash: line 1: /dev/tcp/localhost/2222: Address not available[\n]"
[DEBUG] 2022-03-25 09:11:36.250 [docker-java-stream--355075128] wire - http-outgoing-4 << "[0x2][0x0][0x0][0x0][0x0][0x0][0x0]l/bin/bash: connect: Address not available[\n]"
[DEBUG] 2022-03-25 09:11:36.250 [docker-java-stream--355075128] wire - http-outgoing-4 << "/bin/bash: line 1: /dev/tcp/localhost/2222: Address not available[\n]"

So I do not understand the underlying mechanism of Testcontainers in this case.

By the way, I also ran same integration test on keycloak image, and discovered that I always have nc not found error as well. But I think that's because Keycloak base image hardens the image for security purposes.

tepetrol
  • 94
  • 8

2 Answers2

1

Testcontainers runs a couple of commands from within the container to determine when the intenral port of the container is available: https://github.com/testcontainers/testcontainers-java/blob/5e6dbc791b6184f0de6d1f9d184a78324c1ec945/core/src/main/java/org/testcontainers/containers/wait/internal/InternalCommandPortListeningCheck.java#L29-L38

This is part of the default HostPortWaitStrategy. It might be the case, that the atmoz/sftp:latest does not allow any of those commands to be run.

Also, your log level is on DEBUG, you should not give extra attention to those messages. What is the actual error you are getting?

Kevin Wittek
  • 1,369
  • 9
  • 26
  • Thanks for your pointer @KevinWittek ! This is my assumption as well! It seems that these commands are required to run some commands like this. So some containers do not allow these executions for various reasons (e.g. harderning container for security), like `keycloak` image does not allow `nc` to be run. So there is a little bit of challenge on using testcontainer for integration testing for **any** containers :( – tepetrol Apr 06 '22 at 08:06
  • You can still use any image, you just have to configure your wait strategies accordingly and might not be able to use the given implementation of the default `HostPortWaitStrategy`. – Kevin Wittek Jan 03 '23 at 08:21
0

First check if port 2222 is free in the machine that is launching the TestContainer.

Second, change you setup of GenericContainer to:

sftpContainer = new GenericContainer<>(DockerImageName.parse("atmoz/sftp:latest")).withExposedPorts(PORT)
        .withCommand("foo:pass:::folder")
        .withCreateContainerCmdModifier(
            cmd -> cmd.withHostConfig(
                new HostConfig()
                    .withPortBindings(new PortBinding(Ports.Binding.bindPort(22), new ExposedPort(PORT)))));

You need to map the exposed port (in your case 2222) into the internal port (22).

This example run on version 1.16.0 of testcontainers.

pringi
  • 3,987
  • 5
  • 35
  • 45
  • Hi Thanks for your answer. I did of course check the port, maybe I forgot to describe it clearly (edited my original question). Secondly, i couldn't find the method `withCreateContainerCmdModifier` in testcontainers 1.16.0. Are you sure you are using the right version? I originally use 1.16.2 btw. – tepetrol Mar 25 '22 at 10:58
  • I updated the answer. You have to remove the final keyword from the sftpContainer variable. To have access to withCreateContainerCmdModifier you have to use it in a builder style. – pringi Mar 25 '22 at 11:38
  • I wanted to accept this as an answer, because it did spin up the container successfully (thanks indeed). However, the localhost and the exposed port is not reachable from another application, and upon checking on `docker ps` ```30746bd77a5 atmoz/sftp:latest "/entrypoint foo:pas…" 3 seconds ago Up 2 seconds 22/tcp suspicious_proskuriakova``` it seems it's not exposed to the host (the `22/tcp` is the value of the column `PORTS`) – tepetrol Mar 25 '22 at 12:18
  • The application is on your machine? Is it another container? I have this in mine: "1e4e3a16f249 atmoz/sftp:latest "/entrypoint foo:pas…" 16 seconds ago Up 15 seconds 22/tcp, 0.0.0.0:22->2222/tcp lucid_ferm". I'm running with docker desktop (windows) 3.2.2. – pringi Mar 25 '22 at 12:40
  • No when running my integration test with JUnit5/Jupiter, my spring app (via `@SpringBootTest`, so not a separate container) is calling this sftp Testcontainer via `localhost` with port 2222. While in that process/moment, i just quickly checked with `docker ps` in my terminal and captured the stats whether Testcontainer exposed the port to localhost or not (which appears to be not mapped to localhost. – tepetrol Mar 25 '22 at 12:45
  • Testcontainers does not recommend the usage of fixed ports. Why do you think this would be necessary? – Kevin Wittek Mar 28 '22 at 07:27
  • Doesn't that beg the question, why can't i use fixed port when that predefined fixed port is available (simply by checking `lsof` and the port number is non-reserved Linux ports)? I think this comment doesn't answer the underlying problem, even tho the recommendation is possibly good practice (but not leading to the actual issue, so thx for your comment). – tepetrol Apr 06 '22 at 08:01
  • Checking the free port with `lsof` and binding to the free part afterward introduces a race condition, since it is not an atomic operation. Publishing to a free port using Docker's publish-port feature is ideally race condition free. – Kevin Wittek Apr 07 '22 at 07:51