google.golang.org/grpc@v1.62.1/service_config.go (about) 1 /* 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 19 package grpc 20 21 import ( 22 "encoding/json" 23 "errors" 24 "fmt" 25 "reflect" 26 "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 34 const 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 42 type MethodConfig = internalserviceconfig.MethodConfig 43 44 type 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 55 type ServiceConfig struct { 56 serviceconfig.Config 57 58 // 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. 61 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. 100 type healthCheckConfig struct { 101 // serviceName is the service name to use in the health-checking request. 102 ServiceName string 103 } 104 105 type jsonRetryPolicy struct { 106 MaxAttempts int 107 InitialBackoff internalserviceconfig.Duration 108 MaxBackoff internalserviceconfig.Duration 109 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 116 type 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 130 type jsonName struct { 131 Service string 132 Method string 133 } 134 135 var ( 136 errDuplicatedName = errors.New("duplicated name") 137 errEmptyServiceNonEmptyMethod = errors.New("cannot combine empty 'service' and non-empty 'method'") 138 ) 139 140 func (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. 155 type jsonMC struct { 156 Name *[]jsonName 157 WaitForReady *bool 158 Timeout *internalserviceconfig.Duration 159 MaxRequestMessageBytes *int64 160 MaxResponseMessageBytes *int64 161 RetryPolicy *jsonRetryPolicy 162 } 163 164 // TODO(lyuxuan): delete this struct after cleaning up old service config implementation. 165 type jsonSC struct { 166 LoadBalancingPolicy *string 167 LoadBalancingConfig *internalserviceconfig.BalancerConfig 168 MethodConfig *[]jsonMC 169 RetryThrottling *retryThrottlingPolicy 170 HealthCheckConfig *healthCheckConfig 171 } 172 173 func init() { 174 internal.ParseServiceConfig = parseServiceConfig 175 } 176 func 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 { 183 logger.Warningf("grpc: unmarshaling service config %s: %v", js, err) 184 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 } 209 210 mc := MethodConfig{ 211 WaitForReady: m.WaitForReady, 212 Timeout: (*time.Duration)(m.Timeout), 213 } 214 if mc.RetryPolicy, err = convertRetryPolicy(m.RetryPolicy); err != nil { 215 logger.Warningf("grpc: unmarshaling service config %s: %v", js, err) 216 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 { 235 logger.Warningf("grpc: error unmarshaling service config %s due to methodConfig[%d]: %v", js, i, err) 236 return &serviceconfig.ParseResult{Err: err} 237 } 238 239 if _, ok := paths[path]; ok { 240 err = errDuplicatedName 241 logger.Warningf("grpc: error unmarshaling service config %s due to methodConfig[%d]: %v", js, i, err) 242 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 260 func convertRetryPolicy(jrp *jsonRetryPolicy) (p *internalserviceconfig.RetryPolicy, err error) { 261 if jrp == nil { 262 return nil, nil 263 } 264 265 if jrp.MaxAttempts <= 1 || 266 jrp.InitialBackoff <= 0 || 267 jrp.MaxBackoff <= 0 || 268 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, 276 InitialBackoff: time.Duration(jrp.InitialBackoff), 277 MaxBackoff: time.Duration(jrp.MaxBackoff), 278 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 291 func min(a, b *int) *int { 292 if *a < *b { 293 return a 294 } 295 return b 296 } 297 298 func 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 311 func newInt(b int) *int { 312 return &b 313 } 314 315 func 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. 323 func equalServiceConfig(a, b serviceconfig.Config) bool { 324 if a == nil && b == nil { 325 return true 326 } 327 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 }