core: add LoadBalancer.canHandleEmptyAddressListFromNameResolution() (#5148)

Currently ManagedChannelImpl will interpret empty address list from
NameResolver as an error, and LoadBalancer will NEVER receive an empty
list from handleResolvedAddressGroups().  There is a case in the
request-routing design where a LoadBalancer only receives service
config with which it constructs children NameResolver/LoadBalancer
pairs, thus no address is expected at this level.

canHandleEmptyAddressListFromNameResolution() is a signal to Channel
(and to the parent LoadBalancer in case of hierachical LoadBalancers)
about whether it accepts empty address lists.  The default is false,
which is the current behavior.

The logic is currently duplicated in ManagedChannelImpl and
AutoConfiguredLoadBalancer.  The one in ManagedChannelImpl will be
removed once we delete ManagedChannelBuilder.loadBalancerFactory() as
AutoConfiguredLoadBalancer will always be the top-level LoadBalancer
by then.
This commit is contained in:
Kun Zhang 2018-12-10 10:18:19 -08:00 committed by GitHub
parent 01f79bb909
commit c0175e4cbe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 251 additions and 75 deletions

View File

@ -168,6 +168,20 @@ public abstract class LoadBalancer {
*/
public abstract void shutdown();
/**
* Whether this LoadBalancer can handle empty address group list to be passed to {@link
* #handleResolvedAddressGroups}. The default implementation returns {@code false}, meaning that
* if the NameResolver returns an empty list, the Channel will turn that into an error and call
* {@link #handleNameResolutionError}. LoadBalancers that want to accept empty lists should
* override this method and return {@code true}.
*
* <p>This method should always return a constant value. It's not specified when this will be
* called.
*/
public boolean canHandleEmptyAddressListFromNameResolution() {
return false;
}
/**
* The main balancing logic. It <strong>must be thread-safe</strong>. Typically it should only
* synchronize on its own state, and avoid synchronizing with the LoadBalancer's state.

View File

@ -137,7 +137,17 @@ public final class AutoConfiguredLoadBalancerFactory extends LoadBalancer.Factor
attributes =
attributes.toBuilder().set(ATTR_LOAD_BALANCING_CONFIG, selection.config).build();
}
getDelegate().handleResolvedAddressGroups(selection.serverList, attributes);
LoadBalancer delegate = getDelegate();
if (selection.serverList.isEmpty()
&& !delegate.canHandleEmptyAddressListFromNameResolution()) {
delegate.handleNameResolutionError(
Status.UNAVAILABLE.withDescription(
"Name resolver returned no usable address. addrs="
+ servers + ", attrs=" + attributes));
} else {
delegate.handleResolvedAddressGroups(selection.serverList, attributes);
}
}
@Override
@ -150,6 +160,11 @@ public final class AutoConfiguredLoadBalancerFactory extends LoadBalancer.Factor
getDelegate().handleSubchannelState(subchannel, stateInfo);
}
@Override
public boolean canHandleEmptyAddressListFromNameResolution() {
return true;
}
@Override
public void shutdown() {
delegate.shutdown();

View File

@ -1257,11 +1257,6 @@ final class ManagedChannelImpl extends ManagedChannel implements
@Override
public void onAddresses(final List<EquivalentAddressGroup> servers, final Attributes config) {
if (servers.isEmpty()) {
onError(Status.UNAVAILABLE.withDescription(
"Name resolver " + resolver + " returned an empty list"));
return;
}
channelLogger.log(
ChannelLogLevel.DEBUG, "Resolved address: {0}, config={1}", servers, config);
@ -1300,7 +1295,12 @@ final class ManagedChannelImpl extends ManagedChannel implements
}
}
helper.lb.handleResolvedAddressGroups(servers, config);
if (servers.isEmpty() && !helper.lb.canHandleEmptyAddressListFromNameResolution()) {
onError(Status.UNAVAILABLE.withDescription(
"Name resolver " + resolver + " returned an empty list"));
} else {
helper.lb.handleResolvedAddressGroups(servers, config);
}
}
}

View File

@ -57,6 +57,11 @@ public abstract class ForwardingLoadBalancer extends LoadBalancer {
delegate().shutdown();
}
@Override
public boolean canHandleEmptyAddressListFromNameResolution() {
return delegate().canHandleEmptyAddressListFromNameResolution();
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this).add("delegate", delegate()).toString();

View File

@ -26,11 +26,13 @@ import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.same;
import static org.mockito.Matchers.startsWith;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.atLeast;
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.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import io.grpc.Attributes;
import io.grpc.ChannelLogger;
@ -46,6 +48,7 @@ import io.grpc.LoadBalancerProvider;
import io.grpc.LoadBalancerRegistry;
import io.grpc.ManagedChannel;
import io.grpc.Status;
import io.grpc.Status.Code;
import io.grpc.SynchronizationContext;
import io.grpc.grpclb.GrpclbLoadBalancerProvider;
import io.grpc.internal.AutoConfiguredLoadBalancerFactory.AutoConfiguredLoadBalancer;
@ -74,22 +77,34 @@ import org.mockito.ArgumentCaptor;
*/
@RunWith(JUnit4.class)
public class AutoConfiguredLoadBalancerFactoryTest {
private static final LoadBalancerRegistry defaultRegistry =
LoadBalancerRegistry.getDefaultRegistry();
private final AutoConfiguredLoadBalancerFactory lbf =
new AutoConfiguredLoadBalancerFactory(GrpcUtil.DEFAULT_LB_POLICY);
private final ChannelLogger channelLogger = mock(ChannelLogger.class);
private final LoadBalancer testLbBalancer = mock(LoadBalancer.class);
private final LoadBalancer testLbBalancer2 = mock(LoadBalancer.class);
private final LoadBalancerProvider testLbBalancerProvider =
mock(LoadBalancerProvider.class, delegatesTo(new TestLbLoadBalancerProvider()));
mock(LoadBalancerProvider.class,
delegatesTo(new FakeLoadBalancerProvider("test_lb", testLbBalancer)));
private final LoadBalancerProvider testLbBalancerProvider2 =
mock(LoadBalancerProvider.class,
delegatesTo(new FakeLoadBalancerProvider("test_lb2", testLbBalancer2)));
@Before
public void setUp() {
LoadBalancerRegistry.getDefaultRegistry().register(testLbBalancerProvider);
when(testLbBalancer.canHandleEmptyAddressListFromNameResolution()).thenCallRealMethod();
assertThat(testLbBalancer.canHandleEmptyAddressListFromNameResolution()).isFalse();
when(testLbBalancer2.canHandleEmptyAddressListFromNameResolution()).thenReturn(true);
defaultRegistry.register(testLbBalancerProvider);
defaultRegistry.register(testLbBalancerProvider2);
}
@After
public void tearDown() {
LoadBalancerRegistry.getDefaultRegistry().deregister(testLbBalancerProvider);
defaultRegistry.deregister(testLbBalancerProvider);
defaultRegistry.deregister(testLbBalancerProvider2);
}
@Test
@ -164,11 +179,6 @@ public class AutoConfiguredLoadBalancerFactoryTest {
assertThat(addrs).isEqualTo(servers);
return new TestSubchannel(addrs, attrs);
}
@Override
public void updateBalancingState(ConnectivityState newState, SubchannelPicker newPicker) {
// noop
}
};
AutoConfiguredLoadBalancer lb =
(AutoConfiguredLoadBalancer) lbf.newLoadBalancer(helper);
@ -195,11 +205,6 @@ public class AutoConfiguredLoadBalancerFactoryTest {
assertThat(addrs).isEqualTo(servers);
return new TestSubchannel(addrs, attrs);
}
@Override
public void updateBalancingState(ConnectivityState newState, SubchannelPicker newPicker) {
// noop
}
};
AutoConfiguredLoadBalancer lb =
(AutoConfiguredLoadBalancer) lbf.newLoadBalancer(helper);
@ -240,12 +245,7 @@ public class AutoConfiguredLoadBalancerFactoryTest {
.build();
final List<EquivalentAddressGroup> servers =
Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){}));
Helper helper = new TestHelper() {
@Override
public void updateBalancingState(ConnectivityState newState, SubchannelPicker newPicker) {
// noop
}
};
Helper helper = new TestHelper();
AutoConfiguredLoadBalancer lb =
(AutoConfiguredLoadBalancer) lbf.newLoadBalancer(helper);
@ -257,6 +257,7 @@ public class AutoConfiguredLoadBalancerFactoryTest {
verify(testLbBalancer).handleResolvedAddressGroups(eq(servers), attrsCaptor.capture());
assertThat(attrsCaptor.getValue().get(ATTR_LOAD_BALANCING_CONFIG))
.isEqualTo(Collections.singletonMap("setting1", "high"));
verify(testLbBalancer, atLeast(0)).canHandleEmptyAddressListFromNameResolution();
verifyNoMoreInteractions(testLbBalancer);
serviceConfig =
@ -282,7 +283,7 @@ public class AutoConfiguredLoadBalancerFactoryTest {
// This case only happens when grpclb is missing. We will use a local registry
LoadBalancerRegistry registry = new LoadBalancerRegistry();
registry.register(new PickFirstLoadBalancerProvider());
registry.register(new FakeRoundRobinLoadBalancerProvider());
registry.register(new FakeLoadBalancerProvider("round_robin", testLbBalancer));
final List<EquivalentAddressGroup> servers =
Arrays.asList(
@ -290,12 +291,7 @@ public class AutoConfiguredLoadBalancerFactoryTest {
new EquivalentAddressGroup(
new SocketAddress(){},
Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, "ok").build()));
Helper helper = new TestHelper() {
@Override
public void updateBalancingState(ConnectivityState newState, SubchannelPicker newPicker) {
// noop
}
};
Helper helper = new TestHelper();
AutoConfiguredLoadBalancer lb =
(AutoConfiguredLoadBalancer) new AutoConfiguredLoadBalancerFactory(
registry, GrpcUtil.DEFAULT_LB_POLICY).newLoadBalancer(helper);
@ -307,6 +303,55 @@ public class AutoConfiguredLoadBalancerFactoryTest {
eq(Collections.singletonList(servers.get(0))), any(Attributes.class));
}
@Test
public void handleResolvedAddressGroups_delegateDoNotAcceptEmptyAddressList_nothing()
throws Exception {
Helper helper = new TestHelper();
AutoConfiguredLoadBalancer lb =
(AutoConfiguredLoadBalancer) lbf.newLoadBalancer(helper);
Map<String, Object> serviceConfig =
parseConfig("{\"loadBalancingConfig\": [ {\"test_lb\": { \"setting1\": \"high\" } } ] }");
lb.handleResolvedAddressGroups(
Collections.<EquivalentAddressGroup>emptyList(),
Attributes.newBuilder()
.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig).build());
assertThat(lb.getDelegate()).isSameAs(testLbBalancer);
assertThat(testLbBalancer.canHandleEmptyAddressListFromNameResolution()).isFalse();
ArgumentCaptor<Status> statusCaptor = ArgumentCaptor.forClass(null);
verify(testLbBalancer).handleNameResolutionError(statusCaptor.capture());
Status error = statusCaptor.getValue();
assertThat(error.getCode()).isEqualTo(Code.UNAVAILABLE);
assertThat(error.getDescription()).startsWith("Name resolver returned no usable address");
}
@Test
public void handleResolvedAddressGroups_delegateAcceptsEmptyAddressList()
throws Exception {
Helper helper = new TestHelper();
AutoConfiguredLoadBalancer lb =
(AutoConfiguredLoadBalancer) lbf.newLoadBalancer(helper);
Map<String, Object> serviceConfig =
parseConfig("{\"loadBalancingConfig\": [ {\"test_lb2\": { \"setting1\": \"high\" } } ] }");
lb.handleResolvedAddressGroups(
Collections.<EquivalentAddressGroup>emptyList(),
Attributes.newBuilder()
.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig).build());
assertThat(lb.getDelegate()).isSameAs(testLbBalancer2);
assertThat(testLbBalancer2.canHandleEmptyAddressListFromNameResolution()).isTrue();
ArgumentCaptor<Attributes> attrsCaptor = ArgumentCaptor.forClass(null);
verify(testLbBalancer2).handleResolvedAddressGroups(
eq(Collections.<EquivalentAddressGroup>emptyList()), attrsCaptor.capture());
Map<String, Object> lbConfig =
attrsCaptor.getValue().get(LoadBalancer.ATTR_LOAD_BALANCING_CONFIG);
assertThat(lbConfig).isEqualTo(Collections.<String, Object>singletonMap("setting1", "high"));
assertThat(attrsCaptor.getValue().get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG))
.isSameAs(serviceConfig);
}
@Test
public void decideLoadBalancerProvider_noBalancerAddresses_noServiceConfig_pickFirst()
throws Exception {
@ -402,7 +447,9 @@ public class AutoConfiguredLoadBalancerFactoryTest {
throws Exception {
LoadBalancerRegistry registry = new LoadBalancerRegistry();
registry.register(new PickFirstLoadBalancerProvider());
registry.register(new FakeRoundRobinLoadBalancerProvider());
LoadBalancerProvider fakeRondRobinProvider =
new FakeLoadBalancerProvider("round_robin", testLbBalancer);
registry.register(fakeRondRobinProvider);
AutoConfiguredLoadBalancer lb =
(AutoConfiguredLoadBalancer) new AutoConfiguredLoadBalancerFactory(
registry, GrpcUtil.DEFAULT_LB_POLICY).newLoadBalancer(new TestHelper());
@ -416,7 +463,7 @@ public class AutoConfiguredLoadBalancerFactoryTest {
new EquivalentAddressGroup(new SocketAddress(){}));
PolicySelection selection = lb.decideLoadBalancerProvider(servers, serviceConfig);
assertThat(selection.provider).isInstanceOf(FakeRoundRobinLoadBalancerProvider.class);
assertThat(selection.provider).isSameAs(fakeRondRobinProvider);
assertThat(selection.config).isNull();
verify(channelLogger).log(
eq(ChannelLogLevel.ERROR),
@ -425,7 +472,7 @@ public class AutoConfiguredLoadBalancerFactoryTest {
// Called for the second time, the warning is only logged once
selection = lb.decideLoadBalancerProvider(servers, serviceConfig);
assertThat(selection.provider).isInstanceOf(FakeRoundRobinLoadBalancerProvider.class);
assertThat(selection.provider).isSameAs(fakeRondRobinProvider);
// Balancer addresses are filtered out in the server list passed to round_robin
assertThat(selection.serverList).containsExactly(servers.get(1));
assertThat(selection.config).isNull();
@ -437,7 +484,7 @@ public class AutoConfiguredLoadBalancerFactoryTest {
throws Exception {
LoadBalancerRegistry registry = new LoadBalancerRegistry();
registry.register(new PickFirstLoadBalancerProvider());
registry.register(new FakeRoundRobinLoadBalancerProvider());
registry.register(new FakeLoadBalancerProvider("round_robin", testLbBalancer));
AutoConfiguredLoadBalancer lb =
(AutoConfiguredLoadBalancer) new AutoConfiguredLoadBalancerFactory(
registry, GrpcUtil.DEFAULT_LB_POLICY).newLoadBalancer(new TestHelper());
@ -582,11 +629,6 @@ public class AutoConfiguredLoadBalancerFactoryTest {
return "fake_authority";
}
@Override
public void updateBalancingState(ConnectivityState newState, SubchannelPicker newPicker) {
// noop
}
@Override
public SynchronizationContext getSynchronizationContext() {
return new SynchronizationContext(
@ -703,6 +745,11 @@ public class AutoConfiguredLoadBalancerFactoryTest {
public ChannelLogger getChannelLogger() {
return channelLogger;
}
@Override
public void updateBalancingState(ConnectivityState newState, SubchannelPicker newPicker) {
// noop
}
}
private static class TestSubchannel extends Subchannel {
@ -733,8 +780,14 @@ public class AutoConfiguredLoadBalancerFactoryTest {
}
}
private final class TestLbLoadBalancerProvider extends LoadBalancerProvider {
static final String POLICY_NAME = "test_lb";
private static final class FakeLoadBalancerProvider extends LoadBalancerProvider {
private final String policyName;
private final LoadBalancer balancer;
FakeLoadBalancerProvider(String policyName, LoadBalancer balancer) {
this.policyName = policyName;
this.balancer = balancer;
}
@Override
public boolean isAvailable() {
@ -748,36 +801,12 @@ public class AutoConfiguredLoadBalancerFactoryTest {
@Override
public String getPolicyName() {
return POLICY_NAME;
return policyName;
}
@Override
public LoadBalancer newLoadBalancer(Helper helper) {
return testLbBalancer;
}
}
private final class FakeRoundRobinLoadBalancerProvider extends LoadBalancerProvider {
static final String POLICY_NAME = "round_robin";
@Override
public boolean isAvailable() {
return true;
}
@Override
public int getPriority() {
return 5;
}
@Override
public String getPolicyName() {
return POLICY_NAME;
}
@Override
public LoadBalancer newLoadBalancer(Helper helper) {
return testLbBalancer;
return balancer;
}
}
}

View File

@ -280,6 +280,7 @@ public class ManagedChannelImplTest {
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
when(mockLoadBalancer.canHandleEmptyAddressListFromNameResolution()).thenCallRealMethod();
LoadBalancerRegistry.getDefaultRegistry().register(mockLoadBalancerProvider);
expectedUri = new URI(TARGET);
transports = TestUtils.captureTransports(mockTransportFactory);
@ -825,10 +826,82 @@ public class ManagedChannelImplTest {
}
@Test
public void nameResolverReturnsEmptySubLists() {
public void nameResolverReturnsEmptySubLists_becomeErrorByDefault() throws Exception {
String errorDescription = "Name resolver returned no usable address";
// Pass a FakeNameResolverFactory with an empty list and LB config
FakeNameResolverFactory nameResolverFactory =
new FakeNameResolverFactory.Builder(expectedUri).build();
Map<String, Object> serviceConfig =
parseConfig("{\"loadBalancingConfig\": [ {\"mock_lb\": { \"setting1\": \"high\" } } ] }");
Attributes serviceConfigAttrs =
Attributes.newBuilder()
.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig)
.build();
nameResolverFactory.nextResolvedAttributes.set(serviceConfigAttrs);
channelBuilder.nameResolverFactory(nameResolverFactory);
createChannel();
// LoadBalancer received the error
verify(mockLoadBalancerProvider).newLoadBalancer(any(Helper.class));
verify(mockLoadBalancer).handleNameResolutionError(statusCaptor.capture());
Status status = statusCaptor.getValue();
assertSame(Status.Code.UNAVAILABLE, status.getCode());
Truth.assertThat(status.getDescription()).startsWith(errorDescription);
}
@Test
public void nameResolverReturnsEmptySubLists_optionallyAllowed() throws Exception {
when(mockLoadBalancer.canHandleEmptyAddressListFromNameResolution()).thenReturn(true);
// Pass a FakeNameResolverFactory with an empty list and LB config
FakeNameResolverFactory nameResolverFactory =
new FakeNameResolverFactory.Builder(expectedUri).build();
Map<String, Object> serviceConfig =
parseConfig("{\"loadBalancingConfig\": [ {\"mock_lb\": { \"setting1\": \"high\" } } ] }");
Attributes serviceConfigAttrs =
Attributes.newBuilder()
.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig)
.build();
nameResolverFactory.nextResolvedAttributes.set(serviceConfigAttrs);
channelBuilder.nameResolverFactory(nameResolverFactory);
createChannel();
// LoadBalancer received the empty list and the LB config
verify(mockLoadBalancerProvider).newLoadBalancer(any(Helper.class));
ArgumentCaptor<Attributes> attrsCaptor = ArgumentCaptor.forClass(null);
verify(mockLoadBalancer).handleResolvedAddressGroups(
eq(ImmutableList.<EquivalentAddressGroup>of()), attrsCaptor.capture());
Map<String, Object> lbConfig =
attrsCaptor.getValue().get(LoadBalancer.ATTR_LOAD_BALANCING_CONFIG);
assertEquals(ImmutableMap.<String, Object>of("setting1", "high"), lbConfig);
assertSame(
serviceConfig, attrsCaptor.getValue().get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG));
}
@Test
@Deprecated
public void nameResolverReturnsEmptySubLists_becomeErrorByDefault_lbFactorySetDirectly()
throws Exception {
String errorDescription = "returned an empty list";
// Pass a FakeNameResolverFactory with an empty list
// Pass a FakeNameResolverFactory with an empty list and LB config
FakeNameResolverFactory nameResolverFactory =
new FakeNameResolverFactory.Builder(expectedUri).build();
Map<String, Object> serviceConfig =
parseConfig("{\"loadBalancingConfig\": [ {\"mock_lb\": { \"setting1\": \"high\" } } ] }");
Attributes serviceConfigAttrs =
Attributes.newBuilder()
.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig)
.build();
nameResolverFactory.nextResolvedAttributes.set(serviceConfigAttrs);
channelBuilder.nameResolverFactory(nameResolverFactory);
// Pass a LoadBalancerFactory directly to the builder, bypassing
// AutoConfiguredLoadBalancerFactory. The empty-list check is done in ManagedChannelImpl rather
// than AutoConfiguredLoadBalancerFactory
channelBuilder.loadBalancerFactory(mockLoadBalancerProvider);
createChannel();
// LoadBalancer received the error
@ -839,6 +912,37 @@ public class ManagedChannelImplTest {
Truth.assertThat(status.getDescription()).contains(errorDescription);
}
@Test
@Deprecated
public void nameResolverReturnsEmptySubLists_optionallyAllowed_lbFactorySetDirectly()
throws Exception {
when(mockLoadBalancer.canHandleEmptyAddressListFromNameResolution()).thenReturn(true);
// Pass a FakeNameResolverFactory with an empty list and LB config
FakeNameResolverFactory nameResolverFactory =
new FakeNameResolverFactory.Builder(expectedUri).build();
Map<String, Object> serviceConfig =
parseConfig("{\"loadBalancingConfig\": [ {\"mock_lb\": { \"setting1\": \"high\" } } ] }");
Attributes serviceConfigAttrs =
Attributes.newBuilder()
.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig)
.build();
nameResolverFactory.nextResolvedAttributes.set(serviceConfigAttrs);
channelBuilder.nameResolverFactory(nameResolverFactory);
// Pass a LoadBalancerFactory directly to the builder, bypassing
// AutoConfiguredLoadBalancerFactory. The empty-list check is done in ManagedChannelImpl rather
// than AutoConfiguredLoadBalancerFactory
channelBuilder.loadBalancerFactory(mockLoadBalancerProvider);
createChannel();
// LoadBalancer received the empty list and the LB config
verify(mockLoadBalancerProvider).newLoadBalancer(any(Helper.class));
verify(mockLoadBalancer).handleResolvedAddressGroups(
eq(ImmutableList.<EquivalentAddressGroup>of()), same(serviceConfigAttrs));
}
@Test
public void loadBalancerThrowsInHandleResolvedAddresses() {
RuntimeException ex = new RuntimeException("simulated");
@ -2366,11 +2470,15 @@ public class ManagedChannelImplTest {
public void channelTracing_nameResolvingErrorEvent() throws Exception {
timer.forwardNanos(1234);
channelBuilder.maxTraceEvents(10);
Status error = Status.UNAVAILABLE.withDescription("simulated error");
FakeNameResolverFactory nameResolverFactory =
new FakeNameResolverFactory.Builder(expectedUri).setError(error).build();
channelBuilder.nameResolverFactory(nameResolverFactory);
createChannel();
assertThat(getStats(channel).channelTrace.events).contains(new ChannelTrace.Event.Builder()
.setDescription("Failed to resolve name:"
+ " Status{code=UNAVAILABLE, description=Name resolver FakeNameResolver"
+ " returned an empty list, cause=null}")
.setDescription("Failed to resolve name: " + error)
.setSeverity(ChannelTrace.Event.Severity.CT_WARNING)
.setTimestampNanos(timer.getTicker().read())
.build());
@ -3288,4 +3396,9 @@ public class ManagedChannelImplTest {
});
return resultCapture.get();
}
@SuppressWarnings("unchecked")
private static Map<String, Object> parseConfig(String json) throws Exception {
return (Map<String, Object>) JsonParser.parse(json);
}
}