Copilot commented on code in PR #23934:
URL: https://github.com/apache/kafka/pull/23934#discussion_r4358864199


##########
clients/src/main/java/org/apache/kafka/clients/ClientUtils.java:
##########
@@ -85,19 +87,26 @@ private static List<InetSocketAddress> resolveAddress(
                 }
             }
         } else {
-            InetSocketAddress address = new InetSocketAddress(host, port);
-            if (address.isUnresolved()) {
+            // Preserve the pre-existing single-address-per-host contract of 
this lookup mode
+            // (equivalent to InetSocketAddress(host, port)'s implicit 
resolution): take only the
+            // first address the resolver returns, rather than all of them 
like the branch above.
+            try {
+                InetAddress[] resolved = hostResolver.resolve(host);
+                addresses.add(new InetSocketAddress(resolved[0], port));

Review Comment:
   When the injected resolver returns an unnamed InetAddress, such as one 
created from an IP literal, this constructor drops the configured bootstrap 
hostname. Cluster.bootstrap() copies the socket's getHostString() into 
Node.host(), and ClusterConnectionStates later resolves that value again. A 
resolver keyed by the bootstrap hostname therefore receives the numeric IP on 
connection attempts and can fail even though bootstrap resolution succeeded. 
Preserve the configured hostname alongside the resolved address without another 
DNS lookup, retaining any IPv6 scope, and add a regression test with an unnamed 
resolver result.



##########
clients/clients-integration-tests/src/test/java/org/apache/kafka/clients/admin/AsyncBootstrapResolutionTest.java:
##########
@@ -0,0 +1,84 @@
+/*
+ * 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.kafka.clients.admin;
+
+import org.apache.kafka.clients.CommonClientConfigs;
+import org.apache.kafka.clients.DefaultHostResolver;
+import org.apache.kafka.clients.HostResolver;
+import org.apache.kafka.common.test.ClusterInstance;
+import org.apache.kafka.common.test.api.ClusterTest;
+import org.apache.kafka.common.test.api.ClusterTestDefaults;
+import org.apache.kafka.common.test.api.Type;
+import org.apache.kafka.common.utils.Utils;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Prototype/spike: proves that KafkaAdminClient#createInternal's HostResolver 
injection point
+ * (the same one ConcurrentListOffsetsRequestTest uses) now also controls 
KIP-909 async bootstrap
+ * DNS resolution, after routing NetworkClient's bootstrap resolution through 
the injected
+ * HostResolver instead of calling InetAddress/InetSocketAddress directly.
+ */
+@ClusterTestDefaults(
+        types = {Type.KRAFT},
+        brokers = 1
+)
+public class AsyncBootstrapResolutionTest {
+    private final ClusterInstance clusterInstance;
+
+    AsyncBootstrapResolutionTest(ClusterInstance clusterInstance) {
+        this.clusterInstance = clusterInstance;
+    }
+
+    @ClusterTest
+    public void adminClientRecoversAfterTransientBootstrapDnsFailure() throws 
Exception {
+        clusterInstance.waitForReadyBrokers();
+
+        String bootstrapServers = clusterInstance.bootstrapServers();
+        String host = Utils.getHost(bootstrapServers);
+        InetAddress realAddress = new DefaultHostResolver().resolve(host)[0];
+
+        AtomicInteger attempts = new AtomicInteger();
+        HostResolver flakyThenWorking = resolveHost -> {
+            if (attempts.getAndIncrement() < 2) {
+                throw new UnknownHostException("Simulated transient DNS 
failure for " + resolveHost);
+            }
+            return new InetAddress[] {realAddress};
+        };
+
+        Map<String, Object> props = Map.of(
+                CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers,

Review Comment:
   This test can pass even if bootstrap resolution bypasses the injected 
resolver. The real cluster hostname resolves through JVM DNS, and the existing 
connection-time resolver can consume the two simulated failures before 
listTopics succeeds, satisfying both assertions. Use a bootstrap hostname that 
only the injected resolver can resolve, while retaining the real broker port, 
so reverting bootstrap injection makes this test fail.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to