This is an automated email from the ASF dual-hosted git repository. jamesnetherton pushed a commit to branch 3.27.x in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git
commit 6933d2e39fa84098908992b38659b0e10271323c Author: Lukas Lowinger <[email protected]> AuthorDate: Fri Oct 24 08:14:00 2025 +0200 [relates #4259] Refactor Kudu testcontainer --- integration-tests/kudu/pom.xml | 7 -- .../kudu/it/KuduInfrastructureTestHelper.java | 98 ---------------------- .../component/kudu/it/KuduTestResource.java | 71 ++++++++++++++-- 3 files changed, 65 insertions(+), 111 deletions(-) diff --git a/integration-tests/kudu/pom.xml b/integration-tests/kudu/pom.xml index de295d3623..737e41673a 100644 --- a/integration-tests/kudu/pom.xml +++ b/integration-tests/kudu/pom.xml @@ -30,13 +30,6 @@ <name>Camel Quarkus :: Integration Tests :: Kudu</name> <description>Integration tests for Camel Quarkus Kudu extension</description> - <properties> - <!-- This is to allow the "deep reflection" we do in KuduInfrastructureTestHelper --> - <opens>java.base/java.net=ALL-UNNAMED</opens> - <argLine>--add-opens ${opens}</argLine> - <quarkus.native.additional-build-args>-J--add-opens=${opens}</quarkus.native.additional-build-args> - </properties> - <dependencies> <dependency> <groupId>io.quarkus</groupId> diff --git a/integration-tests/kudu/src/main/java/org/apache/camel/quarkus/component/kudu/it/KuduInfrastructureTestHelper.java b/integration-tests/kudu/src/main/java/org/apache/camel/quarkus/component/kudu/it/KuduInfrastructureTestHelper.java deleted file mode 100644 index fcf34ecb1c..0000000000 --- a/integration-tests/kudu/src/main/java/org/apache/camel/quarkus/component/kudu/it/KuduInfrastructureTestHelper.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.camel.quarkus.component.kudu.it; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.net.InetAddress; -import java.util.concurrent.ConcurrentHashMap; - -import io.quarkus.runtime.StartupEvent; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.event.Observes; -import org.eclipse.microprofile.config.ConfigProvider; -import org.jboss.logging.Logger; - -/** - * In order to run Kudu integration tests, {@code KuduTest} and {@code KuduIT} should have access to: - * 1) A Kudu master server needed to create a table and also to obtain the host/port of the associated tablet server - * 2) A Kudu tablet server needed to insert and scan records - * - * As such, one solution could be to use a custom setup where Kudu servers run on the same network than integration - * tests. Please note that Kudu servers are not able to run on Windows machine. - * Another solution could be to use the container based setup where Kudu servers are managed by - * {@code KuduTestResource}. - * - * A) How to run integration tests against a custom setup (advised when not running on top of OpenJDK): - * Install Kudu master and tablet servers on the same network than integration tests. - * Configure "camel.kudu.test.master.rpc-authority" in "application.properties", for instance: - * camel.kudu.test.master.rpc-authority=kudu-master-hostname:7051 - * Run integration tests with mvn clean integration-test -P native - * - * B) How to run integration tests against the container based setup: - * The container based setup should run out of the box as {@code KuduTestResource} runs master and tablet server - * containers in a shared network. - * Simply run integration tests with mvn clean integration-test -P native - * Note that the test harness is NOT guaranteed to work when NOT running on top of OpenJDK. - * - * Troubleshooting the container based setup: - * If a message like "Unknown host kudu-tserver" is issued, it may be that - * {@link KuduInfrastructureTestHelper#overrideTabletServerHostnameResolution()} - * is not working. Please try to manually override the tablet server hostname resolution on your Operating System. - * For instance, adding an entry in /etc/hosts file like: "127.0.0.1 kudu-tserver" - * - * If a message like "Not enough live tablet server" is issued, it may be that the shared network setup by - * {@code KuduTestResource} is not working. In this case please refer to links below for a possible workaround: - * <a href="https://github.com/apache/camel-quarkus/issues/1206"> - * <a href="https://github.com/moby/moby/issues/32138"> - */ -@ApplicationScoped -public class KuduInfrastructureTestHelper { - - static final String KUDU_TABLET_NETWORK_ALIAS = "kudu-tserver"; - static final String DOCKER_HOST = "docker.host"; - private static final Logger LOG = Logger.getLogger(KuduInfrastructureTestHelper.class); - - void onStart(@Observes StartupEvent ev) { - LOG.info("Attempting to override the kudu tablet server hostname resolution on application startup"); - KuduInfrastructureTestHelper.overrideTabletServerHostnameResolution(); - } - - public static void overrideTabletServerHostnameResolution() { - try { - // Warm up the InetAddress cache - String dockerHost = ConfigProvider.getConfig().getValue("docker.host", String.class); - String tabletServerHostName = dockerHost.equals("localhost") || dockerHost.equals("127.0.0.1") ? "localhost" - : KUDU_TABLET_NETWORK_ALIAS; - InetAddress.getByName(tabletServerHostName); - final Field cacheField = InetAddress.class.getDeclaredField("cache"); - cacheField.setAccessible(true); - final Object cache = cacheField.get(null); - final Method get = ConcurrentHashMap.class.getMethod("get", Object.class); - final Object cachedAddresses = get.invoke(cache, tabletServerHostName); - if (cachedAddresses == null) { - throw new IllegalStateException("Unable to resolve host %s. Please add a host entry for %s %s" - .formatted(tabletServerHostName, dockerHost, tabletServerHostName)); - } - - final Method put = ConcurrentHashMap.class.getMethod("put", Object.class, Object.class); - put.invoke(cache, KUDU_TABLET_NETWORK_ALIAS, cachedAddresses); - } catch (Exception e) { - throw new IllegalStateException("Failed to apply kudu tablet server hostname override", e); - } - } -} diff --git a/integration-tests/kudu/src/test/java/org/apache/camel/quarkus/component/kudu/it/KuduTestResource.java b/integration-tests/kudu/src/test/java/org/apache/camel/quarkus/component/kudu/it/KuduTestResource.java index d84f24d2f3..956635934d 100644 --- a/integration-tests/kudu/src/test/java/org/apache/camel/quarkus/component/kudu/it/KuduTestResource.java +++ b/integration-tests/kudu/src/test/java/org/apache/camel/quarkus/component/kudu/it/KuduTestResource.java @@ -17,6 +17,9 @@ package org.apache.camel.quarkus.component.kudu.it; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.Enumeration; import java.util.Map; import java.util.function.Consumer; @@ -35,10 +38,11 @@ import org.testcontainers.containers.Network; import org.testcontainers.containers.output.Slf4jLogConsumer; import org.testcontainers.containers.wait.strategy.Wait; -import static org.apache.camel.quarkus.component.kudu.it.KuduInfrastructureTestHelper.DOCKER_HOST; -import static org.apache.camel.quarkus.component.kudu.it.KuduInfrastructureTestHelper.KUDU_TABLET_NETWORK_ALIAS; import static org.apache.camel.quarkus.component.kudu.it.KuduRoute.KUDU_AUTHORITY_CONFIG_KEY; +/** + * Based on https://github.com/apache/kudu/blob/master/docker/quickstart.yml. + */ public class KuduTestResource implements QuarkusTestResourceLifecycleManager { private static final Logger LOG = LoggerFactory.getLogger(KuduTestResource.class); private static final int KUDU_MASTER_RPC_PORT = 7051; @@ -47,6 +51,7 @@ public class KuduTestResource implements QuarkusTestResourceLifecycleManager { private static final int KUDU_TABLET_HTTP_PORT = 8050; private static final String KUDU_IMAGE = ConfigProvider.getConfig().getValue("kudu.container.image", String.class); private static final String KUDU_MASTER_NETWORK_ALIAS = "kudu-master"; + private static final String KUDU_TABLET_NETWORK_ALIAS = "kudu-tserver"; private GenericContainer<?> masterContainer; private GenericContainer<?> tabletContainer; @@ -54,11 +59,17 @@ public class KuduTestResource implements QuarkusTestResourceLifecycleManager { @Override public Map<String, String> start() { Network kuduNetwork = Network.newNetwork(); + final String advertisingIpAddress = getAdvertisingIpAddress(); + LOG.info("Advertising IP address: {}", advertisingIpAddress); // Setup the Kudu master server container masterContainer = new GenericContainer<>(KUDU_IMAGE) .withCommand("master") - .withEnv("MASTER_ARGS", "--unlock_unsafe_flags=true") + .withEnv("MASTER_ARGS", + "--unlock_unsafe_flags=true " + + // we must advertise host IP address, otherwise Kudu client receives address internal to Docker container + "--rpc_advertised_addresses=%s:%s".formatted(advertisingIpAddress, KUDU_MASTER_RPC_PORT)) + .withEnv("KUDU_MASTERS", KUDU_MASTER_NETWORK_ALIAS) .withExposedPorts(KUDU_MASTER_RPC_PORT, KUDU_MASTER_HTTP_PORT) .withNetwork(kuduNetwork) .withNetworkAliases(KUDU_MASTER_NETWORK_ALIAS) @@ -67,6 +78,8 @@ public class KuduTestResource implements QuarkusTestResourceLifecycleManager { masterContainer.start(); // Force host name and port, so that the tablet container is accessible from KuduResource, KuduTest and KuduIT. + // It basically forces to use fixed ports, instead of dynamic, so we can advertise them and use them externally. + // See https://github.com/testcontainers/testcontainers-java/issues/3967 for more context. Consumer<CreateContainerCmd> consumer = cmd -> { Ports portBindings = new Ports(); portBindings.bind(ExposedPort.tcp(KUDU_TABLET_RPC_PORT), Ports.Binding.bindPort(KUDU_TABLET_RPC_PORT)); @@ -80,7 +93,10 @@ public class KuduTestResource implements QuarkusTestResourceLifecycleManager { // Setup the Kudu tablet server container tabletContainer = new GenericContainer<>(KUDU_IMAGE) .withCommand("tserver") - .withEnv("TSERVER_ARGS", "--unlock_unsafe_flags=true") + .withEnv("TSERVER_ARGS", + "--unlock_unsafe_flags=true " + + // we must advertise host IP address, otherwise Kudu client receives address internal to Docker container + "--rpc_advertised_addresses=%s:%s".formatted(advertisingIpAddress, KUDU_TABLET_RPC_PORT)) .withEnv("KUDU_MASTERS", KUDU_MASTER_NETWORK_ALIAS) .withExposedPorts(KUDU_TABLET_RPC_PORT, KUDU_TABLET_HTTP_PORT) .withNetwork(kuduNetwork) @@ -106,8 +122,7 @@ public class KuduTestResource implements QuarkusTestResourceLifecycleManager { LOG.info("Kudu tablet server HTTP accessible at " + tServerHttpAuthority); return CollectionHelper.mapOf( - KUDU_AUTHORITY_CONFIG_KEY, masterRpcAuthority, - DOCKER_HOST, DockerClientFactory.instance().dockerHostIpAddress()); + KUDU_AUTHORITY_CONFIG_KEY, masterRpcAuthority); } @Override @@ -123,4 +138,48 @@ public class KuduTestResource implements QuarkusTestResourceLifecycleManager { LOG.error("An issue occurred while stopping the KuduTestResource", ex); } } + + public String getRealHostIpAddress() { + try { + Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces(); + while (interfaces.hasMoreElements()) { + NetworkInterface iface = interfaces.nextElement(); + + // skip loopback and non-active interfaces + if (iface.isLoopback() || !iface.isUp()) { + continue; + } + + // skip virtual/docker interfaces (they often starts with "docker" or "br-" + String name = iface.getName(); + if (name.startsWith("docker") || name.startsWith("br-")) { + continue; + } + + Enumeration<InetAddress> addresses = iface.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress addr = addresses.nextElement(); + + // only IPv4 and host address (not link-local addresses) + if (addr instanceof java.net.Inet4Address && !addr.isLinkLocalAddress()) { + return addr.getHostAddress(); + } + } + } + return InetAddress.getLocalHost().getHostAddress(); + } catch (Exception e) { + throw new RuntimeException("Error while getting host ip address", e); + } + } + + public String getAdvertisingIpAddress() { + LOG.info("DOCKER_HOST is set to {}", System.getenv("DOCKER_HOST")); + String dockerHostIpAddress = DockerClientFactory.instance().dockerHostIpAddress(); + if ("localhost".equals(dockerHostIpAddress)) { + LOG.info("Docker is running on local host - going to resolve real IP of the host"); + return getRealHostIpAddress(); + } + // else Docker is running remotely and thus use the IP of remote host. + return dockerHostIpAddress; + } }
