io.grpc.internal.ServiceConfigUtil Maven / Gradle / Ivy
/*
* Copyright 2018 The gRPC Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.grpc.internal;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Verify.verify;
import static com.google.common.math.LongMath.checkedAdd;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.base.VerifyException;
import io.grpc.Status;
import io.grpc.internal.RetriableStream.Throttle;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
/**
* Helper utility to work with service configs.
*
* This class contains helper methods to parse service config JSON values into Java types.
*/
public final class ServiceConfigUtil {
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 SERVICE_CONFIG_LOAD_BALANCING_CONFIG_KEY = "loadBalancingConfig";
// TODO(chengyuanzhang): delete this key after shifting to use bootstrap.
private static final String XDS_CONFIG_BALANCER_NAME_KEY = "balancerName";
private static final String XDS_CONFIG_CHILD_POLICY_KEY = "childPolicy";
private static final String XDS_CONFIG_FALLBACK_POLICY_KEY = "fallbackPolicy";
private static final String XDS_CONFIG_EDS_SERVICE_NAME = "edsServiceName";
private static final String XDS_CONFIG_LRS_SERVER_NAME = "lrsLoadReportingServerName";
private static final String SERVICE_CONFIG_STICKINESS_METADATA_KEY = "stickinessMetadataKey";
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 METHOD_CONFIG_HEDGING_POLICY_KEY = "hedgingPolicy";
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 String HEDGING_POLICY_MAX_ATTEMPTS_KEY = "maxAttempts";
private static final String HEDGING_POLICY_HEDGING_DELAY_KEY = "hedgingDelay";
private static final String HEDGING_POLICY_NON_FATAL_STATUS_CODES_KEY = "nonFatalStatusCodes";
private static final long DURATION_SECONDS_MIN = -315576000000L;
private static final long DURATION_SECONDS_MAX = 315576000000L;
private ServiceConfigUtil() {}
/**
* Fetch the health-checked service name from service config. {@code null} if can't find one.
*/
@Nullable
public static String getHealthCheckedServiceName(@Nullable Map serviceConfig) {
String healthCheckKey = "healthCheckConfig";
String serviceNameKey = "serviceName";
if (serviceConfig == null || !serviceConfig.containsKey(healthCheckKey)) {
return null;
}
/* schema as follows
{
"healthCheckConfig": {
// Service name to use in the health-checking request.
"serviceName": string
}
}
*/
Map healthCheck = JsonUtil.getObject(serviceConfig, healthCheckKey);
if (!healthCheck.containsKey(serviceNameKey)) {
return null;
}
return JsonUtil.getString(healthCheck, "serviceName");
}
@Nullable
static Throttle getThrottlePolicy(@Nullable Map serviceConfig) {
String retryThrottlingKey = "retryThrottling";
if (serviceConfig == null || !serviceConfig.containsKey(retryThrottlingKey)) {
return null;
}
/* schema as follows
{
"retryThrottling": {
// The number of tokens starts at maxTokens. The token_count will always be
// between 0 and maxTokens.
//
// This field is required and must be greater than zero.
"maxTokens": number,
// The amount of tokens to add on each successful RPC. Typically this will
// be some number between 0 and 1, e.g., 0.1.
//
// This field is required and must be greater than zero. Up to 3 decimal
// places are supported.
"tokenRatio": number
}
}
*/
Map throttling = JsonUtil.getObject(serviceConfig, retryThrottlingKey);
// TODO(dapengzhang0): check if this is null.
float maxTokens = JsonUtil.getDouble(throttling, "maxTokens").floatValue();
float tokenRatio = JsonUtil.getDouble(throttling, "tokenRatio").floatValue();
checkState(maxTokens > 0f, "maxToken should be greater than zero");
checkState(tokenRatio > 0f, "tokenRatio should be greater than zero");
return new Throttle(maxTokens, tokenRatio);
}
@Nullable
static Integer getMaxAttemptsFromRetryPolicy(Map retryPolicy) {
if (!retryPolicy.containsKey(RETRY_POLICY_MAX_ATTEMPTS_KEY)) {
return null;
}
return JsonUtil.getDouble(retryPolicy, RETRY_POLICY_MAX_ATTEMPTS_KEY).intValue();
}
@Nullable
static Long getInitialBackoffNanosFromRetryPolicy(Map retryPolicy) {
if (!retryPolicy.containsKey(RETRY_POLICY_INITIAL_BACKOFF_KEY)) {
return null;
}
String rawInitialBackoff = JsonUtil.getString(retryPolicy, RETRY_POLICY_INITIAL_BACKOFF_KEY);
try {
return parseDuration(rawInitialBackoff);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
@Nullable
static Long getMaxBackoffNanosFromRetryPolicy(Map retryPolicy) {
if (!retryPolicy.containsKey(RETRY_POLICY_MAX_BACKOFF_KEY)) {
return null;
}
String rawMaxBackoff = JsonUtil.getString(retryPolicy, RETRY_POLICY_MAX_BACKOFF_KEY);
try {
return parseDuration(rawMaxBackoff);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
@Nullable
static Double getBackoffMultiplierFromRetryPolicy(Map retryPolicy) {
if (!retryPolicy.containsKey(RETRY_POLICY_BACKOFF_MULTIPLIER_KEY)) {
return null;
}
return JsonUtil.getDouble(retryPolicy, RETRY_POLICY_BACKOFF_MULTIPLIER_KEY);
}
private static Set getStatusCodesFromList(List> statuses) {
EnumSet codes = EnumSet.noneOf(Status.Code.class);
for (Object status : statuses) {
Status.Code code;
if (status instanceof Double) {
Double statusD = (Double) status;
int codeValue = statusD.intValue();
verify((double) codeValue == statusD, "Status code %s is not integral", status);
code = Status.fromCodeValue(codeValue).getCode();
verify(code.value() == statusD.intValue(), "Status code %s is not valid", status);
} else if (status instanceof String) {
try {
code = Status.Code.valueOf((String) status);
} catch (IllegalArgumentException iae) {
throw new VerifyException("Status code " + status + " is not valid", iae);
}
} else {
throw new VerifyException(
"Can not convert status code " + status + " to Status.Code, because its type is "
+ status.getClass());
}
codes.add(code);
}
return Collections.unmodifiableSet(codes);
}
static Set getRetryableStatusCodesFromRetryPolicy(Map retryPolicy) {
verify(
retryPolicy.containsKey(RETRY_POLICY_RETRYABLE_STATUS_CODES_KEY),
"%s is required in retry policy", RETRY_POLICY_RETRYABLE_STATUS_CODES_KEY);
Set codes =
getStatusCodesFromList(
JsonUtil.getList(retryPolicy, RETRY_POLICY_RETRYABLE_STATUS_CODES_KEY));
verify(!codes.isEmpty(), "%s must not be empty", RETRY_POLICY_RETRYABLE_STATUS_CODES_KEY);
verify(
!codes.contains(Status.Code.OK),
"%s must not contain OK", RETRY_POLICY_RETRYABLE_STATUS_CODES_KEY);
return codes;
}
@Nullable
static Integer getMaxAttemptsFromHedgingPolicy(Map hedgingPolicy) {
if (!hedgingPolicy.containsKey(HEDGING_POLICY_MAX_ATTEMPTS_KEY)) {
return null;
}
return JsonUtil.getDouble(hedgingPolicy, HEDGING_POLICY_MAX_ATTEMPTS_KEY).intValue();
}
@Nullable
static Long getHedgingDelayNanosFromHedgingPolicy(Map hedgingPolicy) {
if (!hedgingPolicy.containsKey(HEDGING_POLICY_HEDGING_DELAY_KEY)) {
return null;
}
String rawHedgingDelay = JsonUtil.getString(hedgingPolicy, HEDGING_POLICY_HEDGING_DELAY_KEY);
try {
return parseDuration(rawHedgingDelay);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
static Set getNonFatalStatusCodesFromHedgingPolicy(Map hedgingPolicy) {
if (!hedgingPolicy.containsKey(HEDGING_POLICY_NON_FATAL_STATUS_CODES_KEY)) {
return Collections.unmodifiableSet(EnumSet.noneOf(Status.Code.class));
}
Set codes =
getStatusCodesFromList(
JsonUtil.getList(hedgingPolicy, HEDGING_POLICY_NON_FATAL_STATUS_CODES_KEY));
verify(
!codes.contains(Status.Code.OK),
"%s must not contain OK", HEDGING_POLICY_NON_FATAL_STATUS_CODES_KEY);
return codes;
}
@Nullable
static String getServiceFromName(Map name) {
if (!name.containsKey(NAME_SERVICE_KEY)) {
return null;
}
return JsonUtil.getString(name, NAME_SERVICE_KEY);
}
@Nullable
static String getMethodFromName(Map name) {
if (!name.containsKey(NAME_METHOD_KEY)) {
return null;
}
return JsonUtil.getString(name, NAME_METHOD_KEY);
}
@Nullable
static Map getRetryPolicyFromMethodConfig(Map methodConfig) {
if (!methodConfig.containsKey(METHOD_CONFIG_RETRY_POLICY_KEY)) {
return null;
}
return JsonUtil.getObject(methodConfig, METHOD_CONFIG_RETRY_POLICY_KEY);
}
@Nullable
static Map getHedgingPolicyFromMethodConfig(Map methodConfig) {
if (!methodConfig.containsKey(METHOD_CONFIG_HEDGING_POLICY_KEY)) {
return null;
}
return JsonUtil.getObject(methodConfig, METHOD_CONFIG_HEDGING_POLICY_KEY);
}
@Nullable
static List