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 }