core: channelBuilder.defaulServiceConfig() and lookUpServiceConfig()

This commit is contained in:
ZHANG Dapeng 2019-03-14 16:57:16 -07:00 committed by GitHub
parent 185cf3d047
commit 1735adc4c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 588 additions and 26 deletions

View File

@ -18,8 +18,10 @@ package io.grpc;
import com.google.common.base.MoreObjects;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
/**
* A {@link ManagedChannelBuilder} that delegates all its builder method to another builder by
@ -242,6 +244,18 @@ public abstract class ForwardingChannelBuilder<T extends ForwardingChannelBuilde
return thisT();
}
@Override
public T defaultServiceConfig(@Nullable Map<String, ?> serviceConfig) {
delegate().defaultServiceConfig(serviceConfig);
return thisT();
}
@Override
public T lookUpServiceConfig(boolean enable) {
delegate().lookUpServiceConfig(enable);
return thisT();
}
/**
* Returns the {@link ManagedChannel} built by the delegate by default. Overriding method can
* return different value.

View File

@ -18,8 +18,10 @@ package io.grpc;
import com.google.common.base.Preconditions;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
/**
* A builder for {@link ManagedChannel} instances.
@ -537,6 +539,60 @@ public abstract class ManagedChannelBuilder<T extends ManagedChannelBuilder<T>>
throw new UnsupportedOperationException();
}
/**
* Provides a service config to the channel. The channel will use the default service config when
* the name resolver provides no service config or if the channel disables lookup service config
* from name resolver (see {@link #lookUpServiceConfig(boolean)}). The argument
* {@code serviceConfig} is a nested map representing a Json object in the most natural way:
*
* <table border="1">
* <tr>
* <td>Json entry</td><td>Java Type</td>
* </tr>
* <tr>
* <td>object</td><td>{@link Map}</td>
* </tr>
* <tr>
* <td>array</td><td>{@link List}</td>
* </tr>
* <tr>
* <td>string</td><td>{@link String}</td>
* </tr>
* <tr>
* <td>number</td><td>{@link Double}</td>
* </tr>
* <tr>
* <td>boolean</td><td>{@link Boolean}</td>
* </tr>
* <tr>
* <td>null</td><td>{@code null}</td>
* </tr>
* </table>
*
* <p>If null is passed, then there will be no default service config.
*
* @throws IllegalArgumentException When the given serviceConfig is invalid or the current version
* of grpc library can not parse it gracefully. The state of the builder is unchanged if
* an exception is thrown.
* @return this
* @since 1.20.0
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/5189")
public T defaultServiceConfig(@Nullable Map<String, ?> serviceConfig) {
throw new UnsupportedOperationException();
}
/**
* Enables or disables service config look-up from the naming system. Enabled by default.
*
* @return this
* @since 1.20.0
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/5189")
public T lookUpServiceConfig(boolean enable) {
throw new UnsupportedOperationException();
}
/**
* Builds a channel using the given parameters.
*

View File

@ -41,7 +41,9 @@ import java.net.URISyntaxException;
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.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
@ -139,6 +141,10 @@ public abstract class AbstractManagedChannelImplBuilder
InternalChannelz channelz = InternalChannelz.instance();
int maxTraceEvents;
@Nullable
Map<String, ?> defaultServiceConfig;
boolean lookUpServiceConfig = true;
protected TransportTracer.Factory transportTracerFactory = TransportTracer.getDefaultFactory();
private int maxInboundMessageSize = GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE;
@ -379,6 +385,78 @@ public abstract class AbstractManagedChannelImplBuilder
return thisT();
}
@Override
public T defaultServiceConfig(@Nullable Map<String, ?> serviceConfig) {
// TODO(notcarl): use real parsing
defaultServiceConfig = checkMapEntryTypes(serviceConfig);
return thisT();
}
@Nullable
private static Map<String, ?> checkMapEntryTypes(@Nullable Map<?, ?> map) {
if (map == null) {
return null;
}
// Not using ImmutableMap.Builder because of extra guava dependency for Android.
Map<String, Object> parsedMap = new LinkedHashMap<>();
for (Map.Entry<?, ?> entry : map.entrySet()) {
checkArgument(
entry.getKey() instanceof String,
"The key of the entry '%s' is not of String type", entry);
String key = (String) entry.getKey();
Object value = entry.getValue();
if (value == null) {
parsedMap.put(key, null);
} else if (value instanceof Map) {
parsedMap.put(key, checkMapEntryTypes((Map<?, ?>) value));
} else if (value instanceof List) {
parsedMap.put(key, checkListEntryTypes((List<?>) value));
} else if (value instanceof String) {
parsedMap.put(key, value);
} else if (value instanceof Double) {
parsedMap.put(key, value);
} else if (value instanceof Boolean) {
parsedMap.put(key, value);
} else {
throw new IllegalArgumentException(
"The value of the map entry '" + entry + "' is of type '" + value.getClass()
+ "', which is not supported");
}
}
return Collections.unmodifiableMap(parsedMap);
}
private static List<?> checkListEntryTypes(List<?> list) {
List<Object> parsedList = new ArrayList<>(list.size());
for (Object value : list) {
if (value == null) {
parsedList.add(null);
} else if (value instanceof Map) {
parsedList.add(checkMapEntryTypes((Map<?, ?>) value));
} else if (value instanceof List) {
parsedList.add(checkListEntryTypes((List<?>) value));
} else if (value instanceof String) {
parsedList.add(value);
} else if (value instanceof Double) {
parsedList.add(value);
} else if (value instanceof Boolean) {
parsedList.add(value);
} else {
throw new IllegalArgumentException(
"The entry '" + value + "' is of type '" + value.getClass()
+ "', which is not supported");
}
}
return Collections.unmodifiableList(parsedList);
}
@Override
public T lookUpServiceConfig(boolean enable) {
this.lookUpServiceConfig = enable;
return thisT();
}
/**
* Disable or enable stats features. Enabled by default.
*
@ -490,7 +568,7 @@ public abstract class AbstractManagedChannelImplBuilder
/**
* Subclasses can override this method to provide a default port to {@link NameResolver} for use
* in cases where the target string doesn't include a port. The default implementation returns
* {@link GrpcUtil.DEFAULT_PORT_SSL}.
* {@link GrpcUtil#DEFAULT_PORT_SSL}.
*/
protected int getDefaultPort() {
return GrpcUtil.DEFAULT_PORT_SSL;

View File

@ -237,9 +237,17 @@ final class ManagedChannelImpl extends ManagedChannel implements
// Must be mutated and read from syncContext
@CheckForNull
private Boolean haveBackends; // a flag for doing channel tracing when flipped
// Must be mutated and read from syncContext
// Must be mutated and read from constructor or syncContext
// TODO(notcarl): check this value when error in service config resolution
@Nullable
private Map<String, ?> lastServiceConfig; // used for channel tracing when value changed
@Nullable
private final Map<String, ?> defaultServiceConfig;
// Must be mutated and read from constructor or syncContext
// See service config error handling spec for reference.
// TODO(notcarl): check this value when error in service config resolution
private boolean waitingForServiceConfig = true;
private final boolean lookUpServiceConfig;
// One instance per channel.
private final ChannelBufferMeter channelBufferUsed = new ChannelBufferMeter();
@ -581,6 +589,9 @@ final class ManagedChannelImpl extends ManagedChannel implements
serviceConfigInterceptor = new ServiceConfigInterceptor(
retryEnabled, builder.maxRetryAttempts, builder.maxHedgedAttempts);
this.defaultServiceConfig = builder.defaultServiceConfig;
this.lastServiceConfig = defaultServiceConfig;
this.lookUpServiceConfig = builder.lookUpServiceConfig;
Channel channel = new RealChannel(nameResolver.getServiceAuthority());
channel = ClientInterceptors.intercept(channel, serviceConfigInterceptor);
if (builder.binlog != null) {
@ -621,6 +632,23 @@ final class ManagedChannelImpl extends ManagedChannel implements
channelCallTracer = callTracerFactory.create();
this.channelz = checkNotNull(builder.channelz);
channelz.addRootChannel(this);
if (!lookUpServiceConfig) {
if (defaultServiceConfig != null) {
channelLogger.log(
ChannelLogLevel.INFO, "Service config look-up disabled, using default service config");
}
handleServiceConfigUpdate();
}
}
// May only be called in constructor or syncContext
private void handleServiceConfigUpdate() {
waitingForServiceConfig = false;
serviceConfigInterceptor.handleUpdate(lastServiceConfig);
if (retryEnabled) {
throttle = ServiceConfigUtil.getThrottlePolicy(lastServiceConfig);
}
}
@VisibleForTesting
@ -1278,32 +1306,57 @@ final class ManagedChannelImpl extends ManagedChannel implements
}
@Override
public void onAddresses(final List<EquivalentAddressGroup> servers, final Attributes config) {
public void onAddresses(final List<EquivalentAddressGroup> servers, final Attributes attrs) {
final class NamesResolved implements Runnable {
@SuppressWarnings("ReferenceEquality")
@Override
public void run() {
channelLogger.log(
ChannelLogLevel.DEBUG, "Resolved address: {0}, config={1}", servers, config);
ChannelLogLevel.DEBUG, "Resolved address: {0}, config={1}", servers, attrs);
if (haveBackends == null || !haveBackends) {
channelLogger.log(ChannelLogLevel.INFO, "Address resolved: {0}", servers);
haveBackends = true;
}
final Map<String, ?> serviceConfig =
config.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG);
if (serviceConfig != null && !serviceConfig.equals(lastServiceConfig)) {
channelLogger.log(ChannelLogLevel.INFO, "Service config changed");
lastServiceConfig = serviceConfig;
}
nameResolverBackoffPolicy = null;
// Assuming no error in config resolution for now.
final Map<String, ?> serviceConfig =
attrs.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG);
Map<String, ?> effectiveServiceConfig;
if (!lookUpServiceConfig) {
if (serviceConfig != null) {
try {
serviceConfigInterceptor.handleUpdate(serviceConfig);
if (retryEnabled) {
throttle = ServiceConfigUtil.getThrottlePolicy(serviceConfig);
channelLogger.log(
ChannelLogLevel.INFO,
"Service config from name resolver discarded by channel settings");
}
effectiveServiceConfig = defaultServiceConfig;
} else {
// Try to use config if returned from name resolver
// Otherwise, try to use the default config if available
if (serviceConfig != null) {
effectiveServiceConfig = serviceConfig;
} else {
effectiveServiceConfig = defaultServiceConfig;
if (defaultServiceConfig != null) {
channelLogger.log(
ChannelLogLevel.INFO,
"Received no service config, using default service config");
}
}
// FIXME(notcarl): reference equality is not right (although not harmful) right now.
// Name resolver should return the same config if txt record is the same
if (effectiveServiceConfig != lastServiceConfig) {
channelLogger.log(ChannelLogLevel.INFO,
"Service config changed{0}", effectiveServiceConfig == null ? " to null" : "");
lastServiceConfig = effectiveServiceConfig;
}
try {
handleServiceConfigUpdate();
} catch (RuntimeException re) {
logger.log(
Level.WARNING,
@ -1318,7 +1371,13 @@ final class ManagedChannelImpl extends ManagedChannel implements
handleErrorInSyncContext(Status.UNAVAILABLE.withDescription(
"Name resolver " + resolver + " returned an empty list"));
} else {
helper.lb.handleResolvedAddressGroups(servers, config);
Attributes effectiveAttrs = attrs;
if (effectiveServiceConfig != serviceConfig) {
effectiveAttrs = attrs.toBuilder()
.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, effectiveServiceConfig)
.build();
}
helper.lb.handleResolvedAddressGroups(servers, effectiveAttrs);
}
}
}

View File

@ -26,11 +26,12 @@ import io.grpc.ClientInterceptor;
import io.grpc.Deadline;
import io.grpc.MethodDescriptor;
import io.grpc.internal.ManagedChannelServiceConfig.MethodInfo;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Modifies RPCs in conformance with a Service Config.
@ -47,7 +48,7 @@ final class ServiceConfigInterceptor implements ClientInterceptor {
private final int maxHedgedAttemptsLimit;
// Setting this to true and observing this equal to true are run in different threads.
private volatile boolean nameResolveComplete;
private volatile boolean initComplete;
ServiceConfigInterceptor(
boolean retryEnabled, int maxRetryAttemptsLimit, int maxHedgedAttemptsLimit) {
@ -56,11 +57,17 @@ final class ServiceConfigInterceptor implements ClientInterceptor {
this.maxHedgedAttemptsLimit = maxHedgedAttemptsLimit;
}
void handleUpdate(@Nonnull Map<String, ?> serviceConfig) {
ManagedChannelServiceConfig conf = ManagedChannelServiceConfig.fromServiceConfig(
void handleUpdate(@Nullable Map<String, ?> serviceConfig) {
ManagedChannelServiceConfig conf;
if (serviceConfig == null) {
conf = new ManagedChannelServiceConfig(
new HashMap<String, MethodInfo>(), new HashMap<String, MethodInfo>());
} else {
conf = ManagedChannelServiceConfig.fromServiceConfig(
serviceConfig, retryEnabled, maxRetryAttemptsLimit, maxHedgedAttemptsLimit);
}
managedChannelServiceConfig.set(conf);
nameResolveComplete = true;
initComplete = true;
}
static final CallOptions.Key<RetryPolicy.Provider> RETRY_POLICY_KEY =
@ -72,7 +79,7 @@ final class ServiceConfigInterceptor implements ClientInterceptor {
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
final MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
if (retryEnabled) {
if (nameResolveComplete) {
if (initComplete) {
final RetryPolicy retryPolicy = getRetryPolicyFromConfig(method);
final class ImmediateRetryPolicyProvider implements RetryPolicy.Provider {
@Override
@ -106,7 +113,7 @@ final class ServiceConfigInterceptor implements ClientInterceptor {
*/
@Override
public RetryPolicy get() {
if (!nameResolveComplete) {
if (!initComplete) {
return RetryPolicy.DEFAULT;
}
return getRetryPolicyFromConfig(method);
@ -122,7 +129,7 @@ final class ServiceConfigInterceptor implements ClientInterceptor {
*/
@Override
public HedgingPolicy get() {
if (!nameResolveComplete) {
if (!initComplete) {
return HedgingPolicy.DEFAULT;
}
HedgingPolicy hedgingPolicy = getHedgingPolicyFromConfig(method);

View File

@ -42,7 +42,11 @@ import io.grpc.internal.testing.StatsTestUtils.FakeTagger;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import org.junit.Rule;
@ -429,6 +433,70 @@ public class AbstractManagedChannelImplBuilderTest {
assertFalse(builder.retryEnabled);
}
@Test
public void defaultServiceConfig_nullKey() {
Builder builder = new Builder("target");
Map<String, Object> config = new HashMap<>();
config.put(null, "val");
thrown.expect(IllegalArgumentException.class);
builder.defaultServiceConfig(config);
}
@Test
public void defaultServiceConfig_intKey() {
Builder builder = new Builder("target");
Map<Integer, Object> subConfig = new HashMap<>();
subConfig.put(3, "val");
Map<String, Object> config = new HashMap<>();
config.put("key", subConfig);
thrown.expect(IllegalArgumentException.class);
builder.defaultServiceConfig(config);
}
@Test
public void defaultServiceConfig_intValue() {
Builder builder = new Builder("target");
Map<String, Object> config = new HashMap<>();
config.put("key", 3);
thrown.expect(IllegalArgumentException.class);
builder.defaultServiceConfig(config);
}
@Test
public void defaultServiceConfig_nested() {
Builder builder = new Builder("target");
Map<String, Object> config = new HashMap<>();
List<Object> list1 = new ArrayList<>();
list1.add(123D);
list1.add(null);
list1.add(true);
list1.add("str");
Map<String, Object> map2 = new HashMap<>();
map2.put("key2", false);
map2.put("key3", null);
map2.put("key4", Collections.singletonList("v4"));
map2.put("key4", 3.14D);
map2.put("key5", new HashMap<String, Object>());
list1.add(map2);
config.put("key1", list1);
builder.defaultServiceConfig(config);
assertThat(builder.defaultServiceConfig).containsExactlyEntriesIn(config);
}
@Test
public void disableNameResolverServiceConfig() {
Builder builder = new Builder("target");
assertThat(builder.lookUpServiceConfig).isTrue();
builder.lookUpServiceConfig(false);
assertThat(builder.lookUpServiceConfig).isFalse();
}
static class Builder extends AbstractManagedChannelImplBuilder<Builder> {
Builder(String target) {
super(target);

View File

@ -3433,6 +3433,271 @@ public class ManagedChannelImplTest {
assertThat(coe.getError().getCause()).isInstanceOf(ClassCastException.class);
}
@Test
public void disableServiceConfigLookUp_noDefaultConfig() throws Exception {
LoadBalancerRegistry.getDefaultRegistry().register(mockLoadBalancerProvider);
try {
FakeNameResolverFactory nameResolverFactory =
new FakeNameResolverFactory.Builder(expectedUri)
.setServers(ImmutableList.of(addressGroup)).build();
channelBuilder.nameResolverFactory(nameResolverFactory);
channelBuilder.lookUpServiceConfig(false);
Map<String, Object> serviceConfig =
parseConfig("{\"methodConfig\":[{"
+ "\"name\":[{\"service\":\"SimpleService1\"}],"
+ "\"waitForReady\":true}]}");
Attributes serviceConfigAttrs =
Attributes.newBuilder()
.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig)
.build();
nameResolverFactory.nextResolvedAttributes.set(serviceConfigAttrs);
createChannel();
ArgumentCaptor<Attributes> attributesCaptor = ArgumentCaptor.forClass(Attributes.class);
verify(mockLoadBalancer).handleResolvedAddressGroups(
eq(ImmutableList.of(addressGroup)),
attributesCaptor.capture());
assertThat(attributesCaptor.getValue().get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG))
.isNull();
verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class));
} finally {
LoadBalancerRegistry.getDefaultRegistry().deregister(mockLoadBalancerProvider);
}
}
@Test
public void disableServiceConfigLookUp_withDefaultConfig() throws Exception {
LoadBalancerRegistry.getDefaultRegistry().register(mockLoadBalancerProvider);
try {
FakeNameResolverFactory nameResolverFactory =
new FakeNameResolverFactory.Builder(expectedUri)
.setServers(ImmutableList.of(addressGroup)).build();
channelBuilder.nameResolverFactory(nameResolverFactory);
channelBuilder.lookUpServiceConfig(false);
Map<String, Object> defaultServiceConfig =
parseConfig("{\"methodConfig\":[{"
+ "\"name\":[{\"service\":\"SimpleService1\"}],"
+ "\"waitForReady\":true}]}");
channelBuilder.defaultServiceConfig(defaultServiceConfig);
Map<String, Object> serviceConfig = new HashMap<>();
Attributes serviceConfigAttrs =
Attributes.newBuilder()
.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig)
.build();
nameResolverFactory.nextResolvedAttributes.set(serviceConfigAttrs);
createChannel();
ArgumentCaptor<Attributes> attributesCaptor = ArgumentCaptor.forClass(Attributes.class);
verify(mockLoadBalancer).handleResolvedAddressGroups(
eq(ImmutableList.of(addressGroup)),
attributesCaptor.capture());
assertThat(attributesCaptor.getValue().get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG))
.isEqualTo(defaultServiceConfig);
verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class));
} finally {
LoadBalancerRegistry.getDefaultRegistry().deregister(mockLoadBalancerProvider);
}
}
@Test
public void enableServiceConfigLookUp_noDefaultConfig() throws Exception {
LoadBalancerRegistry.getDefaultRegistry().register(mockLoadBalancerProvider);
try {
FakeNameResolverFactory nameResolverFactory =
new FakeNameResolverFactory.Builder(expectedUri)
.setServers(ImmutableList.of(addressGroup)).build();
channelBuilder.nameResolverFactory(nameResolverFactory);
Map<String, Object> serviceConfig =
parseConfig("{\"methodConfig\":[{"
+ "\"name\":[{\"service\":\"SimpleService1\"}],"
+ "\"waitForReady\":true}]}");
Attributes serviceConfigAttrs =
Attributes.newBuilder()
.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig)
.build();
nameResolverFactory.nextResolvedAttributes.set(serviceConfigAttrs);
createChannel();
ArgumentCaptor<Attributes> attributesCaptor = ArgumentCaptor.forClass(Attributes.class);
verify(mockLoadBalancer).handleResolvedAddressGroups(
eq(ImmutableList.of(addressGroup)),
attributesCaptor.capture());
assertThat(attributesCaptor.getValue().get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG))
.isEqualTo(serviceConfig);
verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class));
// new config
serviceConfig =
parseConfig("{\"methodConfig\":[{"
+ "\"name\":[{\"service\":\"SimpleService1\"}],"
+ "\"waitForReady\":false}]}");
serviceConfigAttrs =
Attributes.newBuilder()
.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig)
.build();
nameResolverFactory.nextResolvedAttributes.set(serviceConfigAttrs);
nameResolverFactory.allResolved();
attributesCaptor = ArgumentCaptor.forClass(Attributes.class);
verify(mockLoadBalancer, times(2)).handleResolvedAddressGroups(
eq(ImmutableList.of(addressGroup)),
attributesCaptor.capture());
assertThat(attributesCaptor.getValue().get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG))
.isEqualTo(serviceConfig);
verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class));
} finally {
LoadBalancerRegistry.getDefaultRegistry().deregister(mockLoadBalancerProvider);
}
}
@Test
public void enableServiceConfigLookUp_withDefaultConfig() throws Exception {
LoadBalancerRegistry.getDefaultRegistry().register(mockLoadBalancerProvider);
try {
FakeNameResolverFactory nameResolverFactory =
new FakeNameResolverFactory.Builder(expectedUri)
.setServers(ImmutableList.of(addressGroup)).build();
channelBuilder.nameResolverFactory(nameResolverFactory);
Map<String, Object> defaultServiceConfig =
parseConfig("{\"methodConfig\":[{"
+ "\"name\":[{\"service\":\"SimpleService1\"}],"
+ "\"waitForReady\":true}]}");
channelBuilder.defaultServiceConfig(defaultServiceConfig);
Map<String, Object> serviceConfig =
parseConfig("{\"methodConfig\":[{"
+ "\"name\":[{\"service\":\"SimpleService2\"}],"
+ "\"waitForReady\":false}]}");
Attributes serviceConfigAttrs =
Attributes.newBuilder()
.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig)
.build();
nameResolverFactory.nextResolvedAttributes.set(serviceConfigAttrs);
createChannel();
ArgumentCaptor<Attributes> attributesCaptor = ArgumentCaptor.forClass(Attributes.class);
verify(mockLoadBalancer).handleResolvedAddressGroups(
eq(ImmutableList.of(addressGroup)),
attributesCaptor.capture());
assertThat(attributesCaptor.getValue().get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG))
.isEqualTo(serviceConfig);
verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class));
} finally {
LoadBalancerRegistry.getDefaultRegistry().deregister(mockLoadBalancerProvider);
}
}
@Test
public void enableServiceConfigLookUp_resolverReturnsNoConfig_withDefaultConfig()
throws Exception {
LoadBalancerRegistry.getDefaultRegistry().register(mockLoadBalancerProvider);
try {
FakeNameResolverFactory nameResolverFactory =
new FakeNameResolverFactory.Builder(expectedUri)
.setServers(ImmutableList.of(addressGroup)).build();
channelBuilder.nameResolverFactory(nameResolverFactory);
Map<String, Object> defaultServiceConfig =
parseConfig("{\"methodConfig\":[{"
+ "\"name\":[{\"service\":\"SimpleService1\"}],"
+ "\"waitForReady\":true}]}");
channelBuilder.defaultServiceConfig(defaultServiceConfig);
Attributes serviceConfigAttrs = Attributes.EMPTY;
nameResolverFactory.nextResolvedAttributes.set(serviceConfigAttrs);
createChannel();
ArgumentCaptor<Attributes> attributesCaptor = ArgumentCaptor.forClass(Attributes.class);
verify(mockLoadBalancer).handleResolvedAddressGroups(
eq(ImmutableList.of(addressGroup)),
attributesCaptor.capture());
assertThat(attributesCaptor.getValue().get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG))
.isEqualTo(defaultServiceConfig);
verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class));
} finally {
LoadBalancerRegistry.getDefaultRegistry().deregister(mockLoadBalancerProvider);
}
}
@Test
public void enableServiceConfigLookUp_resolverReturnsNoConfig_noDefaultConfig() {
LoadBalancerRegistry.getDefaultRegistry().register(mockLoadBalancerProvider);
try {
FakeNameResolverFactory nameResolverFactory =
new FakeNameResolverFactory.Builder(expectedUri)
.setServers(ImmutableList.of(addressGroup)).build();
channelBuilder.nameResolverFactory(nameResolverFactory);
Attributes serviceConfigAttrs = Attributes.EMPTY;
nameResolverFactory.nextResolvedAttributes.set(serviceConfigAttrs);
createChannel();
ArgumentCaptor<Attributes> attributesCaptor = ArgumentCaptor.forClass(Attributes.class);
verify(mockLoadBalancer).handleResolvedAddressGroups(
eq(ImmutableList.of(addressGroup)),
attributesCaptor.capture());
assertThat(attributesCaptor.getValue().get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG))
.isNull();
verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class));
} finally {
LoadBalancerRegistry.getDefaultRegistry().deregister(mockLoadBalancerProvider);
}
}
@Test
public void useDefaultImmediatelyIfDisableLookUp() throws Exception {
FakeNameResolverFactory nameResolverFactory =
new FakeNameResolverFactory.Builder(expectedUri)
.setServers(ImmutableList.of(addressGroup)).build();
channelBuilder.nameResolverFactory(nameResolverFactory);
channelBuilder.lookUpServiceConfig(false);
Map<String, Object> defaultServiceConfig =
parseConfig("{\"methodConfig\":[{"
+ "\"name\":[{\"service\":\"SimpleService1\"}],"
+ "\"waitForReady\":true}]}");
channelBuilder.defaultServiceConfig(defaultServiceConfig);
requestConnection = false;
channelBuilder.maxTraceEvents(10);
createChannel();
int size = getStats(channel).channelTrace.events.size();
assertThat(getStats(channel).channelTrace.events.get(size - 1))
.isEqualTo(new ChannelTrace.Event.Builder()
.setDescription("Service config look-up disabled, using default service config")
.setSeverity(ChannelTrace.Event.Severity.CT_INFO)
.setTimestampNanos(timer.getTicker().read())
.build());
}
@Test
public void notUseDefaultImmediatelyIfEnableLookUp() throws Exception {
FakeNameResolverFactory nameResolverFactory =
new FakeNameResolverFactory.Builder(expectedUri)
.setServers(ImmutableList.of(addressGroup)).build();
channelBuilder.nameResolverFactory(nameResolverFactory);
channelBuilder.lookUpServiceConfig(true);
Map<String, Object> defaultServiceConfig =
parseConfig("{\"methodConfig\":[{"
+ "\"name\":[{\"service\":\"SimpleService1\"}],"
+ "\"waitForReady\":true}]}");
channelBuilder.defaultServiceConfig(defaultServiceConfig);
requestConnection = false;
channelBuilder.maxTraceEvents(10);
createChannel();
int size = getStats(channel).channelTrace.events.size();
assertThat(getStats(channel).channelTrace.events.get(size - 1))
.isNotEqualTo(new ChannelTrace.Event.Builder()
.setDescription("Using default service config")
.setSeverity(ChannelTrace.Event.Severity.CT_INFO)
.setTimestampNanos(timer.getTicker().read())
.build());
}
private static final class ChannelBuilder
extends AbstractManagedChannelImplBuilder<ChannelBuilder> {

View File

@ -101,6 +101,21 @@ public class ServiceConfigInterceptorTest {
assertThat(callOptionsCap.getValue().isWaitForReady()).isTrue();
}
@Test
public void handleNullConfig() {
JsonObj name = new JsonObj("service", "service");
JsonObj methodConfig = new JsonObj("name", new JsonList(name), "waitForReady", true);
JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
interceptor.handleUpdate(serviceConfig);
interceptor.handleUpdate(null);
interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT.withoutWaitForReady(), channel);
verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture());
assertThat(callOptionsCap.getValue().isWaitForReady()).isFalse();
}
@Test
public void handleUpdateNotCalledBeforeInterceptCall() {
interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT.withoutWaitForReady(), channel);