mirror of https://github.com/grpc/grpc-java.git
core: apply service config
This commit is contained in:
parent
49c8bdb60a
commit
2df76cc710
|
|
@ -164,7 +164,7 @@ final class AutoConfiguredLoadBalancerFactory extends LoadBalancer.Factory {
|
|||
}
|
||||
|
||||
String serviceConfigChoiceBalancingPolicy =
|
||||
ServiceConfigUtil.getLoadBalancingPolicy(config);
|
||||
ServiceConfigUtil.getLoadBalancingPolicyFromServiceConfig(config);
|
||||
|
||||
// Check for an explicitly present lb choice
|
||||
if (serviceConfigChoiceBalancingPolicy != null) {
|
||||
|
|
|
|||
|
|
@ -35,9 +35,12 @@ import java.net.UnknownHostException;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
|
@ -63,6 +66,22 @@ final class DnsNameResolver extends NameResolver {
|
|||
|
||||
private static final boolean JNDI_AVAILABLE = jndiAvailable();
|
||||
|
||||
private static final String SERVICE_CONFIG_CHOICE_CLIENT_LANGUAGE_KEY = "clientLanguage";
|
||||
private static final String SERVICE_CONFIG_CHOICE_PERCENTAGE_KEY = "percentage";
|
||||
private static final String SERVICE_CONFIG_CHOICE_CLIENT_HOSTNAME_KEY = "clientHostname";
|
||||
private static final String SERVICE_CONFIG_CHOICE_SERVICE_CONFIG_KEY = "serviceConfig";
|
||||
|
||||
// From https://github.com/grpc/proposal/blob/master/A2-service-configs-in-dns.md
|
||||
static final String SERVICE_CONFIG_PREFIX = "_grpc_config=";
|
||||
private static final Set<String> SERVICE_CONFIG_CHOICE_KEYS =
|
||||
Collections.unmodifiableSet(
|
||||
new HashSet<String>(
|
||||
Arrays.asList(
|
||||
SERVICE_CONFIG_CHOICE_CLIENT_LANGUAGE_KEY,
|
||||
SERVICE_CONFIG_CHOICE_PERCENTAGE_KEY,
|
||||
SERVICE_CONFIG_CHOICE_CLIENT_HOSTNAME_KEY,
|
||||
SERVICE_CONFIG_CHOICE_SERVICE_CONFIG_KEY)));
|
||||
|
||||
// From https://github.com/grpc/proposal/blob/master/A2-service-configs-in-dns.md
|
||||
private static final String SERVICE_CONFIG_NAME_PREFIX = "_grpc_config.";
|
||||
// From https://github.com/grpc/proposal/blob/master/A5-grpclb-in-dns.md
|
||||
|
|
@ -196,11 +215,10 @@ final class DnsNameResolver extends NameResolver {
|
|||
Map<String, Object> serviceConfig = null;
|
||||
try {
|
||||
for (Map<String, Object> possibleConfig :
|
||||
ServiceConfigUtil.parseTxtResults(resolvedInetAddrs.txtRecords)) {
|
||||
parseTxtResults(resolvedInetAddrs.txtRecords)) {
|
||||
try {
|
||||
serviceConfig =
|
||||
ServiceConfigUtil.maybeChooseServiceConfig(
|
||||
possibleConfig, random, getLocalHostname());
|
||||
maybeChooseServiceConfig(possibleConfig, random, getLocalHostname());
|
||||
} catch (RuntimeException e) {
|
||||
logger.log(Level.WARNING, "Bad service config choice " + possibleConfig, e);
|
||||
}
|
||||
|
|
@ -265,6 +283,120 @@ final class DnsNameResolver extends NameResolver {
|
|||
this.delegateResolver = delegateResolver;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@VisibleForTesting
|
||||
static List<Map<String, Object>> parseTxtResults(List<String> txtRecords) {
|
||||
List<Map<String, Object>> serviceConfigs = new ArrayList<Map<String, Object>>();
|
||||
for (String txtRecord : txtRecords) {
|
||||
if (txtRecord.startsWith(SERVICE_CONFIG_PREFIX)) {
|
||||
List<Map<String, Object>> choices;
|
||||
try {
|
||||
Object rawChoices = JsonParser.parse(txtRecord.substring(SERVICE_CONFIG_PREFIX.length()));
|
||||
if (!(rawChoices instanceof List)) {
|
||||
throw new IOException("wrong type " + rawChoices);
|
||||
}
|
||||
List<Object> listChoices = (List<Object>) rawChoices;
|
||||
for (Object obj : listChoices) {
|
||||
if (!(obj instanceof Map)) {
|
||||
throw new IOException("wrong element type " + rawChoices);
|
||||
}
|
||||
}
|
||||
choices = (List<Map<String, Object>>) (List<?>) listChoices;
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.WARNING, "Bad service config: " + txtRecord, e);
|
||||
continue;
|
||||
}
|
||||
serviceConfigs.addAll(choices);
|
||||
} else {
|
||||
logger.log(Level.FINE, "Ignoring non service config {0}", new Object[]{txtRecord});
|
||||
}
|
||||
}
|
||||
return serviceConfigs;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static final Double getPercentageFromChoice(
|
||||
Map<String, Object> serviceConfigChoice) {
|
||||
if (!serviceConfigChoice.containsKey(SERVICE_CONFIG_CHOICE_PERCENTAGE_KEY)) {
|
||||
return null;
|
||||
}
|
||||
return ServiceConfigUtil.getDouble(serviceConfigChoice, SERVICE_CONFIG_CHOICE_PERCENTAGE_KEY);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static final List<String> getClientLanguagesFromChoice(
|
||||
Map<String, Object> serviceConfigChoice) {
|
||||
if (!serviceConfigChoice.containsKey(SERVICE_CONFIG_CHOICE_CLIENT_LANGUAGE_KEY)) {
|
||||
return null;
|
||||
}
|
||||
return ServiceConfigUtil.checkStringList(
|
||||
ServiceConfigUtil.getList(serviceConfigChoice, SERVICE_CONFIG_CHOICE_CLIENT_LANGUAGE_KEY));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static final List<String> getHostnamesFromChoice(
|
||||
Map<String, Object> serviceConfigChoice) {
|
||||
if (!serviceConfigChoice.containsKey(SERVICE_CONFIG_CHOICE_CLIENT_HOSTNAME_KEY)) {
|
||||
return null;
|
||||
}
|
||||
return ServiceConfigUtil.checkStringList(
|
||||
ServiceConfigUtil.getList(serviceConfigChoice, SERVICE_CONFIG_CHOICE_CLIENT_HOSTNAME_KEY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a given Service Config choice applies, and if so, returns it.
|
||||
*
|
||||
* @see <a href="https://github.com/grpc/proposal/blob/master/A2-service-configs-in-dns.md">
|
||||
* Service Config in DNS</a>
|
||||
* @param choice The service config choice.
|
||||
* @return The service config object or {@code null} if this choice does not apply.
|
||||
*/
|
||||
@Nullable
|
||||
@SuppressWarnings("BetaApi") // Verify isn't all that beta
|
||||
@VisibleForTesting
|
||||
static Map<String, Object> maybeChooseServiceConfig(
|
||||
Map<String, Object> choice, Random random, String hostname) {
|
||||
for (Entry<String, ?> entry : choice.entrySet()) {
|
||||
Verify.verify(SERVICE_CONFIG_CHOICE_KEYS.contains(entry.getKey()), "Bad key: %s", entry);
|
||||
}
|
||||
|
||||
List<String> clientLanguages = getClientLanguagesFromChoice(choice);
|
||||
if (clientLanguages != null && clientLanguages.size() != 0) {
|
||||
boolean javaPresent = false;
|
||||
for (String lang : clientLanguages) {
|
||||
if ("java".equalsIgnoreCase(lang)) {
|
||||
javaPresent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!javaPresent) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Double percentage = getPercentageFromChoice(choice);
|
||||
if (percentage != null) {
|
||||
int pct = percentage.intValue();
|
||||
Verify.verify(pct >= 0 && pct <= 100, "Bad percentage: %s", percentage);
|
||||
if (random.nextInt(100) >= pct) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
List<String> clientHostnames = getHostnamesFromChoice(choice);
|
||||
if (clientHostnames != null && clientHostnames.size() != 0) {
|
||||
boolean hostnamePresent = false;
|
||||
for (String clientHostname : clientHostnames) {
|
||||
if (clientHostname.equals(hostname)) {
|
||||
hostnamePresent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hostnamePresent) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return ServiceConfigUtil.getObject(choice, SERVICE_CONFIG_CHOICE_SERVICE_CONFIG_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the JNDI DNS resolver is available. This is accomplished by looking up a
|
||||
* particular class. It is believed to be the default (only?) DNS resolver that will actually be
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ import java.util.ArrayList;
|
|||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
|
|
@ -134,6 +135,8 @@ final class ManagedChannelImpl extends ManagedChannel implements Instrumented<Ch
|
|||
|
||||
private final ConnectivityStateManager channelStateManager = new ConnectivityStateManager();
|
||||
|
||||
private final ServiceConfigInterceptor serviceConfigInterceptor = new ServiceConfigInterceptor();
|
||||
|
||||
private final BackoffPolicy.Provider backoffPolicyProvider;
|
||||
|
||||
/**
|
||||
|
|
@ -537,6 +540,7 @@ final class ManagedChannelImpl extends ManagedChannel implements Instrumented<Ch
|
|||
this.transportFactory =
|
||||
new CallCredentialsApplyingTransportFactory(clientTransportFactory, this.executor);
|
||||
Channel channel = new RealChannel();
|
||||
channel = ClientInterceptors.intercept(channel, serviceConfigInterceptor);
|
||||
if (builder.binlogProvider != null) {
|
||||
channel = builder.binlogProvider.wrapChannel(channel);
|
||||
}
|
||||
|
|
@ -1161,7 +1165,11 @@ final class ManagedChannelImpl extends ManagedChannel implements Instrumented<Ch
|
|||
|
||||
nameResolverBackoffPolicy = null;
|
||||
|
||||
Map<String, Object> serviceConfig =
|
||||
config.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG);
|
||||
|
||||
try {
|
||||
serviceConfigInterceptor.handleUpdate(serviceConfig);
|
||||
if (retryEnabled) {
|
||||
retryPolicies = getRetryPolicies(config);
|
||||
throttle = getThrottle(config);
|
||||
|
|
@ -1223,7 +1231,7 @@ final class ManagedChannelImpl extends ManagedChannel implements Instrumented<Ch
|
|||
}
|
||||
}
|
||||
|
||||
// TODO(zdapeng): test retryEnabled = true/flase really works as expected.
|
||||
// TODO(zdapeng): test retryEnabled = true/flase really works as expected
|
||||
private RetryPolicies getRetryPolicies(Attributes config) {
|
||||
return ServiceConfigUtil.getRetryPolicies(
|
||||
config.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG), maxRetryAttempts);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,325 @@
|
|||
/*
|
||||
* Copyright 2018, gRPC Authors All rights reserved.
|
||||
*
|
||||
* 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.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.base.Strings;
|
||||
import io.grpc.CallOptions;
|
||||
import io.grpc.Channel;
|
||||
import io.grpc.ClientCall;
|
||||
import io.grpc.ClientInterceptor;
|
||||
import io.grpc.Deadline;
|
||||
import io.grpc.MethodDescriptor;
|
||||
import io.grpc.Status.Code;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Modifies RPCs in in conformance with a Service Config.
|
||||
*/
|
||||
final class ServiceConfigInterceptor implements ClientInterceptor {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ServiceConfigInterceptor.class.getName());
|
||||
|
||||
// Map from method name to MethodInfo
|
||||
@VisibleForTesting
|
||||
final AtomicReference<Map<String, MethodInfo>> serviceMethodMap
|
||||
= new AtomicReference<Map<String, MethodInfo>>();
|
||||
@VisibleForTesting
|
||||
final AtomicReference<Map<String, MethodInfo>> serviceMap
|
||||
= new AtomicReference<Map<String, MethodInfo>>();
|
||||
|
||||
ServiceConfigInterceptor() {}
|
||||
|
||||
void handleUpdate(Map<String, Object> serviceConfig) {
|
||||
Map<String, MethodInfo> newServiceMethodConfigs = new HashMap<String, MethodInfo>();
|
||||
Map<String, MethodInfo> newServiceConfigs = new HashMap<String, MethodInfo>();
|
||||
|
||||
// Try and do as much validation here before we swap out the existing configuration. In case
|
||||
// the input is invalid, we don't want to lose the existing configuration.
|
||||
|
||||
List<Map<String, Object>> methodConfigs =
|
||||
ServiceConfigUtil.getMethodConfigFromServiceConfig(serviceConfig);
|
||||
if (methodConfigs == null) {
|
||||
logger.log(Level.FINE, "No method configs found, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
for (Map<String, Object> methodConfig : methodConfigs) {
|
||||
MethodInfo info = new MethodInfo(methodConfig);
|
||||
|
||||
List<Map<String, Object>> nameList =
|
||||
ServiceConfigUtil.getNameListFromMethodConfig(methodConfig);
|
||||
|
||||
checkArgument(
|
||||
nameList != null && !nameList.isEmpty(), "no names in method config %s", methodConfig);
|
||||
for (Map<String, Object> name : nameList) {
|
||||
String serviceName = ServiceConfigUtil.getServiceFromName(name);
|
||||
checkArgument(!Strings.isNullOrEmpty(serviceName), "missing service name");
|
||||
String methodName = ServiceConfigUtil.getMethodFromName(name);
|
||||
if (Strings.isNullOrEmpty(methodName)) {
|
||||
// Service scoped config
|
||||
checkArgument(
|
||||
!newServiceConfigs.containsKey(serviceName), "Duplicate service %s", serviceName);
|
||||
newServiceConfigs.put(serviceName, info);
|
||||
} else {
|
||||
// Method scoped config
|
||||
String fullMethodName = MethodDescriptor.generateFullMethodName(serviceName, methodName);
|
||||
checkArgument(
|
||||
!newServiceMethodConfigs.containsKey(fullMethodName),
|
||||
"Duplicate method name %s",
|
||||
fullMethodName);
|
||||
newServiceMethodConfigs.put(fullMethodName, info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Okay, service config is good, swap it.
|
||||
serviceMethodMap.set(Collections.unmodifiableMap(newServiceMethodConfigs));
|
||||
serviceMap.set(Collections.unmodifiableMap(newServiceConfigs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent of MethodConfig from a ServiceConfig.
|
||||
*/
|
||||
static final class MethodInfo {
|
||||
final Long timeoutNanos;
|
||||
final Boolean waitForReady;
|
||||
final Integer maxInboundMessageSize;
|
||||
final Integer maxOutboundMessageSize;
|
||||
final RetryPolicy retryPolicy;
|
||||
|
||||
MethodInfo(Map<String, Object> methodConfig) {
|
||||
timeoutNanos = ServiceConfigUtil.getTimeoutFromMethodConfig(methodConfig);
|
||||
waitForReady = ServiceConfigUtil.getWaitForReadyFromMethodConfig(methodConfig);
|
||||
maxInboundMessageSize =
|
||||
ServiceConfigUtil.getMaxResponseMessageBytesFromMethodConfig(methodConfig);
|
||||
if (maxInboundMessageSize != null) {
|
||||
checkArgument(
|
||||
maxInboundMessageSize >= 0,
|
||||
"maxInboundMessageSize %s exceeds bounds", maxInboundMessageSize);
|
||||
}
|
||||
maxOutboundMessageSize =
|
||||
ServiceConfigUtil.getMaxRequestMessageBytesFromMethodConfig(methodConfig);
|
||||
if (maxOutboundMessageSize != null) {
|
||||
checkArgument(
|
||||
maxOutboundMessageSize >= 0,
|
||||
"maxOutboundMessageSize %s exceeds bounds", maxOutboundMessageSize);
|
||||
}
|
||||
|
||||
Map<String, Object> policy = ServiceConfigUtil.getRetryPolicyFromMethodConfig(methodConfig);
|
||||
retryPolicy = policy == null ? null : new RetryPolicy(policy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(
|
||||
timeoutNanos, waitForReady, maxInboundMessageSize, maxOutboundMessageSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof MethodInfo)) {
|
||||
return false;
|
||||
}
|
||||
MethodInfo that = (MethodInfo) other;
|
||||
return Objects.equal(this.timeoutNanos, that.timeoutNanos)
|
||||
&& Objects.equal(this.waitForReady, that.waitForReady)
|
||||
&& Objects.equal(this.maxInboundMessageSize, that.maxInboundMessageSize)
|
||||
&& Objects.equal(this.maxOutboundMessageSize, that.maxOutboundMessageSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("timeoutNanos", timeoutNanos)
|
||||
.add("waitForReady", waitForReady)
|
||||
.add("maxInboundMessageSize", maxInboundMessageSize)
|
||||
.add("maxOutboundMessageSize", maxOutboundMessageSize)
|
||||
.toString();
|
||||
}
|
||||
|
||||
static final class RetryPolicy {
|
||||
final int maxAttempts;
|
||||
final long initialBackoffNanos;
|
||||
final long maxBackoffNanos;
|
||||
final double backoffMultiplier;
|
||||
final Set<Code> retryableStatusCodes;
|
||||
|
||||
RetryPolicy(Map<String, Object> retryPolicy) {
|
||||
maxAttempts = checkNotNull(
|
||||
ServiceConfigUtil.getMaxAttemptsFromRetryPolicy(retryPolicy),
|
||||
"maxAttempts cannot be empty");
|
||||
checkArgument(maxAttempts >= 2, "maxAttempts must be greater than 1: %s", maxAttempts);
|
||||
|
||||
initialBackoffNanos = checkNotNull(
|
||||
ServiceConfigUtil.getInitialBackoffNanosFromRetryPolicy(retryPolicy),
|
||||
"initialBackoff cannot be empty");
|
||||
checkArgument(
|
||||
initialBackoffNanos > 0,
|
||||
"initialBackoffNanos must be greater than 0: %s",
|
||||
initialBackoffNanos);
|
||||
|
||||
maxBackoffNanos = checkNotNull(
|
||||
ServiceConfigUtil.getMaxBackoffNanosFromRetryPolicy(retryPolicy),
|
||||
"maxBackoff cannot be empty");
|
||||
checkArgument(
|
||||
maxBackoffNanos > 0, "maxBackoff must be greater than 0: %s", maxBackoffNanos);
|
||||
|
||||
backoffMultiplier = checkNotNull(
|
||||
ServiceConfigUtil.getBackoffMultiplierFromRetryPolicy(retryPolicy),
|
||||
"backoffMultiplier cannot be empty");
|
||||
checkArgument(
|
||||
backoffMultiplier > 0,
|
||||
"backoffMultiplier must be greater than 0: %s",
|
||||
backoffMultiplier);
|
||||
|
||||
List<String> rawCodes =
|
||||
ServiceConfigUtil.getRetryableStatusCodesFromRetryPolicy(retryPolicy);
|
||||
checkNotNull(rawCodes, "rawCodes must be present");
|
||||
checkArgument(!rawCodes.isEmpty(), "rawCodes can't be empty");
|
||||
EnumSet<Code> codes = EnumSet.noneOf(Code.class);
|
||||
// service config doesn't say if duplicates are allowed, so just accept them.
|
||||
for (String rawCode : rawCodes) {
|
||||
codes.add(Code.valueOf(rawCode));
|
||||
}
|
||||
retryableStatusCodes = Collections.unmodifiableSet(codes);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
RetryPolicy(
|
||||
int maxAttempts,
|
||||
long initialBackoffNanos,
|
||||
long maxBackoffNanos,
|
||||
double backoffMultiplier,
|
||||
Set<Code> retryableStatusCodes) {
|
||||
this.maxAttempts = maxAttempts;
|
||||
this.initialBackoffNanos = initialBackoffNanos;
|
||||
this.maxBackoffNanos = maxBackoffNanos;
|
||||
this.backoffMultiplier = backoffMultiplier;
|
||||
this.retryableStatusCodes = retryableStatusCodes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(
|
||||
maxAttempts,
|
||||
initialBackoffNanos,
|
||||
maxBackoffNanos,
|
||||
backoffMultiplier,
|
||||
retryableStatusCodes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof RetryPolicy)) {
|
||||
return false;
|
||||
}
|
||||
RetryPolicy that = (RetryPolicy) other;
|
||||
return Objects.equal(this.maxAttempts, that.maxAttempts)
|
||||
&& Objects.equal(this.initialBackoffNanos, that.initialBackoffNanos)
|
||||
&& Objects.equal(this.maxBackoffNanos, that.maxBackoffNanos)
|
||||
&& Objects.equal(this.backoffMultiplier, that.backoffMultiplier)
|
||||
&& Objects.equal(this.retryableStatusCodes, that.retryableStatusCodes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("maxAttempts", maxAttempts)
|
||||
.add("initialBackoffNanos", initialBackoffNanos)
|
||||
.add("maxBackoffNanos", maxBackoffNanos)
|
||||
.add("backoffMultiplier", backoffMultiplier)
|
||||
.add("retryableStatusCodes", retryableStatusCodes)
|
||||
.toString();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static final CallOptions.Key<MethodInfo.RetryPolicy> RETRY_POLICY_KEY =
|
||||
CallOptions.Key.of("internal-retry-policy", null);
|
||||
|
||||
@Override
|
||||
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
|
||||
MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
|
||||
Map<String, MethodInfo> localServiceMethodMap = serviceMethodMap.get();
|
||||
MethodInfo info = null;
|
||||
if (localServiceMethodMap != null) {
|
||||
info = localServiceMethodMap.get(method.getFullMethodName());
|
||||
}
|
||||
if (info == null) {
|
||||
Map<String, MethodInfo> localServiceMap = serviceMap.get();
|
||||
if (localServiceMap != null) {
|
||||
info = localServiceMap.get(
|
||||
MethodDescriptor.extractFullServiceName(method.getFullMethodName()));
|
||||
}
|
||||
}
|
||||
if (info == null) {
|
||||
return next.newCall(method, callOptions);
|
||||
}
|
||||
|
||||
if (info.timeoutNanos != null) {
|
||||
Deadline newDeadline = Deadline.after(info.timeoutNanos, TimeUnit.NANOSECONDS);
|
||||
Deadline existingDeadline = callOptions.getDeadline();
|
||||
// If the new deadline is sooner than the existing deadline, swap them.
|
||||
if (existingDeadline == null || newDeadline.compareTo(existingDeadline) < 0) {
|
||||
callOptions = callOptions.withDeadline(newDeadline);
|
||||
}
|
||||
}
|
||||
if (info.waitForReady != null) {
|
||||
callOptions =
|
||||
info.waitForReady ? callOptions.withWaitForReady() : callOptions.withoutWaitForReady();
|
||||
}
|
||||
if (info.maxInboundMessageSize != null) {
|
||||
Integer existingLimit = callOptions.getMaxInboundMessageSize();
|
||||
if (existingLimit != null) {
|
||||
callOptions = callOptions.withMaxInboundMessageSize(
|
||||
Math.min(existingLimit, info.maxInboundMessageSize));
|
||||
} else {
|
||||
callOptions = callOptions.withMaxInboundMessageSize(info.maxInboundMessageSize);
|
||||
}
|
||||
}
|
||||
if (info.maxOutboundMessageSize != null) {
|
||||
Integer existingLimit = callOptions.getMaxOutboundMessageSize();
|
||||
if (existingLimit != null) {
|
||||
callOptions = callOptions.withMaxOutboundMessageSize(
|
||||
Math.min(existingLimit, info.maxOutboundMessageSize));
|
||||
} else {
|
||||
callOptions = callOptions.withMaxOutboundMessageSize(info.maxOutboundMessageSize);
|
||||
}
|
||||
}
|
||||
if (info.retryPolicy != null) {
|
||||
callOptions = callOptions.withOption(RETRY_POLICY_KEY, info.retryPolicy);
|
||||
}
|
||||
|
||||
return next.newCall(method, callOptions);
|
||||
}
|
||||
}
|
||||
|
|
@ -18,26 +18,20 @@ package io.grpc.internal;
|
|||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.common.math.LongMath.checkedAdd;
|
||||
|
||||
import com.google.common.base.Verify;
|
||||
import io.grpc.MethodDescriptor;
|
||||
import io.grpc.Status.Code;
|
||||
import io.grpc.internal.RetriableStream.RetryPolicies;
|
||||
import io.grpc.internal.RetriableStream.RetryPolicy;
|
||||
import io.grpc.internal.RetriableStream.Throttle;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.text.ParseException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Logger;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
|
@ -48,24 +42,29 @@ final class ServiceConfigUtil {
|
|||
|
||||
private static final Logger logger = Logger.getLogger(ServiceConfigUtil.class.getName());
|
||||
|
||||
// From https://github.com/grpc/proposal/blob/master/A2-service-configs-in-dns.md
|
||||
static final String SERVICE_CONFIG_PREFIX = "_grpc_config=";
|
||||
private static final Set<String> SERVICE_CONFIG_CHOICE_KEYS =
|
||||
Collections.unmodifiableSet(
|
||||
new HashSet<String>(
|
||||
Arrays.asList("clientLanguage", "percentage", "clientHostname", "serviceConfig")));
|
||||
private static final String SERVICE_CONFIG_METHOD_CONFIG_KEY = "methodConfig";
|
||||
private static final String SERVICE_CONFIG_LOAD_BALANCING_POLICY_key = "loadBalancingPolicy";
|
||||
private static final String METHOD_CONFIG_NAME_KEY = "name";
|
||||
private static final String METHOD_CONFIG_TIMEOUT_KEY = "timeout";
|
||||
private static final String METHOD_CONFIG_WAIT_FOR_READY_KEY = "waitForReady";
|
||||
private static final String METHOD_CONFIG_MAX_REQUEST_MESSAGE_BYTES_KEY =
|
||||
"maxRequestMessageBytes";
|
||||
private static final String METHOD_CONFIG_MAX_RESPONSE_MESSAGE_BYTES_KEY =
|
||||
"maxResponseMessageBytes";
|
||||
private static final String METHOD_CONFIG_RETRY_POLICY_KEY = "retryPolicy";
|
||||
private static final String NAME_SERVICE_KEY = "service";
|
||||
private static final String NAME_METHOD_KEY = "method";
|
||||
private static final String RETRY_POLICY_MAX_ATTEMPTS_KEY = "maxAttempts";
|
||||
private static final String RETRY_POLICY_INITIAL_BACKOFF_KEY = "initialBackoff";
|
||||
private static final String RETRY_POLICY_MAX_BACKOFF_KEY = "maxBackoff";
|
||||
private static final String RETRY_POLICY_BACKOFF_MULTIPLIER_KEY = "backoffMultiplier";
|
||||
private static final String RETRY_POLICY_RETRYABLE_STATUS_CODES_KEY = "retryableStatusCodes";
|
||||
|
||||
private static final long DURATION_SECONDS_MIN = -315576000000L;
|
||||
private static final long DURATION_SECONDS_MAX = 315576000000L;
|
||||
|
||||
private ServiceConfigUtil() {}
|
||||
|
||||
@Nullable
|
||||
static String getLoadBalancingPolicy(Map<String, Object> serviceConfig) {
|
||||
String key = "loadBalancingPolicy";
|
||||
if (serviceConfig.containsKey(key)) {
|
||||
return getString(serviceConfig, key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets retry policies from the service config.
|
||||
*
|
||||
|
|
@ -94,10 +93,10 @@ final class ServiceConfigUtil {
|
|||
"maxBackoff": string, // Long decimal with "s" appended
|
||||
"backoffMultiplier": number
|
||||
"retryableStatusCodes": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
*/
|
||||
|
||||
if (serviceConfig.containsKey("methodConfig")) {
|
||||
|
|
@ -204,98 +203,152 @@ final class ServiceConfigUtil {
|
|||
return new Throttle(maxTokens, tokenRatio);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a given Service Config choice applies, and if so, returns it.
|
||||
*
|
||||
* @see <a href="https://github.com/grpc/proposal/blob/master/A2-service-configs-in-dns.md">
|
||||
* Service Config in DNS</a>
|
||||
* @param choice The service config choice.
|
||||
* @return The service config object or {@code null} if this choice does not apply.
|
||||
*/
|
||||
@Nullable
|
||||
@SuppressWarnings("BetaApi") // Verify isn't all that beta
|
||||
static Map<String, Object> maybeChooseServiceConfig(
|
||||
Map<String, Object> choice, Random random, String hostname) {
|
||||
for (Entry<String, ?> entry : choice.entrySet()) {
|
||||
Verify.verify(SERVICE_CONFIG_CHOICE_KEYS.contains(entry.getKey()), "Bad key: %s", entry);
|
||||
static Integer getMaxAttemptsFromRetryPolicy(Map<String, Object> retryPolicy) {
|
||||
if (!retryPolicy.containsKey(RETRY_POLICY_MAX_ATTEMPTS_KEY)) {
|
||||
return null;
|
||||
}
|
||||
if (choice.containsKey("clientLanguage")) {
|
||||
List<Object> clientLanguages = getList(choice, "clientLanguage");
|
||||
if (!clientLanguages.isEmpty()) {
|
||||
boolean javaPresent = false;
|
||||
for (int i = 0; i < clientLanguages.size(); i++) {
|
||||
String lang = getString(clientLanguages, i).toLowerCase(Locale.ROOT);
|
||||
if ("java".equals(lang)) {
|
||||
javaPresent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!javaPresent) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (choice.containsKey("percentage")) {
|
||||
int pct = getDouble(choice, "percentage").intValue();
|
||||
Verify.verify(pct >= 0 && pct <= 100, "Bad percentage", choice.get("percentage"));
|
||||
if (random.nextInt(100) >= pct) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (choice.containsKey("clientHostname")) {
|
||||
List<Object> clientHostnames = getList(choice, "clientHostname");
|
||||
if (!clientHostnames.isEmpty()) {
|
||||
boolean hostnamePresent = false;
|
||||
for (int i = 0; i < clientHostnames.size(); i++) {
|
||||
if (getString(clientHostnames, i).equals(hostname)) {
|
||||
hostnamePresent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hostnamePresent) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return getObject(choice, "serviceConfig");
|
||||
return getDouble(retryPolicy, RETRY_POLICY_MAX_ATTEMPTS_KEY).intValue();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static List<Map<String, Object>> parseTxtResults(List<String> txtRecords) {
|
||||
List<Map<String, Object>> serviceConfigs = new ArrayList<Map<String, Object>>();
|
||||
|
||||
for (String txtRecord : txtRecords) {
|
||||
if (txtRecord.startsWith(SERVICE_CONFIG_PREFIX)) {
|
||||
List<Map<String, Object>> choices;
|
||||
try {
|
||||
Object rawChoices = JsonParser.parse(txtRecord.substring(SERVICE_CONFIG_PREFIX.length()));
|
||||
if (!(rawChoices instanceof List)) {
|
||||
throw new IOException("wrong type" + rawChoices);
|
||||
}
|
||||
List<Object> listChoices = (List<Object>) rawChoices;
|
||||
for (Object obj : listChoices) {
|
||||
if (!(obj instanceof Map)) {
|
||||
throw new IOException("wrong element type" + rawChoices);
|
||||
}
|
||||
}
|
||||
choices = (List<Map<String, Object>>) (List) listChoices;
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.WARNING, "Bad service config: " + txtRecord, e);
|
||||
continue;
|
||||
}
|
||||
serviceConfigs.addAll(choices);
|
||||
} else {
|
||||
logger.log(Level.FINE, "Ignoring non service config {0}", new Object[]{txtRecord});
|
||||
}
|
||||
@Nullable
|
||||
static Long getInitialBackoffNanosFromRetryPolicy(Map<String, Object> retryPolicy) {
|
||||
if (!retryPolicy.containsKey(RETRY_POLICY_INITIAL_BACKOFF_KEY)) {
|
||||
return null;
|
||||
}
|
||||
return serviceConfigs;
|
||||
String rawInitialBackoff = getString(retryPolicy, RETRY_POLICY_INITIAL_BACKOFF_KEY);
|
||||
try {
|
||||
return parseDuration(rawInitialBackoff);
|
||||
} catch (ParseException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static Long getMaxBackoffNanosFromRetryPolicy(Map<String, Object> retryPolicy) {
|
||||
if (!retryPolicy.containsKey(RETRY_POLICY_MAX_BACKOFF_KEY)) {
|
||||
return null;
|
||||
}
|
||||
String rawMaxBackoff = getString(retryPolicy, RETRY_POLICY_MAX_BACKOFF_KEY);
|
||||
try {
|
||||
return parseDuration(rawMaxBackoff);
|
||||
} catch (ParseException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static Double getBackoffMultiplierFromRetryPolicy(Map<String, Object> retryPolicy) {
|
||||
if (!retryPolicy.containsKey(RETRY_POLICY_BACKOFF_MULTIPLIER_KEY)) {
|
||||
return null;
|
||||
}
|
||||
return getDouble(retryPolicy, RETRY_POLICY_BACKOFF_MULTIPLIER_KEY);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static List<String> getRetryableStatusCodesFromRetryPolicy(Map<String, Object> retryPolicy) {
|
||||
if (!retryPolicy.containsKey(RETRY_POLICY_RETRYABLE_STATUS_CODES_KEY)) {
|
||||
return null;
|
||||
}
|
||||
return checkStringList(getList(retryPolicy, RETRY_POLICY_RETRYABLE_STATUS_CODES_KEY));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static String getServiceFromName(Map<String, Object> name) {
|
||||
if (!name.containsKey(NAME_SERVICE_KEY)) {
|
||||
return null;
|
||||
}
|
||||
return getString(name, NAME_SERVICE_KEY);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static String getMethodFromName(Map<String, Object> name) {
|
||||
if (!name.containsKey(NAME_METHOD_KEY)) {
|
||||
return null;
|
||||
}
|
||||
return getString(name, NAME_METHOD_KEY);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static Map<String, Object> getRetryPolicyFromMethodConfig(Map<String, Object> methodConfig) {
|
||||
if (!methodConfig.containsKey(METHOD_CONFIG_RETRY_POLICY_KEY)) {
|
||||
return null;
|
||||
}
|
||||
return getObject(methodConfig, METHOD_CONFIG_RETRY_POLICY_KEY);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static List<Map<String, Object>> getNameListFromMethodConfig(Map<String, Object> methodConfig) {
|
||||
if (!methodConfig.containsKey(METHOD_CONFIG_NAME_KEY)) {
|
||||
return null;
|
||||
}
|
||||
return checkObjectList(getList(methodConfig, METHOD_CONFIG_NAME_KEY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of nanoseconds of timeout for the given method config.
|
||||
*
|
||||
* @return duration nanoseconds, or {@code null} if it isn't present.
|
||||
*/
|
||||
@Nullable
|
||||
static Long getTimeoutFromMethodConfig(Map<String, Object> methodConfig) {
|
||||
if (!methodConfig.containsKey(METHOD_CONFIG_TIMEOUT_KEY)) {
|
||||
return null;
|
||||
}
|
||||
String rawTimeout = getString(methodConfig, METHOD_CONFIG_TIMEOUT_KEY);
|
||||
try {
|
||||
return parseDuration(rawTimeout);
|
||||
} catch (ParseException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static Boolean getWaitForReadyFromMethodConfig(Map<String, Object> methodConfig) {
|
||||
if (!methodConfig.containsKey(METHOD_CONFIG_WAIT_FOR_READY_KEY)) {
|
||||
return null;
|
||||
}
|
||||
return getBoolean(methodConfig, METHOD_CONFIG_WAIT_FOR_READY_KEY);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static Integer getMaxRequestMessageBytesFromMethodConfig(Map<String, Object> methodConfig) {
|
||||
if (!methodConfig.containsKey(METHOD_CONFIG_MAX_REQUEST_MESSAGE_BYTES_KEY)) {
|
||||
return null;
|
||||
}
|
||||
return getDouble(methodConfig, METHOD_CONFIG_MAX_REQUEST_MESSAGE_BYTES_KEY).intValue();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static Integer getMaxResponseMessageBytesFromMethodConfig(Map<String, Object> methodConfig) {
|
||||
if (!methodConfig.containsKey(METHOD_CONFIG_MAX_RESPONSE_MESSAGE_BYTES_KEY)) {
|
||||
return null;
|
||||
}
|
||||
return getDouble(methodConfig, METHOD_CONFIG_MAX_RESPONSE_MESSAGE_BYTES_KEY).intValue();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static List<Map<String, Object>> getMethodConfigFromServiceConfig(
|
||||
Map<String, Object> serviceConfig) {
|
||||
if (!serviceConfig.containsKey(SERVICE_CONFIG_METHOD_CONFIG_KEY)) {
|
||||
return null;
|
||||
}
|
||||
return checkObjectList(getList(serviceConfig, SERVICE_CONFIG_METHOD_CONFIG_KEY));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static String getLoadBalancingPolicyFromServiceConfig(Map<String, Object> serviceConfig) {
|
||||
if (!serviceConfig.containsKey(SERVICE_CONFIG_LOAD_BALANCING_POLICY_key)) {
|
||||
return null;
|
||||
}
|
||||
return getString(serviceConfig, SERVICE_CONFIG_LOAD_BALANCING_POLICY_key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list from an object for the given key.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<Object> getList(Map<String, Object> obj, String key) {
|
||||
static List<Object> getList(Map<String, Object> obj, String key) {
|
||||
assert obj.containsKey(key);
|
||||
Object value = checkNotNull(obj.get(key), "no such key %s", key);
|
||||
if (value instanceof List) {
|
||||
|
|
@ -309,7 +362,7 @@ final class ServiceConfigUtil {
|
|||
* Gets an object from an object for the given key.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Map<String, Object> getObject(Map<String, Object> obj, String key) {
|
||||
static Map<String, Object> getObject(Map<String, Object> obj, String key) {
|
||||
assert obj.containsKey(key);
|
||||
Object value = checkNotNull(obj.get(key), "no such key %s", key);
|
||||
if (value instanceof Map) {
|
||||
|
|
@ -337,7 +390,7 @@ final class ServiceConfigUtil {
|
|||
* Gets a double from an object for the given key.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Double getDouble(Map<String, Object> obj, String key) {
|
||||
static Double getDouble(Map<String, Object> obj, String key) {
|
||||
assert obj.containsKey(key);
|
||||
Object value = checkNotNull(obj.get(key), "no such key %s", key);
|
||||
if (value instanceof Double) {
|
||||
|
|
@ -351,7 +404,7 @@ final class ServiceConfigUtil {
|
|||
* Gets a string from an object for the given key.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static String getString(Map<String, Object> obj, String key) {
|
||||
static String getString(Map<String, Object> obj, String key) {
|
||||
assert obj.containsKey(key);
|
||||
Object value = checkNotNull(obj.get(key), "no such key %s", key);
|
||||
if (value instanceof String) {
|
||||
|
|
@ -365,7 +418,7 @@ final class ServiceConfigUtil {
|
|||
* Gets a string from an object for the given index.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static String getString(List<Object> list, int i) {
|
||||
static String getString(List<Object> list, int i) {
|
||||
assert i >= 0 && i < list.size();
|
||||
Object value = checkNotNull(list.get(i), "idx %s in %s is null", i, list);
|
||||
if (value instanceof String) {
|
||||
|
|
@ -374,4 +427,170 @@ final class ServiceConfigUtil {
|
|||
throw new ClassCastException(
|
||||
String.format("value %s for idx %d in %s is not String", value, i, list));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a boolean from an object for the given key.
|
||||
*/
|
||||
static Boolean getBoolean(Map<String, Object> obj, String key) {
|
||||
assert obj.containsKey(key);
|
||||
Object value = checkNotNull(obj.get(key), "no such key %s", key);
|
||||
if (value instanceof Boolean) {
|
||||
return (Boolean) value;
|
||||
}
|
||||
throw new ClassCastException(
|
||||
String.format("value %s for key %s in %s is not Boolean", value, key, obj));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<Map<String, Object>> checkObjectList(List<Object> rawList) {
|
||||
for (int i = 0; i < rawList.size(); i++) {
|
||||
if (!(rawList.get(i) instanceof Map)) {
|
||||
throw new ClassCastException(
|
||||
String.format("value %s for idx %d in %s is not object", rawList.get(i), i, rawList));
|
||||
}
|
||||
}
|
||||
return (List<Map<String, Object>>) (List<?>) rawList;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static List<String> checkStringList(List<Object> rawList) {
|
||||
for (int i = 0; i < rawList.size(); i++) {
|
||||
if (!(rawList.get(i) instanceof String)) {
|
||||
throw new ClassCastException(
|
||||
String.format("value %s for idx %d in %s is not string", rawList.get(i), i, rawList));
|
||||
}
|
||||
}
|
||||
return (List<String>) (List<?>) rawList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse from a string to produce a duration. Copy of
|
||||
* {@link com.google.protobuf.util.Durations#parse}.
|
||||
*
|
||||
* @return A Duration parsed from the string.
|
||||
* @throws ParseException if parsing fails.
|
||||
*/
|
||||
private static long parseDuration(String value) throws ParseException {
|
||||
// Must ended with "s".
|
||||
if (value.isEmpty() || value.charAt(value.length() - 1) != 's') {
|
||||
throw new ParseException("Invalid duration string: " + value, 0);
|
||||
}
|
||||
boolean negative = false;
|
||||
if (value.charAt(0) == '-') {
|
||||
negative = true;
|
||||
value = value.substring(1);
|
||||
}
|
||||
String secondValue = value.substring(0, value.length() - 1);
|
||||
String nanoValue = "";
|
||||
int pointPosition = secondValue.indexOf('.');
|
||||
if (pointPosition != -1) {
|
||||
nanoValue = secondValue.substring(pointPosition + 1);
|
||||
secondValue = secondValue.substring(0, pointPosition);
|
||||
}
|
||||
long seconds = Long.parseLong(secondValue);
|
||||
int nanos = nanoValue.isEmpty() ? 0 : parseNanos(nanoValue);
|
||||
if (seconds < 0) {
|
||||
throw new ParseException("Invalid duration string: " + value, 0);
|
||||
}
|
||||
if (negative) {
|
||||
seconds = -seconds;
|
||||
nanos = -nanos;
|
||||
}
|
||||
try {
|
||||
return normalizedDuration(seconds, nanos);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new ParseException("Duration value is out of range.", 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy of {@link com.google.protobuf.util.Timestamps#parseNanos}.
|
||||
*/
|
||||
private static int parseNanos(String value) throws ParseException {
|
||||
int result = 0;
|
||||
for (int i = 0; i < 9; ++i) {
|
||||
result = result * 10;
|
||||
if (i < value.length()) {
|
||||
if (value.charAt(i) < '0' || value.charAt(i) > '9') {
|
||||
throw new ParseException("Invalid nanoseconds.", 0);
|
||||
}
|
||||
result += value.charAt(i) - '0';
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static final long NANOS_PER_SECOND = TimeUnit.SECONDS.toNanos(1);
|
||||
|
||||
/**
|
||||
* Copy of {@link com.google.protobuf.util.Durations#normalizedDuration}.
|
||||
*/
|
||||
@SuppressWarnings("NarrowingCompoundAssignment")
|
||||
private static long normalizedDuration(long seconds, int nanos) {
|
||||
if (nanos <= -NANOS_PER_SECOND || nanos >= NANOS_PER_SECOND) {
|
||||
seconds = checkedAdd(seconds, nanos / NANOS_PER_SECOND);
|
||||
nanos %= NANOS_PER_SECOND;
|
||||
}
|
||||
if (seconds > 0 && nanos < 0) {
|
||||
nanos += NANOS_PER_SECOND; // no overflow since nanos is negative (and we're adding)
|
||||
seconds--; // no overflow since seconds is positive (and we're decrementing)
|
||||
}
|
||||
if (seconds < 0 && nanos > 0) {
|
||||
nanos -= NANOS_PER_SECOND; // no overflow since nanos is positive (and we're subtracting)
|
||||
seconds++; // no overflow since seconds is negative (and we're incrementing)
|
||||
}
|
||||
if (!durationIsValid(seconds, nanos)) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Duration is not valid. See proto definition for valid values. "
|
||||
+ "Seconds (%s) must be in range [-315,576,000,000, +315,576,000,000]. "
|
||||
+ "Nanos (%s) must be in range [-999,999,999, +999,999,999]. "
|
||||
+ "Nanos must have the same sign as seconds", seconds, nanos));
|
||||
}
|
||||
return saturatedAdd(TimeUnit.SECONDS.toNanos(seconds), nanos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given number of seconds and nanos is a valid {@code Duration}. The {@code
|
||||
* seconds} value must be in the range [-315,576,000,000, +315,576,000,000]. The {@code nanos}
|
||||
* value must be in the range [-999,999,999, +999,999,999].
|
||||
*
|
||||
* <p><b>Note:</b> Durations less than one second are represented with a 0 {@code seconds} field
|
||||
* and a positive or negative {@code nanos} field. For durations of one second or more, a non-zero
|
||||
* value for the {@code nanos} field must be of the same sign as the {@code seconds} field.
|
||||
*
|
||||
* <p>Copy of {@link com.google.protobuf.util.Duration#isValid}.</p>
|
||||
*/
|
||||
private static boolean durationIsValid(long seconds, int nanos) {
|
||||
if (seconds < DURATION_SECONDS_MIN || seconds > DURATION_SECONDS_MAX) {
|
||||
return false;
|
||||
}
|
||||
if (nanos < -999999999L || nanos >= NANOS_PER_SECOND) {
|
||||
return false;
|
||||
}
|
||||
if (seconds < 0 || nanos < 0) {
|
||||
if (seconds > 0 || nanos > 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sum of {@code a} and {@code b} unless it would overflow or underflow in which case
|
||||
* {@code Long.MAX_VALUE} or {@code Long.MIN_VALUE} is returned, respectively.
|
||||
*
|
||||
* <p>Copy of {@link com.google.common.math.LongMath#saturatedAdd}.</p>
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("ShortCircuitBoolean")
|
||||
private static long saturatedAdd(long a, long b) {
|
||||
long naiveSum = a + b;
|
||||
if ((a ^ b) < 0 | (a ^ naiveSum) >= 0) {
|
||||
// If a and b have different signs or a has the same sign as the result then there was no
|
||||
// overflow, return.
|
||||
return naiveSum;
|
||||
}
|
||||
// we did over/under flow, if the sign is negative we should return MAX otherwise MIN
|
||||
return Long.MAX_VALUE + ((naiveSum >>> (Long.SIZE - 1)) ^ 1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package io.grpc.internal;
|
|||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
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;
|
||||
|
|
@ -45,15 +46,19 @@ import java.net.UnknownHostException;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import org.junit.After;
|
||||
import org.junit.Assume;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.rules.Timeout;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
|
@ -68,6 +73,11 @@ public class DnsNameResolverTest {
|
|||
|
||||
@Rule public final Timeout globalTimeout = Timeout.seconds(10);
|
||||
|
||||
@Rule
|
||||
public final ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private final Map<String, Object> serviceConfig = new LinkedHashMap<String, Object>();
|
||||
|
||||
private static final int DEFAULT_PORT = 887;
|
||||
private static final Attributes NAME_RESOLVER_PARAMS =
|
||||
Attributes.newBuilder().set(NameResolver.Factory.PARAMS_DEFAULT_PORT, DEFAULT_PORT).build();
|
||||
|
|
@ -281,6 +291,239 @@ public class DnsNameResolverTest {
|
|||
assertEquals("blah\\blah", DnsNameResolver.unquote("\"blah\\\\blah\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maybeChooseServiceConfig_failsOnMisspelling() {
|
||||
Map<String, Object> bad = new LinkedHashMap<String, Object>();
|
||||
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<String, Object>();
|
||||
List<Object> langs = new ArrayList<Object>();
|
||||
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<String, Object>();
|
||||
List<Object> langs = new ArrayList<Object>();
|
||||
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<String, Object>();
|
||||
List<Object> langs = new ArrayList<Object>();
|
||||
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<String, Object>();
|
||||
List<Object> langs = new ArrayList<Object>();
|
||||
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<String, Object>();
|
||||
List<Object> langs = new ArrayList<Object>();
|
||||
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<String, Object>();
|
||||
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<String, Object>();
|
||||
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<String, Object>();
|
||||
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<String, Object>();
|
||||
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<String, Object>();
|
||||
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<String, Object>();
|
||||
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<String, Object>();
|
||||
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<String, Object>();
|
||||
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<String, Object>();
|
||||
List<Object> hosts = new ArrayList<Object>();
|
||||
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<String, Object>();
|
||||
List<Object> hosts = new ArrayList<Object>();
|
||||
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<String, Object>();
|
||||
List<Object> hosts = new ArrayList<Object>();
|
||||
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<String, Object>();
|
||||
List<Object> hosts = new ArrayList<Object>();
|
||||
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<String, Object>();
|
||||
List<Object> hosts = new ArrayList<Object>();
|
||||
hosts.add("localhorse");
|
||||
hosts.add("localhost");
|
||||
choice.put("clientHostname", hosts);
|
||||
choice.put("serviceConfig", serviceConfig);
|
||||
|
||||
assertNotNull(DnsNameResolver.maybeChooseServiceConfig(choice, new Random(), "localhost"));
|
||||
}
|
||||
|
||||
private void testInvalidUri(URI uri) {
|
||||
try {
|
||||
provider.newNameResolver(uri, NAME_RESOLVER_PARAMS);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,415 @@
|
|||
/*
|
||||
* Copyright 2018, gRPC Authors All rights reserved.
|
||||
*
|
||||
* 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.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import io.grpc.CallOptions;
|
||||
import io.grpc.Channel;
|
||||
import io.grpc.Deadline;
|
||||
import io.grpc.MethodDescriptor;
|
||||
import io.grpc.MethodDescriptor.MethodType;
|
||||
import io.grpc.internal.ServiceConfigInterceptor.MethodInfo;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ServiceConfigInterceptor}.
|
||||
*/
|
||||
@RunWith(JUnit4.class)
|
||||
public class ServiceConfigInterceptorTest {
|
||||
|
||||
@Rule public final ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Mock private Channel channel;
|
||||
@Captor private ArgumentCaptor<CallOptions> callOptionsCap;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
private final ServiceConfigInterceptor interceptor = new ServiceConfigInterceptor();
|
||||
|
||||
private final String fullMethodName =
|
||||
MethodDescriptor.generateFullMethodName("service", "method");
|
||||
private final MethodDescriptor<Void, Void> methodDescriptor =
|
||||
MethodDescriptor.newBuilder(new NoopMarshaller(), new NoopMarshaller())
|
||||
.setType(MethodType.UNARY)
|
||||
.setFullMethodName(fullMethodName)
|
||||
.build();
|
||||
|
||||
|
||||
|
||||
private static final class JsonObj extends HashMap<String, Object> {
|
||||
private JsonObj(Object ... kv) {
|
||||
for (int i = 0; i < kv.length; i += 2) {
|
||||
put((String) kv[i], kv[i + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class JsonList extends ArrayList<Object> {
|
||||
private JsonList(Object ... values) {
|
||||
addAll(Arrays.asList(values));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withWaitForReady() {
|
||||
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.interceptCall(methodDescriptor, CallOptions.DEFAULT.withoutWaitForReady(), channel);
|
||||
|
||||
verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture());
|
||||
assertThat(callOptionsCap.getValue().isWaitForReady()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withMaxRequestSize() {
|
||||
JsonObj name = new JsonObj("service", "service");
|
||||
JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxRequestMessageBytes", 1d);
|
||||
JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
|
||||
|
||||
interceptor.handleUpdate(serviceConfig);
|
||||
|
||||
interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT, channel);
|
||||
|
||||
verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture());
|
||||
assertThat(callOptionsCap.getValue().getMaxOutboundMessageSize()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withMaxRequestSize_pickSmallerExisting() {
|
||||
JsonObj name = new JsonObj("service", "service");
|
||||
JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxRequestMessageBytes", 10d);
|
||||
JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
|
||||
|
||||
interceptor.handleUpdate(serviceConfig);
|
||||
|
||||
interceptor.interceptCall(
|
||||
methodDescriptor, CallOptions.DEFAULT.withMaxOutboundMessageSize(5), channel);
|
||||
|
||||
verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture());
|
||||
assertThat(callOptionsCap.getValue().getMaxOutboundMessageSize()).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withMaxRequestSize_pickSmallerNew() {
|
||||
JsonObj name = new JsonObj("service", "service");
|
||||
JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxRequestMessageBytes", 5d);
|
||||
JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
|
||||
|
||||
interceptor.handleUpdate(serviceConfig);
|
||||
|
||||
interceptor.interceptCall(
|
||||
methodDescriptor, CallOptions.DEFAULT.withMaxOutboundMessageSize(10), channel);
|
||||
|
||||
verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture());
|
||||
assertThat(callOptionsCap.getValue().getMaxOutboundMessageSize()).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withMaxResponseSize() {
|
||||
JsonObj name = new JsonObj("service", "service");
|
||||
JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxResponseMessageBytes", 1d);
|
||||
JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
|
||||
|
||||
interceptor.handleUpdate(serviceConfig);
|
||||
|
||||
interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT, channel);
|
||||
|
||||
verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture());
|
||||
assertThat(callOptionsCap.getValue().getMaxInboundMessageSize()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withMaxResponseSize_pickSmallerExisting() {
|
||||
JsonObj name = new JsonObj("service", "service");
|
||||
JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxResponseMessageBytes", 5d);
|
||||
JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
|
||||
|
||||
interceptor.handleUpdate(serviceConfig);
|
||||
|
||||
interceptor.interceptCall(
|
||||
methodDescriptor, CallOptions.DEFAULT.withMaxInboundMessageSize(10), channel);
|
||||
|
||||
verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture());
|
||||
assertThat(callOptionsCap.getValue().getMaxInboundMessageSize()).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withMaxResponseSize_pickSmallerNew() {
|
||||
JsonObj name = new JsonObj("service", "service");
|
||||
JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxResponseMessageBytes", 10d);
|
||||
JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
|
||||
|
||||
interceptor.handleUpdate(serviceConfig);
|
||||
|
||||
interceptor.interceptCall(
|
||||
methodDescriptor, CallOptions.DEFAULT.withMaxInboundMessageSize(5), channel);
|
||||
|
||||
verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture());
|
||||
assertThat(callOptionsCap.getValue().getMaxInboundMessageSize()).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withoutWaitForReady() {
|
||||
JsonObj name = new JsonObj("service", "service");
|
||||
JsonObj methodConfig = new JsonObj("name", new JsonList(name), "waitForReady", false);
|
||||
JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
|
||||
|
||||
interceptor.handleUpdate(serviceConfig);
|
||||
|
||||
interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT.withWaitForReady(), channel);
|
||||
|
||||
verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture());
|
||||
assertThat(callOptionsCap.getValue().isWaitForReady()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fullMethodMatched() {
|
||||
// Put in service that matches, but has no deadline. It should be lower priority
|
||||
JsonObj name1 = new JsonObj("service", "service");
|
||||
JsonObj methodConfig1 = new JsonObj("name", new JsonList(name1));
|
||||
|
||||
JsonObj name2 = new JsonObj("service", "service", "method", "method");
|
||||
JsonObj methodConfig2 = new JsonObj("name", new JsonList(name2), "timeout", "1s");
|
||||
|
||||
JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig1, methodConfig2));
|
||||
|
||||
interceptor.handleUpdate(serviceConfig);
|
||||
|
||||
interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT, channel);
|
||||
|
||||
verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture());
|
||||
assertThat(callOptionsCap.getValue().getDeadline()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nearerDeadlineKept_existing() {
|
||||
JsonObj name = new JsonObj("service", "service");
|
||||
JsonObj methodConfig = new JsonObj("name", new JsonList(name), "timeout", "100000s");
|
||||
JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
|
||||
|
||||
interceptor.handleUpdate(serviceConfig);
|
||||
|
||||
Deadline existingDeadline = Deadline.after(1000, TimeUnit.NANOSECONDS);
|
||||
interceptor.interceptCall(
|
||||
methodDescriptor, CallOptions.DEFAULT.withDeadline(existingDeadline), channel);
|
||||
|
||||
verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture());
|
||||
assertThat(callOptionsCap.getValue().getDeadline()).isEqualTo(existingDeadline);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nearerDeadlineKept_new() {
|
||||
// TODO(carl-mastrangelo): the deadlines are very large because they change over time.
|
||||
// This should be fixed, and is tracked in https://github.com/grpc/grpc-java/issues/2531
|
||||
JsonObj name = new JsonObj("service", "service");
|
||||
JsonObj methodConfig = new JsonObj("name", new JsonList(name), "timeout", "1s");
|
||||
JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
|
||||
|
||||
interceptor.handleUpdate(serviceConfig);
|
||||
|
||||
Deadline existingDeadline = Deadline.after(1234567890, TimeUnit.NANOSECONDS);
|
||||
interceptor.interceptCall(
|
||||
methodDescriptor, CallOptions.DEFAULT.withDeadline(existingDeadline), channel);
|
||||
|
||||
verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture());
|
||||
assertThat(callOptionsCap.getValue().getDeadline()).isNotEqualTo(existingDeadline);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void handleUpdate_failsOnMissingServiceName() {
|
||||
JsonObj name = new JsonObj("method", "method");
|
||||
JsonObj methodConfig = new JsonObj("name", new JsonList(name));
|
||||
JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
|
||||
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("missing service");
|
||||
|
||||
interceptor.handleUpdate(serviceConfig);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void handleUpdate_failsOnDuplicateMethod() {
|
||||
JsonObj name1 = new JsonObj("service", "service", "method", "method");
|
||||
JsonObj name2 = new JsonObj("service", "service", "method", "method");
|
||||
JsonObj methodConfig = new JsonObj("name", new JsonList(name1, name2));
|
||||
JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
|
||||
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("Duplicate method");
|
||||
|
||||
interceptor.handleUpdate(serviceConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleUpdate_failsOnEmptyName() {
|
||||
JsonObj methodConfig = new JsonObj();
|
||||
JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
|
||||
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("no names in method config");
|
||||
|
||||
interceptor.handleUpdate(serviceConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleUpdate_failsOnDuplicateService() {
|
||||
JsonObj name1 = new JsonObj("service", "service");
|
||||
JsonObj name2 = new JsonObj("service", "service");
|
||||
JsonObj methodConfig = new JsonObj("name", new JsonList(name1, name2));
|
||||
JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
|
||||
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("Duplicate service");
|
||||
|
||||
interceptor.handleUpdate(serviceConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleUpdate_failsOnDuplicateServiceMultipleConfig() {
|
||||
JsonObj name1 = new JsonObj("service", "service");
|
||||
JsonObj name2 = new JsonObj("service", "service");
|
||||
JsonObj methodConfig1 = new JsonObj("name", new JsonList(name1));
|
||||
JsonObj methodConfig2 = new JsonObj("name", new JsonList(name2));
|
||||
JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig1, methodConfig2));
|
||||
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("Duplicate service");
|
||||
|
||||
interceptor.handleUpdate(serviceConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleUpdate_replaceExistingConfig() {
|
||||
JsonObj name1 = new JsonObj("service", "service");
|
||||
JsonObj methodConfig1 = new JsonObj("name", new JsonList(name1));
|
||||
JsonObj serviceConfig1 = new JsonObj("methodConfig", new JsonList(methodConfig1));
|
||||
|
||||
JsonObj name2 = new JsonObj("service", "service", "method", "method");
|
||||
JsonObj methodConfig2 = new JsonObj("name", new JsonList(name2));
|
||||
JsonObj serviceConfig2 = new JsonObj("methodConfig", new JsonList(methodConfig2));
|
||||
|
||||
interceptor.handleUpdate(serviceConfig1);
|
||||
|
||||
assertThat(interceptor.serviceMap.get()).isNotEmpty();
|
||||
assertThat(interceptor.serviceMethodMap.get()).isEmpty();
|
||||
|
||||
interceptor.handleUpdate(serviceConfig2);
|
||||
|
||||
assertThat(interceptor.serviceMap.get()).isEmpty();
|
||||
assertThat(interceptor.serviceMethodMap.get()).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleUpdate_matchNames() {
|
||||
JsonObj name1 = new JsonObj("service", "service2");
|
||||
JsonObj name2 = new JsonObj("service", "service", "method", "method");
|
||||
JsonObj methodConfig = new JsonObj("name", new JsonList(name1, name2));
|
||||
JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
|
||||
|
||||
interceptor.handleUpdate(serviceConfig);
|
||||
|
||||
assertThat(interceptor.serviceMethodMap.get())
|
||||
.containsExactly(
|
||||
methodDescriptor.getFullMethodName(),
|
||||
new MethodInfo(methodConfig));
|
||||
assertThat(interceptor.serviceMap.get())
|
||||
.containsExactly("service2", new MethodInfo(methodConfig));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void methodInfo_validateDeadline() {
|
||||
JsonObj name = new JsonObj("service", "service");
|
||||
JsonObj methodConfig = new JsonObj("name", new JsonList(name), "timeout", "10000000000000000s");
|
||||
|
||||
thrown.expectMessage("Duration value is out of range");
|
||||
|
||||
new MethodInfo(methodConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void methodInfo_saturateDeadline() {
|
||||
JsonObj name = new JsonObj("service", "service");
|
||||
JsonObj methodConfig = new JsonObj("name", new JsonList(name), "timeout", "315576000000s");
|
||||
|
||||
MethodInfo info = new MethodInfo(methodConfig);
|
||||
|
||||
assertThat(info.timeoutNanos).isEqualTo(Long.MAX_VALUE);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void methodInfo_badMaxRequestSize() {
|
||||
JsonObj name = new JsonObj("service", "service");
|
||||
JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxRequestMessageBytes", -1d);
|
||||
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("exceeds bounds");
|
||||
|
||||
new MethodInfo(methodConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void methodInfo_badMaxResponseSize() {
|
||||
JsonObj name = new JsonObj("service", "service");
|
||||
JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxResponseMessageBytes", -1d);
|
||||
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("exceeds bounds");
|
||||
|
||||
new MethodInfo(methodConfig);
|
||||
}
|
||||
|
||||
private static final class NoopMarshaller implements MethodDescriptor.Marshaller<Void> {
|
||||
|
||||
@Override
|
||||
public InputStream stream(Void value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void parse(InputStream stream) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,276 +0,0 @@
|
|||
/*
|
||||
* Copyright 2018, gRPC Authors All rights reserved.
|
||||
*
|
||||
* 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 org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ServiceConfigUtil}.
|
||||
*/
|
||||
@RunWith(JUnit4.class)
|
||||
public class ServiceConfigUtilTest {
|
||||
|
||||
@Rule
|
||||
public final ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private final Map<String, Object> serviceConfig = new LinkedHashMap<String, Object>();
|
||||
|
||||
@Test
|
||||
public void maybeChooseServiceConfig_failsOnMisspelling() {
|
||||
Map<String, Object> bad = new LinkedHashMap<String, Object>();
|
||||
bad.put("parcentage", 1.0);
|
||||
thrown.expectMessage("Bad key");
|
||||
|
||||
ServiceConfigUtil.maybeChooseServiceConfig(bad, new Random(), "host");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maybeChooseServiceConfig_clientLanguageMatchesJava() {
|
||||
Map<String, Object> choice = new LinkedHashMap<String, Object>();
|
||||
List<Object> langs = new ArrayList<Object>();
|
||||
langs.add("java");
|
||||
choice.put("clientLanguage", langs);
|
||||
choice.put("serviceConfig", serviceConfig);
|
||||
|
||||
assertNotNull(ServiceConfigUtil.maybeChooseServiceConfig(choice, new Random(), "host"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maybeChooseServiceConfig_clientLanguageDoesntMatchGo() {
|
||||
Map<String, Object> choice = new LinkedHashMap<String, Object>();
|
||||
List<Object> langs = new ArrayList<Object>();
|
||||
langs.add("go");
|
||||
choice.put("clientLanguage", langs);
|
||||
choice.put("serviceConfig", serviceConfig);
|
||||
|
||||
assertNull(ServiceConfigUtil.maybeChooseServiceConfig(choice, new Random(), "host"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maybeChooseServiceConfig_clientLanguageCaseInsensitive() {
|
||||
Map<String, Object> choice = new LinkedHashMap<String, Object>();
|
||||
List<Object> langs = new ArrayList<Object>();
|
||||
langs.add("JAVA");
|
||||
choice.put("clientLanguage", langs);
|
||||
choice.put("serviceConfig", serviceConfig);
|
||||
|
||||
assertNotNull(ServiceConfigUtil.maybeChooseServiceConfig(choice, new Random(), "host"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maybeChooseServiceConfig_clientLanguageMatchesEmtpy() {
|
||||
Map<String, Object> choice = new LinkedHashMap<String, Object>();
|
||||
List<Object> langs = new ArrayList<Object>();
|
||||
choice.put("clientLanguage", langs);
|
||||
choice.put("serviceConfig", serviceConfig);
|
||||
|
||||
assertNotNull(ServiceConfigUtil.maybeChooseServiceConfig(choice, new Random(), "host"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maybeChooseServiceConfig_clientLanguageMatchesMulti() {
|
||||
Map<String, Object> choice = new LinkedHashMap<String, Object>();
|
||||
List<Object> langs = new ArrayList<Object>();
|
||||
langs.add("go");
|
||||
langs.add("java");
|
||||
choice.put("clientLanguage", langs);
|
||||
choice.put("serviceConfig", serviceConfig);
|
||||
|
||||
assertNotNull(ServiceConfigUtil.maybeChooseServiceConfig(choice, new Random(), "host"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maybeChooseServiceConfig_percentageZeroAlwaysFails() {
|
||||
Map<String, Object> choice = new LinkedHashMap<String, Object>();
|
||||
choice.put("percentage", 0D);
|
||||
choice.put("serviceConfig", serviceConfig);
|
||||
|
||||
assertNull(ServiceConfigUtil.maybeChooseServiceConfig(choice, new Random(), "host"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maybeChooseServiceConfig_percentageHundredAlwaysSucceeds() {
|
||||
Map<String, Object> choice = new LinkedHashMap<String, Object>();
|
||||
choice.put("percentage", 100D);
|
||||
choice.put("serviceConfig", serviceConfig);
|
||||
|
||||
assertNotNull(ServiceConfigUtil.maybeChooseServiceConfig(choice, new Random(), "host"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maybeChooseServiceConfig_percentageAboveMatches50() {
|
||||
Map<String, Object> choice = new LinkedHashMap<String, Object>();
|
||||
choice.put("percentage", 50D);
|
||||
choice.put("serviceConfig", serviceConfig);
|
||||
|
||||
Random r = new Random() {
|
||||
@Override
|
||||
public int nextInt(int bound) {
|
||||
return 49;
|
||||
}
|
||||
};
|
||||
|
||||
assertNotNull(ServiceConfigUtil.maybeChooseServiceConfig(choice, r, "host"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maybeChooseServiceConfig_percentageAtFails50() {
|
||||
Map<String, Object> choice = new LinkedHashMap<String, Object>();
|
||||
choice.put("percentage", 50D);
|
||||
choice.put("serviceConfig", serviceConfig);
|
||||
|
||||
Random r = new Random() {
|
||||
@Override
|
||||
public int nextInt(int bound) {
|
||||
return 50;
|
||||
}
|
||||
};
|
||||
|
||||
assertNull(ServiceConfigUtil.maybeChooseServiceConfig(choice, r, "host"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maybeChooseServiceConfig_percentageAboveMatches99() {
|
||||
Map<String, Object> choice = new LinkedHashMap<String, Object>();
|
||||
choice.put("percentage", 99D);
|
||||
choice.put("serviceConfig", serviceConfig);
|
||||
|
||||
Random r = new Random() {
|
||||
@Override
|
||||
public int nextInt(int bound) {
|
||||
return 98;
|
||||
}
|
||||
};
|
||||
|
||||
assertNotNull(ServiceConfigUtil.maybeChooseServiceConfig(choice, r, "host"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maybeChooseServiceConfig_percentageAtFails99() {
|
||||
Map<String, Object> choice = new LinkedHashMap<String, Object>();
|
||||
choice.put("percentage", 99D);
|
||||
choice.put("serviceConfig", serviceConfig);
|
||||
|
||||
Random r = new Random() {
|
||||
@Override
|
||||
public int nextInt(int bound) {
|
||||
return 99;
|
||||
}
|
||||
};
|
||||
|
||||
assertNull(ServiceConfigUtil.maybeChooseServiceConfig(choice, r, "host"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maybeChooseServiceConfig_percentageAboveMatches1() {
|
||||
Map<String, Object> choice = new LinkedHashMap<String, Object>();
|
||||
choice.put("percentage", 1D);
|
||||
choice.put("serviceConfig", serviceConfig);
|
||||
|
||||
Random r = new Random() {
|
||||
@Override
|
||||
public int nextInt(int bound) {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
assertNotNull(ServiceConfigUtil.maybeChooseServiceConfig(choice, r, "host"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maybeChooseServiceConfig_percentageAtFails1() {
|
||||
Map<String, Object> choice = new LinkedHashMap<String, Object>();
|
||||
choice.put("percentage", 1D);
|
||||
choice.put("serviceConfig", serviceConfig);
|
||||
|
||||
Random r = new Random() {
|
||||
@Override
|
||||
public int nextInt(int bound) {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
assertNull(ServiceConfigUtil.maybeChooseServiceConfig(choice, r, "host"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maybeChooseServiceConfig_hostnameMatches() {
|
||||
Map<String, Object> choice = new LinkedHashMap<String, Object>();
|
||||
List<Object> hosts = new ArrayList<Object>();
|
||||
hosts.add("localhost");
|
||||
choice.put("clientHostname", hosts);
|
||||
choice.put("serviceConfig", serviceConfig);
|
||||
|
||||
assertNotNull(ServiceConfigUtil.maybeChooseServiceConfig(choice, new Random(), "localhost"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maybeChooseServiceConfig_hostnameDoesntMatch() {
|
||||
Map<String, Object> choice = new LinkedHashMap<String, Object>();
|
||||
List<Object> hosts = new ArrayList<Object>();
|
||||
hosts.add("localhorse");
|
||||
choice.put("clientHostname", hosts);
|
||||
choice.put("serviceConfig", serviceConfig);
|
||||
|
||||
assertNull(ServiceConfigUtil.maybeChooseServiceConfig(choice, new Random(), "localhost"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maybeChooseServiceConfig_clientLanguageCaseSensitive() {
|
||||
Map<String, Object> choice = new LinkedHashMap<String, Object>();
|
||||
List<Object> hosts = new ArrayList<Object>();
|
||||
hosts.add("LOCALHOST");
|
||||
choice.put("clientHostname", hosts);
|
||||
choice.put("serviceConfig", serviceConfig);
|
||||
|
||||
assertNull(ServiceConfigUtil.maybeChooseServiceConfig(choice, new Random(), "localhost"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maybeChooseServiceConfig_hostnameMatchesEmtpy() {
|
||||
Map<String, Object> choice = new LinkedHashMap<String, Object>();
|
||||
List<Object> hosts = new ArrayList<Object>();
|
||||
choice.put("clientHostname", hosts);
|
||||
choice.put("serviceConfig", serviceConfig);
|
||||
|
||||
assertNotNull(ServiceConfigUtil.maybeChooseServiceConfig(choice, new Random(), "host"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maybeChooseServiceConfig_hostnameMatchesMulti() {
|
||||
Map<String, Object> choice = new LinkedHashMap<String, Object>();
|
||||
List<Object> hosts = new ArrayList<Object>();
|
||||
hosts.add("localhorse");
|
||||
hosts.add("localhost");
|
||||
choice.put("clientHostname", hosts);
|
||||
choice.put("serviceConfig", serviceConfig);
|
||||
|
||||
assertNotNull(ServiceConfigUtil.maybeChooseServiceConfig(choice, new Random(), "localhost"));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue