• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2019 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.checkArgument;
20 import static com.google.common.base.Preconditions.checkNotNull;
21 
22 import com.google.common.annotations.VisibleForTesting;
23 import com.google.common.base.MoreObjects;
24 import com.google.common.base.Objects;
25 import com.google.common.base.Strings;
26 import io.grpc.CallOptions;
27 import io.grpc.InternalConfigSelector;
28 import io.grpc.LoadBalancer.PickSubchannelArgs;
29 import io.grpc.MethodDescriptor;
30 import io.grpc.Status.Code;
31 import io.grpc.internal.RetriableStream.Throttle;
32 import java.util.Collections;
33 import java.util.HashMap;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Set;
37 import javax.annotation.Nullable;
38 
39 /**
40  * {@link ManagedChannelServiceConfig} is a fully parsed and validated representation of service
41  * configuration data.
42  */
43 final class ManagedChannelServiceConfig {
44 
45   @Nullable
46   private final MethodInfo defaultMethodConfig;
47   private final Map<String, MethodInfo> serviceMethodMap;
48   private final Map<String, MethodInfo> serviceMap;
49   @Nullable
50   private final Throttle retryThrottling;
51   @Nullable
52   private final Object loadBalancingConfig;
53   @Nullable
54   private final Map<String, ?> healthCheckingConfig;
55 
ManagedChannelServiceConfig( @ullable MethodInfo defaultMethodConfig, Map<String, MethodInfo> serviceMethodMap, Map<String, MethodInfo> serviceMap, @Nullable Throttle retryThrottling, @Nullable Object loadBalancingConfig, @Nullable Map<String, ?> healthCheckingConfig)56   ManagedChannelServiceConfig(
57       @Nullable MethodInfo defaultMethodConfig,
58       Map<String, MethodInfo> serviceMethodMap,
59       Map<String, MethodInfo> serviceMap,
60       @Nullable Throttle retryThrottling,
61       @Nullable Object loadBalancingConfig,
62       @Nullable Map<String, ?> healthCheckingConfig) {
63     this.defaultMethodConfig = defaultMethodConfig;
64     this.serviceMethodMap = Collections.unmodifiableMap(new HashMap<>(serviceMethodMap));
65     this.serviceMap = Collections.unmodifiableMap(new HashMap<>(serviceMap));
66     this.retryThrottling = retryThrottling;
67     this.loadBalancingConfig = loadBalancingConfig;
68     this.healthCheckingConfig =
69         healthCheckingConfig != null
70             ? Collections.unmodifiableMap(new HashMap<>(healthCheckingConfig))
71             : null;
72   }
73 
74   /** Returns an empty {@link ManagedChannelServiceConfig}. */
empty()75   static ManagedChannelServiceConfig empty() {
76     return
77         new ManagedChannelServiceConfig(
78             null,
79             new HashMap<String, MethodInfo>(),
80             new HashMap<String, MethodInfo>(),
81             /* retryThrottling= */ null,
82             /* loadBalancingConfig= */ null,
83             /* healthCheckingConfig= */ null);
84   }
85 
86   /**
87    * Parses the Channel level config values (e.g. excludes load balancing)
88    */
fromServiceConfig( Map<String, ?> serviceConfig, boolean retryEnabled, int maxRetryAttemptsLimit, int maxHedgedAttemptsLimit, @Nullable Object loadBalancingConfig)89   static ManagedChannelServiceConfig fromServiceConfig(
90       Map<String, ?> serviceConfig,
91       boolean retryEnabled,
92       int maxRetryAttemptsLimit,
93       int maxHedgedAttemptsLimit,
94       @Nullable Object loadBalancingConfig) {
95     Throttle retryThrottling = null;
96     if (retryEnabled) {
97       retryThrottling = ServiceConfigUtil.getThrottlePolicy(serviceConfig);
98     }
99     Map<String, MethodInfo> serviceMethodMap = new HashMap<>();
100     Map<String, MethodInfo> serviceMap = new HashMap<>();
101     Map<String, ?> healthCheckingConfig =
102         ServiceConfigUtil.getHealthCheckedService(serviceConfig);
103 
104     // Try and do as much validation here before we swap out the existing configuration.  In case
105     // the input is invalid, we don't want to lose the existing configuration.
106     List<Map<String, ?>> methodConfigs =
107         ServiceConfigUtil.getMethodConfigFromServiceConfig(serviceConfig);
108 
109     if (methodConfigs == null) {
110       // this is surprising, but possible.
111       return
112           new ManagedChannelServiceConfig(
113               null,
114               serviceMethodMap,
115               serviceMap,
116               retryThrottling,
117               loadBalancingConfig,
118               healthCheckingConfig);
119     }
120 
121     MethodInfo defaultMethodConfig = null;
122     for (Map<String, ?> methodConfig : methodConfigs) {
123       MethodInfo info = new MethodInfo(
124           methodConfig, retryEnabled, maxRetryAttemptsLimit, maxHedgedAttemptsLimit);
125 
126       List<Map<String, ?>> nameList =
127           ServiceConfigUtil.getNameListFromMethodConfig(methodConfig);
128 
129       if (nameList == null || nameList.isEmpty()) {
130         continue;
131       }
132       for (Map<String, ?> name : nameList) {
133         String serviceName = ServiceConfigUtil.getServiceFromName(name);
134         String methodName = ServiceConfigUtil.getMethodFromName(name);
135         if (Strings.isNullOrEmpty(serviceName)) {
136           checkArgument(
137               Strings.isNullOrEmpty(methodName), "missing service name for method %s", methodName);
138           checkArgument(
139               defaultMethodConfig == null,
140               "Duplicate default method config in service config %s",
141               serviceConfig);
142           defaultMethodConfig = info;
143         } else if (Strings.isNullOrEmpty(methodName)) {
144           // Service scoped config
145           checkArgument(
146               !serviceMap.containsKey(serviceName), "Duplicate service %s", serviceName);
147           serviceMap.put(serviceName, info);
148         } else {
149           // Method scoped config
150           String fullMethodName = MethodDescriptor.generateFullMethodName(serviceName, methodName);
151           checkArgument(
152               !serviceMethodMap.containsKey(fullMethodName),
153               "Duplicate method name %s",
154               fullMethodName);
155           serviceMethodMap.put(fullMethodName, info);
156         }
157       }
158     }
159 
160     return
161         new ManagedChannelServiceConfig(
162             defaultMethodConfig,
163             serviceMethodMap,
164             serviceMap,
165             retryThrottling,
166             loadBalancingConfig,
167             healthCheckingConfig);
168   }
169 
170   @Nullable
getHealthCheckingConfig()171   Map<String, ?> getHealthCheckingConfig() {
172     return healthCheckingConfig;
173   }
174 
175   /**
176    * Used as a fallback per-RPC config supplier when the attributes value of {@link
177    * InternalConfigSelector#KEY} is not available. Returns {@code null} if there is no method
178    * config in this service config.
179    */
180   @Nullable
getDefaultConfigSelector()181   InternalConfigSelector getDefaultConfigSelector() {
182     if (serviceMap.isEmpty() && serviceMethodMap.isEmpty() && defaultMethodConfig == null) {
183       return null;
184     }
185     return new ServiceConfigConvertedSelector(this);
186   }
187 
188   @VisibleForTesting
189   @Nullable
getLoadBalancingConfig()190   Object getLoadBalancingConfig() {
191     return loadBalancingConfig;
192   }
193 
194   @Nullable
getRetryThrottling()195   Throttle getRetryThrottling() {
196     return retryThrottling;
197   }
198 
199   @Nullable
getMethodConfig(MethodDescriptor<?, ?> method)200   MethodInfo getMethodConfig(MethodDescriptor<?, ?> method) {
201     MethodInfo methodInfo = serviceMethodMap.get(method.getFullMethodName());
202     if (methodInfo == null) {
203       String serviceName = method.getServiceName();
204       methodInfo = serviceMap.get(serviceName);
205     }
206     if (methodInfo == null) {
207       methodInfo = defaultMethodConfig;
208     }
209     return methodInfo;
210   }
211 
212   @Override
equals(Object o)213   public boolean equals(Object o) {
214     if (this == o) {
215       return true;
216     }
217     if (o == null || getClass() != o.getClass()) {
218       return false;
219     }
220     ManagedChannelServiceConfig that = (ManagedChannelServiceConfig) o;
221     return Objects.equal(defaultMethodConfig, that.defaultMethodConfig)
222         && Objects.equal(serviceMethodMap, that.serviceMethodMap)
223         && Objects.equal(serviceMap, that.serviceMap)
224         && Objects.equal(retryThrottling, that.retryThrottling)
225         && Objects.equal(loadBalancingConfig, that.loadBalancingConfig);
226   }
227 
228   @Override
hashCode()229   public int hashCode() {
230     return Objects.hashCode(
231         defaultMethodConfig, serviceMethodMap, serviceMap, retryThrottling, loadBalancingConfig);
232   }
233 
234   @Override
toString()235   public String toString() {
236     return MoreObjects.toStringHelper(this)
237         .add("defaultMethodConfig", defaultMethodConfig)
238         .add("serviceMethodMap", serviceMethodMap)
239         .add("serviceMap", serviceMap)
240         .add("retryThrottling", retryThrottling)
241         .add("loadBalancingConfig", loadBalancingConfig)
242         .toString();
243   }
244 
245   /**
246    * Equivalent of MethodConfig from a ServiceConfig with restrictions from Channel setting.
247    */
248   static final class MethodInfo {
249     static final CallOptions.Key<MethodInfo> KEY =
250         CallOptions.Key.create("io.grpc.internal.ManagedChannelServiceConfig.MethodInfo");
251 
252     // TODO(carl-mastrangelo): add getters for these fields and make them private.
253     final Long timeoutNanos;
254     final Boolean waitForReady;
255     final Integer maxInboundMessageSize;
256     final Integer maxOutboundMessageSize;
257     final RetryPolicy retryPolicy;
258     final HedgingPolicy hedgingPolicy;
259 
260     /**
261      * Constructor.
262      *
263      * @param retryEnabled when false, the argument maxRetryAttemptsLimit will have no effect.
264      */
MethodInfo( Map<String, ?> methodConfig, boolean retryEnabled, int maxRetryAttemptsLimit, int maxHedgedAttemptsLimit)265     MethodInfo(
266         Map<String, ?> methodConfig, boolean retryEnabled, int maxRetryAttemptsLimit,
267         int maxHedgedAttemptsLimit) {
268       timeoutNanos = ServiceConfigUtil.getTimeoutFromMethodConfig(methodConfig);
269       waitForReady = ServiceConfigUtil.getWaitForReadyFromMethodConfig(methodConfig);
270       maxInboundMessageSize =
271           ServiceConfigUtil.getMaxResponseMessageBytesFromMethodConfig(methodConfig);
272       if (maxInboundMessageSize != null) {
273         checkArgument(
274             maxInboundMessageSize >= 0,
275             "maxInboundMessageSize %s exceeds bounds", maxInboundMessageSize);
276       }
277       maxOutboundMessageSize =
278           ServiceConfigUtil.getMaxRequestMessageBytesFromMethodConfig(methodConfig);
279       if (maxOutboundMessageSize != null) {
280         checkArgument(
281             maxOutboundMessageSize >= 0,
282             "maxOutboundMessageSize %s exceeds bounds", maxOutboundMessageSize);
283       }
284 
285       Map<String, ?> retryPolicyMap =
286           retryEnabled ? ServiceConfigUtil.getRetryPolicyFromMethodConfig(methodConfig) : null;
287       retryPolicy = retryPolicyMap == null
288           ? null : retryPolicy(retryPolicyMap, maxRetryAttemptsLimit);
289 
290       Map<String, ?> hedgingPolicyMap =
291           retryEnabled ? ServiceConfigUtil.getHedgingPolicyFromMethodConfig(methodConfig) : null;
292       hedgingPolicy = hedgingPolicyMap == null
293           ? null : hedgingPolicy(hedgingPolicyMap, maxHedgedAttemptsLimit);
294     }
295 
296     @Override
hashCode()297     public int hashCode() {
298       return Objects.hashCode(
299           timeoutNanos,
300           waitForReady,
301           maxInboundMessageSize,
302           maxOutboundMessageSize,
303           retryPolicy,
304           hedgingPolicy);
305     }
306 
307     @Override
equals(Object other)308     public boolean equals(Object other) {
309       if (!(other instanceof MethodInfo)) {
310         return false;
311       }
312       MethodInfo that = (MethodInfo) other;
313       return Objects.equal(this.timeoutNanos, that.timeoutNanos)
314           && Objects.equal(this.waitForReady, that.waitForReady)
315           && Objects.equal(this.maxInboundMessageSize, that.maxInboundMessageSize)
316           && Objects.equal(this.maxOutboundMessageSize, that.maxOutboundMessageSize)
317           && Objects.equal(this.retryPolicy, that.retryPolicy)
318           && Objects.equal(this.hedgingPolicy, that.hedgingPolicy);
319     }
320 
321     @Override
toString()322     public String toString() {
323       return MoreObjects.toStringHelper(this)
324           .add("timeoutNanos", timeoutNanos)
325           .add("waitForReady", waitForReady)
326           .add("maxInboundMessageSize", maxInboundMessageSize)
327           .add("maxOutboundMessageSize", maxOutboundMessageSize)
328           .add("retryPolicy", retryPolicy)
329           .add("hedgingPolicy", hedgingPolicy)
330           .toString();
331     }
332 
retryPolicy(Map<String, ?> retryPolicy, int maxAttemptsLimit)333     private static RetryPolicy retryPolicy(Map<String, ?> retryPolicy, int maxAttemptsLimit) {
334       int maxAttempts = checkNotNull(
335           ServiceConfigUtil.getMaxAttemptsFromRetryPolicy(retryPolicy),
336           "maxAttempts cannot be empty");
337       checkArgument(maxAttempts >= 2, "maxAttempts must be greater than 1: %s", maxAttempts);
338       maxAttempts = Math.min(maxAttempts, maxAttemptsLimit);
339 
340       long initialBackoffNanos = checkNotNull(
341           ServiceConfigUtil.getInitialBackoffNanosFromRetryPolicy(retryPolicy),
342           "initialBackoff cannot be empty");
343       checkArgument(
344           initialBackoffNanos > 0,
345           "initialBackoffNanos must be greater than 0: %s",
346           initialBackoffNanos);
347 
348       long maxBackoffNanos = checkNotNull(
349           ServiceConfigUtil.getMaxBackoffNanosFromRetryPolicy(retryPolicy),
350           "maxBackoff cannot be empty");
351       checkArgument(
352           maxBackoffNanos > 0, "maxBackoff must be greater than 0: %s", maxBackoffNanos);
353 
354       double backoffMultiplier = checkNotNull(
355           ServiceConfigUtil.getBackoffMultiplierFromRetryPolicy(retryPolicy),
356           "backoffMultiplier cannot be empty");
357       checkArgument(
358           backoffMultiplier > 0,
359           "backoffMultiplier must be greater than 0: %s",
360           backoffMultiplier);
361 
362       Long perAttemptRecvTimeout =
363           ServiceConfigUtil.getPerAttemptRecvTimeoutNanosFromRetryPolicy(retryPolicy);
364       checkArgument(
365           perAttemptRecvTimeout == null || perAttemptRecvTimeout >= 0,
366           "perAttemptRecvTimeout cannot be negative: %s",
367           perAttemptRecvTimeout);
368 
369       Set<Code> retryableCodes =
370           ServiceConfigUtil.getRetryableStatusCodesFromRetryPolicy(retryPolicy);
371       checkArgument(
372           perAttemptRecvTimeout != null || !retryableCodes.isEmpty(),
373           "retryableStatusCodes cannot be empty without perAttemptRecvTimeout");
374 
375       return new RetryPolicy(
376           maxAttempts, initialBackoffNanos, maxBackoffNanos, backoffMultiplier,
377           perAttemptRecvTimeout, retryableCodes);
378     }
379 
hedgingPolicy( Map<String, ?> hedgingPolicy, int maxAttemptsLimit)380     private static HedgingPolicy hedgingPolicy(
381         Map<String, ?> hedgingPolicy, int maxAttemptsLimit) {
382       int maxAttempts = checkNotNull(
383           ServiceConfigUtil.getMaxAttemptsFromHedgingPolicy(hedgingPolicy),
384           "maxAttempts cannot be empty");
385       checkArgument(maxAttempts >= 2, "maxAttempts must be greater than 1: %s", maxAttempts);
386       maxAttempts = Math.min(maxAttempts, maxAttemptsLimit);
387 
388       long hedgingDelayNanos = checkNotNull(
389           ServiceConfigUtil.getHedgingDelayNanosFromHedgingPolicy(hedgingPolicy),
390           "hedgingDelay cannot be empty");
391       checkArgument(
392           hedgingDelayNanos >= 0, "hedgingDelay must not be negative: %s", hedgingDelayNanos);
393 
394       return new HedgingPolicy(
395           maxAttempts, hedgingDelayNanos,
396           ServiceConfigUtil.getNonFatalStatusCodesFromHedgingPolicy(hedgingPolicy));
397     }
398   }
399 
400   static final class ServiceConfigConvertedSelector extends InternalConfigSelector {
401 
402     final ManagedChannelServiceConfig config;
403 
404     /** Converts the service config to config selector. */
ServiceConfigConvertedSelector(ManagedChannelServiceConfig config)405     private ServiceConfigConvertedSelector(ManagedChannelServiceConfig config) {
406       this.config = config;
407     }
408 
409     @Override
selectConfig(PickSubchannelArgs args)410     public Result selectConfig(PickSubchannelArgs args) {
411       return Result.newBuilder()
412           .setConfig(config)
413           .build();
414     }
415   }
416 }
417