package org.testcontainers.containers.wait;
import lombok.extern.slf4j.Slf4j;
import org.rnorth.ducttape.TimeoutException;
import org.rnorth.ducttape.unreliables.Unreliables;
import org.testcontainers.DockerClientFactory;
import org.testcontainers.containers.ContainerLaunchException;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.dockerclient.ProxiedUnixSocketClientProviderStrategy;
import org.testcontainers.dockerclient.WindowsClientProviderStrategy;
import java.net.Socket;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
/**
* Waits until a socket connection can be established on a port exposed or mapped by the container.
*
* @author richardnorth
*/
@Slf4j
public class HostPortWaitStrategy extends GenericContainer.AbstractWaitStrategy {
private static final String SUCCESS_MARKER = "TESTCONTAINERS_SUCCESS";
@Override
protected void waitUntilReady() {
final Integer port = getLivenessCheckPort();
if (null == port) {
log.debug("Liveness check port of {} is empty. Not waiting.", container.getContainerName());
return;
}
Callable<Boolean> checkStrategy;
if (shouldCheckWithCommand()) {
List<Integer> exposedPorts = container.getExposedPorts();
Integer exposedPort = exposedPorts.stream()
.filter(it -> port.equals(container.getMappedPort(it)))
.findFirst()
.orElse(null);
if (null == exposedPort) {
log.warn("Liveness check port of {} is set to {}, but it's not listed in exposed ports.",
container.getContainerName(), port);
return;
}
String[][] commands = {
{ "/bin/sh", "-c", "nc -vz -w 1 localhost " + exposedPort + " && echo " + SUCCESS_MARKER },
{ "/bin/bash", "-c", "</dev/tcp/localhost/" + exposedPort + " && echo " + SUCCESS_MARKER }
};
checkStrategy = () -> {
for (String[] command : commands) {
try {
if (container.execInContainer(command).getStdout().contains(SUCCESS_MARKER)) {
return true;
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (Exception e) {
continue;
}
}
return false;
};
} else {
checkStrategy = () -> {
new Socket(container.getContainerIpAddress(), port).close();
return true;
};
}
try {
Unreliables.retryUntilTrue((int) startupTimeout.getSeconds(), TimeUnit.SECONDS, () -> {
return getRateLimiter().getWhenReady(() -> {
try {
return checkStrategy.call();
} catch (Exception e) {
throw new RuntimeException(e);
}
});
});
} catch (TimeoutException e) {
throw new ContainerLaunchException("Timed out waiting for container port to open (" +
container.getContainerIpAddress() + ":" + port + " should be listening)");
}
}
private boolean shouldCheckWithCommand() {
// Special case for Docker for Mac, see #160
if(DockerClientFactory.instance().isUsing(ProxiedUnixSocketClientProviderStrategy.class)
&& System.getProperty("os.name").toLowerCase().contains("mac")) {
return true;
}
// Special case for Docker for Windows, see #160
if (DockerClientFactory.instance().isUsing(WindowsClientProviderStrategy.class)) {
return true;
}
return false;
}
}