blob: 192d03d33412b40b7be6e7e854a5194ea33e2c7b [file] [log] [blame]
/*
* Copyright 2015 The gRPC Authors
*
* Licensed 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 io.grpc.internal;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import com.google.common.base.Stopwatch;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.net.InetAddresses;
import com.google.common.testing.FakeTicker;
import io.grpc.ChannelLogger;
import io.grpc.EquivalentAddressGroup;
import io.grpc.HttpConnectProxiedSocketAddress;
import io.grpc.NameResolver;
import io.grpc.NameResolver.ConfigOrError;
import io.grpc.NameResolver.ResolutionResult;
import io.grpc.NameResolver.ServiceConfigParser;
import io.grpc.ProxyDetector;
import io.grpc.StaticTestingClassLoader;
import io.grpc.Status;
import io.grpc.Status.Code;
import io.grpc.SynchronizationContext;
import io.grpc.internal.DnsNameResolver.AddressResolver;
import io.grpc.internal.DnsNameResolver.ResolutionResults;
import io.grpc.internal.DnsNameResolver.ResourceResolver;
import io.grpc.internal.DnsNameResolver.ResourceResolverFactory;
import io.grpc.internal.JndiResourceResolverFactory.JndiResourceResolver;
import io.grpc.internal.JndiResourceResolverFactory.RecordFetcher;
import io.grpc.internal.SharedResourceHolder.Resource;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.DisableOnDebug;
import org.junit.rules.ExpectedException;
import org.junit.rules.TestRule;
import org.junit.rules.Timeout;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
/** Unit tests for {@link DnsNameResolver}. */
@RunWith(JUnit4.class)
public class DnsNameResolverTest {
@Rule public final TestRule globalTimeout = new DisableOnDebug(Timeout.seconds(10));
@Rule public final MockitoRule mocks = MockitoJUnit.rule();
@Rule public final ExpectedException thrown = ExpectedException.none();
private final Map<String, ?> serviceConfig = new LinkedHashMap<>();
private static final int DEFAULT_PORT = 887;
private final SynchronizationContext syncContext = new SynchronizationContext(
new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
throw new AssertionError(e);
}
});
private final NameResolver.Args args = NameResolver.Args.newBuilder()
.setDefaultPort(DEFAULT_PORT)
.setProxyDetector(GrpcUtil.DEFAULT_PROXY_DETECTOR)
.setSynchronizationContext(syncContext)
.setServiceConfigParser(mock(ServiceConfigParser.class))
.setChannelLogger(mock(ChannelLogger.class))
.build();
private final DnsNameResolverProvider provider = new DnsNameResolverProvider();
private final FakeClock fakeClock = new FakeClock();
private final FakeClock fakeExecutor = new FakeClock();
private final FakeExecutorResource fakeExecutorResource = new FakeExecutorResource();
private final class FakeExecutorResource implements Resource<Executor> {
private final AtomicInteger createCount = new AtomicInteger();
@Override
public Executor create() {
createCount.incrementAndGet();
return fakeExecutor.getScheduledExecutorService();
}
@Override
public void close(Executor instance) {}
}
@Mock
private NameResolver.Listener2 mockListener;
@Captor
private ArgumentCaptor<ResolutionResult> resultCaptor;
@Nullable
private String networkaddressCacheTtlPropertyValue;
@Mock
private RecordFetcher recordFetcher;
private DnsNameResolver newResolver(String name, int defaultPort) {
return newResolver(
name, defaultPort, GrpcUtil.NOOP_PROXY_DETECTOR, Stopwatch.createUnstarted());
}
private DnsNameResolver newResolver(String name, int defaultPort, boolean isAndroid) {
return newResolver(
name, defaultPort, GrpcUtil.NOOP_PROXY_DETECTOR, Stopwatch.createUnstarted(), isAndroid);
}
private DnsNameResolver newResolver(
String name,
int defaultPort,
ProxyDetector proxyDetector,
Stopwatch stopwatch) {
return newResolver(name, defaultPort, proxyDetector, stopwatch, false);
}
private DnsNameResolver newResolver(
String name,
final int defaultPort,
final ProxyDetector proxyDetector,
Stopwatch stopwatch,
boolean isAndroid) {
NameResolver.Args args =
NameResolver.Args.newBuilder()
.setDefaultPort(defaultPort)
.setProxyDetector(proxyDetector)
.setSynchronizationContext(syncContext)
.setServiceConfigParser(mock(ServiceConfigParser.class))
.setChannelLogger(mock(ChannelLogger.class))
.build();
return newResolver(name, stopwatch, isAndroid, args);
}
private DnsNameResolver newResolver(
String name, Stopwatch stopwatch, boolean isAndroid, NameResolver.Args args) {
DnsNameResolver dnsResolver =
new DnsNameResolver(
null, name, args, fakeExecutorResource, stopwatch, isAndroid, /* enableSrv= */ false);
// By default, using the mocked ResourceResolver to avoid I/O
dnsResolver.setResourceResolver(new JndiResourceResolver(recordFetcher));
return dnsResolver;
}
@Before
public void setUp() {
DnsNameResolver.enableJndi = true;
networkaddressCacheTtlPropertyValue =
System.getProperty(DnsNameResolver.NETWORKADDRESS_CACHE_TTL_PROPERTY);
}
@After
public void restoreSystemProperty() {
if (networkaddressCacheTtlPropertyValue == null) {
System.clearProperty(DnsNameResolver.NETWORKADDRESS_CACHE_TTL_PROPERTY);
} else {
System.setProperty(
DnsNameResolver.NETWORKADDRESS_CACHE_TTL_PROPERTY,
networkaddressCacheTtlPropertyValue);
}
}
@After
public void noMorePendingTasks() {
assertEquals(0, fakeClock.numPendingTasks());
assertEquals(0, fakeExecutor.numPendingTasks());
}
@Test
public void invalidDnsName() throws Exception {
testInvalidUri(new URI("dns", null, "/[invalid]", null));
}
@Test
public void validIpv6() throws Exception {
testValidUri(new URI("dns", null, "/[::1]", null), "[::1]", DEFAULT_PORT);
}
@Test
public void validDnsNameWithoutPort() throws Exception {
testValidUri(new URI("dns", null, "/foo.googleapis.com", null),
"foo.googleapis.com", DEFAULT_PORT);
}
@Test
public void validDnsNameWithPort() throws Exception {
testValidUri(new URI("dns", null, "/foo.googleapis.com:456", null),
"foo.googleapis.com:456", 456);
}
@Test
public void nullDnsName() {
try {
newResolver(null, DEFAULT_PORT);
fail("Expected NullPointerException");
} catch (NullPointerException e) {
// expected
}
}
@Test
public void invalidDnsName_containsUnderscore() {
try {
newResolver("host_1", DEFAULT_PORT);
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
// expected
}
}
@Test
public void resolve_androidIgnoresPropertyValue() throws Exception {
System.setProperty(DnsNameResolver.NETWORKADDRESS_CACHE_TTL_PROPERTY, Long.toString(2));
resolveNeverCache(true);
}
@Test
public void resolve_androidIgnoresPropertyValueCacheForever() throws Exception {
System.setProperty(DnsNameResolver.NETWORKADDRESS_CACHE_TTL_PROPERTY, Long.toString(-1));
resolveNeverCache(true);
}
@Test
public void resolve_neverCache() throws Exception {
System.setProperty(DnsNameResolver.NETWORKADDRESS_CACHE_TTL_PROPERTY, "0");
resolveNeverCache(false);
}
private void resolveNeverCache(boolean isAndroid) throws Exception {
final List<InetAddress> answer1 = createAddressList(2);
final List<InetAddress> answer2 = createAddressList(1);
String name = "foo.googleapis.com";
DnsNameResolver resolver = newResolver(name, 81, isAndroid);
AddressResolver mockResolver = mock(AddressResolver.class);
when(mockResolver.resolveAddress(anyString())).thenReturn(answer1).thenReturn(answer2);
resolver.setAddressResolver(mockResolver);
resolver.start(mockListener);
assertEquals(1, fakeExecutor.runDueTasks());
verify(mockListener).onResult(resultCaptor.capture());
assertAnswerMatches(answer1, 81, resultCaptor.getValue());
assertEquals(0, fakeClock.numPendingTasks());
resolver.refresh();
assertEquals(1, fakeExecutor.runDueTasks());
verify(mockListener, times(2)).onResult(resultCaptor.capture());
assertAnswerMatches(answer2, 81, resultCaptor.getValue());
assertEquals(0, fakeClock.numPendingTasks());
resolver.shutdown();
verify(mockResolver, times(2)).resolveAddress(anyString());
}
@Test
public void testExecutor_default() throws Exception {
final List<InetAddress> answer = createAddressList(2);
DnsNameResolver resolver = newResolver("foo.googleapis.com", 81);
AddressResolver mockResolver = mock(AddressResolver.class);
when(mockResolver.resolveAddress(anyString())).thenReturn(answer);
resolver.setAddressResolver(mockResolver);
resolver.start(mockListener);
assertEquals(1, fakeExecutor.runDueTasks());
verify(mockListener).onResult(resultCaptor.capture());
assertAnswerMatches(answer, 81, resultCaptor.getValue());
assertEquals(0, fakeClock.numPendingTasks());
resolver.shutdown();
assertThat(fakeExecutorResource.createCount.get()).isEqualTo(1);
}
@Test
public void testExecutor_custom() throws Exception {
final List<InetAddress> answer = createAddressList(2);
final AtomicInteger executions = new AtomicInteger();
NameResolver.Args args =
NameResolver.Args.newBuilder()
.setDefaultPort(81)
.setProxyDetector(GrpcUtil.NOOP_PROXY_DETECTOR)
.setSynchronizationContext(syncContext)
.setServiceConfigParser(mock(ServiceConfigParser.class))
.setChannelLogger(mock(ChannelLogger.class))
.setOffloadExecutor(
new Executor() {
@Override
public void execute(Runnable command) {
executions.incrementAndGet();
command.run();
}
})
.build();
DnsNameResolver resolver =
newResolver("foo.googleapis.com", Stopwatch.createUnstarted(), false, args);
AddressResolver mockResolver = mock(AddressResolver.class);
when(mockResolver.resolveAddress(anyString())).thenReturn(answer);
resolver.setAddressResolver(mockResolver);
resolver.start(mockListener);
assertEquals(0, fakeExecutor.runDueTasks());
verify(mockListener).onResult(resultCaptor.capture());
assertAnswerMatches(answer, 81, resultCaptor.getValue());
assertEquals(0, fakeClock.numPendingTasks());
resolver.shutdown();
assertThat(fakeExecutorResource.createCount.get()).isEqualTo(0);
assertThat(executions.get()).isEqualTo(1);
}
@Test
public void resolveAll_failsOnEmptyResult() {
DnsNameResolver nr = newResolver("dns:///addr.fake:1234", 443);
nr.setAddressResolver(new AddressResolver() {
@Override
public List<InetAddress> resolveAddress(String host) throws Exception {
return Collections.emptyList();
}
});
nr.start(mockListener);
assertThat(fakeExecutor.runDueTasks()).isEqualTo(1);
ArgumentCaptor<Status> ac = ArgumentCaptor.forClass(Status.class);
verify(mockListener).onError(ac.capture());
verifyNoMoreInteractions(mockListener);
assertThat(ac.getValue().getCode()).isEqualTo(Code.UNAVAILABLE);
assertThat(ac.getValue().getDescription()).contains("No DNS backend or balancer addresses");
}
@Test
public void resolve_cacheForever() throws Exception {
System.setProperty(DnsNameResolver.NETWORKADDRESS_CACHE_TTL_PROPERTY, "-1");
final List<InetAddress> answer1 = createAddressList(2);
String name = "foo.googleapis.com";
FakeTicker fakeTicker = new FakeTicker();
DnsNameResolver resolver =
newResolver(name, 81, GrpcUtil.NOOP_PROXY_DETECTOR, Stopwatch.createUnstarted(fakeTicker));
AddressResolver mockResolver = mock(AddressResolver.class);
when(mockResolver.resolveAddress(anyString()))
.thenReturn(answer1)
.thenThrow(new AssertionError("should not called twice"));
resolver.setAddressResolver(mockResolver);
resolver.start(mockListener);
assertEquals(1, fakeExecutor.runDueTasks());
verify(mockListener).onResult(resultCaptor.capture());
assertAnswerMatches(answer1, 81, resultCaptor.getValue());
assertEquals(0, fakeClock.numPendingTasks());
fakeTicker.advance(1, TimeUnit.DAYS);
resolver.refresh();
assertEquals(0, fakeExecutor.runDueTasks());
assertEquals(0, fakeClock.numPendingTasks());
verifyNoMoreInteractions(mockListener);
resolver.shutdown();
verify(mockResolver).resolveAddress(anyString());
}
@Test
public void resolve_usingCache() throws Exception {
long ttl = 60;
System.setProperty(DnsNameResolver.NETWORKADDRESS_CACHE_TTL_PROPERTY, Long.toString(ttl));
final List<InetAddress> answer = createAddressList(2);
String name = "foo.googleapis.com";
FakeTicker fakeTicker = new FakeTicker();
DnsNameResolver resolver =
newResolver(name, 81, GrpcUtil.NOOP_PROXY_DETECTOR, Stopwatch.createUnstarted(fakeTicker));
AddressResolver mockResolver = mock(AddressResolver.class);
when(mockResolver.resolveAddress(anyString()))
.thenReturn(answer)
.thenThrow(new AssertionError("should not reach here."));
resolver.setAddressResolver(mockResolver);
resolver.start(mockListener);
assertEquals(1, fakeExecutor.runDueTasks());
verify(mockListener).onResult(resultCaptor.capture());
assertAnswerMatches(answer, 81, resultCaptor.getValue());
assertEquals(0, fakeClock.numPendingTasks());
// this refresh should return cached result
fakeTicker.advance(ttl - 1, TimeUnit.SECONDS);
resolver.refresh();
assertEquals(0, fakeExecutor.runDueTasks());
assertEquals(0, fakeClock.numPendingTasks());
verifyNoMoreInteractions(mockListener);
resolver.shutdown();
verify(mockResolver).resolveAddress(anyString());
}
@Test
public void resolve_cacheExpired() throws Exception {
long ttl = 60;
System.setProperty(DnsNameResolver.NETWORKADDRESS_CACHE_TTL_PROPERTY, Long.toString(ttl));
final List<InetAddress> answer1 = createAddressList(2);
final List<InetAddress> answer2 = createAddressList(1);
String name = "foo.googleapis.com";
FakeTicker fakeTicker = new FakeTicker();
DnsNameResolver resolver =
newResolver(name, 81, GrpcUtil.NOOP_PROXY_DETECTOR, Stopwatch.createUnstarted(fakeTicker));
AddressResolver mockResolver = mock(AddressResolver.class);
when(mockResolver.resolveAddress(anyString())).thenReturn(answer1)
.thenReturn(answer2);
resolver.setAddressResolver(mockResolver);
resolver.start(mockListener);
assertEquals(1, fakeExecutor.runDueTasks());
verify(mockListener).onResult(resultCaptor.capture());
assertAnswerMatches(answer1, 81, resultCaptor.getValue());
assertEquals(0, fakeClock.numPendingTasks());
fakeTicker.advance(ttl + 1, TimeUnit.SECONDS);
resolver.refresh();
assertEquals(1, fakeExecutor.runDueTasks());
verify(mockListener, times(2)).onResult(resultCaptor.capture());
assertAnswerMatches(answer2, 81, resultCaptor.getValue());
assertEquals(0, fakeClock.numPendingTasks());
resolver.shutdown();
verify(mockResolver, times(2)).resolveAddress(anyString());
}
@Test
public void resolve_invalidTtlPropertyValue() throws Exception {
System.setProperty(DnsNameResolver.NETWORKADDRESS_CACHE_TTL_PROPERTY, "not_a_number");
resolveDefaultValue();
}
@Test
public void resolve_noPropertyValue() throws Exception {
System.clearProperty(DnsNameResolver.NETWORKADDRESS_CACHE_TTL_PROPERTY);
resolveDefaultValue();
}
private void resolveDefaultValue() throws Exception {
final List<InetAddress> answer1 = createAddressList(2);
final List<InetAddress> answer2 = createAddressList(1);
String name = "foo.googleapis.com";
FakeTicker fakeTicker = new FakeTicker();
DnsNameResolver resolver =
newResolver(name, 81, GrpcUtil.NOOP_PROXY_DETECTOR, Stopwatch.createUnstarted(fakeTicker));
AddressResolver mockResolver = mock(AddressResolver.class);
when(mockResolver.resolveAddress(anyString())).thenReturn(answer1).thenReturn(answer2);
resolver.setAddressResolver(mockResolver);
resolver.start(mockListener);
assertEquals(1, fakeExecutor.runDueTasks());
verify(mockListener).onResult(resultCaptor.capture());
assertAnswerMatches(answer1, 81, resultCaptor.getValue());
assertEquals(0, fakeClock.numPendingTasks());
fakeTicker.advance(DnsNameResolver.DEFAULT_NETWORK_CACHE_TTL_SECONDS, TimeUnit.SECONDS);
resolver.refresh();
assertEquals(0, fakeExecutor.runDueTasks());
assertEquals(0, fakeClock.numPendingTasks());
verifyNoMoreInteractions(mockListener);
fakeTicker.advance(1, TimeUnit.SECONDS);
resolver.refresh();
assertEquals(1, fakeExecutor.runDueTasks());
verify(mockListener, times(2)).onResult(resultCaptor.capture());
assertAnswerMatches(answer2, 81, resultCaptor.getValue());
assertEquals(0, fakeClock.numPendingTasks());
resolver.shutdown();
verify(mockResolver, times(2)).resolveAddress(anyString());
}
@Test
public void resolveAll_nullResourceResolver() throws Exception {
final String hostname = "addr.fake";
final Inet4Address backendAddr = InetAddresses.fromInteger(0x7f000001);
AddressResolver mockResolver = mock(AddressResolver.class);
when(mockResolver.resolveAddress(anyString()))
.thenReturn(Collections.<InetAddress>singletonList(backendAddr));
ResourceResolver resourceResolver = null;
boolean resovleSrv = true;
boolean resolveTxt = true;
ResolutionResults res = DnsNameResolver.resolveAll(
mockResolver, resourceResolver, resovleSrv, resolveTxt, hostname);
assertThat(res.addresses).containsExactly(backendAddr);
assertThat(res.balancerAddresses).isEmpty();
assertThat(res.txtRecords).isEmpty();
verify(mockResolver).resolveAddress(hostname);
}
@Test
public void resolveAll_nullResourceResolver_addressFailure() throws Exception {
final String hostname = "addr.fake";
AddressResolver mockResolver = mock(AddressResolver.class);
when(mockResolver.resolveAddress(anyString()))
.thenThrow(new IOException("no addr"));
ResourceResolver resourceResolver = null;
boolean resovleSrv = true;
boolean resolveTxt = true;
thrown.expect(RuntimeException.class);
thrown.expectMessage("no addr");
DnsNameResolver.resolveAll(mockResolver, resourceResolver, resovleSrv, resolveTxt, hostname);
}
@Test
public void resolveAll_presentResourceResolver() throws Exception {
final String hostname = "addr.fake";
final Inet4Address backendAddr = InetAddresses.fromInteger(0x7f000001);
final EquivalentAddressGroup balancerAddr = new EquivalentAddressGroup(new SocketAddress() {});
AddressResolver mockAddressResolver = mock(AddressResolver.class);
when(mockAddressResolver.resolveAddress(anyString()))
.thenReturn(Collections.<InetAddress>singletonList(backendAddr));
ResourceResolver mockResourceResolver = mock(ResourceResolver.class);
when(mockResourceResolver.resolveTxt(anyString()))
.thenReturn(Collections.singletonList("service config"));
when(mockResourceResolver.resolveSrv(ArgumentMatchers.any(AddressResolver.class), anyString()))
.thenReturn(Collections.singletonList(balancerAddr));
boolean resovleSrv = true;
boolean resolveTxt = true;
ResolutionResults res = DnsNameResolver.resolveAll(
mockAddressResolver, mockResourceResolver, resovleSrv, resolveTxt, hostname);
assertThat(res.addresses).containsExactly(backendAddr);
assertThat(res.balancerAddresses).containsExactly(balancerAddr);
assertThat(res.txtRecords).containsExactly("service config");
verify(mockAddressResolver).resolveAddress(hostname);
verify(mockResourceResolver).resolveTxt("_grpc_config." + hostname);
verify(mockResourceResolver).resolveSrv(mockAddressResolver, "_grpclb._tcp." + hostname);
}
@Test
public void resolveAll_onlyBalancers() throws Exception {
String hostname = "addr.fake";
EquivalentAddressGroup balancerAddr = new EquivalentAddressGroup(new SocketAddress() {});
AddressResolver mockAddressResolver = mock(AddressResolver.class);
when(mockAddressResolver.resolveAddress(anyString()))
.thenThrow(new UnknownHostException("I really tried"));
ResourceResolver mockResourceResolver = mock(ResourceResolver.class);
when(mockResourceResolver.resolveTxt(anyString()))
.thenReturn(Collections.<String>emptyList());
when(mockResourceResolver.resolveSrv(ArgumentMatchers.any(AddressResolver.class), anyString()))
.thenReturn(Collections.singletonList(balancerAddr));
boolean resovleSrv = true;
boolean resolveTxt = true;
ResolutionResults res = DnsNameResolver.resolveAll(
mockAddressResolver, mockResourceResolver, resovleSrv, resolveTxt, hostname);
assertThat(res.addresses).isEmpty();
assertThat(res.balancerAddresses).containsExactly(balancerAddr);
assertThat(res.txtRecords).isEmpty();
verify(mockAddressResolver).resolveAddress(hostname);
verify(mockResourceResolver).resolveTxt("_grpc_config." + hostname);
verify(mockResourceResolver).resolveSrv(mockAddressResolver, "_grpclb._tcp." + hostname);
}
@Test
public void resolveAll_balancerLookupFails() throws Exception {
final String hostname = "addr.fake";
final Inet4Address backendAddr = InetAddresses.fromInteger(0x7f000001);
AddressResolver mockAddressResolver = mock(AddressResolver.class);
when(mockAddressResolver.resolveAddress(anyString()))
.thenReturn(Collections.<InetAddress>singletonList(backendAddr));
ResourceResolver mockResourceResolver = mock(ResourceResolver.class);
when(mockResourceResolver.resolveTxt(anyString()))
.thenReturn(Collections.singletonList("service config"));
when(mockResourceResolver.resolveSrv(ArgumentMatchers.any(AddressResolver.class), anyString()))
.thenThrow(new Exception("something like javax.naming.NamingException"));
boolean resovleSrv = true;
boolean resolveTxt = true;
ResolutionResults res = DnsNameResolver.resolveAll(
mockAddressResolver, mockResourceResolver, resovleSrv, resolveTxt, hostname);
assertThat(res.addresses).containsExactly(backendAddr);
assertThat(res.balancerAddresses).isEmpty();
assertThat(res.txtRecords).containsExactly("service config");
verify(mockAddressResolver).resolveAddress(hostname);
verify(mockResourceResolver).resolveTxt("_grpc_config." + hostname);
verify(mockResourceResolver).resolveSrv(mockAddressResolver, "_grpclb._tcp." + hostname);
}
@Test
public void skipMissingJndiResolverResolver() throws Exception {
ClassLoader cl = new ClassLoader() {
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if ("io.grpc.internal.JndiResourceResolverFactory".equals(name)) {
throw new ClassNotFoundException();
}
return super.loadClass(name, resolve);
}
};
ResourceResolverFactory factory = DnsNameResolver.getResourceResolverFactory(cl);
assertThat(factory).isNull();
}
@Test
public void skipWrongJndiResolverResolver() throws Exception {
ClassLoader cl = new StaticTestingClassLoader(
DnsNameResolverTest.class.getClassLoader(),
Pattern.compile("io\\.grpc\\..+"));
ResourceResolverFactory factory = DnsNameResolver.getResourceResolverFactory(cl);
assertThat(factory).isNull();
}
@Test
public void doNotResolveWhenProxyDetected() throws Exception {
final String name = "foo.googleapis.com";
final int port = 81;
final InetSocketAddress proxyAddress =
new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 1000);
ProxyDetector alwaysDetectProxy = new ProxyDetector() {
@Override
public HttpConnectProxiedSocketAddress proxyFor(SocketAddress targetAddress) {
return HttpConnectProxiedSocketAddress.newBuilder()
.setTargetAddress((InetSocketAddress) targetAddress)
.setProxyAddress(proxyAddress)
.setUsername("username")
.setPassword("password").build();
}
};
DnsNameResolver resolver =
newResolver(name, port, alwaysDetectProxy, Stopwatch.createUnstarted());
AddressResolver mockAddressResolver = mock(AddressResolver.class);
when(mockAddressResolver.resolveAddress(anyString())).thenThrow(new AssertionError());
resolver.setAddressResolver(mockAddressResolver);
resolver.start(mockListener);
assertEquals(1, fakeExecutor.runDueTasks());
verify(mockListener).onResult(resultCaptor.capture());
List<EquivalentAddressGroup> result = resultCaptor.getValue().getAddresses();
assertThat(result).hasSize(1);
EquivalentAddressGroup eag = result.get(0);
assertThat(eag.getAddresses()).hasSize(1);
HttpConnectProxiedSocketAddress socketAddress =
(HttpConnectProxiedSocketAddress) eag.getAddresses().get(0);
assertSame(proxyAddress, socketAddress.getProxyAddress());
assertEquals("username", socketAddress.getUsername());
assertEquals("password", socketAddress.getPassword());
assertTrue(socketAddress.getTargetAddress().isUnresolved());
}
@Test
public void maybeChooseServiceConfig_failsOnMisspelling() {
Map<String, Object> bad = new LinkedHashMap<>();
bad.put("parcentage", 1.0);
thrown.expectMessage("Bad key");
DnsNameResolver.maybeChooseServiceConfig(bad, new Random(), "host");
}
@Test
public void maybeChooseServiceConfig_clientLanguageMatchesJava() {
Map<String, Object> choice = new LinkedHashMap<>();
List<String> langs = new ArrayList<>();
langs.add("java");
choice.put("clientLanguage", langs);
choice.put("serviceConfig", serviceConfig);
assertNotNull(DnsNameResolver.maybeChooseServiceConfig(choice, new Random(), "host"));
}
@Test
public void maybeChooseServiceConfig_clientLanguageDoesntMatchGo() {
Map<String, Object> choice = new LinkedHashMap<>();
List<String> langs = new ArrayList<>();
langs.add("go");
choice.put("clientLanguage", langs);
choice.put("serviceConfig", serviceConfig);
assertNull(DnsNameResolver.maybeChooseServiceConfig(choice, new Random(), "host"));
}
@Test
public void maybeChooseServiceConfig_clientLanguageCaseInsensitive() {
Map<String, Object> choice = new LinkedHashMap<>();
List<String> langs = new ArrayList<>();
langs.add("JAVA");
choice.put("clientLanguage", langs);
choice.put("serviceConfig", serviceConfig);
assertNotNull(DnsNameResolver.maybeChooseServiceConfig(choice, new Random(), "host"));
}
@Test
public void maybeChooseServiceConfig_clientLanguageMatchesEmtpy() {
Map<String, Object> choice = new LinkedHashMap<>();
List<String> langs = new ArrayList<>();
choice.put("clientLanguage", langs);
choice.put("serviceConfig", serviceConfig);
assertNotNull(DnsNameResolver.maybeChooseServiceConfig(choice, new Random(), "host"));
}
@Test
public void maybeChooseServiceConfig_clientLanguageMatchesMulti() {
Map<String, Object> choice = new LinkedHashMap<>();
List<String> langs = new ArrayList<>();
langs.add("go");
langs.add("java");
choice.put("clientLanguage", langs);
choice.put("serviceConfig", serviceConfig);
assertNotNull(DnsNameResolver.maybeChooseServiceConfig(choice, new Random(), "host"));
}
@Test
public void maybeChooseServiceConfig_percentageZeroAlwaysFails() {
Map<String, Object> choice = new LinkedHashMap<>();
choice.put("percentage", 0D);
choice.put("serviceConfig", serviceConfig);
assertNull(DnsNameResolver.maybeChooseServiceConfig(choice, new Random(), "host"));
}
@Test
public void maybeChooseServiceConfig_percentageHundredAlwaysSucceeds() {
Map<String, Object> choice = new LinkedHashMap<>();
choice.put("percentage", 100D);
choice.put("serviceConfig", serviceConfig);
assertNotNull(DnsNameResolver.maybeChooseServiceConfig(choice, new Random(), "host"));
}
@Test
public void maybeChooseServiceConfig_percentageAboveMatches50() {
Map<String, Object> choice = new LinkedHashMap<>();
choice.put("percentage", 50D);
choice.put("serviceConfig", serviceConfig);
Random r = new Random() {
@Override
public int nextInt(int bound) {
return 49;
}
};
assertNotNull(DnsNameResolver.maybeChooseServiceConfig(choice, r, "host"));
}
@Test
public void maybeChooseServiceConfig_percentageAtFails50() {
Map<String, Object> choice = new LinkedHashMap<>();
choice.put("percentage", 50D);
choice.put("serviceConfig", serviceConfig);
Random r = new Random() {
@Override
public int nextInt(int bound) {
return 50;
}
};
assertNull(DnsNameResolver.maybeChooseServiceConfig(choice, r, "host"));
}
@Test
public void maybeChooseServiceConfig_percentageAboveMatches99() {
Map<String, Object> choice = new LinkedHashMap<>();
choice.put("percentage", 99D);
choice.put("serviceConfig", serviceConfig);
Random r = new Random() {
@Override
public int nextInt(int bound) {
return 98;
}
};
assertNotNull(DnsNameResolver.maybeChooseServiceConfig(choice, r, "host"));
}
@Test
public void maybeChooseServiceConfig_percentageAtFails99() {
Map<String, Object> choice = new LinkedHashMap<>();
choice.put("percentage", 99D);
choice.put("serviceConfig", serviceConfig);
Random r = new Random() {
@Override
public int nextInt(int bound) {
return 99;
}
};
assertNull(DnsNameResolver.maybeChooseServiceConfig(choice, r, "host"));
}
@Test
public void maybeChooseServiceConfig_percentageAboveMatches1() {
Map<String, Object> choice = new LinkedHashMap<>();
choice.put("percentage", 1D);
choice.put("serviceConfig", serviceConfig);
Random r = new Random() {
@Override
public int nextInt(int bound) {
return 0;
}
};
assertNotNull(DnsNameResolver.maybeChooseServiceConfig(choice, r, "host"));
}
@Test
public void maybeChooseServiceConfig_percentageAtFails1() {
Map<String, Object> choice = new LinkedHashMap<>();
choice.put("percentage", 1D);
choice.put("serviceConfig", serviceConfig);
Random r = new Random() {
@Override
public int nextInt(int bound) {
return 1;
}
};
assertNull(DnsNameResolver.maybeChooseServiceConfig(choice, r, "host"));
}
@Test
public void maybeChooseServiceConfig_hostnameMatches() {
Map<String, Object> choice = new LinkedHashMap<>();
List<String> hosts = new ArrayList<>();
hosts.add("localhost");
choice.put("clientHostname", hosts);
choice.put("serviceConfig", serviceConfig);
assertNotNull(DnsNameResolver.maybeChooseServiceConfig(choice, new Random(), "localhost"));
}
@Test
public void maybeChooseServiceConfig_hostnameDoesntMatch() {
Map<String, Object> choice = new LinkedHashMap<>();
List<String> hosts = new ArrayList<>();
hosts.add("localhorse");
choice.put("clientHostname", hosts);
choice.put("serviceConfig", serviceConfig);
assertNull(DnsNameResolver.maybeChooseServiceConfig(choice, new Random(), "localhost"));
}
@Test
public void maybeChooseServiceConfig_clientLanguageCaseSensitive() {
Map<String, Object> choice = new LinkedHashMap<>();
List<String> hosts = new ArrayList<>();
hosts.add("LOCALHOST");
choice.put("clientHostname", hosts);
choice.put("serviceConfig", serviceConfig);
assertNull(DnsNameResolver.maybeChooseServiceConfig(choice, new Random(), "localhost"));
}
@Test
public void maybeChooseServiceConfig_hostnameMatchesEmtpy() {
Map<String, Object> choice = new LinkedHashMap<>();
List<String> hosts = new ArrayList<>();
choice.put("clientHostname", hosts);
choice.put("serviceConfig", serviceConfig);
assertNotNull(DnsNameResolver.maybeChooseServiceConfig(choice, new Random(), "host"));
}
@Test
public void maybeChooseServiceConfig_hostnameMatchesMulti() {
Map<String, Object> choice = new LinkedHashMap<>();
List<String> hosts = new ArrayList<>();
hosts.add("localhorse");
hosts.add("localhost");
choice.put("clientHostname", hosts);
choice.put("serviceConfig", serviceConfig);
assertNotNull(DnsNameResolver.maybeChooseServiceConfig(choice, new Random(), "localhost"));
}
@Test
public void parseTxtResults_misspelledName() throws Exception {
List<String> txtRecords = new ArrayList<>();
txtRecords.add("some_record");
txtRecords.add("_grpc_config=[]");
List<? extends Map<String, ?>> results = DnsNameResolver.parseTxtResults(txtRecords);
assertThat(results).isEmpty();
}
@Test
public void parseTxtResults_badTypeFails() throws Exception {
List<String> txtRecords = new ArrayList<>();
txtRecords.add("some_record");
txtRecords.add("grpc_config={}");
thrown.expect(ClassCastException.class);
thrown.expectMessage("wrong type");
DnsNameResolver.parseTxtResults(txtRecords);
}
@Test
public void parseTxtResults_badInnerTypeFails() throws Exception {
List<String> txtRecords = new ArrayList<>();
txtRecords.add("some_record");
txtRecords.add("grpc_config=[\"bogus\"]");
thrown.expect(ClassCastException.class);
thrown.expectMessage("not object");
DnsNameResolver.parseTxtResults(txtRecords);
}
@Test
public void parseTxtResults_combineAll() throws Exception {
Logger logger = Logger.getLogger(DnsNameResolver.class.getName());
Level level = logger.getLevel();
logger.setLevel(Level.SEVERE);
try {
List<String> txtRecords = new ArrayList<>();
txtRecords.add("some_record");
txtRecords.add("grpc_config=[{}, {}]"); // 2 records
txtRecords.add("grpc_config=[{\"\":{}}]"); // 1 record
List<? extends Map<String, ?>> results = DnsNameResolver.parseTxtResults(txtRecords);
assertThat(results).hasSize(2 + 1);
} finally {
logger.setLevel(level);
}
}
@Test
public void shouldUseJndi_alwaysFalseIfDisabled() {
boolean enableJndi = false;
boolean enableJndiLocalhost = true;
String host = "seemingly.valid.host";
assertFalse(DnsNameResolver.shouldUseJndi(enableJndi, enableJndiLocalhost, host));
}
@Test
public void shouldUseJndi_falseIfDisabledForLocalhost() {
boolean enableJndi = true;
boolean enableJndiLocalhost = false;
assertFalse(DnsNameResolver.shouldUseJndi(enableJndi, enableJndiLocalhost, "localhost"));
assertFalse(DnsNameResolver.shouldUseJndi(enableJndi, enableJndiLocalhost, "LOCALHOST"));
}
@Test
public void shouldUseJndi_trueIfLocalhostOverriden() {
boolean enableJndi = true;
boolean enableJndiLocalhost = true;
String host = "localhost";
assertTrue(DnsNameResolver.shouldUseJndi(enableJndi, enableJndiLocalhost, host));
}
@Test
public void shouldUseJndi_falseForIpv6() {
boolean enableJndi = true;
boolean enableJndiLocalhost = false;
assertFalse(DnsNameResolver.shouldUseJndi(enableJndi, enableJndiLocalhost, "::"));
assertFalse(DnsNameResolver.shouldUseJndi(enableJndi, enableJndiLocalhost, "::1"));
assertFalse(DnsNameResolver.shouldUseJndi(enableJndi, enableJndiLocalhost, "2001:db8:1234::"));
assertFalse(DnsNameResolver.shouldUseJndi(
enableJndi, enableJndiLocalhost, "[2001:db8:1234::]"));
assertFalse(DnsNameResolver.shouldUseJndi(
enableJndi, enableJndiLocalhost, "2001:db8:1234::%3"));
}
@Test
public void shouldUseJndi_falseForIpv4() {
boolean enableJndi = true;
boolean enableJndiLocalhost = false;
assertFalse(DnsNameResolver.shouldUseJndi(enableJndi, enableJndiLocalhost, "127.0.0.1"));
assertFalse(DnsNameResolver.shouldUseJndi(enableJndi, enableJndiLocalhost, "192.168.0.1"));
assertFalse(DnsNameResolver.shouldUseJndi(enableJndi, enableJndiLocalhost, "134744072"));
}
@Test
public void shouldUseJndi_falseForEmpty() {
boolean enableJndi = true;
boolean enableJndiLocalhost = false;
assertFalse(DnsNameResolver.shouldUseJndi(enableJndi, enableJndiLocalhost, ""));
}
@Test
public void shouldUseJndi_trueIfItMightPossiblyBeValid() {
boolean enableJndi = true;
boolean enableJndiLocalhost = false;
assertTrue(DnsNameResolver.shouldUseJndi(enableJndi, enableJndiLocalhost, "remotehost"));
assertTrue(DnsNameResolver.shouldUseJndi(enableJndi, enableJndiLocalhost, "remotehost.gov"));
assertTrue(DnsNameResolver.shouldUseJndi(enableJndi, enableJndiLocalhost, "f.q.d.n."));
assertTrue(DnsNameResolver.shouldUseJndi(
enableJndi, enableJndiLocalhost, "8.8.8.8.in-addr.arpa."));
assertTrue(DnsNameResolver.shouldUseJndi(
enableJndi, enableJndiLocalhost, "2001-db8-1234--as3.ipv6-literal.net"));
}
@Test
public void parseServiceConfig_capturesParseError() {
ConfigOrError result = DnsNameResolver.parseServiceConfig(
Arrays.asList("grpc_config=bogus"), new Random(), "localhost");
assertThat(result).isNotNull();
assertThat(result.getError().getCode()).isEqualTo(Status.Code.UNKNOWN);
assertThat(result.getError().getDescription()).contains("failed to parse TXT records");
}
@Test
public void parseServiceConfig_capturesChoiceError() {
ConfigOrError result = DnsNameResolver.parseServiceConfig(
Arrays.asList("grpc_config=[{\"hi\":{}}]"), new Random(), "localhost");
assertThat(result).isNotNull();
assertThat(result.getError().getCode()).isEqualTo(Status.Code.UNKNOWN);
assertThat(result.getError().getDescription()).contains("failed to pick");
}
@Test
public void parseServiceConfig_noChoiceIsNull() {
ConfigOrError result = DnsNameResolver.parseServiceConfig(
Arrays.asList("grpc_config=[]"), new Random(), "localhost");
assertThat(result).isNull();
}
@Test
public void parseServiceConfig_matches() {
ConfigOrError result = DnsNameResolver.parseServiceConfig(
Arrays.asList("grpc_config=[{\"serviceConfig\":{}}]"), new Random(), "localhost");
assertThat(result).isNotNull();
assertThat(result.getError()).isNull();
assertThat(result.getConfig()).isEqualTo(ImmutableMap.of());
}
private void testInvalidUri(URI uri) {
try {
provider.newNameResolver(uri, args);
fail("Should have failed");
} catch (IllegalArgumentException e) {
// expected
}
}
private void testValidUri(URI uri, String exportedAuthority, int expectedPort) {
DnsNameResolver resolver = provider.newNameResolver(uri, args);
assertNotNull(resolver);
assertEquals(expectedPort, resolver.getPort());
assertEquals(exportedAuthority, resolver.getServiceAuthority());
}
private byte lastByte = 0;
private List<InetAddress> createAddressList(int n) throws UnknownHostException {
List<InetAddress> list = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
list.add(InetAddress.getByAddress(new byte[] {127, 0, 0, ++lastByte}));
}
return list;
}
private static void assertAnswerMatches(
List<InetAddress> addrs, int port, ResolutionResult resolutionResult) {
assertThat(resolutionResult.getAddresses()).hasSize(addrs.size());
for (int i = 0; i < addrs.size(); i++) {
EquivalentAddressGroup addrGroup = resolutionResult.getAddresses().get(i);
InetSocketAddress socketAddr =
(InetSocketAddress) Iterables.getOnlyElement(addrGroup.getAddresses());
assertEquals("Addr " + i, port, socketAddr.getPort());
assertEquals("Addr " + i, addrs.get(i), socketAddr.getAddress());
}
}
}