• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 The gRPC Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package io.grpc.internal;
18 
19 import static com.google.common.base.Preconditions.checkNotNull;
20 import static com.google.common.base.Preconditions.checkState;
21 import static com.google.common.base.Verify.verify;
22 
23 import com.google.common.annotations.VisibleForTesting;
24 import com.google.common.base.MoreObjects;
25 import com.google.common.base.Objects;
26 import com.google.common.base.VerifyException;
27 import io.grpc.LoadBalancerProvider;
28 import io.grpc.LoadBalancerRegistry;
29 import io.grpc.NameResolver.ConfigOrError;
30 import io.grpc.Status;
31 import io.grpc.internal.RetriableStream.Throttle;
32 import java.util.ArrayList;
33 import java.util.Collections;
34 import java.util.EnumSet;
35 import java.util.List;
36 import java.util.Locale;
37 import java.util.Map;
38 import java.util.Set;
39 import java.util.logging.Level;
40 import java.util.logging.Logger;
41 import javax.annotation.Nullable;
42 
43 /**
44  * Helper utility to work with service configs.
45  *
46  * <p>This class contains helper methods to parse service config JSON values into Java types.
47  */
48 public final class ServiceConfigUtil {
49 
ServiceConfigUtil()50   private ServiceConfigUtil() {}
51 
52   /**
53    * Fetches the health-checked service config from service config. {@code null} if can't find one.
54    */
55   @Nullable
getHealthCheckedService(@ullable Map<String, ?> serviceConfig)56   public static Map<String, ?> getHealthCheckedService(@Nullable  Map<String, ?> serviceConfig) {
57     if (serviceConfig == null) {
58       return null;
59     }
60 
61     /* schema as follows
62     {
63       "healthCheckConfig": {
64         // Service name to use in the health-checking request.
65         "serviceName": string
66       }
67     }
68     */
69     return JsonUtil.getObject(serviceConfig, "healthCheckConfig");
70   }
71 
72   /**
73    * Fetches the health-checked service name from health-checked service config. {@code null} if
74    * can't find one.
75    */
76   @Nullable
getHealthCheckedServiceName( @ullable Map<String, ?> healthCheckedServiceConfig)77   public static String getHealthCheckedServiceName(
78       @Nullable Map<String, ?> healthCheckedServiceConfig) {
79     if (healthCheckedServiceConfig == null) {
80       return null;
81     }
82     return JsonUtil.getString(healthCheckedServiceConfig, "serviceName");
83   }
84 
85   @Nullable
getThrottlePolicy(@ullable Map<String, ?> serviceConfig)86   static Throttle getThrottlePolicy(@Nullable Map<String, ?> serviceConfig) {
87     if (serviceConfig == null) {
88       return null;
89     }
90 
91     /* schema as follows
92     {
93       "retryThrottling": {
94         // The number of tokens starts at maxTokens. The token_count will always be
95         // between 0 and maxTokens.
96         //
97         // This field is required and must be greater than zero.
98         "maxTokens": number,
99 
100         // The amount of tokens to add on each successful RPC. Typically this will
101         // be some number between 0 and 1, e.g., 0.1.
102         //
103         // This field is required and must be greater than zero. Up to 3 decimal
104         // places are supported.
105         "tokenRatio": number
106       }
107     }
108     */
109 
110     Map<String, ?> throttling = JsonUtil.getObject(serviceConfig, "retryThrottling");
111     if (throttling == null) {
112       return null;
113     }
114 
115     // TODO(dapengzhang0): check if this is null.
116     float maxTokens = JsonUtil.getNumberAsDouble(throttling, "maxTokens").floatValue();
117     float tokenRatio = JsonUtil.getNumberAsDouble(throttling, "tokenRatio").floatValue();
118     checkState(maxTokens > 0f, "maxToken should be greater than zero");
119     checkState(tokenRatio > 0f, "tokenRatio should be greater than zero");
120     return new Throttle(maxTokens, tokenRatio);
121   }
122 
123   @Nullable
getMaxAttemptsFromRetryPolicy(Map<String, ?> retryPolicy)124   static Integer getMaxAttemptsFromRetryPolicy(Map<String, ?> retryPolicy) {
125     return JsonUtil.getNumberAsInteger(retryPolicy, "maxAttempts");
126   }
127 
128   @Nullable
getInitialBackoffNanosFromRetryPolicy(Map<String, ?> retryPolicy)129   static Long getInitialBackoffNanosFromRetryPolicy(Map<String, ?> retryPolicy) {
130     return JsonUtil.getStringAsDuration(retryPolicy, "initialBackoff");
131   }
132 
133   @Nullable
getMaxBackoffNanosFromRetryPolicy(Map<String, ?> retryPolicy)134   static Long getMaxBackoffNanosFromRetryPolicy(Map<String, ?> retryPolicy) {
135     return JsonUtil.getStringAsDuration(retryPolicy, "maxBackoff");
136   }
137 
138   @Nullable
getBackoffMultiplierFromRetryPolicy(Map<String, ?> retryPolicy)139   static Double getBackoffMultiplierFromRetryPolicy(Map<String, ?> retryPolicy) {
140     return JsonUtil.getNumberAsDouble(retryPolicy, "backoffMultiplier");
141   }
142 
143   @Nullable
getPerAttemptRecvTimeoutNanosFromRetryPolicy(Map<String, ?> retryPolicy)144   static Long getPerAttemptRecvTimeoutNanosFromRetryPolicy(Map<String, ?> retryPolicy) {
145     return JsonUtil.getStringAsDuration(retryPolicy, "perAttemptRecvTimeout");
146   }
147 
getListOfStatusCodesAsSet(Map<String, ?> obj, String key)148   private static Set<Status.Code> getListOfStatusCodesAsSet(Map<String, ?> obj, String key) {
149     List<?> statuses = JsonUtil.getList(obj, key);
150     if (statuses == null) {
151       return null;
152     }
153     return getStatusCodesFromList(statuses);
154   }
155 
getStatusCodesFromList(List<?> statuses)156   private static Set<Status.Code> getStatusCodesFromList(List<?> statuses) {
157     EnumSet<Status.Code> codes = EnumSet.noneOf(Status.Code.class);
158     for (Object status : statuses) {
159       Status.Code code;
160       if (status instanceof Double) {
161         Double statusD = (Double) status;
162         int codeValue = statusD.intValue();
163         verify((double) codeValue == statusD, "Status code %s is not integral", status);
164         code = Status.fromCodeValue(codeValue).getCode();
165         verify(code.value() == statusD.intValue(), "Status code %s is not valid", status);
166       } else if (status instanceof String) {
167         try {
168           code = Status.Code.valueOf((String) status);
169         } catch (IllegalArgumentException iae) {
170           throw new VerifyException("Status code " + status + " is not valid", iae);
171         }
172       } else {
173         throw new VerifyException(
174             "Can not convert status code " + status + " to Status.Code, because its type is "
175                 + status.getClass());
176       }
177       codes.add(code);
178     }
179     return Collections.unmodifiableSet(codes);
180   }
181 
getRetryableStatusCodesFromRetryPolicy(Map<String, ?> retryPolicy)182   static Set<Status.Code> getRetryableStatusCodesFromRetryPolicy(Map<String, ?> retryPolicy) {
183     String retryableStatusCodesKey = "retryableStatusCodes";
184     Set<Status.Code> codes = getListOfStatusCodesAsSet(retryPolicy, retryableStatusCodesKey);
185     verify(codes != null, "%s is required in retry policy", retryableStatusCodesKey);
186     verify(!codes.contains(Status.Code.OK), "%s must not contain OK", retryableStatusCodesKey);
187     return codes;
188   }
189 
190   @Nullable
getMaxAttemptsFromHedgingPolicy(Map<String, ?> hedgingPolicy)191   static Integer getMaxAttemptsFromHedgingPolicy(Map<String, ?> hedgingPolicy) {
192     return JsonUtil.getNumberAsInteger(hedgingPolicy, "maxAttempts");
193   }
194 
195   @Nullable
getHedgingDelayNanosFromHedgingPolicy(Map<String, ?> hedgingPolicy)196   static Long getHedgingDelayNanosFromHedgingPolicy(Map<String, ?> hedgingPolicy) {
197     return JsonUtil.getStringAsDuration(hedgingPolicy, "hedgingDelay");
198   }
199 
getNonFatalStatusCodesFromHedgingPolicy(Map<String, ?> hedgingPolicy)200   static Set<Status.Code> getNonFatalStatusCodesFromHedgingPolicy(Map<String, ?> hedgingPolicy) {
201     String nonFatalStatusCodesKey = "nonFatalStatusCodes";
202     Set<Status.Code> codes = getListOfStatusCodesAsSet(hedgingPolicy, nonFatalStatusCodesKey);
203     if (codes == null) {
204       return Collections.unmodifiableSet(EnumSet.noneOf(Status.Code.class));
205     }
206     verify(!codes.contains(Status.Code.OK), "%s must not contain OK", nonFatalStatusCodesKey);
207     return codes;
208   }
209 
210   @Nullable
getServiceFromName(Map<String, ?> name)211   static String getServiceFromName(Map<String, ?> name) {
212     return JsonUtil.getString(name, "service");
213   }
214 
215   @Nullable
getMethodFromName(Map<String, ?> name)216   static String getMethodFromName(Map<String, ?> name) {
217     return JsonUtil.getString(name, "method");
218   }
219 
220   @Nullable
getRetryPolicyFromMethodConfig(Map<String, ?> methodConfig)221   static Map<String, ?> getRetryPolicyFromMethodConfig(Map<String, ?> methodConfig) {
222     return JsonUtil.getObject(methodConfig, "retryPolicy");
223   }
224 
225   @Nullable
getHedgingPolicyFromMethodConfig(Map<String, ?> methodConfig)226   static Map<String, ?> getHedgingPolicyFromMethodConfig(Map<String, ?> methodConfig) {
227     return JsonUtil.getObject(methodConfig, "hedgingPolicy");
228   }
229 
230   @Nullable
getNameListFromMethodConfig( Map<String, ?> methodConfig)231   static List<Map<String, ?>> getNameListFromMethodConfig(
232       Map<String, ?> methodConfig) {
233     return JsonUtil.getListOfObjects(methodConfig, "name");
234   }
235 
236   /**
237    * Returns the number of nanoseconds of timeout for the given method config.
238    *
239    * @return duration nanoseconds, or {@code null} if it isn't present.
240    */
241   @Nullable
getTimeoutFromMethodConfig(Map<String, ?> methodConfig)242   static Long getTimeoutFromMethodConfig(Map<String, ?> methodConfig) {
243     return JsonUtil.getStringAsDuration(methodConfig, "timeout");
244   }
245 
246   @Nullable
getWaitForReadyFromMethodConfig(Map<String, ?> methodConfig)247   static Boolean getWaitForReadyFromMethodConfig(Map<String, ?> methodConfig) {
248     return JsonUtil.getBoolean(methodConfig, "waitForReady");
249   }
250 
251   @Nullable
getMaxRequestMessageBytesFromMethodConfig(Map<String, ?> methodConfig)252   static Integer getMaxRequestMessageBytesFromMethodConfig(Map<String, ?> methodConfig) {
253     return JsonUtil.getNumberAsInteger(methodConfig, "maxRequestMessageBytes");
254   }
255 
256   @Nullable
getMaxResponseMessageBytesFromMethodConfig(Map<String, ?> methodConfig)257   static Integer getMaxResponseMessageBytesFromMethodConfig(Map<String, ?> methodConfig) {
258     return JsonUtil.getNumberAsInteger(methodConfig, "maxResponseMessageBytes");
259   }
260 
261   @Nullable
getMethodConfigFromServiceConfig( Map<String, ?> serviceConfig)262   static List<Map<String, ?>> getMethodConfigFromServiceConfig(
263       Map<String, ?> serviceConfig) {
264     return JsonUtil.getListOfObjects(serviceConfig, "methodConfig");
265   }
266 
267   /**
268    * Extracts load balancing configs from a service config.
269    */
270   @VisibleForTesting
getLoadBalancingConfigsFromServiceConfig( Map<String, ?> serviceConfig)271   public static List<Map<String, ?>> getLoadBalancingConfigsFromServiceConfig(
272       Map<String, ?> serviceConfig) {
273     /* schema as follows
274     {
275       "loadBalancingConfig": [
276         {"xds" :
277           {
278             "childPolicy": [...],
279             "fallbackPolicy": [...],
280           }
281         },
282         {"round_robin": {}}
283       ],
284       "loadBalancingPolicy": "ROUND_ROBIN"  // The deprecated policy key
285     }
286     */
287     List<Map<String, ?>> lbConfigs = new ArrayList<>();
288     String loadBalancingConfigKey = "loadBalancingConfig";
289     if (serviceConfig.containsKey(loadBalancingConfigKey)) {
290       lbConfigs.addAll(JsonUtil.getListOfObjects(
291           serviceConfig, loadBalancingConfigKey));
292     }
293     if (lbConfigs.isEmpty()) {
294       // No LoadBalancingConfig found.  Fall back to the deprecated LoadBalancingPolicy
295       String policy = JsonUtil.getString(serviceConfig, "loadBalancingPolicy");
296       if (policy != null) {
297         // Convert the policy to a config, so that the caller can handle them in the same way.
298         policy = policy.toLowerCase(Locale.ROOT);
299         Map<String, ?> fakeConfig = Collections.singletonMap(policy, Collections.emptyMap());
300         lbConfigs.add(fakeConfig);
301       }
302     }
303     return Collections.unmodifiableList(lbConfigs);
304   }
305 
306   /**
307    * Unwrap a LoadBalancingConfig JSON object into a {@link LbConfig}.  The input is a JSON object
308    * (map) with exactly one entry, where the key is the policy name and the value is a config object
309    * for that policy.
310    */
unwrapLoadBalancingConfig(Map<String, ?> lbConfig)311   public static LbConfig unwrapLoadBalancingConfig(Map<String, ?> lbConfig) {
312     if (lbConfig.size() != 1) {
313       throw new RuntimeException(
314           "There are " + lbConfig.size() + " fields in a LoadBalancingConfig object. Exactly one"
315           + " is expected. Config=" + lbConfig);
316     }
317     String key = lbConfig.entrySet().iterator().next().getKey();
318     return new LbConfig(key, JsonUtil.getObject(lbConfig, key));
319   }
320 
321   /**
322    * Given a JSON list of LoadBalancingConfigs, and convert it into a list of LbConfig.
323    */
unwrapLoadBalancingConfigList(List<Map<String, ?>> list)324   public static List<LbConfig> unwrapLoadBalancingConfigList(List<Map<String, ?>> list) {
325     if (list == null) {
326       return null;
327     }
328     ArrayList<LbConfig> result = new ArrayList<>();
329     for (Map<String, ?> rawChildPolicy : list) {
330       result.add(unwrapLoadBalancingConfig(rawChildPolicy));
331     }
332     return Collections.unmodifiableList(result);
333   }
334 
335   /**
336    * Parses and selects a load balancing policy from a non-empty list of raw configs. If selection
337    * is successful, the returned ConfigOrError object will include a {@link
338    * ServiceConfigUtil.PolicySelection} as its config value.
339    */
selectLbPolicyFromList( List<LbConfig> lbConfigs, LoadBalancerRegistry lbRegistry)340   public static ConfigOrError selectLbPolicyFromList(
341       List<LbConfig> lbConfigs, LoadBalancerRegistry lbRegistry) {
342     List<String> policiesTried = new ArrayList<>();
343     for (LbConfig lbConfig : lbConfigs) {
344       String policy = lbConfig.getPolicyName();
345       LoadBalancerProvider provider = lbRegistry.getProvider(policy);
346       if (provider == null) {
347         policiesTried.add(policy);
348       } else {
349         if (!policiesTried.isEmpty()) {
350           Logger.getLogger(ServiceConfigUtil.class.getName()).log(
351               Level.FINEST,
352               "{0} specified by Service Config are not available", policiesTried);
353         }
354         ConfigOrError parsedLbPolicyConfig =
355             provider.parseLoadBalancingPolicyConfig(lbConfig.getRawConfigValue());
356         if (parsedLbPolicyConfig.getError() != null) {
357           return parsedLbPolicyConfig;
358         }
359         return ConfigOrError.fromConfig(
360             new PolicySelection(provider, parsedLbPolicyConfig.getConfig()));
361       }
362     }
363     return ConfigOrError.fromError(
364         Status.UNKNOWN.withDescription(
365             "None of " + policiesTried + " specified by Service Config are available."));
366   }
367 
368   /**
369    * A LoadBalancingConfig that includes the policy name (the key) and its raw config value (parsed
370    * JSON).
371    */
372   public static final class LbConfig {
373     private final String policyName;
374     private final Map<String, ?> rawConfigValue;
375 
LbConfig(String policyName, Map<String, ?> rawConfigValue)376     public LbConfig(String policyName, Map<String, ?> rawConfigValue) {
377       this.policyName = checkNotNull(policyName, "policyName");
378       this.rawConfigValue = checkNotNull(rawConfigValue, "rawConfigValue");
379     }
380 
getPolicyName()381     public String getPolicyName() {
382       return policyName;
383     }
384 
getRawConfigValue()385     public Map<String, ?> getRawConfigValue() {
386       return rawConfigValue;
387     }
388 
389     @Override
equals(Object o)390     public boolean equals(Object o) {
391       if (o instanceof LbConfig) {
392         LbConfig other = (LbConfig) o;
393         return policyName.equals(other.policyName)
394             && rawConfigValue.equals(other.rawConfigValue);
395       }
396       return false;
397     }
398 
399     @Override
hashCode()400     public int hashCode() {
401       return Objects.hashCode(policyName, rawConfigValue);
402     }
403 
404     @Override
toString()405     public String toString() {
406       return MoreObjects.toStringHelper(this)
407           .add("policyName", policyName)
408           .add("rawConfigValue", rawConfigValue)
409           .toString();
410     }
411   }
412 
413   public static final class PolicySelection {
414     final LoadBalancerProvider provider;
415     @Nullable
416     final Object config;
417 
418     /** Constructs a PolicySelection with selected LB provider and the deeply parsed LB config. */
PolicySelection( LoadBalancerProvider provider, @Nullable Object config)419     public PolicySelection(
420         LoadBalancerProvider provider,
421         @Nullable Object config) {
422       this.provider = checkNotNull(provider, "provider");
423       this.config = config;
424     }
425 
getProvider()426     public LoadBalancerProvider getProvider() {
427       return provider;
428     }
429 
430     @Nullable
getConfig()431     public Object getConfig() {
432       return config;
433     }
434 
435     @Override
equals(Object o)436     public boolean equals(Object o) {
437       if (this == o) {
438         return true;
439       }
440       if (o == null || getClass() != o.getClass()) {
441         return false;
442       }
443       PolicySelection that = (PolicySelection) o;
444       return Objects.equal(provider, that.provider)
445           && Objects.equal(config, that.config);
446     }
447 
448     @Override
hashCode()449     public int hashCode() {
450       return Objects.hashCode(provider, config);
451     }
452 
453     @Override
toString()454     public String toString() {
455       return MoreObjects.toStringHelper(this)
456           .add("provider", provider)
457           .add("config", config)
458           .toString();
459     }
460   }
461 }
462