| /* |
| * 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.math.LongMath.checkedAdd; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.MoreObjects; |
| import com.google.common.base.Objects; |
| import io.grpc.internal.RetriableStream.Throttle; |
| import java.text.ParseException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.concurrent.TimeUnit; |
| import javax.annotation.Nullable; |
| |
| /** |
| * Helper utility to work with service configs. |
| */ |
| 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"; |
| 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 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<String, Object> 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<String, Object> healthCheck = getObject(serviceConfig, healthCheckKey); |
| if (!healthCheck.containsKey(serviceNameKey)) { |
| return null; |
| } |
| return getString(healthCheck, "serviceName"); |
| } |
| |
| @Nullable |
| static Throttle getThrottlePolicy(@Nullable Map<String, Object> 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<String, Object> throttling = getObject(serviceConfig, retryThrottlingKey); |
| |
| float maxTokens = getDouble(throttling, "maxTokens").floatValue(); |
| float tokenRatio = 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<String, Object> retryPolicy) { |
| if (!retryPolicy.containsKey(RETRY_POLICY_MAX_ATTEMPTS_KEY)) { |
| return null; |
| } |
| return getDouble(retryPolicy, RETRY_POLICY_MAX_ATTEMPTS_KEY).intValue(); |
| } |
| |
| @Nullable |
| static Long getInitialBackoffNanosFromRetryPolicy(Map<String, Object> retryPolicy) { |
| if (!retryPolicy.containsKey(RETRY_POLICY_INITIAL_BACKOFF_KEY)) { |
| return null; |
| } |
| 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 Integer getMaxAttemptsFromHedgingPolicy(Map<String, Object> hedgingPolicy) { |
| if (!hedgingPolicy.containsKey(HEDGING_POLICY_MAX_ATTEMPTS_KEY)) { |
| return null; |
| } |
| return getDouble(hedgingPolicy, HEDGING_POLICY_MAX_ATTEMPTS_KEY).intValue(); |
| } |
| |
| @Nullable |
| static Long getHedgingDelayNanosFromHedgingPolicy(Map<String, Object> hedgingPolicy) { |
| if (!hedgingPolicy.containsKey(HEDGING_POLICY_HEDGING_DELAY_KEY)) { |
| return null; |
| } |
| String rawHedgingDelay = getString(hedgingPolicy, HEDGING_POLICY_HEDGING_DELAY_KEY); |
| try { |
| return parseDuration(rawHedgingDelay); |
| } catch (ParseException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| @Nullable |
| static List<String> getNonFatalStatusCodesFromHedgingPolicy(Map<String, Object> hedgingPolicy) { |
| if (!hedgingPolicy.containsKey(HEDGING_POLICY_NON_FATAL_STATUS_CODES_KEY)) { |
| return null; |
| } |
| return checkStringList(getList(hedgingPolicy, HEDGING_POLICY_NON_FATAL_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 Map<String, Object> getHedgingPolicyFromMethodConfig(Map<String, Object> methodConfig) { |
| if (!methodConfig.containsKey(METHOD_CONFIG_HEDGING_POLICY_KEY)) { |
| return null; |
| } |
| return getObject(methodConfig, METHOD_CONFIG_HEDGING_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)); |
| } |
| |
| /** |
| * Extracts load balancing configs from a service config. |
| */ |
| @SuppressWarnings("unchecked") |
| @VisibleForTesting |
| public static List<Map<String, Object>> getLoadBalancingConfigsFromServiceConfig( |
| Map<String, Object> serviceConfig) { |
| /* schema as follows |
| { |
| "loadBalancingConfig": { |
| [ |
| {"xds" : |
| { |
| "balancerName": "balancer1", |
| "childPolicy": [...], |
| "fallbackPolicy": [...], |
| } |
| }, |
| {"round_robin": {}} |
| ] |
| }, |
| "loadBalancingPolicy": "ROUND_ROBIN" // The deprecated policy key |
| } |
| */ |
| List<Map<String, Object>> lbConfigs = new ArrayList<>(); |
| if (serviceConfig.containsKey(SERVICE_CONFIG_LOAD_BALANCING_CONFIG_KEY)) { |
| List<Object> configs = getList(serviceConfig, SERVICE_CONFIG_LOAD_BALANCING_CONFIG_KEY); |
| for (Object config : configs) { |
| lbConfigs.add((Map<String, Object>) config); |
| } |
| } |
| if (lbConfigs.isEmpty()) { |
| // No LoadBalancingConfig found. Fall back to the deprecated LoadBalancingPolicy |
| if (serviceConfig.containsKey(SERVICE_CONFIG_LOAD_BALANCING_POLICY_KEY)) { |
| String policy = getString(serviceConfig, SERVICE_CONFIG_LOAD_BALANCING_POLICY_KEY); |
| // Convert the policy to a config, so that the caller can handle them in the same way. |
| policy = policy.toLowerCase(Locale.ROOT); |
| Map<String, Object> fakeConfig = |
| Collections.singletonMap(policy, (Object) Collections.emptyMap()); |
| lbConfigs.add(fakeConfig); |
| } |
| } |
| return Collections.unmodifiableList(lbConfigs); |
| } |
| |
| /** |
| * Unwrap a LoadBalancingConfig JSON object into a {@link LbConfig}. The input is a JSON object |
| * (map) with exactly one entry, where the key is the policy name and the value is a config object |
| * for that policy. |
| */ |
| @SuppressWarnings("unchecked") |
| public static LbConfig unwrapLoadBalancingConfig(Object lbConfig) { |
| Map<String, Object> map; |
| try { |
| map = (Map<String, Object>) lbConfig; |
| } catch (ClassCastException e) { |
| ClassCastException ex = new ClassCastException("Invalid type. Config=" + lbConfig); |
| ex.initCause(e); |
| throw ex; |
| } |
| if (map.size() != 1) { |
| throw new RuntimeException( |
| "There are " + map.size() + " fields in a LoadBalancingConfig object. Exactly one" |
| + " is expected. Config=" + lbConfig); |
| } |
| Map.Entry<String, Object> entry = map.entrySet().iterator().next(); |
| Map<String, Object> configValue; |
| try { |
| configValue = (Map<String, Object>) entry.getValue(); |
| } catch (ClassCastException e) { |
| ClassCastException ex = |
| new ClassCastException("Invalid value type. value=" + entry.getValue()); |
| ex.initCause(e); |
| throw ex; |
| } |
| return new LbConfig(entry.getKey(), configValue); |
| } |
| |
| /** |
| * Given a JSON list of LoadBalancingConfigs, and convert it into a list of LbConfig. |
| */ |
| @SuppressWarnings("unchecked") |
| public static List<LbConfig> unwrapLoadBalancingConfigList(Object listObject) { |
| List<?> list; |
| try { |
| list = (List<?>) listObject; |
| } catch (ClassCastException e) { |
| ClassCastException ex = new ClassCastException("List expected, but is " + listObject); |
| ex.initCause(e); |
| throw ex; |
| } |
| ArrayList<LbConfig> result = new ArrayList<>(); |
| for (Object rawChildPolicy : list) { |
| result.add(unwrapLoadBalancingConfig(rawChildPolicy)); |
| } |
| return Collections.unmodifiableList(result); |
| } |
| |
| /** |
| * Extracts the loadbalancer name from xds loadbalancer config. |
| */ |
| public static String getBalancerNameFromXdsConfig(LbConfig xdsConfig) { |
| Map<String, Object> map = xdsConfig.getRawConfigValue(); |
| return getString(map, XDS_CONFIG_BALANCER_NAME_KEY); |
| } |
| |
| /** |
| * Extracts list of child policies from xds loadbalancer config. |
| */ |
| @Nullable |
| public static List<LbConfig> getChildPolicyFromXdsConfig(LbConfig xdsConfig) { |
| Map<String, Object> map = xdsConfig.getRawConfigValue(); |
| Object rawChildPolicies = map.get(XDS_CONFIG_CHILD_POLICY_KEY); |
| if (rawChildPolicies != null) { |
| return unwrapLoadBalancingConfigList(rawChildPolicies); |
| } |
| return null; |
| } |
| |
| /** |
| * Extracts list of fallback policies from xds loadbalancer config. |
| */ |
| @Nullable |
| public static List<LbConfig> getFallbackPolicyFromXdsConfig(LbConfig xdsConfig) { |
| Map<String, Object> map = xdsConfig.getRawConfigValue(); |
| Object rawFallbackPolicies = map.get(XDS_CONFIG_FALLBACK_POLICY_KEY); |
| if (rawFallbackPolicies != null) { |
| return unwrapLoadBalancingConfigList(rawFallbackPolicies); |
| } |
| return null; |
| } |
| |
| /** |
| * Extracts the stickiness metadata key from a service config, or {@code null}. |
| */ |
| @Nullable |
| public static String getStickinessMetadataKeyFromServiceConfig( |
| Map<String, Object> serviceConfig) { |
| if (!serviceConfig.containsKey(SERVICE_CONFIG_STICKINESS_METADATA_KEY)) { |
| return null; |
| } |
| return getString(serviceConfig, SERVICE_CONFIG_STICKINESS_METADATA_KEY); |
| } |
| |
| /** |
| * Gets a list from an object for the given key. |
| */ |
| @SuppressWarnings("unchecked") |
| 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) { |
| return (List<Object>) value; |
| } |
| throw new ClassCastException( |
| String.format("value %s for key %s in %s is not List", value, key, obj)); |
| } |
| |
| /** |
| * Gets an object from an object for the given key. |
| */ |
| @SuppressWarnings("unchecked") |
| 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) { |
| return (Map<String, Object>) value; |
| } |
| throw new ClassCastException( |
| String.format("value %s for key %s in %s is not object", value, key, obj)); |
| } |
| |
| /** |
| * Gets a double from an object for the given key. |
| */ |
| @SuppressWarnings("unchecked") |
| 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) { |
| return (Double) value; |
| } |
| throw new ClassCastException( |
| String.format("value %s for key %s in %s is not Double", value, key, obj)); |
| } |
| |
| /** |
| * Gets a string from an object for the given key. |
| */ |
| @SuppressWarnings("unchecked") |
| 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) { |
| return (String) value; |
| } |
| throw new ClassCastException( |
| String.format("value %s for key %s in %s is not String", value, key, obj)); |
| } |
| |
| /** |
| * Gets a string from an object for the given index. |
| */ |
| @SuppressWarnings("unchecked") |
| 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) { |
| return (String) value; |
| } |
| 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); |
| } |
| |
| /** |
| * A LoadBalancingConfig that includes the policy name (the key) and its raw config value (parsed |
| * JSON). |
| */ |
| public static final class LbConfig { |
| private final String policyName; |
| private final Map<String, Object> rawConfigValue; |
| |
| public LbConfig(String policyName, Map<String, Object> rawConfigValue) { |
| this.policyName = checkNotNull(policyName, "policyName"); |
| this.rawConfigValue = checkNotNull(rawConfigValue, "rawConfigValue"); |
| } |
| |
| public String getPolicyName() { |
| return policyName; |
| } |
| |
| public Map<String, Object> getRawConfigValue() { |
| return rawConfigValue; |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (o instanceof LbConfig) { |
| LbConfig other = (LbConfig) o; |
| return policyName.equals(other.policyName) |
| && rawConfigValue.equals(other.rawConfigValue); |
| } |
| return false; |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hashCode(policyName, rawConfigValue); |
| } |
| |
| @Override |
| public String toString() { |
| return MoreObjects.toStringHelper(this) |
| .add("policyName", policyName) |
| .add("rawConfigValue", rawConfigValue) |
| .toString(); |
| } |
| } |
| } |