blob: 0df11fc098825e210e7cb360b0fe1ec494b7c9e3 [file] [log] [blame]
khenaidoo5fc5cea2021-08-11 17:39:16 -04001/*
2 *
3 * Copyright 2017 gRPC authors.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19package grpc
20
21import (
22 "encoding/json"
23 "errors"
24 "fmt"
25 "reflect"
khenaidoo5fc5cea2021-08-11 17:39:16 -040026 "time"
27
28 "google.golang.org/grpc/codes"
29 "google.golang.org/grpc/internal"
30 internalserviceconfig "google.golang.org/grpc/internal/serviceconfig"
31 "google.golang.org/grpc/serviceconfig"
32)
33
34const maxInt = int(^uint(0) >> 1)
35
36// MethodConfig defines the configuration recommended by the service providers for a
37// particular method.
38//
39// Deprecated: Users should not use this struct. Service config should be received
40// through name resolver, as specified here
41// https://github.com/grpc/grpc/blob/master/doc/service_config.md
42type MethodConfig = internalserviceconfig.MethodConfig
43
44type lbConfig struct {
45 name string
46 cfg serviceconfig.LoadBalancingConfig
47}
48
49// ServiceConfig is provided by the service provider and contains parameters for how
50// clients that connect to the service should behave.
51//
52// Deprecated: Users should not use this struct. Service config should be received
53// through name resolver, as specified here
54// https://github.com/grpc/grpc/blob/master/doc/service_config.md
55type ServiceConfig struct {
56 serviceconfig.Config
57
Akash Kankanala761955c2024-02-21 19:32:20 +053058 // LB is the load balancer the service providers recommends. This is
59 // deprecated; lbConfigs is preferred. If lbConfig and LB are both present,
60 // lbConfig will be used.
khenaidoo5fc5cea2021-08-11 17:39:16 -040061 LB *string
62
63 // lbConfig is the service config's load balancing configuration. If
64 // lbConfig and LB are both present, lbConfig will be used.
65 lbConfig *lbConfig
66
67 // Methods contains a map for the methods in this service. If there is an
68 // exact match for a method (i.e. /service/method) in the map, use the
69 // corresponding MethodConfig. If there's no exact match, look for the
70 // default config for the service (/service/) and use the corresponding
71 // MethodConfig if it exists. Otherwise, the method has no MethodConfig to
72 // use.
73 Methods map[string]MethodConfig
74
75 // If a retryThrottlingPolicy is provided, gRPC will automatically throttle
76 // retry attempts and hedged RPCs when the client’s ratio of failures to
77 // successes exceeds a threshold.
78 //
79 // For each server name, the gRPC client will maintain a token_count which is
80 // initially set to maxTokens, and can take values between 0 and maxTokens.
81 //
82 // Every outgoing RPC (regardless of service or method invoked) will change
83 // token_count as follows:
84 //
85 // - Every failed RPC will decrement the token_count by 1.
86 // - Every successful RPC will increment the token_count by tokenRatio.
87 //
88 // If token_count is less than or equal to maxTokens / 2, then RPCs will not
89 // be retried and hedged RPCs will not be sent.
90 retryThrottling *retryThrottlingPolicy
91 // healthCheckConfig must be set as one of the requirement to enable LB channel
92 // health check.
93 healthCheckConfig *healthCheckConfig
94 // rawJSONString stores service config json string that get parsed into
95 // this service config struct.
96 rawJSONString string
97}
98
99// healthCheckConfig defines the go-native version of the LB channel health check config.
100type healthCheckConfig struct {
101 // serviceName is the service name to use in the health-checking request.
102 ServiceName string
103}
104
105type jsonRetryPolicy struct {
106 MaxAttempts int
Akash Kankanala761955c2024-02-21 19:32:20 +0530107 InitialBackoff internalserviceconfig.Duration
108 MaxBackoff internalserviceconfig.Duration
khenaidoo5fc5cea2021-08-11 17:39:16 -0400109 BackoffMultiplier float64
110 RetryableStatusCodes []codes.Code
111}
112
113// retryThrottlingPolicy defines the go-native version of the retry throttling
114// policy defined by the service config here:
115// https://github.com/grpc/proposal/blob/master/A6-client-retries.md#integration-with-service-config
116type retryThrottlingPolicy struct {
117 // The number of tokens starts at maxTokens. The token_count will always be
118 // between 0 and maxTokens.
119 //
120 // This field is required and must be greater than zero.
121 MaxTokens float64
122 // The amount of tokens to add on each successful RPC. Typically this will
123 // be some number between 0 and 1, e.g., 0.1.
124 //
125 // This field is required and must be greater than zero. Up to 3 decimal
126 // places are supported.
127 TokenRatio float64
128}
129
khenaidoo5fc5cea2021-08-11 17:39:16 -0400130type jsonName struct {
131 Service string
132 Method string
133}
134
135var (
136 errDuplicatedName = errors.New("duplicated name")
137 errEmptyServiceNonEmptyMethod = errors.New("cannot combine empty 'service' and non-empty 'method'")
138)
139
140func (j jsonName) generatePath() (string, error) {
141 if j.Service == "" {
142 if j.Method != "" {
143 return "", errEmptyServiceNonEmptyMethod
144 }
145 return "", nil
146 }
147 res := "/" + j.Service + "/"
148 if j.Method != "" {
149 res += j.Method
150 }
151 return res, nil
152}
153
154// TODO(lyuxuan): delete this struct after cleaning up old service config implementation.
155type jsonMC struct {
156 Name *[]jsonName
157 WaitForReady *bool
Akash Kankanala761955c2024-02-21 19:32:20 +0530158 Timeout *internalserviceconfig.Duration
khenaidoo5fc5cea2021-08-11 17:39:16 -0400159 MaxRequestMessageBytes *int64
160 MaxResponseMessageBytes *int64
161 RetryPolicy *jsonRetryPolicy
162}
163
164// TODO(lyuxuan): delete this struct after cleaning up old service config implementation.
165type jsonSC struct {
166 LoadBalancingPolicy *string
167 LoadBalancingConfig *internalserviceconfig.BalancerConfig
168 MethodConfig *[]jsonMC
169 RetryThrottling *retryThrottlingPolicy
170 HealthCheckConfig *healthCheckConfig
171}
172
173func init() {
Akash Kankanala761955c2024-02-21 19:32:20 +0530174 internal.ParseServiceConfig = parseServiceConfig
khenaidoo5fc5cea2021-08-11 17:39:16 -0400175}
176func parseServiceConfig(js string) *serviceconfig.ParseResult {
177 if len(js) == 0 {
178 return &serviceconfig.ParseResult{Err: fmt.Errorf("no JSON service config provided")}
179 }
180 var rsc jsonSC
181 err := json.Unmarshal([]byte(js), &rsc)
182 if err != nil {
Akash Kankanala761955c2024-02-21 19:32:20 +0530183 logger.Warningf("grpc: unmarshaling service config %s: %v", js, err)
khenaidoo5fc5cea2021-08-11 17:39:16 -0400184 return &serviceconfig.ParseResult{Err: err}
185 }
186 sc := ServiceConfig{
187 LB: rsc.LoadBalancingPolicy,
188 Methods: make(map[string]MethodConfig),
189 retryThrottling: rsc.RetryThrottling,
190 healthCheckConfig: rsc.HealthCheckConfig,
191 rawJSONString: js,
192 }
193 if c := rsc.LoadBalancingConfig; c != nil {
194 sc.lbConfig = &lbConfig{
195 name: c.Name,
196 cfg: c.Config,
197 }
198 }
199
200 if rsc.MethodConfig == nil {
201 return &serviceconfig.ParseResult{Config: &sc}
202 }
203
204 paths := map[string]struct{}{}
205 for _, m := range *rsc.MethodConfig {
206 if m.Name == nil {
207 continue
208 }
khenaidoo5fc5cea2021-08-11 17:39:16 -0400209
210 mc := MethodConfig{
211 WaitForReady: m.WaitForReady,
Akash Kankanala761955c2024-02-21 19:32:20 +0530212 Timeout: (*time.Duration)(m.Timeout),
khenaidoo5fc5cea2021-08-11 17:39:16 -0400213 }
214 if mc.RetryPolicy, err = convertRetryPolicy(m.RetryPolicy); err != nil {
Akash Kankanala761955c2024-02-21 19:32:20 +0530215 logger.Warningf("grpc: unmarshaling service config %s: %v", js, err)
khenaidoo5fc5cea2021-08-11 17:39:16 -0400216 return &serviceconfig.ParseResult{Err: err}
217 }
218 if m.MaxRequestMessageBytes != nil {
219 if *m.MaxRequestMessageBytes > int64(maxInt) {
220 mc.MaxReqSize = newInt(maxInt)
221 } else {
222 mc.MaxReqSize = newInt(int(*m.MaxRequestMessageBytes))
223 }
224 }
225 if m.MaxResponseMessageBytes != nil {
226 if *m.MaxResponseMessageBytes > int64(maxInt) {
227 mc.MaxRespSize = newInt(maxInt)
228 } else {
229 mc.MaxRespSize = newInt(int(*m.MaxResponseMessageBytes))
230 }
231 }
232 for i, n := range *m.Name {
233 path, err := n.generatePath()
234 if err != nil {
Akash Kankanala761955c2024-02-21 19:32:20 +0530235 logger.Warningf("grpc: error unmarshaling service config %s due to methodConfig[%d]: %v", js, i, err)
khenaidoo5fc5cea2021-08-11 17:39:16 -0400236 return &serviceconfig.ParseResult{Err: err}
237 }
238
239 if _, ok := paths[path]; ok {
240 err = errDuplicatedName
Akash Kankanala761955c2024-02-21 19:32:20 +0530241 logger.Warningf("grpc: error unmarshaling service config %s due to methodConfig[%d]: %v", js, i, err)
khenaidoo5fc5cea2021-08-11 17:39:16 -0400242 return &serviceconfig.ParseResult{Err: err}
243 }
244 paths[path] = struct{}{}
245 sc.Methods[path] = mc
246 }
247 }
248
249 if sc.retryThrottling != nil {
250 if mt := sc.retryThrottling.MaxTokens; mt <= 0 || mt > 1000 {
251 return &serviceconfig.ParseResult{Err: fmt.Errorf("invalid retry throttling config: maxTokens (%v) out of range (0, 1000]", mt)}
252 }
253 if tr := sc.retryThrottling.TokenRatio; tr <= 0 {
254 return &serviceconfig.ParseResult{Err: fmt.Errorf("invalid retry throttling config: tokenRatio (%v) may not be negative", tr)}
255 }
256 }
257 return &serviceconfig.ParseResult{Config: &sc}
258}
259
260func convertRetryPolicy(jrp *jsonRetryPolicy) (p *internalserviceconfig.RetryPolicy, err error) {
261 if jrp == nil {
262 return nil, nil
263 }
khenaidoo5fc5cea2021-08-11 17:39:16 -0400264
265 if jrp.MaxAttempts <= 1 ||
Akash Kankanala761955c2024-02-21 19:32:20 +0530266 jrp.InitialBackoff <= 0 ||
267 jrp.MaxBackoff <= 0 ||
khenaidoo5fc5cea2021-08-11 17:39:16 -0400268 jrp.BackoffMultiplier <= 0 ||
269 len(jrp.RetryableStatusCodes) == 0 {
270 logger.Warningf("grpc: ignoring retry policy %v due to illegal configuration", jrp)
271 return nil, nil
272 }
273
274 rp := &internalserviceconfig.RetryPolicy{
275 MaxAttempts: jrp.MaxAttempts,
Akash Kankanala761955c2024-02-21 19:32:20 +0530276 InitialBackoff: time.Duration(jrp.InitialBackoff),
277 MaxBackoff: time.Duration(jrp.MaxBackoff),
khenaidoo5fc5cea2021-08-11 17:39:16 -0400278 BackoffMultiplier: jrp.BackoffMultiplier,
279 RetryableStatusCodes: make(map[codes.Code]bool),
280 }
281 if rp.MaxAttempts > 5 {
282 // TODO(retry): Make the max maxAttempts configurable.
283 rp.MaxAttempts = 5
284 }
285 for _, code := range jrp.RetryableStatusCodes {
286 rp.RetryableStatusCodes[code] = true
287 }
288 return rp, nil
289}
290
291func min(a, b *int) *int {
292 if *a < *b {
293 return a
294 }
295 return b
296}
297
298func getMaxSize(mcMax, doptMax *int, defaultVal int) *int {
299 if mcMax == nil && doptMax == nil {
300 return &defaultVal
301 }
302 if mcMax != nil && doptMax != nil {
303 return min(mcMax, doptMax)
304 }
305 if mcMax != nil {
306 return mcMax
307 }
308 return doptMax
309}
310
311func newInt(b int) *int {
312 return &b
313}
314
315func init() {
316 internal.EqualServiceConfigForTesting = equalServiceConfig
317}
318
319// equalServiceConfig compares two configs. The rawJSONString field is ignored,
320// because they may diff in white spaces.
321//
322// If any of them is NOT *ServiceConfig, return false.
323func equalServiceConfig(a, b serviceconfig.Config) bool {
Akash Kankanala761955c2024-02-21 19:32:20 +0530324 if a == nil && b == nil {
325 return true
326 }
khenaidoo5fc5cea2021-08-11 17:39:16 -0400327 aa, ok := a.(*ServiceConfig)
328 if !ok {
329 return false
330 }
331 bb, ok := b.(*ServiceConfig)
332 if !ok {
333 return false
334 }
335 aaRaw := aa.rawJSONString
336 aa.rawJSONString = ""
337 bbRaw := bb.rawJSONString
338 bb.rawJSONString = ""
339 defer func() {
340 aa.rawJSONString = aaRaw
341 bb.rawJSONString = bbRaw
342 }()
343 // Using reflect.DeepEqual instead of cmp.Equal because many balancer
344 // configs are unexported, and cmp.Equal cannot compare unexported fields
345 // from unexported structs.
346 return reflect.DeepEqual(aa, bb)
347}