Skip to content
This repository was archived by the owner on Dec 9, 2024. It is now read-only.

Commit 968f29a

Browse files
authored
Change DNS lookup method (#323) (#325)
* Change DNS lookup method (#323) * Fix checkstyle errors * Improve exception handling. * Unwrap and throw UnknownHostException from ExecutionException Add testcases for handling UnknownHostException and TimeoutException * Fix indentation
1 parent 1a736ce commit 968f29a

File tree

2 files changed

+89
-78
lines changed

2 files changed

+89
-78
lines changed

src/main/java/com/hazelcast/kubernetes/DnsEndpointResolver.java

Lines changed: 43 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -17,66 +17,50 @@
1717
package com.hazelcast.kubernetes;
1818

1919
import com.hazelcast.config.NetworkConfig;
20-
import com.hazelcast.core.HazelcastException;
2120
import com.hazelcast.logging.ILogger;
2221
import com.hazelcast.cluster.Address;
2322
import com.hazelcast.spi.discovery.DiscoveryNode;
2423
import com.hazelcast.spi.discovery.SimpleDiscoveryNode;
2524

26-
import javax.naming.Context;
27-
import javax.naming.NameNotFoundException;
28-
import javax.naming.NamingEnumeration;
29-
import javax.naming.NamingException;
30-
import javax.naming.directory.Attribute;
31-
import javax.naming.directory.Attributes;
32-
import javax.naming.directory.DirContext;
33-
import javax.naming.directory.InitialDirContext;
3425
import java.net.InetAddress;
3526
import java.net.UnknownHostException;
3627
import java.util.ArrayList;
3728
import java.util.Collections;
3829
import java.util.HashSet;
39-
import java.util.Hashtable;
4030
import java.util.List;
4131
import java.util.Set;
32+
import java.util.concurrent.Callable;
33+
import java.util.concurrent.ExecutionException;
34+
import java.util.concurrent.ExecutorService;
35+
import java.util.concurrent.Executors;
36+
import java.util.concurrent.Future;
37+
import java.util.concurrent.TimeUnit;
38+
import java.util.concurrent.TimeoutException;
4239

4340
final class DnsEndpointResolver
4441
extends HazelcastKubernetesDiscoveryStrategy.EndpointResolver {
42+
// executor service for dns lookup calls
43+
private static final ExecutorService DNS_LOOKUP_SERVICE = Executors.newCachedThreadPool();
4544

4645
private final String serviceDns;
4746
private final int port;
48-
private final DirContext dirContext;
47+
private final int serviceDnsTimeout;
4948

50-
DnsEndpointResolver(ILogger logger, String serviceDns, int port, DirContext dirContext) {
49+
DnsEndpointResolver(ILogger logger, String serviceDns, int port, int serviceDnsTimeout) {
5150
super(logger);
5251
this.serviceDns = serviceDns;
5352
this.port = port;
54-
this.dirContext = dirContext;
55-
}
56-
57-
DnsEndpointResolver(ILogger logger, String serviceDns, int port, int serviceDnsTimeout) {
58-
this(logger, serviceDns, port, createDirContext(serviceDnsTimeout));
59-
60-
}
61-
62-
@SuppressWarnings("checkstyle:magicnumber")
63-
private static DirContext createDirContext(int serviceDnsTimeout) {
64-
Hashtable<String, String> env = new Hashtable<String, String>();
65-
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
66-
env.put(Context.PROVIDER_URL, "dns:");
67-
env.put("com.sun.jndi.dns.timeout.initial", String.valueOf(serviceDnsTimeout * 1000L));
68-
try {
69-
return new InitialDirContext(env);
70-
} catch (NamingException e) {
71-
throw new HazelcastException("Error while initializing DirContext", e);
72-
}
53+
this.serviceDnsTimeout = serviceDnsTimeout;
7354
}
7455

7556
List<DiscoveryNode> resolve() {
7657
try {
7758
return lookup();
78-
} catch (NameNotFoundException e) {
79-
logger.warning(String.format("DNS lookup for serviceDns '%s' failed: name not found", serviceDns));
59+
} catch (TimeoutException e) {
60+
logger.warning(String.format("DNS lookup for serviceDns '%s' failed: DNS resolution timeout", serviceDns));
61+
return Collections.emptyList();
62+
} catch (UnknownHostException e) {
63+
logger.warning(String.format("DNS lookup for serviceDns '%s' failed: unknown host", serviceDns));
8064
return Collections.emptyList();
8165
} catch (Exception e) {
8266
logger.warning(String.format("DNS lookup for serviceDns '%s' failed", serviceDns), e);
@@ -85,20 +69,32 @@ List<DiscoveryNode> resolve() {
8569
}
8670

8771
private List<DiscoveryNode> lookup()
88-
throws NamingException, UnknownHostException {
72+
throws UnknownHostException, InterruptedException, ExecutionException, TimeoutException {
8973
Set<String> addresses = new HashSet<String>();
90-
Attributes attributes = dirContext.getAttributes(serviceDns, new String[]{"SRV"});
91-
Attribute srvAttribute = attributes.get("srv");
92-
if (srvAttribute != null) {
93-
NamingEnumeration<?> servers = srvAttribute.getAll();
94-
while (servers.hasMore()) {
95-
String server = (String) servers.next();
96-
String serverHost = extractHost(server);
97-
InetAddress address = InetAddress.getByName(serverHost);
74+
75+
Future<InetAddress[]> future = DNS_LOOKUP_SERVICE.submit(new Callable<InetAddress[]>() {
76+
@Override
77+
public InetAddress[] call() throws Exception {
78+
return getAllInetAddresses();
79+
}
80+
});
81+
82+
try {
83+
for (InetAddress address : future.get(serviceDnsTimeout, TimeUnit.SECONDS)) {
9884
if (addresses.add(address.getHostAddress()) && logger.isFinestEnabled()) {
9985
logger.finest("Found node service with address: " + address);
10086
}
10187
}
88+
} catch (ExecutionException e) {
89+
if (e.getCause() instanceof UnknownHostException) {
90+
throw (UnknownHostException) e.getCause();
91+
} else {
92+
throw e;
93+
}
94+
} catch (TimeoutException e) {
95+
// cancel DNS lookup
96+
future.cancel(true);
97+
throw e;
10298
}
10399

104100
if (addresses.size() == 0) {
@@ -114,13 +110,12 @@ private List<DiscoveryNode> lookup()
114110
}
115111

116112
/**
117-
* Extracts host from the DNS record.
118-
* <p>
119-
* Sample record: "10 25 0 6235386366386436.my-release-hazelcast.default.svc.cluster.local".
113+
* Do the actual lookup
114+
* @return array of resolved inet addresses
115+
* @throws UnknownHostException
120116
*/
121-
private static String extractHost(String server) {
122-
String host = server.split(" ")[3];
123-
return host.replaceAll("\\\\.$", "");
117+
private InetAddress[] getAllInetAddresses() throws UnknownHostException {
118+
return InetAddress.getAllByName(serviceDns);
124119
}
125120

126121
private static int getHazelcastPort(int port) {

src/test/java/com/hazelcast/kubernetes/DnsEndpointResolverTest.java

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,24 @@
2222
import org.junit.Before;
2323
import org.junit.Test;
2424
import org.junit.runner.RunWith;
25-
import org.mockito.Mock;
25+
import org.mockito.invocation.InvocationOnMock;
26+
import org.mockito.stubbing.Answer;
2627
import org.powermock.api.mockito.PowerMockito;
2728
import org.powermock.core.classloader.annotations.PrepareForTest;
2829
import org.powermock.modules.junit4.PowerMockRunner;
2930

30-
import javax.naming.NameNotFoundException;
31-
import javax.naming.NamingEnumeration;
32-
import javax.naming.directory.Attribute;
33-
import javax.naming.directory.Attributes;
34-
import javax.naming.directory.DirContext;
3531
import java.net.InetAddress;
32+
import java.net.UnknownHostException;
3633
import java.util.HashSet;
3734
import java.util.List;
3835
import java.util.Set;
3936

4037
import static org.junit.Assert.assertEquals;
38+
import static org.mockito.Mockito.any;
39+
import static org.mockito.Mockito.anyString;
4140
import static org.mockito.Mockito.mock;
41+
import static org.mockito.Mockito.never;
42+
import static org.mockito.Mockito.verify;
4243
import static org.mockito.Mockito.when;
4344

4445
@RunWith(PowerMockRunner.class)
@@ -47,45 +48,30 @@ public class DnsEndpointResolverTest {
4748
private static final ILogger LOGGER = new NoLogFactory().getLogger("no");
4849

4950
private static final String SERVICE_DNS = "my-release-hazelcast.default.svc.cluster.local";
51+
private static final int DEFAULT_SERVICE_DNS_TIMEOUT_SECONDS = 5;
52+
private static final int TEST_DNS_TIMEOUT_SECONDS = 1;
5053
private static final int UNSET_PORT = 0;
5154
private static final int DEFAULT_PORT = 5701;
5255
private static final int CUSTOM_PORT = 5702;
53-
private static final String DNS_SERVER_1 = String.format("12345.%s", SERVICE_DNS);
54-
private static final String DNS_SERVER_2 = String.format("6789.%s", SERVICE_DNS);
55-
private static final String DNS_ENTRY_SERVER_1 = String.format("10 25 0 %s", DNS_SERVER_1);
56-
private static final String DNS_ENTRY_SERVER_2 = String.format("10 25 0 %s", DNS_SERVER_2);
5756
private static final String IP_SERVER_1 = "192.168.0.5";
5857
private static final String IP_SERVER_2 = "192.168.0.6";
5958

60-
@Mock
61-
private NamingEnumeration servers;
62-
@Mock
63-
private DirContext dirContext;
64-
6559
@Before
6660
public void setUp()
6761
throws Exception {
6862
PowerMockito.mockStatic(InetAddress.class);
6963

70-
Attributes attributes = mock(Attributes.class);
71-
when(dirContext.getAttributes(SERVICE_DNS, new String[]{"SRV"})).thenReturn(attributes);
72-
Attribute attribute = mock(Attribute.class);
73-
when(attributes.get("srv")).thenReturn(attribute);
74-
when(attribute.getAll()).thenReturn(servers);
75-
when(servers.next()).thenReturn(DNS_ENTRY_SERVER_1, DNS_ENTRY_SERVER_2);
76-
when(servers.hasMore()).thenReturn(true, true, false);
7764
InetAddress address1 = mock(InetAddress.class);
78-
PowerMockito.when(InetAddress.getByName(DNS_SERVER_1)).thenReturn(address1);
7965
InetAddress address2 = mock(InetAddress.class);
80-
PowerMockito.when(InetAddress.getByName(DNS_SERVER_2)).thenReturn(address2);
8166
when(address1.getHostAddress()).thenReturn(IP_SERVER_1);
8267
when(address2.getHostAddress()).thenReturn(IP_SERVER_2);
68+
PowerMockito.when(InetAddress.getAllByName(SERVICE_DNS)).thenReturn(new InetAddress[]{address1, address2});
8369
}
8470

8571
@Test
8672
public void resolve() {
8773
// given
88-
DnsEndpointResolver dnsEndpointResolver = new DnsEndpointResolver(LOGGER, SERVICE_DNS, UNSET_PORT, dirContext);
74+
DnsEndpointResolver dnsEndpointResolver = new DnsEndpointResolver(LOGGER, SERVICE_DNS, UNSET_PORT, DEFAULT_SERVICE_DNS_TIMEOUT_SECONDS);
8975

9076
// when
9177
List<DiscoveryNode> result = dnsEndpointResolver.resolve();
@@ -101,7 +87,7 @@ public void resolve() {
10187
@Test
10288
public void resolveCustomPort() {
10389
// given
104-
DnsEndpointResolver dnsEndpointResolver = new DnsEndpointResolver(LOGGER, SERVICE_DNS, CUSTOM_PORT, dirContext);
90+
DnsEndpointResolver dnsEndpointResolver = new DnsEndpointResolver(LOGGER, SERVICE_DNS, CUSTOM_PORT, DEFAULT_SERVICE_DNS_TIMEOUT_SECONDS);
10591

10692
// when
10793
List<DiscoveryNode> result = dnsEndpointResolver.resolve();
@@ -118,28 +104,58 @@ public void resolveCustomPort() {
118104
public void resolveException()
119105
throws Exception {
120106
// given
121-
when(dirContext.getAttributes(SERVICE_DNS, new String[]{"SRV"})).thenThrow(new NameNotFoundException());
122-
DnsEndpointResolver dnsEndpointResolver = new DnsEndpointResolver(LOGGER, SERVICE_DNS, UNSET_PORT, dirContext);
107+
ILogger logger = mock(ILogger.class);
108+
PowerMockito.when(InetAddress.getAllByName(SERVICE_DNS)).thenThrow(new UnknownHostException());
109+
DnsEndpointResolver dnsEndpointResolver = new DnsEndpointResolver(logger, SERVICE_DNS, UNSET_PORT, DEFAULT_SERVICE_DNS_TIMEOUT_SECONDS);
123110

124111
// when
125112
List<DiscoveryNode> result = dnsEndpointResolver.resolve();
126113

127114
// then
128115
assertEquals(0, result.size());
116+
verify(logger).warning(String.format("DNS lookup for serviceDns '%s' failed: unknown host", SERVICE_DNS));
117+
verify(logger, never()).warning(anyString(), any(Throwable.class));
129118
}
130119

131120
@Test
132121
public void resolveNotFound()
133122
throws Exception {
134123
// given
135-
when(servers.hasMore()).thenReturn(false);
136-
DnsEndpointResolver dnsEndpointResolver = new DnsEndpointResolver(LOGGER, SERVICE_DNS, UNSET_PORT, dirContext);
124+
PowerMockito.when(InetAddress.getAllByName(SERVICE_DNS)).thenReturn(new InetAddress[0]);
125+
DnsEndpointResolver dnsEndpointResolver = new DnsEndpointResolver(LOGGER, SERVICE_DNS, UNSET_PORT, DEFAULT_SERVICE_DNS_TIMEOUT_SECONDS);
126+
127+
// when
128+
List<DiscoveryNode> result = dnsEndpointResolver.resolve();
129+
130+
// then
131+
assertEquals(0, result.size());
132+
}
133+
134+
@Test
135+
public void resolveTimeout()
136+
throws Exception {
137+
// given
138+
ILogger logger = mock(ILogger.class);
139+
PowerMockito.when(InetAddress.getAllByName(SERVICE_DNS)).then(waitAndAnswer());
140+
DnsEndpointResolver dnsEndpointResolver = new DnsEndpointResolver(logger, SERVICE_DNS, UNSET_PORT, TEST_DNS_TIMEOUT_SECONDS);
137141

138142
// when
139143
List<DiscoveryNode> result = dnsEndpointResolver.resolve();
140144

141145
// then
142146
assertEquals(0, result.size());
147+
verify(logger).warning(String.format("DNS lookup for serviceDns '%s' failed: DNS resolution timeout", SERVICE_DNS));
148+
verify(logger, never()).warning(anyString(), any(Throwable.class));
149+
}
150+
151+
private static Answer<InetAddress[]> waitAndAnswer() {
152+
return new Answer<InetAddress[]>() {
153+
@Override
154+
public InetAddress[] answer(InvocationOnMock invocation) throws Throwable {
155+
Thread.sleep(TEST_DNS_TIMEOUT_SECONDS * 5 * 1000);
156+
return new InetAddress[0];
157+
}
158+
};
143159
}
144160

145161
private static Set<?> setOf(Object... objects) {

0 commit comments

Comments
 (0)