KAFKA-18546: Use mocks instead of a real DNS lookup to the outside (#18565)

Since the example.com DNS lookup changed the second time within one
year, we rewrote the unit tests for ClientUtils so that they do
not make a real DNS lookup to the outside but use mocks.

Reviewers: PoAn Yang <payang@apache.org>, Chia-Ping Tsai <chia7712@gmail.com>, Lianet Magrans <lmagrans@confluent.io>
This commit is contained in:
Bruno Cadonna 2025-01-16 16:18:44 +01:00 committed by Bruno Cadonna
parent 4038edfafe
commit e7a7efd602
1 changed files with 61 additions and 29 deletions

View File

@ -19,24 +19,27 @@ package org.apache.kafka.clients;
import org.apache.kafka.common.config.ConfigException;
import org.junit.jupiter.api.Test;
import org.mockito.MockedConstruction;
import org.mockito.MockedStatic;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockConstruction;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
public class ClientUtilsTest {
private final HostResolver hostResolver = new DefaultHostResolver();
@Test
public void testParseAndValidateAddresses() {
checkWithoutLookup("127.0.0.1:8000");
@ -57,28 +60,39 @@ public class ClientUtilsTest {
checkWithoutLookup("[::1]:8000");
checkWithoutLookup("[2001:db8:85a3:8d3:1319:8a2e:370:7348]:1234", "localhost:10000");
// With lookup of example.com, either one or two addresses are expected depending on
// whether ipv4 and ipv6 are enabled
List<InetSocketAddress> validatedAddresses = checkWithLookup(Collections.singletonList("example.com:10000"));
assertFalse(validatedAddresses.isEmpty(), "Unexpected addresses " + validatedAddresses);
List<String> validatedHostNames = validatedAddresses.stream().map(InetSocketAddress::getHostName)
.collect(Collectors.toList());
List<String> expectedHostNames = asList(
"a23-215-0-136.deploy.static.akamaitechnologies.com",
"a23-192-228-84.deploy.static.akamaitechnologies.com",
"a23-215-0-138.deploy.static.akamaitechnologies.com",
"a96-7-128-175.deploy.static.akamaitechnologies.com",
"a23-192-228-80.deploy.static.akamaitechnologies.com",
"a96-7-128-198.deploy.static.akamaitechnologies.com",
"2600:1406:3a00:21:0:0:173e:2e66",
"2600:1408:ec00:36:0:0:1736:7f31",
"2600:1406:3a00:21:0:0:173e:2e65",
"2600:1408:ec00:36:0:0:1736:7f24",
"2600:1406:bc00:53:0:0:b81e:94ce",
"2600:1406:bc00:53:0:0:b81e:94c8"
);
assertTrue(expectedHostNames.containsAll(validatedHostNames), "Unexpected addresses " + validatedHostNames);
validatedAddresses.forEach(address -> assertEquals(10000, address.getPort()));
String hostname = "example.com";
Integer port = 10000;
String canonicalHostname1 = "canonical_hostname1";
String canonicalHostname2 = "canonical_hostname2";
try (final MockedStatic<InetAddress> inetAddress = mockStatic(InetAddress.class)) {
InetAddress inetAddress1 = mock(InetAddress.class);
when(inetAddress1.getCanonicalHostName()).thenReturn(canonicalHostname1);
InetAddress inetAddress2 = mock(InetAddress.class);
when(inetAddress2.getCanonicalHostName()).thenReturn(canonicalHostname2);
inetAddress.when(() -> InetAddress.getAllByName(hostname))
.thenReturn(new InetAddress[]{inetAddress1, inetAddress2});
try (MockedConstruction<InetSocketAddress> inetSocketAddress =
mockConstruction(
InetSocketAddress.class,
(mock, context) -> {
when(mock.isUnresolved()).thenReturn(false);
when(mock.getHostName()).thenReturn((String) context.arguments().get(0));
when(mock.getPort()).thenReturn((Integer) context.arguments().get(1));
})
) {
List<InetSocketAddress> validatedAddresses = checkWithLookup(Collections.singletonList(hostname + ":" + port));
assertEquals(2, inetSocketAddress.constructed().size());
assertEquals(2, validatedAddresses.size());
assertTrue(validatedAddresses.containsAll(List.of(
inetSocketAddress.constructed().get(0),
inetSocketAddress.constructed().get(1)))
);
validatedAddresses.forEach(address -> assertEquals(port, address.getPort()));
validatedAddresses.stream().map(InetSocketAddress::getHostName).forEach(
hostName -> assertTrue(List.of(canonicalHostname1, canonicalHostname2).contains(hostName))
);
}
}
}
@Test
@ -99,7 +113,21 @@ public class ClientUtilsTest {
@Test
public void testOnlyBadHostname() {
assertThrows(ConfigException.class, () -> checkWithoutLookup("some.invalid.hostname.foo.bar.local:9999"));
try (MockedConstruction<InetSocketAddress> inetSocketAddress =
mockConstruction(
InetSocketAddress.class,
(mock, context) -> when(mock.isUnresolved()).thenReturn(true)
)
) {
Exception exception = assertThrows(
ConfigException.class,
() -> checkWithoutLookup("some.invalid.hostname.foo.bar.local:9999")
);
assertEquals(
"No resolvable bootstrap urls given in " + CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG,
exception.getMessage()
);
}
}
@Test
@ -122,8 +150,13 @@ public class ClientUtilsTest {
@Test
public void testResolveUnknownHostException() {
assertThrows(UnknownHostException.class,
() -> ClientUtils.resolve("some.invalid.hostname.foo.bar.local", hostResolver));
HostResolver throwingHostResolver = host -> {
throw new UnknownHostException();
};
assertThrows(
UnknownHostException.class,
() -> ClientUtils.resolve("some.invalid.hostname.foo.bar.local", throwingHostResolver)
);
}
@Test
@ -142,5 +175,4 @@ public class ClientUtilsTest {
private List<InetSocketAddress> checkWithLookup(List<String> url) {
return ClientUtils.parseAndValidateAddresses(url, ClientDnsLookup.RESOLVE_CANONICAL_BOOTSTRAP_SERVERS_ONLY);
}
}