From 446f3d2feb91579117887f14ce1853ec07422dbb Mon Sep 17 00:00:00 2001 From: Chengyuan Zhang Date: Wed, 19 Feb 2020 11:00:15 -0800 Subject: [PATCH] Add test for GrpclbNameResolver --- .../io/grpc/internal/DnsNameResolver.java | 15 +- .../java/io/grpc/internal/GrpcAttributes.java | 1 - .../io/grpc/grpclb/GrpclbNameResolver.java | 24 ++ .../grpc/grpclb/GrpclbNameResolverTest.java | 315 ++++++++++++++++++ 4 files changed, 348 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/DnsNameResolver.java b/core/src/main/java/io/grpc/internal/DnsNameResolver.java index d66108ded23..369f3f16974 100644 --- a/core/src/main/java/io/grpc/internal/DnsNameResolver.java +++ b/core/src/main/java/io/grpc/internal/DnsNameResolver.java @@ -532,11 +532,12 @@ private static long getNetworkAddressCacheTtlNanos(boolean isAndroid) { /** * Describes a parsed SRV record. */ - protected static final class SrvRecord { + @VisibleForTesting + public static final class SrvRecord { public final String host; public final int port; - SrvRecord(String host, int port) { + public SrvRecord(String host, int port) { this.host = host; this.port = port; } @@ -569,12 +570,12 @@ public String toString() { } @VisibleForTesting - void setAddressResolver(AddressResolver addressResolver) { + protected void setAddressResolver(AddressResolver addressResolver) { this.addressResolver = addressResolver; } @VisibleForTesting - void setResourceResolver(ResourceResolver resourceResolver) { + protected void setResourceResolver(ResourceResolver resourceResolver) { this.resourceResolver.set(resourceResolver); } @@ -600,7 +601,8 @@ interface ResourceResolverFactory { /** * AddressResolver resolves a hostname into a list of addresses. */ - protected interface AddressResolver { + @VisibleForTesting + public interface AddressResolver { List resolveAddress(String host) throws Exception; } @@ -616,7 +618,8 @@ public List resolveAddress(String host) throws UnknownHostException /** * {@link ResourceResolver} is a Dns ResourceRecord resolver. */ - protected interface ResourceResolver { + @VisibleForTesting + public interface ResourceResolver { List resolveTxt(String host) throws Exception; List resolveSrv(String host) throws Exception; diff --git a/core/src/main/java/io/grpc/internal/GrpcAttributes.java b/core/src/main/java/io/grpc/internal/GrpcAttributes.java index 186dd8acb6b..3401c1d8385 100644 --- a/core/src/main/java/io/grpc/internal/GrpcAttributes.java +++ b/core/src/main/java/io/grpc/internal/GrpcAttributes.java @@ -21,7 +21,6 @@ import io.grpc.Grpc; import io.grpc.NameResolver; import io.grpc.SecurityLevel; -import java.util.List; import java.util.Map; /** diff --git a/grpclb/src/main/java/io/grpc/grpclb/GrpclbNameResolver.java b/grpclb/src/main/java/io/grpc/grpclb/GrpclbNameResolver.java index 175f973b0c7..76a25639c7a 100644 --- a/grpclb/src/main/java/io/grpc/grpclb/GrpclbNameResolver.java +++ b/grpclb/src/main/java/io/grpc/grpclb/GrpclbNameResolver.java @@ -16,6 +16,7 @@ package io.grpc.grpclb; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Stopwatch; import io.grpc.Attributes; import io.grpc.EquivalentAddressGroup; @@ -140,4 +141,27 @@ private List resolveBalancerAddresses() { } return Collections.unmodifiableList(balancerAddresses); } + + @VisibleForTesting + @Override + protected void setAddressResolver(AddressResolver addressResolver) { + super.setAddressResolver(addressResolver); + } + + @VisibleForTesting + @Override + protected void setResourceResolver(ResourceResolver resourceResolver) { + super.setResourceResolver(resourceResolver); + } + + @VisibleForTesting + @Override + protected String getHost() { + return super.getHost(); + } + + @VisibleForTesting + static void setEnableTxt(boolean enableTxt) { + DnsNameResolver.enableTxt = enableTxt; + } } diff --git a/grpclb/src/test/java/io/grpc/grpclb/GrpclbNameResolverTest.java b/grpclb/src/test/java/io/grpc/grpclb/GrpclbNameResolverTest.java index f3f6cc09e07..317e7e28e8b 100644 --- a/grpclb/src/test/java/io/grpc/grpclb/GrpclbNameResolverTest.java +++ b/grpclb/src/test/java/io/grpc/grpclb/GrpclbNameResolverTest.java @@ -16,7 +16,322 @@ package io.grpc.grpclb; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.common.collect.Iterables; +import io.grpc.Attributes; +import io.grpc.ChannelLogger; +import io.grpc.EquivalentAddressGroup; +import io.grpc.NameResolver; +import io.grpc.NameResolver.ConfigOrError; +import io.grpc.NameResolver.ResolutionResult; +import io.grpc.NameResolver.ServiceConfigParser; +import io.grpc.Status; +import io.grpc.Status.Code; +import io.grpc.SynchronizationContext; +import io.grpc.internal.DnsNameResolver.AddressResolver; +import io.grpc.internal.DnsNameResolver.ResourceResolver; +import io.grpc.internal.DnsNameResolver.SrvRecord; +import io.grpc.internal.FakeClock; +import io.grpc.internal.GrpcUtil; +import io.grpc.internal.SharedResourceHolder.Resource; +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Executor; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +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.invocation.InvocationOnMock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.mockito.stubbing.Answer; + /** Unit tests for {@link GrpclbNameResolver}. */ +@RunWith(JUnit4.class) public class GrpclbNameResolverTest { + @Rule + public final MockitoRule mocks = MockitoJUnit.rule(); + + private static final String NAME = "foo.googleapis.com"; + 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 FakeClock fakeClock = new FakeClock(); + private final FakeExecutorResource fakeExecutorResource = new FakeExecutorResource(); + + private final class FakeExecutorResource implements Resource { + + @Override + public Executor create() { + return fakeClock.getScheduledExecutorService(); + } + + @Override + public void close(Executor instance) {} + } + + @Captor private ArgumentCaptor resultCaptor; + @Captor private ArgumentCaptor errorCaptor; + @Mock private ServiceConfigParser serviceConfigParser; + @Mock private NameResolver.Listener2 mockListener; + + private GrpclbNameResolver resolver; + private String hostName; + + @Before + public void setUp() { + GrpclbNameResolver.setEnableTxt(true); + NameResolver.Args args = + NameResolver.Args.newBuilder() + .setDefaultPort(DEFAULT_PORT) + .setProxyDetector(GrpcUtil.NOOP_PROXY_DETECTOR) + .setSynchronizationContext(syncContext) + .setServiceConfigParser(serviceConfigParser) + .setChannelLogger(mock(ChannelLogger.class)) + .build(); + resolver = + new GrpclbNameResolver( + null, NAME, args, fakeExecutorResource, fakeClock.getStopwatchSupplier().get(), + /* isAndroid */false); + hostName = resolver.getHost(); + assertThat(hostName).isEqualTo(NAME); + } + + @Test + public void resolve_emptyResult() { + resolver.setAddressResolver(new AddressResolver() { + @Override + public List resolveAddress(String host) throws Exception { + return Collections.emptyList(); + } + }); + resolver.setResourceResolver(new ResourceResolver() { + @Override + public List resolveTxt(String host) throws Exception { + return Collections.emptyList(); + } + + @Override + public List resolveSrv(String host) throws Exception { + return Collections.emptyList(); + } + }); + + resolver.start(mockListener); + assertThat(fakeClock.runDueTasks()).isEqualTo(1); + + verify(mockListener).onResult(resultCaptor.capture()); + ResolutionResult result = resultCaptor.getValue(); + assertThat(result.getAddresses()).isEmpty(); + assertThat(result.getAttributes()).isEqualTo(Attributes.EMPTY); + assertThat(result.getServiceConfig()).isNull(); + } + + @Test + public void resolve_presentResourceResolver() throws Exception { + InetAddress backendAddr = InetAddress.getByAddress(new byte[] {127, 0, 0, 0}); + InetAddress lbAddr = InetAddress.getByAddress(new byte[] {10, 1, 0, 0}); + int lbPort = 8080; + String lbName = "foo.example.com."; // original name in SRV record + SrvRecord srvRecord = new SrvRecord(lbName, 8080); + AddressResolver mockAddressResolver = mock(AddressResolver.class); + when(mockAddressResolver.resolveAddress(hostName)) + .thenReturn(Collections.singletonList(backendAddr)); + when(mockAddressResolver.resolveAddress(lbName)) + .thenReturn(Collections.singletonList(lbAddr)); + ResourceResolver mockResourceResolver = mock(ResourceResolver.class); + when(mockResourceResolver.resolveTxt(anyString())) + .thenReturn( + Collections.singletonList( + "grpc_config=[{\"clientLanguage\": [\"java\"], \"serviceConfig\": {}}]")); + when(mockResourceResolver.resolveSrv(anyString())) + .thenReturn(Collections.singletonList(srvRecord)); + when(serviceConfigParser.parseServiceConfig(ArgumentMatchers.anyMap())) + .thenAnswer(new Answer() { + @Override + public ConfigOrError answer(InvocationOnMock invocation) { + Object[] args = invocation.getArguments(); + return ConfigOrError.fromConfig(args[0]); + } + }); + + resolver.setAddressResolver(mockAddressResolver); + resolver.setResourceResolver(mockResourceResolver); + + resolver.start(mockListener); + assertThat(fakeClock.runDueTasks()).isEqualTo(1); + verify(mockListener).onResult(resultCaptor.capture()); + ResolutionResult result = resultCaptor.getValue(); + InetSocketAddress resolvedBackendAddr = + (InetSocketAddress) Iterables.getOnlyElement( + Iterables.getOnlyElement(result.getAddresses()).getAddresses()); + assertThat(resolvedBackendAddr.getAddress()).isEqualTo(backendAddr); + EquivalentAddressGroup resolvedBalancerAddr = + Iterables.getOnlyElement(result.getAttributes().get(GrpclbConstants.ATTR_LB_ADDRS)); + assertThat(resolvedBalancerAddr.getAttributes().get(GrpclbConstants.ATTR_LB_ADDR_AUTHORITY)) + .isEqualTo("foo.example.com"); + InetSocketAddress resolvedBalancerSockAddr = + (InetSocketAddress) Iterables.getOnlyElement(resolvedBalancerAddr.getAddresses()); + assertThat(resolvedBalancerSockAddr.getAddress()).isEqualTo(lbAddr); + assertThat(resolvedBalancerSockAddr.getPort()).isEqualTo(lbPort); + assertThat(result.getServiceConfig().getConfig()).isNotNull(); + verify(mockAddressResolver).resolveAddress(hostName); + verify(mockResourceResolver).resolveTxt("_grpc_config." + hostName); + verify(mockResourceResolver).resolveSrv("_grpclb._tcp." + hostName); + } + + @Test + public void resolve_nullResourceResolver() throws Exception { + InetAddress backendAddr = InetAddress.getByAddress(new byte[] {127, 0, 0, 0}); + AddressResolver mockAddressResolver = mock(AddressResolver.class); + when(mockAddressResolver.resolveAddress(anyString())) + .thenReturn(Collections.singletonList(backendAddr)); + ResourceResolver resourceResolver = null; + + resolver.setAddressResolver(mockAddressResolver); + resolver.setResourceResolver(resourceResolver); + + resolver.start(mockListener); + assertThat(fakeClock.runDueTasks()).isEqualTo(1); + verify(mockListener).onResult(resultCaptor.capture()); + ResolutionResult result = resultCaptor.getValue(); + assertThat(result.getAddresses()) + .containsExactly( + new EquivalentAddressGroup(new InetSocketAddress(backendAddr, DEFAULT_PORT))); + assertThat(result.getAttributes()).isEqualTo(Attributes.EMPTY); + assertThat(result.getServiceConfig()).isNull(); + } + + @Test + public void resolve_nullResourceResolver_addressFailure() throws Exception { + AddressResolver mockAddressResolver = mock(AddressResolver.class); + when(mockAddressResolver.resolveAddress(anyString())).thenThrow(new IOException("no addr")); + ResourceResolver resourceResolver = null; + + resolver.setAddressResolver(mockAddressResolver); + resolver.setResourceResolver(resourceResolver); + + resolver.start(mockListener); + assertThat(fakeClock.runDueTasks()).isEqualTo(1); + verify(mockListener).onError(errorCaptor.capture()); + Status errorStatus = errorCaptor.getValue(); + assertThat(errorStatus.getCode()).isEqualTo(Code.UNAVAILABLE); + assertThat(errorStatus.getCause()).hasMessageThat().contains("no addr"); + } + + @Test + public void resolve_addressFailure_stillLookUpBalancersAndServiceConfig() throws Exception { + InetAddress lbAddr = InetAddress.getByAddress(new byte[] {10, 1, 0, 0}); + int lbPort = 8080; + String lbName = "foo.example.com."; // original name in SRV record + SrvRecord srvRecord = new SrvRecord(lbName, 8080); + AddressResolver mockAddressResolver = mock(AddressResolver.class); + when(mockAddressResolver.resolveAddress(hostName)) + .thenThrow(new UnknownHostException("I really tried")); + when(mockAddressResolver.resolveAddress(lbName)) + .thenReturn(Collections.singletonList(lbAddr)); + ResourceResolver mockResourceResolver = mock(ResourceResolver.class); + when(mockResourceResolver.resolveTxt(anyString())).thenReturn(Collections.emptyList()); + when(mockResourceResolver.resolveSrv(anyString())) + .thenReturn(Collections.singletonList(srvRecord)); + + resolver.setAddressResolver(mockAddressResolver); + resolver.setResourceResolver(mockResourceResolver); + + resolver.start(mockListener); + assertThat(fakeClock.runDueTasks()).isEqualTo(1); + verify(mockListener).onResult(resultCaptor.capture()); + ResolutionResult result = resultCaptor.getValue(); + assertThat(result.getAddresses()).isEmpty(); + EquivalentAddressGroup resolvedBalancerAddr = + Iterables.getOnlyElement(result.getAttributes().get(GrpclbConstants.ATTR_LB_ADDRS)); + assertThat(resolvedBalancerAddr.getAttributes().get(GrpclbConstants.ATTR_LB_ADDR_AUTHORITY)) + .isEqualTo("foo.example.com"); + InetSocketAddress resolvedBalancerSockAddr = + (InetSocketAddress) Iterables.getOnlyElement(resolvedBalancerAddr.getAddresses()); + assertThat(resolvedBalancerSockAddr.getAddress()).isEqualTo(lbAddr); + assertThat(resolvedBalancerSockAddr.getPort()).isEqualTo(lbPort); + assertThat(result.getServiceConfig()).isNull(); + verify(mockAddressResolver).resolveAddress(hostName); + verify(mockResourceResolver).resolveTxt("_grpc_config." + hostName); + verify(mockResourceResolver).resolveSrv("_grpclb._tcp." + hostName); + } + + @Test + public void resolveAll_balancerLookupFails_stillLookUpServiceConfig() throws Exception { + InetAddress backendAddr = InetAddress.getByAddress(new byte[] {127, 0, 0, 0}); + AddressResolver mockAddressResolver = mock(AddressResolver.class); + when(mockAddressResolver.resolveAddress(hostName)) + .thenReturn(Collections.singletonList(backendAddr)); + ResourceResolver mockResourceResolver = mock(ResourceResolver.class); + when(mockResourceResolver.resolveTxt(anyString())) + .thenReturn(Collections.emptyList()); + when(mockResourceResolver.resolveSrv(anyString())) + .thenThrow(new Exception("something like javax.naming.NamingException")); + + resolver.setAddressResolver(mockAddressResolver); + resolver.setResourceResolver(mockResourceResolver); + + resolver.start(mockListener); + assertThat(fakeClock.runDueTasks()).isEqualTo(1); + verify(mockListener).onResult(resultCaptor.capture()); + ResolutionResult result = resultCaptor.getValue(); + + InetSocketAddress resolvedBackendAddr = + (InetSocketAddress) Iterables.getOnlyElement( + Iterables.getOnlyElement(result.getAddresses()).getAddresses()); + assertThat(resolvedBackendAddr.getAddress()).isEqualTo(backendAddr); + assertThat(result.getAttributes().get(GrpclbConstants.ATTR_LB_ADDRS)).isNull(); + verify(mockAddressResolver).resolveAddress(hostName); + verify(mockResourceResolver).resolveTxt("_grpc_config." + hostName); + verify(mockResourceResolver).resolveSrv("_grpclb._tcp." + hostName); + } + + @Test + public void resolve_addressAndBalancersLookupFail_neverLookupServiceConfig() throws Exception { + AddressResolver mockAddressResolver = mock(AddressResolver.class); + when(mockAddressResolver.resolveAddress(anyString())) + .thenThrow(new UnknownHostException("I really tried")); + ResourceResolver mockResourceResolver = mock(ResourceResolver.class); + lenient().when(mockResourceResolver.resolveTxt(anyString())) + .thenThrow(new Exception("something like javax.naming.NamingException")); + when(mockResourceResolver.resolveSrv(anyString())) + .thenThrow(new Exception("something like javax.naming.NamingException")); + + resolver.setAddressResolver(mockAddressResolver); + resolver.setResourceResolver(mockResourceResolver); + + resolver.start(mockListener); + assertThat(fakeClock.runDueTasks()).isEqualTo(1); + verify(mockListener).onError(errorCaptor.capture()); + Status errorStatus = errorCaptor.getValue(); + assertThat(errorStatus.getCode()).isEqualTo(Code.UNAVAILABLE); + verify(mockAddressResolver).resolveAddress(hostName); + verify(mockResourceResolver, never()).resolveTxt("_grpc_config." + hostName); + verify(mockResourceResolver).resolveSrv("_grpclb._tcp." + hostName); + } } \ No newline at end of file