-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
[Feature]: Add a method to GenericContainer
to expose a random container port with the same number as the host port
#9553
Comments
Hey @linghengqian, what you are trying to achieve there, should already work like this:
I don't see why the port within the container needs to be the same and why the internal port needs to be random (I understand it can be arbitrary though). |
java.lang.IllegalStateException: Mapped port can only be obtained after the container is started
at org.testcontainers.shaded.com.google.common.base.Preconditions.checkState(Preconditions.java:513)
at org.testcontainers.containers.ContainerState.getMappedPort(ContainerState.java:161)
at io.github.linghengqian.hive.server2.jdbc.driver.thin.ZookeeperServiceDiscoveryTest.assertShardingInLocalTransactions(ZookeeperServiceDiscoveryTest.java:75)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1597)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1597)
|
Hi, what you can try is create a custom script, see LocalStackContainer for reference. Lines 126 to 132 in 37b6f87
The script must be copied to the container when it is starting Lines 204 to 212 in 37b6f87
Hope that helps. |
services:
zookeeper:
image: zookeeper:3.9.3-jre-17
ports:
- "12181:2181"
apache-hive-1:
image: apache/hive:4.0.1
depends_on:
- zookeeper
environment:
SERVICE_NAME: hiveserver2
SERVICE_OPTS: >-
-Dhive.server2.support.dynamic.service.discovery=true
-Dhive.zookeeper.quorum=zookeeper:2181
-Dhive.server2.thrift.bind.host=0.0.0.0
-Dhive.server2.thrift.port=23593
ports:
- "23593:23593"
|
the entrypoint in apache/hive image is /entrypoint.sh. What I am suggesting is override the entrypoint to create a custom one with |
[main] ERROR tc.apache/hive:4.0.1 - Could not start container
org.testcontainers.containers.ContainerLaunchException: Timed out waiting for container port to open (localhost ports: [32818] should be listening)
at org.testcontainers.containers.wait.strategy.HostPortWaitStrategy.waitUntilReady(HostPortWaitStrategy.java:112)
at org.testcontainers.containers.wait.strategy.AbstractWaitStrategy.waitUntilReady(AbstractWaitStrategy.java:52)
at org.testcontainers.containers.GenericContainer.waitUntilContainerStarted(GenericContainer.java:909)
at org.testcontainers.containers.GenericContainer.tryStart(GenericContainer.java:500)
import com.github.dockerjava.api.command.InspectContainerResponse;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.images.builder.Transferable;
import java.io.IOException;
import java.net.ServerSocket;
@SuppressWarnings({"resource"})
public class HS2Container extends GenericContainer<HS2Container> {
String zookeeperConnectionString;
private static final String STARTER_SCRIPT = "/testcontainers_start.sh";
private final int randomPortFirst = getRandomPort();
public HS2Container(final String dockerImageName) {
super(dockerImageName);
withEnv("SERVICE_NAME", "hiveserver2");
withExposedPorts(randomPortFirst);
withCreateContainerCmdModifier(cmd ->
cmd.withEntrypoint("sh", "-c", "while [ ! -f " + STARTER_SCRIPT + " ]; do sleep 0.1; done; " + STARTER_SCRIPT)
);
}
public HS2Container withZookeeperConnectionString(final String zookeeperConnectionString) {
this.zookeeperConnectionString = zookeeperConnectionString;
return self();
}
@Override
protected void containerIsStarting(InspectContainerResponse containerInfo) {
String command = """
#!/bin/bash
export SERVICE_OPTS="-Dhive.server2.support.dynamic.service.discovery=true -Dhive.zookeeper.quorum=%s -Dhive.server2.thrift.bind.host=0.0.0.0 -Dhive.server2.thrift.port=%s"
/entrypoint.sh
""".formatted(zookeeperConnectionString, getMappedPort(randomPortFirst));
copyFileToContainer(Transferable.of(command, 0777), STARTER_SCRIPT);
}
private int getRandomPort() {
try (ServerSocket server = new ServerSocket(0)) {
server.setReuseAddress(true);
return server.getLocalPort();
} catch (IOException exception) {
throw new Error(exception);
}
}
} import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.Duration;
import java.util.List;
import java.util.Properties;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
@SuppressWarnings({"SqlDialectInspection", "SqlNoDataSourceInspection", "resource"})
@Testcontainers
class ZookeeperServiceDiscoveryTest {
private static final Network NETWORK = Network.newNetwork();
@Container
private static final GenericContainer<?> ZOOKEEPER_CONTAINER = new GenericContainer<>("zookeeper:3.9.3-jre-17")
.withNetwork(NETWORK)
.withNetworkAliases("foo")
.withExposedPorts(2181);
private final String jdbcUrlSuffix = ";serviceDiscoveryMode=zooKeeper;zooKeeperNamespace=hiveserver2";
private final String jdbcUrlPrefix = "jdbc:hive2://" + ZOOKEEPER_CONTAINER.getHost() + ":" + ZOOKEEPER_CONTAINER.getMappedPort(2181) + "/";
@AfterAll
static void afterAll() {
NETWORK.close();
}
@Test
void assertShardingInLocalTransactions() throws SQLException {
try (GenericContainer<?> hs2FirstContainer = new HS2Container("apache/hive:4.0.1")
.withNetwork(NETWORK)
.withZookeeperConnectionString(ZOOKEEPER_CONTAINER.getNetworkAliases().get(0) + ":" + ZOOKEEPER_CONTAINER.getMappedPort(2181))
.dependsOn(ZOOKEEPER_CONTAINER)) {
hs2FirstContainer.start();
awaitHS2(hs2FirstContainer.getFirstMappedPort());
HikariConfig config = new HikariConfig();
config.setDriverClassName("org.apache.hive.jdbc.HiveDriver");
config.setJdbcUrl(jdbcUrlPrefix + jdbcUrlSuffix);
DataSource dataSource = new HikariDataSource(config);
extractedSQL(dataSource);
}
}
private static void extractedSQL(final DataSource dataSource) throws SQLException {
try (Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement()) {
statement.execute("CREATE DATABASE demo_ds_0");
}
}
private void awaitHS2(final int hiveServer2Port) {
String connectionString = ZOOKEEPER_CONTAINER.getHost() + ":" + ZOOKEEPER_CONTAINER.getMappedPort(2181);
await().atMost(Duration.ofMinutes(1L)).ignoreExceptions().until(() -> {
try (CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString(connectionString)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build()) {
client.start();
List<String> children = client.getChildren().forPath("/hiveserver2");
assertThat(children.size(), is(1));
return children.get(0).startsWith("serverUri=0.0.0.0:" + hiveServer2Port + ";version=4.0.1;sequence=");
}
});
await().atMost(Duration.ofMinutes(1L)).ignoreExceptions().until(() -> {
DriverManager.getConnection(jdbcUrlPrefix + jdbcUrlSuffix, new Properties()).close();
return true;
});
}
} |
Thanks for sharing! Let's revisit the code. HS2Container looks for a random port on the host. Meanwhile, The original title description is not valid because testcontainers checks ports on the container not the host. I'll move this to discussions. |
No problem. Should I just close this issue? I prefer that there is no such proxy. |
Module
Core
Problem
org.testcontainers.containers.FixedHostPortGenericContainer
. For HiveServer2 with Zookeeper service discovery enabled, there are similar operations as follows.org.apache.curator.test.InstanceSpec#getRandomPort()
is to get a random host port. This can sometimes conflict with the port in the container.org.testcontainers.containers.FixedHostPortGenericContainer
to use a random numeric port both on the host, inside the container, and in the container's environment variables.Solution
GenericContainer
to expose the same host port number on a random container port. If this method is calledorg.testcontainers.containers.GenericContainer#withRandomExposedPorts()
, it can expose a random container port. And allow the host to obtain this port number throughorg.testcontainers.containers.GenericContainer#getFirstMappedPort()
, then the use oforg.testcontainers.containers.FixedHostPortGenericContainer
can obviously be simplified to,Benefit
Alternatives
org.testcontainers.containers.FixedHostPortGenericContainer
can indeed directly solve the current issue, which is what Support connecting to HiveServer2 with ZooKeeper Service Discovery enabled in GraalVM Native Image apache/shardingsphere#33768 and Improve GraalVM Reachability Metadata and corresponding nativeTest related unit tests apache/shardingsphere#29052 are doing.org.testcontainers.containers.FixedHostPortGenericContainer
has been deprecated.Would you like to help contributing this feature?
No
The text was updated successfully, but these errors were encountered: