gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/grpc/balancer/rls/internal/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  	"gitee.com/ks-custle/core-gm/grpc/balancer"
    29  	"gitee.com/ks-custle/core-gm/grpc/balancer/rls/internal/keys"
    30  	"gitee.com/ks-custle/core-gm/grpc/internal/pretty"
    31  	rlspb "gitee.com/ks-custle/core-gm/grpc/internal/proto/grpc_lookup_v1"
    32  	"gitee.com/ks-custle/core-gm/grpc/resolver"
    33  	"gitee.com/ks-custle/core-gm/grpc/serviceconfig"
    34  	"github.com/golang/protobuf/ptypes"
    35  	durationpb "github.com/golang/protobuf/ptypes/duration"
    36  	"google.golang.org/protobuf/encoding/protojson"
    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  }
    68  
    69  func (lbCfg *lbConfig) Equal(other *lbConfig) bool {
    70  	return lbCfg.kbMap.Equal(other.kbMap) &&
    71  		lbCfg.lookupService == other.lookupService &&
    72  		lbCfg.lookupServiceTimeout == other.lookupServiceTimeout &&
    73  		lbCfg.maxAge == other.maxAge &&
    74  		lbCfg.staleAge == other.staleAge &&
    75  		lbCfg.cacheSizeBytes == other.cacheSizeBytes &&
    76  		lbCfg.defaultTarget == other.defaultTarget &&
    77  		lbCfg.childPolicyName == other.childPolicyName &&
    78  		lbCfg.childPolicyTargetField == other.childPolicyTargetField &&
    79  		childPolicyConfigEqual(lbCfg.childPolicyConfig, other.childPolicyConfig)
    80  }
    81  
    82  func childPolicyConfigEqual(a, b map[string]json.RawMessage) bool {
    83  	if (b == nil) != (a == nil) {
    84  		return false
    85  	}
    86  	if len(b) != len(a) {
    87  		return false
    88  	}
    89  	for k, jsonA := range a {
    90  		jsonB, ok := b[k]
    91  		if !ok {
    92  			return false
    93  		}
    94  		if !bytes.Equal(jsonA, jsonB) {
    95  			return false
    96  		}
    97  	}
    98  	return true
    99  }
   100  
   101  // This struct resembles the JSON representation of the loadBalancing config
   102  // and makes it easier to unmarshal.
   103  type lbConfigJSON struct {
   104  	RouteLookupConfig                json.RawMessage
   105  	ChildPolicy                      []map[string]json.RawMessage
   106  	ChildPolicyConfigTargetFieldName string
   107  }
   108  
   109  // When parsing a config update, the following validations are performed:
   110  // - routeLookupConfig:
   111  //   - grpc_keybuilders field:
   112  //   - must have at least one entry
   113  //   - must not have two entries with the same `Name`
   114  //   - within each entry:
   115  //   - must have at least one `Name`
   116  //   - must not have a `Name` with the `service` field unset or empty
   117  //   - within each `headers` entry:
   118  //   - must not have `required_match` set
   119  //   - must not have `key` unset or empty
   120  //   - across all `headers`, `constant_keys` and `extra_keys` fields:
   121  //   - must not have the same `key` specified twice
   122  //   - no `key` must be the empty string
   123  //   - `lookup_service` field must be set and and must parse as a target URI
   124  //   - if `max_age` > 5m, it should be set to 5 minutes
   125  //   - if `stale_age` > `max_age`, ignore it
   126  //   - if `stale_age` is set, then `max_age` must also be set
   127  //   - ignore `valid_targets` field
   128  //   - `cache_size_bytes` field must have a value greater than 0, and if its
   129  //     value is greater than 5M, we cap it at 5M
   130  //
   131  // - childPolicy:
   132  //   - must find a valid child policy with a valid config
   133  //
   134  // - childPolicyConfigTargetFieldName:
   135  //   - must be set and non-empty
   136  func (rlsBB) ParseConfig(c json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
   137  	logger.Infof("Received JSON service config: %v", pretty.ToJSON(c))
   138  	cfgJSON := &lbConfigJSON{}
   139  	if err := json.Unmarshal(c, cfgJSON); err != nil {
   140  		return nil, fmt.Errorf("rls: json unmarshal failed for service config %+v: %v", string(c), err)
   141  	}
   142  
   143  	m := protojson.UnmarshalOptions{DiscardUnknown: true}
   144  	rlsProto := &rlspb.RouteLookupConfig{}
   145  	if err := m.Unmarshal(cfgJSON.RouteLookupConfig, rlsProto); err != nil {
   146  		return nil, fmt.Errorf("rls: bad RouteLookupConfig proto %+v: %v", string(cfgJSON.RouteLookupConfig), err)
   147  	}
   148  	lbCfg, err := parseRLSProto(rlsProto)
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  
   153  	if cfgJSON.ChildPolicyConfigTargetFieldName == "" {
   154  		return nil, fmt.Errorf("rls: childPolicyConfigTargetFieldName field is not set in service config %+v", string(c))
   155  	}
   156  	name, config, err := parseChildPolicyConfigs(cfgJSON.ChildPolicy, cfgJSON.ChildPolicyConfigTargetFieldName)
   157  	if err != nil {
   158  		return nil, err
   159  	}
   160  	lbCfg.childPolicyName = name
   161  	lbCfg.childPolicyConfig = config
   162  	lbCfg.childPolicyTargetField = cfgJSON.ChildPolicyConfigTargetFieldName
   163  	return lbCfg, nil
   164  }
   165  
   166  func parseRLSProto(rlsProto *rlspb.RouteLookupConfig) (*lbConfig, error) {
   167  	// Validations specified on the `grpc_keybuilders` field are performed here.
   168  	kbMap, err := keys.MakeBuilderMap(rlsProto)
   169  	if err != nil {
   170  		return nil, err
   171  	}
   172  
   173  	// `lookup_service` field must be set and and must parse as a target URI.
   174  	lookupService := rlsProto.GetLookupService()
   175  	if lookupService == "" {
   176  		return nil, fmt.Errorf("rls: empty lookup_service in route lookup config %+v", rlsProto)
   177  	}
   178  	parsedTarget, err := url.Parse(lookupService)
   179  	if err != nil {
   180  		// url.Parse() fails if scheme is missing. Retry with default scheme.
   181  		parsedTarget, err = url.Parse(resolver.GetDefaultScheme() + ":///" + lookupService)
   182  		if err != nil {
   183  			return nil, fmt.Errorf("rls: invalid target URI in lookup_service %s", lookupService)
   184  		}
   185  	}
   186  	if parsedTarget.Scheme == "" {
   187  		parsedTarget.Scheme = resolver.GetDefaultScheme()
   188  	}
   189  	if resolver.Get(parsedTarget.Scheme) == nil {
   190  		return nil, fmt.Errorf("rls: unregistered scheme in lookup_service %s", lookupService)
   191  	}
   192  
   193  	lookupServiceTimeout, err := convertDuration(rlsProto.GetLookupServiceTimeout())
   194  	if err != nil {
   195  		return nil, fmt.Errorf("rls: failed to parse lookup_service_timeout in route lookup config %+v: %v", rlsProto, err)
   196  	}
   197  	if lookupServiceTimeout == 0 {
   198  		lookupServiceTimeout = defaultLookupServiceTimeout
   199  	}
   200  
   201  	// Validations performed here:
   202  	// - if `max_age` > 5m, it should be set to 5 minutes
   203  	// - if `stale_age` > `max_age`, ignore it
   204  	// - if `stale_age` is set, then `max_age` must also be set
   205  	maxAge, err := convertDuration(rlsProto.GetMaxAge())
   206  	if err != nil {
   207  		return nil, fmt.Errorf("rls: failed to parse max_age in route lookup config %+v: %v", rlsProto, err)
   208  	}
   209  	staleAge, err := convertDuration(rlsProto.GetStaleAge())
   210  	if err != nil {
   211  		return nil, fmt.Errorf("rls: failed to parse staleAge in route lookup config %+v: %v", rlsProto, err)
   212  	}
   213  	if staleAge != 0 && maxAge == 0 {
   214  		return nil, fmt.Errorf("rls: stale_age is set, but max_age is not in route lookup config %+v", rlsProto)
   215  	}
   216  	if staleAge >= maxAge {
   217  		logger.Infof("rls: stale_age %v is not less than max_age %v, ignoring it", staleAge, maxAge)
   218  		staleAge = 0
   219  	}
   220  	if maxAge == 0 || maxAge > maxMaxAge {
   221  		logger.Infof("rls: max_age in route lookup config is %v, using %v", maxAge, maxMaxAge)
   222  		maxAge = maxMaxAge
   223  	}
   224  
   225  	// `cache_size_bytes` field must have a value greater than 0, and if its
   226  	// value is greater than 5M, we cap it at 5M
   227  	cacheSizeBytes := rlsProto.GetCacheSizeBytes()
   228  	if cacheSizeBytes <= 0 {
   229  		return nil, fmt.Errorf("rls: cache_size_bytes must be set to a non-zero value: %+v", rlsProto)
   230  	}
   231  	if cacheSizeBytes > maxCacheSize {
   232  		logger.Info("rls: cache_size_bytes %v is too large, setting it to: %v", cacheSizeBytes, maxCacheSize)
   233  		cacheSizeBytes = maxCacheSize
   234  	}
   235  	return &lbConfig{
   236  		kbMap:                kbMap,
   237  		lookupService:        lookupService,
   238  		lookupServiceTimeout: lookupServiceTimeout,
   239  		maxAge:               maxAge,
   240  		staleAge:             staleAge,
   241  		cacheSizeBytes:       cacheSizeBytes,
   242  		defaultTarget:        rlsProto.GetDefaultTarget(),
   243  	}, nil
   244  }
   245  
   246  // parseChildPolicyConfigs iterates through the list of child policies and picks
   247  // the first registered policy and validates its config.
   248  func parseChildPolicyConfigs(childPolicies []map[string]json.RawMessage, targetFieldName string) (string, map[string]json.RawMessage, error) {
   249  	for i, config := range childPolicies {
   250  		if len(config) != 1 {
   251  			return "", nil, fmt.Errorf("rls: invalid childPolicy: entry %v does not contain exactly 1 policy/config pair: %q", i, config)
   252  		}
   253  
   254  		var name string
   255  		var rawCfg json.RawMessage
   256  		for name, rawCfg = range config {
   257  		}
   258  		builder := balancer.Get(name)
   259  		if builder == nil {
   260  			continue
   261  		}
   262  		parser, ok := builder.(balancer.ConfigParser)
   263  		if !ok {
   264  			return "", nil, fmt.Errorf("rls: childPolicy %q with config %q does not support config parsing", name, string(rawCfg))
   265  		}
   266  
   267  		// To validate child policy configs we do the following:
   268  		// - unmarshal the raw JSON bytes of the child policy config into a map
   269  		// - add an entry with key set to `target_field_name` and a dummy value
   270  		// - marshal the map back to JSON and parse the config using the parser
   271  		// retrieved previously
   272  		var childConfig map[string]json.RawMessage
   273  		if err := json.Unmarshal(rawCfg, &childConfig); err != nil {
   274  			return "", nil, fmt.Errorf("rls: json unmarshal failed for child policy config %q: %v", string(rawCfg), err)
   275  		}
   276  		childConfig[targetFieldName], _ = json.Marshal(dummyChildPolicyTarget)
   277  		jsonCfg, err := json.Marshal(childConfig)
   278  		if err != nil {
   279  			return "", nil, fmt.Errorf("rls: json marshal failed for child policy config {%+v}: %v", childConfig, err)
   280  		}
   281  		if _, err := parser.ParseConfig(jsonCfg); err != nil {
   282  			return "", nil, fmt.Errorf("rls: childPolicy config validation failed: %v", err)
   283  		}
   284  		return name, childConfig, nil
   285  	}
   286  	return "", nil, fmt.Errorf("rls: invalid childPolicy config: no supported policies found in %+v", childPolicies)
   287  }
   288  
   289  func convertDuration(d *durationpb.Duration) (time.Duration, error) {
   290  	if d == nil {
   291  		return 0, nil
   292  	}
   293  	return ptypes.Duration(d)
   294  }