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