service_config.go (11942B)
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 "strconv" 27 "strings" 28 "time" 29 30 "google.golang.org/grpc/codes" 31 "google.golang.org/grpc/internal" 32 internalserviceconfig "google.golang.org/grpc/internal/serviceconfig" 33 "google.golang.org/grpc/serviceconfig" 34 ) 35 36 const maxInt = int(^uint(0) >> 1) 37 38 // MethodConfig defines the configuration recommended by the service providers for a 39 // particular method. 40 // 41 // Deprecated: Users should not use this struct. Service config should be received 42 // through name resolver, as specified here 43 // https://github.com/grpc/grpc/blob/master/doc/service_config.md 44 type MethodConfig = internalserviceconfig.MethodConfig 45 46 type lbConfig struct { 47 name string 48 cfg serviceconfig.LoadBalancingConfig 49 } 50 51 // ServiceConfig is provided by the service provider and contains parameters for how 52 // clients that connect to the service should behave. 53 // 54 // Deprecated: Users should not use this struct. Service config should be received 55 // through name resolver, as specified here 56 // https://github.com/grpc/grpc/blob/master/doc/service_config.md 57 type ServiceConfig struct { 58 serviceconfig.Config 59 60 // LB is the load balancer the service providers recommends. This is 61 // deprecated; lbConfigs is preferred. If lbConfig and LB are both present, 62 // lbConfig will be used. 63 LB *string 64 65 // lbConfig is the service config's load balancing configuration. If 66 // lbConfig and LB are both present, lbConfig will be used. 67 lbConfig *lbConfig 68 69 // Methods contains a map for the methods in this service. If there is an 70 // exact match for a method (i.e. /service/method) in the map, use the 71 // corresponding MethodConfig. If there's no exact match, look for the 72 // default config for the service (/service/) and use the corresponding 73 // MethodConfig if it exists. Otherwise, the method has no MethodConfig to 74 // use. 75 Methods map[string]MethodConfig 76 77 // If a retryThrottlingPolicy is provided, gRPC will automatically throttle 78 // retry attempts and hedged RPCs when the client’s ratio of failures to 79 // successes exceeds a threshold. 80 // 81 // For each server name, the gRPC client will maintain a token_count which is 82 // initially set to maxTokens, and can take values between 0 and maxTokens. 83 // 84 // Every outgoing RPC (regardless of service or method invoked) will change 85 // token_count as follows: 86 // 87 // - Every failed RPC will decrement the token_count by 1. 88 // - Every successful RPC will increment the token_count by tokenRatio. 89 // 90 // If token_count is less than or equal to maxTokens / 2, then RPCs will not 91 // be retried and hedged RPCs will not be sent. 92 retryThrottling *retryThrottlingPolicy 93 // healthCheckConfig must be set as one of the requirement to enable LB channel 94 // health check. 95 healthCheckConfig *healthCheckConfig 96 // rawJSONString stores service config json string that get parsed into 97 // this service config struct. 98 rawJSONString string 99 } 100 101 // healthCheckConfig defines the go-native version of the LB channel health check config. 102 type healthCheckConfig struct { 103 // serviceName is the service name to use in the health-checking request. 104 ServiceName string 105 } 106 107 type jsonRetryPolicy struct { 108 MaxAttempts int 109 InitialBackoff string 110 MaxBackoff string 111 BackoffMultiplier float64 112 RetryableStatusCodes []codes.Code 113 } 114 115 // retryThrottlingPolicy defines the go-native version of the retry throttling 116 // policy defined by the service config here: 117 // https://github.com/grpc/proposal/blob/master/A6-client-retries.md#integration-with-service-config 118 type retryThrottlingPolicy struct { 119 // The number of tokens starts at maxTokens. The token_count will always be 120 // between 0 and maxTokens. 121 // 122 // This field is required and must be greater than zero. 123 MaxTokens float64 124 // The amount of tokens to add on each successful RPC. Typically this will 125 // be some number between 0 and 1, e.g., 0.1. 126 // 127 // This field is required and must be greater than zero. Up to 3 decimal 128 // places are supported. 129 TokenRatio float64 130 } 131 132 func parseDuration(s *string) (*time.Duration, error) { 133 if s == nil { 134 return nil, nil 135 } 136 if !strings.HasSuffix(*s, "s") { 137 return nil, fmt.Errorf("malformed duration %q", *s) 138 } 139 ss := strings.SplitN((*s)[:len(*s)-1], ".", 3) 140 if len(ss) > 2 { 141 return nil, fmt.Errorf("malformed duration %q", *s) 142 } 143 // hasDigits is set if either the whole or fractional part of the number is 144 // present, since both are optional but one is required. 145 hasDigits := false 146 var d time.Duration 147 if len(ss[0]) > 0 { 148 i, err := strconv.ParseInt(ss[0], 10, 32) 149 if err != nil { 150 return nil, fmt.Errorf("malformed duration %q: %v", *s, err) 151 } 152 d = time.Duration(i) * time.Second 153 hasDigits = true 154 } 155 if len(ss) == 2 && len(ss[1]) > 0 { 156 if len(ss[1]) > 9 { 157 return nil, fmt.Errorf("malformed duration %q", *s) 158 } 159 f, err := strconv.ParseInt(ss[1], 10, 64) 160 if err != nil { 161 return nil, fmt.Errorf("malformed duration %q: %v", *s, err) 162 } 163 for i := 9; i > len(ss[1]); i-- { 164 f *= 10 165 } 166 d += time.Duration(f) 167 hasDigits = true 168 } 169 if !hasDigits { 170 return nil, fmt.Errorf("malformed duration %q", *s) 171 } 172 173 return &d, nil 174 } 175 176 type jsonName struct { 177 Service string 178 Method string 179 } 180 181 var ( 182 errDuplicatedName = errors.New("duplicated name") 183 errEmptyServiceNonEmptyMethod = errors.New("cannot combine empty 'service' and non-empty 'method'") 184 ) 185 186 func (j jsonName) generatePath() (string, error) { 187 if j.Service == "" { 188 if j.Method != "" { 189 return "", errEmptyServiceNonEmptyMethod 190 } 191 return "", nil 192 } 193 res := "/" + j.Service + "/" 194 if j.Method != "" { 195 res += j.Method 196 } 197 return res, nil 198 } 199 200 // TODO(lyuxuan): delete this struct after cleaning up old service config implementation. 201 type jsonMC struct { 202 Name *[]jsonName 203 WaitForReady *bool 204 Timeout *string 205 MaxRequestMessageBytes *int64 206 MaxResponseMessageBytes *int64 207 RetryPolicy *jsonRetryPolicy 208 } 209 210 // TODO(lyuxuan): delete this struct after cleaning up old service config implementation. 211 type jsonSC struct { 212 LoadBalancingPolicy *string 213 LoadBalancingConfig *internalserviceconfig.BalancerConfig 214 MethodConfig *[]jsonMC 215 RetryThrottling *retryThrottlingPolicy 216 HealthCheckConfig *healthCheckConfig 217 } 218 219 func init() { 220 internal.ParseServiceConfig = parseServiceConfig 221 } 222 func parseServiceConfig(js string) *serviceconfig.ParseResult { 223 if len(js) == 0 { 224 return &serviceconfig.ParseResult{Err: fmt.Errorf("no JSON service config provided")} 225 } 226 var rsc jsonSC 227 err := json.Unmarshal([]byte(js), &rsc) 228 if err != nil { 229 logger.Warningf("grpc: unmarshaling service config %s: %v", js, err) 230 return &serviceconfig.ParseResult{Err: err} 231 } 232 sc := ServiceConfig{ 233 LB: rsc.LoadBalancingPolicy, 234 Methods: make(map[string]MethodConfig), 235 retryThrottling: rsc.RetryThrottling, 236 healthCheckConfig: rsc.HealthCheckConfig, 237 rawJSONString: js, 238 } 239 if c := rsc.LoadBalancingConfig; c != nil { 240 sc.lbConfig = &lbConfig{ 241 name: c.Name, 242 cfg: c.Config, 243 } 244 } 245 246 if rsc.MethodConfig == nil { 247 return &serviceconfig.ParseResult{Config: &sc} 248 } 249 250 paths := map[string]struct{}{} 251 for _, m := range *rsc.MethodConfig { 252 if m.Name == nil { 253 continue 254 } 255 d, err := parseDuration(m.Timeout) 256 if err != nil { 257 logger.Warningf("grpc: unmarshaling service config %s: %v", js, err) 258 return &serviceconfig.ParseResult{Err: err} 259 } 260 261 mc := MethodConfig{ 262 WaitForReady: m.WaitForReady, 263 Timeout: d, 264 } 265 if mc.RetryPolicy, err = convertRetryPolicy(m.RetryPolicy); err != nil { 266 logger.Warningf("grpc: unmarshaling service config %s: %v", js, err) 267 return &serviceconfig.ParseResult{Err: err} 268 } 269 if m.MaxRequestMessageBytes != nil { 270 if *m.MaxRequestMessageBytes > int64(maxInt) { 271 mc.MaxReqSize = newInt(maxInt) 272 } else { 273 mc.MaxReqSize = newInt(int(*m.MaxRequestMessageBytes)) 274 } 275 } 276 if m.MaxResponseMessageBytes != nil { 277 if *m.MaxResponseMessageBytes > int64(maxInt) { 278 mc.MaxRespSize = newInt(maxInt) 279 } else { 280 mc.MaxRespSize = newInt(int(*m.MaxResponseMessageBytes)) 281 } 282 } 283 for i, n := range *m.Name { 284 path, err := n.generatePath() 285 if err != nil { 286 logger.Warningf("grpc: error unmarshaling service config %s due to methodConfig[%d]: %v", js, i, err) 287 return &serviceconfig.ParseResult{Err: err} 288 } 289 290 if _, ok := paths[path]; ok { 291 err = errDuplicatedName 292 logger.Warningf("grpc: error unmarshaling service config %s due to methodConfig[%d]: %v", js, i, err) 293 return &serviceconfig.ParseResult{Err: err} 294 } 295 paths[path] = struct{}{} 296 sc.Methods[path] = mc 297 } 298 } 299 300 if sc.retryThrottling != nil { 301 if mt := sc.retryThrottling.MaxTokens; mt <= 0 || mt > 1000 { 302 return &serviceconfig.ParseResult{Err: fmt.Errorf("invalid retry throttling config: maxTokens (%v) out of range (0, 1000]", mt)} 303 } 304 if tr := sc.retryThrottling.TokenRatio; tr <= 0 { 305 return &serviceconfig.ParseResult{Err: fmt.Errorf("invalid retry throttling config: tokenRatio (%v) may not be negative", tr)} 306 } 307 } 308 return &serviceconfig.ParseResult{Config: &sc} 309 } 310 311 func convertRetryPolicy(jrp *jsonRetryPolicy) (p *internalserviceconfig.RetryPolicy, err error) { 312 if jrp == nil { 313 return nil, nil 314 } 315 ib, err := parseDuration(&jrp.InitialBackoff) 316 if err != nil { 317 return nil, err 318 } 319 mb, err := parseDuration(&jrp.MaxBackoff) 320 if err != nil { 321 return nil, err 322 } 323 324 if jrp.MaxAttempts <= 1 || 325 *ib <= 0 || 326 *mb <= 0 || 327 jrp.BackoffMultiplier <= 0 || 328 len(jrp.RetryableStatusCodes) == 0 { 329 logger.Warningf("grpc: ignoring retry policy %v due to illegal configuration", jrp) 330 return nil, nil 331 } 332 333 rp := &internalserviceconfig.RetryPolicy{ 334 MaxAttempts: jrp.MaxAttempts, 335 InitialBackoff: *ib, 336 MaxBackoff: *mb, 337 BackoffMultiplier: jrp.BackoffMultiplier, 338 RetryableStatusCodes: make(map[codes.Code]bool), 339 } 340 if rp.MaxAttempts > 5 { 341 // TODO(retry): Make the max maxAttempts configurable. 342 rp.MaxAttempts = 5 343 } 344 for _, code := range jrp.RetryableStatusCodes { 345 rp.RetryableStatusCodes[code] = true 346 } 347 return rp, nil 348 } 349 350 func min(a, b *int) *int { 351 if *a < *b { 352 return a 353 } 354 return b 355 } 356 357 func getMaxSize(mcMax, doptMax *int, defaultVal int) *int { 358 if mcMax == nil && doptMax == nil { 359 return &defaultVal 360 } 361 if mcMax != nil && doptMax != nil { 362 return min(mcMax, doptMax) 363 } 364 if mcMax != nil { 365 return mcMax 366 } 367 return doptMax 368 } 369 370 func newInt(b int) *int { 371 return &b 372 } 373 374 func init() { 375 internal.EqualServiceConfigForTesting = equalServiceConfig 376 } 377 378 // equalServiceConfig compares two configs. The rawJSONString field is ignored, 379 // because they may diff in white spaces. 380 // 381 // If any of them is NOT *ServiceConfig, return false. 382 func equalServiceConfig(a, b serviceconfig.Config) bool { 383 if a == nil && b == nil { 384 return true 385 } 386 aa, ok := a.(*ServiceConfig) 387 if !ok { 388 return false 389 } 390 bb, ok := b.(*ServiceConfig) 391 if !ok { 392 return false 393 } 394 aaRaw := aa.rawJSONString 395 aa.rawJSONString = "" 396 bbRaw := bb.rawJSONString 397 bb.rawJSONString = "" 398 defer func() { 399 aa.rawJSONString = aaRaw 400 bb.rawJSONString = bbRaw 401 }() 402 // Using reflect.DeepEqual instead of cmp.Equal because many balancer 403 // configs are unexported, and cmp.Equal cannot compare unexported fields 404 // from unexported structs. 405 return reflect.DeepEqual(aa, bb) 406 }