github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/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  	"github.com/golang/protobuf/ptypes"
    29  	durationpb "github.com/golang/protobuf/ptypes/duration"
    30  	"github.com/hxx258456/ccgo/grpc/balancer"
    31  	"github.com/hxx258456/ccgo/grpc/balancer/rls/internal/keys"
    32  	"github.com/hxx258456/ccgo/grpc/internal/pretty"
    33  	rlspb "github.com/hxx258456/ccgo/grpc/internal/proto/grpc_lookup_v1"
    34  	"github.com/hxx258456/ccgo/grpc/resolver"
    35  	"github.com/hxx258456/ccgo/grpc/serviceconfig"
    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  // - childPolicy:
   131  //   - must find a valid child policy with a valid config
   132  // - childPolicyConfigTargetFieldName:
   133  //   - must be set and non-empty
   134  func (rlsBB) ParseConfig(c json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
   135  	logger.Infof("Received JSON service config: %v", pretty.ToJSON(c))
   136  	cfgJSON := &lbConfigJSON{}
   137  	if err := json.Unmarshal(c, cfgJSON); err != nil {
   138  		return nil, fmt.Errorf("rls: json unmarshal failed for service config %+v: %v", string(c), err)
   139  	}
   140  
   141  	m := protojson.UnmarshalOptions{DiscardUnknown: true}
   142  	rlsProto := &rlspb.RouteLookupConfig{}
   143  	if err := m.Unmarshal(cfgJSON.RouteLookupConfig, rlsProto); err != nil {
   144  		return nil, fmt.Errorf("rls: bad RouteLookupConfig proto %+v: %v", string(cfgJSON.RouteLookupConfig), err)
   145  	}
   146  	lbCfg, err := parseRLSProto(rlsProto)
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  
   151  	if cfgJSON.ChildPolicyConfigTargetFieldName == "" {
   152  		return nil, fmt.Errorf("rls: childPolicyConfigTargetFieldName field is not set in service config %+v", string(c))
   153  	}
   154  	name, config, err := parseChildPolicyConfigs(cfgJSON.ChildPolicy, cfgJSON.ChildPolicyConfigTargetFieldName)
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  	lbCfg.childPolicyName = name
   159  	lbCfg.childPolicyConfig = config
   160  	lbCfg.childPolicyTargetField = cfgJSON.ChildPolicyConfigTargetFieldName
   161  	return lbCfg, nil
   162  }
   163  
   164  func parseRLSProto(rlsProto *rlspb.RouteLookupConfig) (*lbConfig, error) {
   165  	// Validations specified on the `grpc_keybuilders` field are performed here.
   166  	kbMap, err := keys.MakeBuilderMap(rlsProto)
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  
   171  	// `lookup_service` field must be set and and must parse as a target URI.
   172  	lookupService := rlsProto.GetLookupService()
   173  	if lookupService == "" {
   174  		return nil, fmt.Errorf("rls: empty lookup_service in route lookup config %+v", rlsProto)
   175  	}
   176  	parsedTarget, err := url.Parse(lookupService)
   177  	if err != nil {
   178  		// url.Parse() fails if scheme is missing. Retry with default scheme.
   179  		parsedTarget, err = url.Parse(resolver.GetDefaultScheme() + ":///" + lookupService)
   180  		if err != nil {
   181  			return nil, fmt.Errorf("rls: invalid target URI in lookup_service %s", lookupService)
   182  		}
   183  	}
   184  	if parsedTarget.Scheme == "" {
   185  		parsedTarget.Scheme = resolver.GetDefaultScheme()
   186  	}
   187  	if resolver.Get(parsedTarget.Scheme) == nil {
   188  		return nil, fmt.Errorf("rls: unregistered scheme in lookup_service %s", lookupService)
   189  	}
   190  
   191  	lookupServiceTimeout, err := convertDuration(rlsProto.GetLookupServiceTimeout())
   192  	if err != nil {
   193  		return nil, fmt.Errorf("rls: failed to parse lookup_service_timeout in route lookup config %+v: %v", rlsProto, err)
   194  	}
   195  	if lookupServiceTimeout == 0 {
   196  		lookupServiceTimeout = defaultLookupServiceTimeout
   197  	}
   198  
   199  	// Validations performed here:
   200  	// - if `max_age` > 5m, it should be set to 5 minutes
   201  	// - if `stale_age` > `max_age`, ignore it
   202  	// - if `stale_age` is set, then `max_age` must also be set
   203  	maxAge, err := convertDuration(rlsProto.GetMaxAge())
   204  	if err != nil {
   205  		return nil, fmt.Errorf("rls: failed to parse max_age in route lookup config %+v: %v", rlsProto, err)
   206  	}
   207  	staleAge, err := convertDuration(rlsProto.GetStaleAge())
   208  	if err != nil {
   209  		return nil, fmt.Errorf("rls: failed to parse staleAge in route lookup config %+v: %v", rlsProto, err)
   210  	}
   211  	if staleAge != 0 && maxAge == 0 {
   212  		return nil, fmt.Errorf("rls: stale_age is set, but max_age is not in route lookup config %+v", rlsProto)
   213  	}
   214  	if staleAge >= maxAge {
   215  		logger.Infof("rls: stale_age %v is not less than max_age %v, ignoring it", staleAge, maxAge)
   216  		staleAge = 0
   217  	}
   218  	if maxAge == 0 || maxAge > maxMaxAge {
   219  		logger.Infof("rls: max_age in route lookup config is %v, using %v", maxAge, maxMaxAge)
   220  		maxAge = maxMaxAge
   221  	}
   222  
   223  	// `cache_size_bytes` field must have a value greater than 0, and if its
   224  	// value is greater than 5M, we cap it at 5M
   225  	cacheSizeBytes := rlsProto.GetCacheSizeBytes()
   226  	if cacheSizeBytes <= 0 {
   227  		return nil, fmt.Errorf("rls: cache_size_bytes must be set to a non-zero value: %+v", rlsProto)
   228  	}
   229  	if cacheSizeBytes > maxCacheSize {
   230  		logger.Info("rls: cache_size_bytes %v is too large, setting it to: %v", cacheSizeBytes, maxCacheSize)
   231  		cacheSizeBytes = maxCacheSize
   232  	}
   233  	return &lbConfig{
   234  		kbMap:                kbMap,
   235  		lookupService:        lookupService,
   236  		lookupServiceTimeout: lookupServiceTimeout,
   237  		maxAge:               maxAge,
   238  		staleAge:             staleAge,
   239  		cacheSizeBytes:       cacheSizeBytes,
   240  		defaultTarget:        rlsProto.GetDefaultTarget(),
   241  	}, nil
   242  }
   243  
   244  // parseChildPolicyConfigs iterates through the list of child policies and picks
   245  // the first registered policy and validates its config.
   246  func parseChildPolicyConfigs(childPolicies []map[string]json.RawMessage, targetFieldName string) (string, map[string]json.RawMessage, error) {
   247  	for i, config := range childPolicies {
   248  		if len(config) != 1 {
   249  			return "", nil, fmt.Errorf("rls: invalid childPolicy: entry %v does not contain exactly 1 policy/config pair: %q", i, config)
   250  		}
   251  
   252  		var name string
   253  		var rawCfg json.RawMessage
   254  		for name, rawCfg = range config {
   255  		}
   256  		builder := balancer.Get(name)
   257  		if builder == nil {
   258  			continue
   259  		}
   260  		parser, ok := builder.(balancer.ConfigParser)
   261  		if !ok {
   262  			return "", nil, fmt.Errorf("rls: childPolicy %q with config %q does not support config parsing", name, string(rawCfg))
   263  		}
   264  
   265  		// To validate child policy configs we do the following:
   266  		// - unmarshal the raw JSON bytes of the child policy config into a map
   267  		// - add an entry with key set to `target_field_name` and a dummy value
   268  		// - marshal the map back to JSON and parse the config using the parser
   269  		// retrieved previously
   270  		var childConfig map[string]json.RawMessage
   271  		if err := json.Unmarshal(rawCfg, &childConfig); err != nil {
   272  			return "", nil, fmt.Errorf("rls: json unmarshal failed for child policy config %q: %v", string(rawCfg), err)
   273  		}
   274  		childConfig[targetFieldName], _ = json.Marshal(dummyChildPolicyTarget)
   275  		jsonCfg, err := json.Marshal(childConfig)
   276  		if err != nil {
   277  			return "", nil, fmt.Errorf("rls: json marshal failed for child policy config {%+v}: %v", childConfig, err)
   278  		}
   279  		if _, err := parser.ParseConfig(jsonCfg); err != nil {
   280  			return "", nil, fmt.Errorf("rls: childPolicy config validation failed: %v", err)
   281  		}
   282  		return name, childConfig, nil
   283  	}
   284  	return "", nil, fmt.Errorf("rls: invalid childPolicy config: no supported policies found in %+v", childPolicies)
   285  }
   286  
   287  func convertDuration(d *durationpb.Duration) (time.Duration, error) {
   288  	if d == nil {
   289  		return 0, nil
   290  	}
   291  	return ptypes.Duration(d)
   292  }