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  }