google.golang.org/grpc@v1.72.2/balancer/rls/config.go (about)

     1  /*
     2   *
     3   * Copyright 2020 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 rls
    20  
    21  import (
    22  	"bytes"
    23  	"encoding/json"
    24  	"fmt"
    25  	"net/url"
    26  	"time"
    27  
    28  	"google.golang.org/grpc/balancer"
    29  	"google.golang.org/grpc/balancer/rls/internal/keys"
    30  	"google.golang.org/grpc/internal"
    31  	"google.golang.org/grpc/internal/pretty"
    32  	rlspb "google.golang.org/grpc/internal/proto/grpc_lookup_v1"
    33  	"google.golang.org/grpc/resolver"
    34  	"google.golang.org/grpc/serviceconfig"
    35  	"google.golang.org/protobuf/encoding/protojson"
    36  	"google.golang.org/protobuf/types/known/durationpb"
    37  )
    38  
    39  const (
    40  	// Default max_age if not specified (or greater than this value) in the
    41  	// service config.
    42  	maxMaxAge = 5 * time.Minute
    43  	// Upper limit for cache_size since we don't fully trust the service config.
    44  	maxCacheSize = 5 * 1024 * 1024 * 8 // 5MB in bytes
    45  	// Default lookup_service_timeout if not specified in the service config.
    46  	defaultLookupServiceTimeout = 10 * time.Second
    47  	// Default value for targetNameField in the child policy config during
    48  	// service config validation.
    49  	dummyChildPolicyTarget = "target_name_to_be_filled_in_later"
    50  )
    51  
    52  // lbConfig is the internal representation of the RLS LB policy's config.
    53  type lbConfig struct {
    54  	serviceconfig.LoadBalancingConfig
    55  
    56  	cacheSizeBytes       int64 // Keep this field 64-bit aligned.
    57  	kbMap                keys.BuilderMap
    58  	lookupService        string
    59  	lookupServiceTimeout time.Duration
    60  	maxAge               time.Duration
    61  	staleAge             time.Duration
    62  	defaultTarget        string
    63  
    64  	childPolicyName             string
    65  	childPolicyConfig           map[string]json.RawMessage
    66  	childPolicyTargetField      string
    67  	controlChannelServiceConfig string
    68  }
    69  
    70  func (lbCfg *lbConfig) Equal(other *lbConfig) bool {
    71  	return lbCfg.kbMap.Equal(other.kbMap) &&
    72  		lbCfg.lookupService == other.lookupService &&
    73  		lbCfg.lookupServiceTimeout == other.lookupServiceTimeout &&
    74  		lbCfg.maxAge == other.maxAge &&
    75  		lbCfg.staleAge == other.staleAge &&
    76  		lbCfg.cacheSizeBytes == other.cacheSizeBytes &&
    77  		lbCfg.defaultTarget == other.defaultTarget &&
    78  		lbCfg.childPolicyName == other.childPolicyName &&
    79  		lbCfg.childPolicyTargetField == other.childPolicyTargetField &&
    80  		lbCfg.controlChannelServiceConfig == other.controlChannelServiceConfig &&
    81  		childPolicyConfigEqual(lbCfg.childPolicyConfig, other.childPolicyConfig)
    82  }
    83  
    84  func childPolicyConfigEqual(a, b map[string]json.RawMessage) bool {
    85  	if (b == nil) != (a == nil) {
    86  		return false
    87  	}
    88  	if len(b) != len(a) {
    89  		return false
    90  	}
    91  	for k, jsonA := range a {
    92  		jsonB, ok := b[k]
    93  		if !ok {
    94  			return false
    95  		}
    96  		if !bytes.Equal(jsonA, jsonB) {
    97  			return false
    98  		}
    99  	}
   100  	return true
   101  }
   102  
   103  // This struct resembles the JSON representation of the loadBalancing config
   104  // and makes it easier to unmarshal.
   105  type lbConfigJSON struct {
   106  	RouteLookupConfig                json.RawMessage
   107  	RouteLookupChannelServiceConfig  json.RawMessage
   108  	ChildPolicy                      []map[string]json.RawMessage
   109  	ChildPolicyConfigTargetFieldName string
   110  }
   111  
   112  // ParseConfig parses the JSON load balancer config provided into an
   113  // internal form or returns an error if the config is invalid.
   114  //
   115  //	 When parsing a config update, the following validations are performed:
   116  //	 - routeLookupConfig:
   117  //	   - grpc_keybuilders field:
   118  //	     - must have at least one entry
   119  //	     - must not have two entries with the same `Name`
   120  //	     - within each entry:
   121  //	       - must have at least one `Name`
   122  //	       - must not have a `Name` with the `service` field unset or empty
   123  //	       - within each `headers` entry:
   124  //	         - must not have `required_match` set
   125  //	         - must not have `key` unset or empty
   126  //	       - across all `headers`, `constant_keys` and `extra_keys` fields:
   127  //	         - must not have the same `key` specified twice
   128  //	         - no `key` must be the empty string
   129  //	   - `lookup_service` field must be set and must parse as a target URI
   130  //	   - if `max_age` > 5m, it should be set to 5 minutes
   131  //	   - if `stale_age` > `max_age`, ignore it
   132  //	   - if `stale_age` is set, then `max_age` must also be set
   133  //	   - ignore `valid_targets` field
   134  //	   - `cache_size_bytes` field must have a value greater than 0, and if its
   135  //	     value is greater than 5M, we cap it at 5M
   136  //
   137  //	- routeLookupChannelServiceConfig:
   138  //	  - if specified, must parse as valid service config
   139  //
   140  //	- childPolicy:
   141  //	  - must find a valid child policy with a valid config
   142  //
   143  //	- childPolicyConfigTargetFieldName:
   144  //	  - must be set and non-empty
   145  func (rlsBB) ParseConfig(c json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
   146  	if logger.V(2) {
   147  		logger.Infof("Received JSON service config: %v", pretty.ToJSON(c))
   148  	}
   149  
   150  	cfgJSON := &lbConfigJSON{}
   151  	if err := json.Unmarshal(c, cfgJSON); err != nil {
   152  		return nil, fmt.Errorf("rls: json unmarshal failed for service config %+v: %v", string(c), err)
   153  	}
   154  
   155  	m := protojson.UnmarshalOptions{DiscardUnknown: true}
   156  	rlsProto := &rlspb.RouteLookupConfig{}
   157  	if err := m.Unmarshal(cfgJSON.RouteLookupConfig, rlsProto); err != nil {
   158  		return nil, fmt.Errorf("rls: bad RouteLookupConfig proto %+v: %v", string(cfgJSON.RouteLookupConfig), err)
   159  	}
   160  	lbCfg, err := parseRLSProto(rlsProto)
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  
   165  	if sc := string(cfgJSON.RouteLookupChannelServiceConfig); sc != "" {
   166  		parsed := internal.ParseServiceConfig.(func(string) *serviceconfig.ParseResult)(sc)
   167  		if parsed.Err != nil {
   168  			return nil, fmt.Errorf("rls: bad control channel service config %q: %v", sc, parsed.Err)
   169  		}
   170  		lbCfg.controlChannelServiceConfig = sc
   171  	}
   172  
   173  	if cfgJSON.ChildPolicyConfigTargetFieldName == "" {
   174  		return nil, fmt.Errorf("rls: childPolicyConfigTargetFieldName field is not set in service config %+v", string(c))
   175  	}
   176  	name, config, err := parseChildPolicyConfigs(cfgJSON.ChildPolicy, cfgJSON.ChildPolicyConfigTargetFieldName)
   177  	if err != nil {
   178  		return nil, err
   179  	}
   180  	lbCfg.childPolicyName = name
   181  	lbCfg.childPolicyConfig = config
   182  	lbCfg.childPolicyTargetField = cfgJSON.ChildPolicyConfigTargetFieldName
   183  	return lbCfg, nil
   184  }
   185  
   186  func parseRLSProto(rlsProto *rlspb.RouteLookupConfig) (*lbConfig, error) {
   187  	// Validations specified on the `grpc_keybuilders` field are performed here.
   188  	kbMap, err := keys.MakeBuilderMap(rlsProto)
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  
   193  	// `lookup_service` field must be set and must parse as a target URI.
   194  	lookupService := rlsProto.GetLookupService()
   195  	if lookupService == "" {
   196  		return nil, fmt.Errorf("rls: empty lookup_service in route lookup config %+v", rlsProto)
   197  	}
   198  	parsedTarget, err := url.Parse(lookupService)
   199  	if err != nil {
   200  		// url.Parse() fails if scheme is missing. Retry with default scheme.
   201  		parsedTarget, err = url.Parse(resolver.GetDefaultScheme() + ":///" + lookupService)
   202  		if err != nil {
   203  			return nil, fmt.Errorf("rls: invalid target URI in lookup_service %s", lookupService)
   204  		}
   205  	}
   206  	if parsedTarget.Scheme == "" {
   207  		parsedTarget.Scheme = resolver.GetDefaultScheme()
   208  	}
   209  	if resolver.Get(parsedTarget.Scheme) == nil {
   210  		return nil, fmt.Errorf("rls: unregistered scheme in lookup_service %s", lookupService)
   211  	}
   212  
   213  	lookupServiceTimeout, err := convertDuration(rlsProto.GetLookupServiceTimeout())
   214  	if err != nil {
   215  		return nil, fmt.Errorf("rls: failed to parse lookup_service_timeout in route lookup config %+v: %v", rlsProto, err)
   216  	}
   217  	if lookupServiceTimeout == 0 {
   218  		lookupServiceTimeout = defaultLookupServiceTimeout
   219  	}
   220  
   221  	// Validations performed here:
   222  	// - if `max_age` > 5m, it should be set to 5 minutes
   223  	//   only if stale age is not set
   224  	// - if `stale_age` > `max_age`, ignore it
   225  	// - if `stale_age` is set, then `max_age` must also be set
   226  	maxAgeSet := false
   227  	maxAge, err := convertDuration(rlsProto.GetMaxAge())
   228  	if err != nil {
   229  		return nil, fmt.Errorf("rls: failed to parse max_age in route lookup config %+v: %v", rlsProto, err)
   230  	}
   231  	if maxAge == 0 {
   232  		maxAge = maxMaxAge
   233  	} else {
   234  		maxAgeSet = true
   235  	}
   236  
   237  	staleAgeSet := false
   238  	staleAge, err := convertDuration(rlsProto.GetStaleAge())
   239  	if err != nil {
   240  		return nil, fmt.Errorf("rls: failed to parse staleAge in route lookup config %+v: %v", rlsProto, err)
   241  	}
   242  	if staleAge == 0 {
   243  		staleAge = maxMaxAge
   244  	} else {
   245  		staleAgeSet = true
   246  	}
   247  
   248  	if staleAgeSet && !maxAgeSet {
   249  		return nil, fmt.Errorf("rls: stale_age is set, but max_age is not in route lookup config %+v", rlsProto)
   250  	}
   251  	if staleAge > maxMaxAge {
   252  		staleAge = maxMaxAge
   253  	}
   254  	if !staleAgeSet && maxAge > maxMaxAge {
   255  		maxAge = maxMaxAge
   256  	}
   257  	if staleAge > maxAge {
   258  		staleAge = maxAge
   259  	}
   260  
   261  	// `cache_size_bytes` field must have a value greater than 0, and if its
   262  	// value is greater than 5M, we cap it at 5M
   263  	cacheSizeBytes := rlsProto.GetCacheSizeBytes()
   264  	if cacheSizeBytes <= 0 {
   265  		return nil, fmt.Errorf("rls: cache_size_bytes must be set to a non-zero value: %+v", rlsProto)
   266  	}
   267  	if cacheSizeBytes > maxCacheSize {
   268  		logger.Info("rls: cache_size_bytes %v is too large, setting it to: %v", cacheSizeBytes, maxCacheSize)
   269  		cacheSizeBytes = maxCacheSize
   270  	}
   271  	return &lbConfig{
   272  		kbMap:                kbMap,
   273  		lookupService:        lookupService,
   274  		lookupServiceTimeout: lookupServiceTimeout,
   275  		maxAge:               maxAge,
   276  		staleAge:             staleAge,
   277  		cacheSizeBytes:       cacheSizeBytes,
   278  		defaultTarget:        rlsProto.GetDefaultTarget(),
   279  	}, nil
   280  }
   281  
   282  // parseChildPolicyConfigs iterates through the list of child policies and picks
   283  // the first registered policy and validates its config.
   284  func parseChildPolicyConfigs(childPolicies []map[string]json.RawMessage, targetFieldName string) (string, map[string]json.RawMessage, error) {
   285  	for i, config := range childPolicies {
   286  		if len(config) != 1 {
   287  			return "", nil, fmt.Errorf("rls: invalid childPolicy: entry %v does not contain exactly 1 policy/config pair: %q", i, config)
   288  		}
   289  
   290  		var name string
   291  		var rawCfg json.RawMessage
   292  		for name, rawCfg = range config {
   293  		}
   294  		builder := balancer.Get(name)
   295  		if builder == nil {
   296  			continue
   297  		}
   298  		parser, ok := builder.(balancer.ConfigParser)
   299  		if !ok {
   300  			return "", nil, fmt.Errorf("rls: childPolicy %q with config %q does not support config parsing", name, string(rawCfg))
   301  		}
   302  
   303  		// To validate child policy configs we do the following:
   304  		// - unmarshal the raw JSON bytes of the child policy config into a map
   305  		// - add an entry with key set to `target_field_name` and a dummy value
   306  		// - marshal the map back to JSON and parse the config using the parser
   307  		// retrieved previously
   308  		var childConfig map[string]json.RawMessage
   309  		if err := json.Unmarshal(rawCfg, &childConfig); err != nil {
   310  			return "", nil, fmt.Errorf("rls: json unmarshal failed for child policy config %q: %v", string(rawCfg), err)
   311  		}
   312  		childConfig[targetFieldName], _ = json.Marshal(dummyChildPolicyTarget)
   313  		jsonCfg, err := json.Marshal(childConfig)
   314  		if err != nil {
   315  			return "", nil, fmt.Errorf("rls: json marshal failed for child policy config {%+v}: %v", childConfig, err)
   316  		}
   317  		if _, err := parser.ParseConfig(jsonCfg); err != nil {
   318  			return "", nil, fmt.Errorf("rls: childPolicy config validation failed: %v", err)
   319  		}
   320  		return name, childConfig, nil
   321  	}
   322  	return "", nil, fmt.Errorf("rls: invalid childPolicy config: no supported policies found in %+v", childPolicies)
   323  }
   324  
   325  func convertDuration(d *durationpb.Duration) (time.Duration, error) {
   326  	if d == nil {
   327  		return 0, nil
   328  	}
   329  	return d.AsDuration(), d.CheckValid()
   330  }