dubbo.apache.org/dubbo-go/v3@v3.1.1/xds/resolver/serviceconfig.go (about)

     1  /*
     2   * Licensed to the Apache Software Foundation (ASF) under one or more
     3   * contributor license agreements.  See the NOTICE file distributed with
     4   * this work for additional information regarding copyright ownership.
     5   * The ASF licenses this file to You under the Apache License, Version 2.0
     6   * (the "License"); you may not use this file except in compliance with
     7   * the License.  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   *
    20   * Copyright 2020 gRPC authors.
    21   *
    22   */
    23  
    24  package resolver
    25  
    26  import (
    27  	"context"
    28  	"encoding/json"
    29  	"fmt"
    30  	"math/bits"
    31  	"strings"
    32  	"sync/atomic"
    33  	"time"
    34  )
    35  
    36  import (
    37  	xxhash "github.com/cespare/xxhash/v2"
    38  
    39  	"google.golang.org/grpc/codes"
    40  
    41  	"google.golang.org/grpc/metadata"
    42  
    43  	"google.golang.org/grpc/status"
    44  )
    45  
    46  import (
    47  	"dubbo.apache.org/dubbo-go/v3/xds/balancer/clustermanager"
    48  	"dubbo.apache.org/dubbo-go/v3/xds/balancer/ringhash"
    49  	"dubbo.apache.org/dubbo-go/v3/xds/client/resource"
    50  	"dubbo.apache.org/dubbo-go/v3/xds/httpfilter"
    51  	"dubbo.apache.org/dubbo-go/v3/xds/httpfilter/router"
    52  	"dubbo.apache.org/dubbo-go/v3/xds/utils/envconfig"
    53  	"dubbo.apache.org/dubbo-go/v3/xds/utils/grpcrand"
    54  	iresolver "dubbo.apache.org/dubbo-go/v3/xds/utils/resolver"
    55  	"dubbo.apache.org/dubbo-go/v3/xds/utils/serviceconfig"
    56  	"dubbo.apache.org/dubbo-go/v3/xds/utils/wrr"
    57  )
    58  
    59  const (
    60  	cdsName                      = "cds_experimental"
    61  	xdsClusterManagerName        = "xds_cluster_manager_experimental"
    62  	clusterPrefix                = "cluster:"
    63  	clusterSpecifierPluginPrefix = "cluster_specifier_plugin:"
    64  )
    65  
    66  type serviceConfig struct {
    67  	LoadBalancingConfig balancerConfig `json:"loadBalancingConfig"`
    68  }
    69  
    70  type balancerConfig []map[string]interface{}
    71  
    72  func newBalancerConfig(name string, config interface{}) balancerConfig {
    73  	return []map[string]interface{}{{name: config}}
    74  }
    75  
    76  type cdsBalancerConfig struct {
    77  	Cluster string `json:"cluster"`
    78  }
    79  
    80  type xdsChildConfig struct {
    81  	ChildPolicy balancerConfig `json:"childPolicy"`
    82  }
    83  
    84  type xdsClusterManagerConfig struct {
    85  	Children map[string]xdsChildConfig `json:"children"`
    86  }
    87  
    88  // pruneActiveClusters deletes entries in r.activeClusters with zero
    89  // references.
    90  func (r *xdsResolver) pruneActiveClusters() {
    91  	for cluster, ci := range r.activeClusters {
    92  		if atomic.LoadInt32(&ci.refCount) == 0 {
    93  			delete(r.activeClusters, cluster)
    94  		}
    95  	}
    96  }
    97  
    98  // serviceConfigJSON produces a service config in JSON format representing all
    99  // the clusters referenced in activeClusters.  This includes clusters with zero
   100  // references, so they must be pruned first.
   101  func serviceConfigJSON(activeClusters map[string]*clusterInfo) ([]byte, error) {
   102  	// Generate children (all entries in activeClusters).
   103  	children := make(map[string]xdsChildConfig)
   104  	for cluster, ci := range activeClusters {
   105  		children[cluster] = ci.cfg
   106  	}
   107  
   108  	sc := serviceConfig{
   109  		LoadBalancingConfig: newBalancerConfig(
   110  			xdsClusterManagerName, xdsClusterManagerConfig{Children: children},
   111  		),
   112  	}
   113  
   114  	bs, err := json.Marshal(sc)
   115  	if err != nil {
   116  		return nil, fmt.Errorf("failed to marshal json: %v", err)
   117  	}
   118  	return bs, nil
   119  }
   120  
   121  type virtualHost struct {
   122  	// map from filter name to its config
   123  	httpFilterConfigOverride map[string]httpfilter.FilterConfig
   124  	// retry policy present in virtual host
   125  	retryConfig *resource.RetryConfig
   126  }
   127  
   128  // routeCluster holds information about a cluster as referenced by a route.
   129  type routeCluster struct {
   130  	name string
   131  	// map from filter name to its config
   132  	httpFilterConfigOverride map[string]httpfilter.FilterConfig
   133  }
   134  
   135  type route struct {
   136  	m                 *resource.CompositeMatcher // converted from route matchers
   137  	clusters          wrr.WRR                    // holds *routeCluster entries
   138  	maxStreamDuration time.Duration
   139  	// map from filter name to its config
   140  	httpFilterConfigOverride map[string]httpfilter.FilterConfig
   141  	retryConfig              *resource.RetryConfig
   142  	hashPolicies             []*resource.HashPolicy
   143  }
   144  
   145  func (r route) String() string {
   146  	return fmt.Sprintf("%s -> { clusters: %v, maxStreamDuration: %v }", r.m.String(), r.clusters, r.maxStreamDuration)
   147  }
   148  
   149  type configSelector struct {
   150  	r                *xdsResolver
   151  	virtualHost      virtualHost
   152  	routes           []route
   153  	clusters         map[string]*clusterInfo
   154  	httpFilterConfig []resource.HTTPFilter
   155  }
   156  
   157  var errNoMatchedRouteFound = status.Errorf(codes.Unavailable, "no matched route was found")
   158  
   159  func (cs *configSelector) SelectConfig(rpcInfo iresolver.RPCInfo) (*iresolver.RPCConfig, error) {
   160  	if cs == nil {
   161  		return nil, status.Errorf(codes.Unavailable, "no valid clusters")
   162  	}
   163  	var rt *route
   164  	// Loop through routes in order and select first match.
   165  	for _, r := range cs.routes {
   166  		if r.m.Match(rpcInfo) {
   167  			rt = &r
   168  			break
   169  		}
   170  	}
   171  	if rt == nil || rt.clusters == nil {
   172  		return nil, errNoMatchedRouteFound
   173  	}
   174  
   175  	cluster, ok := rt.clusters.Next().(*routeCluster)
   176  	if !ok {
   177  		return nil, status.Errorf(codes.Internal, "error retrieving cluster for match: %v (%T)", cluster, cluster)
   178  	}
   179  
   180  	// Add a ref to the selected cluster, as this RPC needs this cluster until
   181  	// it is committed.
   182  	ref := &cs.clusters[cluster.name].refCount
   183  	atomic.AddInt32(ref, 1)
   184  
   185  	interceptor, err := cs.newInterceptor(rt, cluster)
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  
   190  	lbCtx := clustermanager.SetPickedCluster(rpcInfo.Context, cluster.name)
   191  	// Request Hashes are only applicable for a Ring Hash LB.
   192  	if envconfig.XDSRingHash {
   193  		lbCtx = ringhash.SetRequestHash(lbCtx, cs.generateHash(rpcInfo, rt.hashPolicies))
   194  	}
   195  
   196  	config := &iresolver.RPCConfig{
   197  		// Communicate to the LB policy the chosen cluster and request hash, if Ring Hash LB policy.
   198  		Context: lbCtx,
   199  		OnCommitted: func() {
   200  			// When the RPC is committed, the cluster is no longer required.
   201  			// Decrease its ref.
   202  			if v := atomic.AddInt32(ref, -1); v == 0 {
   203  				// This entry will be removed from activeClusters when
   204  				// producing the service config for the empty update.
   205  				select {
   206  				case cs.r.updateCh <- suWithError{emptyUpdate: true}:
   207  				default:
   208  				}
   209  			}
   210  		},
   211  		Interceptor: interceptor,
   212  	}
   213  
   214  	if rt.maxStreamDuration != 0 {
   215  		config.MethodConfig.Timeout = &rt.maxStreamDuration
   216  	}
   217  	if rt.retryConfig != nil {
   218  		config.MethodConfig.RetryPolicy = retryConfigToPolicy(rt.retryConfig)
   219  	} else if cs.virtualHost.retryConfig != nil {
   220  		config.MethodConfig.RetryPolicy = retryConfigToPolicy(cs.virtualHost.retryConfig)
   221  	}
   222  
   223  	return config, nil
   224  }
   225  
   226  func retryConfigToPolicy(config *resource.RetryConfig) *serviceconfig.RetryPolicy {
   227  	return &serviceconfig.RetryPolicy{
   228  		MaxAttempts:          int(config.NumRetries) + 1,
   229  		InitialBackoff:       config.RetryBackoff.BaseInterval,
   230  		MaxBackoff:           config.RetryBackoff.MaxInterval,
   231  		BackoffMultiplier:    2,
   232  		RetryableStatusCodes: config.RetryOn,
   233  	}
   234  }
   235  
   236  func (cs *configSelector) generateHash(rpcInfo iresolver.RPCInfo, hashPolicies []*resource.HashPolicy) uint64 {
   237  	var hash uint64
   238  	var generatedHash bool
   239  	for _, policy := range hashPolicies {
   240  		var policyHash uint64
   241  		var generatedPolicyHash bool
   242  		switch policy.HashPolicyType {
   243  		case resource.HashPolicyTypeHeader:
   244  			md, ok := metadata.FromOutgoingContext(rpcInfo.Context)
   245  			if !ok {
   246  				continue
   247  			}
   248  			values := md.Get(policy.HeaderName)
   249  			// If the header isn't present, no-op.
   250  			if len(values) == 0 {
   251  				continue
   252  			}
   253  			joinedValues := strings.Join(values, ",")
   254  			if policy.Regex != nil {
   255  				joinedValues = policy.Regex.ReplaceAllString(joinedValues, policy.RegexSubstitution)
   256  			}
   257  			policyHash = xxhash.Sum64String(joinedValues)
   258  			generatedHash = true
   259  			generatedPolicyHash = true
   260  		case resource.HashPolicyTypeChannelID:
   261  			// Hash the ClientConn pointer which logically uniquely
   262  			// identifies the client.
   263  			policyHash = xxhash.Sum64String(fmt.Sprintf("%p", &cs.r.cc))
   264  			generatedHash = true
   265  			generatedPolicyHash = true
   266  		}
   267  
   268  		// Deterministically combine the hash policies. Rotating prevents
   269  		// duplicate hash policies from canceling each other out and preserves
   270  		// the 64 bits of entropy.
   271  		if generatedPolicyHash {
   272  			hash = bits.RotateLeft64(hash, 1)
   273  			hash = hash ^ policyHash
   274  		}
   275  
   276  		// If terminal policy and a hash has already been generated, ignore the
   277  		// rest of the policies and use that hash already generated.
   278  		if policy.Terminal && generatedHash {
   279  			break
   280  		}
   281  	}
   282  
   283  	if generatedHash {
   284  		return hash
   285  	}
   286  	// If no generated hash return a random long. In the grand scheme of things
   287  	// this logically will map to choosing a random backend to route request to.
   288  	return grpcrand.Uint64()
   289  }
   290  
   291  func (cs *configSelector) newInterceptor(rt *route, cluster *routeCluster) (iresolver.ClientInterceptor, error) {
   292  	if len(cs.httpFilterConfig) == 0 {
   293  		return nil, nil
   294  	}
   295  	interceptors := make([]iresolver.ClientInterceptor, 0, len(cs.httpFilterConfig))
   296  	for _, filter := range cs.httpFilterConfig {
   297  		if router.IsRouterFilter(filter.Filter) {
   298  			// Ignore any filters after the router filter.  The router itself
   299  			// is currently a nop.
   300  			return &interceptorList{interceptors: interceptors}, nil
   301  		}
   302  		override := cluster.httpFilterConfigOverride[filter.Name] // cluster is highest priority
   303  		if override == nil {
   304  			override = rt.httpFilterConfigOverride[filter.Name] // route is second priority
   305  		}
   306  		if override == nil {
   307  			override = cs.virtualHost.httpFilterConfigOverride[filter.Name] // VH is third & lowest priority
   308  		}
   309  		ib, ok := filter.Filter.(httpfilter.ClientInterceptorBuilder)
   310  		if !ok {
   311  			// Should not happen if it passed xdsClient validation.
   312  			return nil, fmt.Errorf("filter does not support use in client")
   313  		}
   314  		i, err := ib.BuildClientInterceptor(filter.Config, override)
   315  		if err != nil {
   316  			return nil, fmt.Errorf("error constructing filter: %v", err)
   317  		}
   318  		if i != nil {
   319  			interceptors = append(interceptors, i)
   320  		}
   321  	}
   322  	return nil, fmt.Errorf("error in xds config: no router filter present")
   323  }
   324  
   325  // stop decrements refs of all clusters referenced by this config selector.
   326  func (cs *configSelector) stop() {
   327  	// The resolver's old configSelector may be nil.  Handle that here.
   328  	if cs == nil {
   329  		return
   330  	}
   331  	// If any refs drop to zero, we'll need a service config update to delete
   332  	// the cluster.
   333  	needUpdate := false
   334  	// Loops over cs.clusters, but these are pointers to entries in
   335  	// activeClusters.
   336  	for _, ci := range cs.clusters {
   337  		if v := atomic.AddInt32(&ci.refCount, -1); v == 0 {
   338  			needUpdate = true
   339  		}
   340  	}
   341  	// We stop the old config selector immediately after sending a new config
   342  	// selector; we need another update to delete clusters from the config (if
   343  	// we don't have another update pending already).
   344  	if needUpdate {
   345  		select {
   346  		case cs.r.updateCh <- suWithError{emptyUpdate: true}:
   347  		default:
   348  		}
   349  	}
   350  }
   351  
   352  // A global for testing.
   353  var newWRR = wrr.NewRandom
   354  
   355  // newConfigSelector creates the config selector for su; may add entries to
   356  // r.activeClusters for previously-unseen clusters.
   357  func (r *xdsResolver) newConfigSelector(su serviceUpdate) (*configSelector, error) {
   358  	cs := &configSelector{
   359  		r: r,
   360  		virtualHost: virtualHost{
   361  			httpFilterConfigOverride: su.virtualHost.HTTPFilterConfigOverride,
   362  			retryConfig:              su.virtualHost.RetryConfig,
   363  		},
   364  		routes:           make([]route, len(su.virtualHost.Routes)),
   365  		clusters:         make(map[string]*clusterInfo),
   366  		httpFilterConfig: su.ldsConfig.httpFilterConfig,
   367  	}
   368  
   369  	for i, rt := range su.virtualHost.Routes {
   370  		clusters := newWRR()
   371  		if rt.ClusterSpecifierPlugin != "" {
   372  			clusterName := clusterSpecifierPluginPrefix + rt.ClusterSpecifierPlugin
   373  			clusters.Add(&routeCluster{
   374  				name: clusterName,
   375  			}, 1)
   376  			cs.initializeCluster(clusterName, xdsChildConfig{
   377  				ChildPolicy: balancerConfig(su.clusterSpecifierPlugins[rt.ClusterSpecifierPlugin]),
   378  			})
   379  		} else {
   380  			for cluster, wc := range rt.WeightedClusters {
   381  				clusterName := clusterPrefix + cluster
   382  				clusters.Add(&routeCluster{
   383  					name:                     clusterName,
   384  					httpFilterConfigOverride: wc.HTTPFilterConfigOverride,
   385  				}, int64(wc.Weight))
   386  				cs.initializeCluster(clusterName, xdsChildConfig{
   387  					ChildPolicy: newBalancerConfig(cdsName, cdsBalancerConfig{Cluster: cluster}),
   388  				})
   389  			}
   390  		}
   391  		cs.routes[i].clusters = clusters
   392  
   393  		var err error
   394  		cs.routes[i].m, err = resource.RouteToMatcher(rt)
   395  		if err != nil {
   396  			return nil, err
   397  		}
   398  		if rt.MaxStreamDuration == nil {
   399  			cs.routes[i].maxStreamDuration = su.ldsConfig.maxStreamDuration
   400  		} else {
   401  			cs.routes[i].maxStreamDuration = *rt.MaxStreamDuration
   402  		}
   403  
   404  		cs.routes[i].httpFilterConfigOverride = rt.HTTPFilterConfigOverride
   405  		cs.routes[i].retryConfig = rt.RetryConfig
   406  		cs.routes[i].hashPolicies = rt.HashPolicies
   407  	}
   408  
   409  	// Account for this config selector's clusters.  Do this after no further
   410  	// errors may occur.  Note: cs.clusters are pointers to entries in
   411  	// activeClusters.
   412  	for _, ci := range cs.clusters {
   413  		atomic.AddInt32(&ci.refCount, 1)
   414  	}
   415  
   416  	return cs, nil
   417  }
   418  
   419  // initializeCluster initializes entries in cs.clusters map, creating entries in
   420  // r.activeClusters as necessary.  Any created entries will have a ref count set
   421  // to zero as their ref count will be incremented by incRefs.
   422  func (cs *configSelector) initializeCluster(clusterName string, cfg xdsChildConfig) {
   423  	ci := cs.r.activeClusters[clusterName]
   424  	if ci == nil {
   425  		ci = &clusterInfo{refCount: 0}
   426  		cs.r.activeClusters[clusterName] = ci
   427  	}
   428  	cs.clusters[clusterName] = ci
   429  	cs.clusters[clusterName].cfg = cfg
   430  }
   431  
   432  type clusterInfo struct {
   433  	// number of references to this cluster; accessed atomically
   434  	refCount int32
   435  	// cfg is the child configuration for this cluster, containing either the
   436  	// csp config or the cds cluster config.
   437  	cfg xdsChildConfig
   438  }
   439  
   440  type interceptorList struct {
   441  	interceptors []iresolver.ClientInterceptor
   442  }
   443  
   444  func (il *interceptorList) NewStream(ctx context.Context, ri iresolver.RPCInfo, done func(), newStream func(ctx context.Context, done func()) (iresolver.ClientStream, error)) (iresolver.ClientStream, error) {
   445  	for i := len(il.interceptors) - 1; i >= 0; i-- {
   446  		ns := newStream
   447  		interceptor := il.interceptors[i]
   448  		newStream = func(ctx context.Context, done func()) (iresolver.ClientStream, error) {
   449  			return interceptor.NewStream(ctx, ri, done, ns)
   450  		}
   451  	}
   452  	return newStream(ctx, func() {})
   453  }