dubbo.apache.org/dubbo-go/v3@v3.1.1/xds/balancer/clusterresolver/clusterresolver.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 2021 gRPC authors.
    21   *
    22   */
    23  
    24  // Package clusterresolver contains EDS balancer implementation.
    25  package clusterresolver
    26  
    27  import (
    28  	"encoding/json"
    29  	"errors"
    30  	"fmt"
    31  )
    32  
    33  import (
    34  	dubbogoLogger "github.com/dubbogo/gost/log/logger"
    35  
    36  	"google.golang.org/grpc/attributes"
    37  
    38  	"google.golang.org/grpc/balancer"
    39  	"google.golang.org/grpc/balancer/base"
    40  
    41  	"google.golang.org/grpc/connectivity"
    42  
    43  	"google.golang.org/grpc/resolver"
    44  
    45  	"google.golang.org/grpc/serviceconfig"
    46  )
    47  
    48  import (
    49  	"dubbo.apache.org/dubbo-go/v3/xds/balancer/priority"
    50  	"dubbo.apache.org/dubbo-go/v3/xds/client"
    51  	"dubbo.apache.org/dubbo-go/v3/xds/client/resource"
    52  	"dubbo.apache.org/dubbo-go/v3/xds/utils/buffer"
    53  	"dubbo.apache.org/dubbo-go/v3/xds/utils/grpcsync"
    54  	"dubbo.apache.org/dubbo-go/v3/xds/utils/pretty"
    55  )
    56  
    57  // Name is the name of the cluster_resolver balancer.
    58  const Name = "cluster_resolver_experimental"
    59  
    60  var (
    61  	errBalancerClosed = errors.New("cdsBalancer is closed")
    62  	newChildBalancer  = func(bb balancer.Builder, cc balancer.ClientConn, o balancer.BuildOptions) balancer.Balancer {
    63  		return bb.Build(cc, o)
    64  	}
    65  )
    66  
    67  func init() {
    68  	balancer.Register(bb{})
    69  }
    70  
    71  type bb struct{}
    72  
    73  // Build helps implement the balancer.Builder interface.
    74  func (bb) Build(cc balancer.ClientConn, opts balancer.BuildOptions) balancer.Balancer {
    75  	priorityBuilder := balancer.Get(priority.Name)
    76  	if priorityBuilder == nil {
    77  		dubbogoLogger.Errorf("priority balancer is needed but not registered")
    78  		return nil
    79  	}
    80  	priorityConfigParser, ok := priorityBuilder.(balancer.ConfigParser)
    81  	if !ok {
    82  		dubbogoLogger.Errorf("priority balancer builder is not a config parser")
    83  		return nil
    84  	}
    85  
    86  	b := &clusterResolverBalancer{
    87  		bOpts:    opts,
    88  		updateCh: buffer.NewUnbounded(),
    89  		closed:   grpcsync.NewEvent(),
    90  		done:     grpcsync.NewEvent(),
    91  
    92  		priorityBuilder:      priorityBuilder,
    93  		priorityConfigParser: priorityConfigParser,
    94  	}
    95  	b.logger = dubbogoLogger.GetLogger()
    96  	b.logger.Infof("Created")
    97  
    98  	b.resourceWatcher = newResourceResolver(b)
    99  	b.cc = &ccWrapper{
   100  		ClientConn:      cc,
   101  		resourceWatcher: b.resourceWatcher,
   102  	}
   103  
   104  	go b.run()
   105  	return b
   106  }
   107  
   108  func (bb) Name() string {
   109  	return Name
   110  }
   111  
   112  func (bb) ParseConfig(c json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
   113  	var cfg LBConfig
   114  	if err := json.Unmarshal(c, &cfg); err != nil {
   115  		return nil, fmt.Errorf("unable to unmarshal balancer config %s into cluster-resolver config, error: %v", string(c), err)
   116  	}
   117  	return &cfg, nil
   118  }
   119  
   120  // ccUpdate wraps a clientConn update received from gRPC (pushed from the
   121  // xdsResolver).
   122  type ccUpdate struct {
   123  	state balancer.ClientConnState
   124  	err   error
   125  }
   126  
   127  // scUpdate wraps a subConn update received from gRPC. This is directly passed
   128  // on to the child balancer.
   129  type scUpdate struct {
   130  	subConn balancer.SubConn
   131  	state   balancer.SubConnState
   132  }
   133  
   134  type exitIdle struct{}
   135  
   136  // clusterResolverBalancer manages xdsClient and the actual EDS balancer implementation that
   137  // does load balancing.
   138  //
   139  // It currently has only an clusterResolverBalancer. Later, we may add fallback.
   140  type clusterResolverBalancer struct {
   141  	cc              balancer.ClientConn
   142  	bOpts           balancer.BuildOptions
   143  	updateCh        *buffer.Unbounded // Channel for updates from gRPC.
   144  	resourceWatcher *resourceResolver
   145  	logger          dubbogoLogger.Logger
   146  	closed          *grpcsync.Event
   147  	done            *grpcsync.Event
   148  
   149  	priorityBuilder      balancer.Builder
   150  	priorityConfigParser balancer.ConfigParser
   151  
   152  	config          *LBConfig
   153  	configRaw       *serviceconfig.ParseResult
   154  	xdsClient       client.XDSClient       // xDS client to watch EDS resource.
   155  	attrsWithClient *attributes.Attributes // Attributes with xdsClient attached to be passed to the child policies.
   156  
   157  	child               balancer.Balancer
   158  	priorities          []priorityConfig
   159  	watchUpdateReceived bool
   160  }
   161  
   162  // handleClientConnUpdate handles a ClientConnUpdate received from gRPC. Good
   163  // updates lead to registration of EDS and DNS watches. Updates with error lead
   164  // to cancellation of existing watch and propagation of the same error to the
   165  // child balancer.
   166  func (b *clusterResolverBalancer) handleClientConnUpdate(update *ccUpdate) {
   167  	// We first handle errors, if any, and then proceed with handling the
   168  	// update, only if the status quo has changed.
   169  	if err := update.err; err != nil {
   170  		b.handleErrorFromUpdate(err, true)
   171  		return
   172  	}
   173  
   174  	b.logger.Infof("Receive update from resolver, balancer config: %v", pretty.ToJSON(update.state.BalancerConfig))
   175  	cfg, _ := update.state.BalancerConfig.(*LBConfig)
   176  	if cfg == nil {
   177  		b.logger.Warnf("xds: unexpected LoadBalancingConfig type: %T", update.state.BalancerConfig)
   178  		return
   179  	}
   180  
   181  	b.config = cfg
   182  	b.configRaw = update.state.ResolverState.ServiceConfig
   183  	b.resourceWatcher.updateMechanisms(cfg.DiscoveryMechanisms)
   184  
   185  	if !b.watchUpdateReceived {
   186  		// If update was not received, wait for it.
   187  		return
   188  	}
   189  	// If eds resp was received before this, the child policy was created. We
   190  	// need to generate a new balancer config and send it to the child, because
   191  	// certain fields (unrelated to EDS watch) might have changed.
   192  	if err := b.updateChildConfig(); err != nil {
   193  		b.logger.Warnf("failed to update child policy config: %v", err)
   194  	}
   195  }
   196  
   197  // handleWatchUpdate handles a watch update from the xDS Client. Good updates
   198  // lead to clientConn updates being invoked on the underlying child balancer.
   199  func (b *clusterResolverBalancer) handleWatchUpdate(update *resourceUpdate) {
   200  	if err := update.err; err != nil {
   201  		b.logger.Warnf("Watch error from xds-client %p: %v", b.xdsClient, err)
   202  		b.handleErrorFromUpdate(err, false)
   203  		return
   204  	}
   205  
   206  	b.logger.Infof("resource update: %+v", pretty.ToJSON(update.priorities))
   207  	b.watchUpdateReceived = true
   208  	b.priorities = update.priorities
   209  
   210  	// A new EDS update triggers new child configs (e.g. different priorities
   211  	// for the priority balancer), and new addresses (the endpoints come from
   212  	// the EDS response).
   213  	if err := b.updateChildConfig(); err != nil {
   214  		b.logger.Warnf("failed to update child policy's balancer config: %v", err)
   215  	}
   216  }
   217  
   218  // updateChildConfig builds a balancer config from eb's cached eds resp and
   219  // service config, and sends that to the child balancer. Note that it also
   220  // generates the addresses, because the endpoints come from the EDS resp.
   221  //
   222  // If child balancer doesn't already exist, one will be created.
   223  func (b *clusterResolverBalancer) updateChildConfig() error {
   224  	// Child was build when the first EDS resp was received, so we just build
   225  	// the config and addresses.
   226  	if b.child == nil {
   227  		b.child = newChildBalancer(b.priorityBuilder, b.cc, b.bOpts)
   228  	}
   229  
   230  	childCfgBytes, addrs, err := buildPriorityConfigJSON(b.priorities, b.config.XDSLBPolicy)
   231  	if err != nil {
   232  		return fmt.Errorf("failed to build priority balancer config: %v", err)
   233  	}
   234  	childCfg, err := b.priorityConfigParser.ParseConfig(childCfgBytes)
   235  	if err != nil {
   236  		return fmt.Errorf("failed to parse generated priority balancer config, this should never happen because the config is generated: %v", err)
   237  	}
   238  	b.logger.Infof("build balancer config: %v", pretty.ToJSON(childCfg))
   239  	return b.child.UpdateClientConnState(balancer.ClientConnState{
   240  		ResolverState: resolver.State{
   241  			Addresses:     addrs,
   242  			ServiceConfig: b.configRaw,
   243  			Attributes:    b.attrsWithClient,
   244  		},
   245  		BalancerConfig: childCfg,
   246  	})
   247  }
   248  
   249  // handleErrorFromUpdate handles both the error from parent ClientConn (from CDS
   250  // balancer) and the error from xds client (from the watcher). fromParent is
   251  // true if error is from parent ClientConn.
   252  //
   253  // If the error is connection error, it should be handled for fallback purposes.
   254  //
   255  // If the error is resource-not-found:
   256  // - If it's from CDS balancer (shows as a resolver error), it means LDS or CDS
   257  // resources were removed. The EDS watch should be canceled.
   258  // - If it's from xds client, it means EDS resource were removed. The EDS
   259  // watcher should keep watching.
   260  // In both cases, the sub-balancers will be receive the error.
   261  func (b *clusterResolverBalancer) handleErrorFromUpdate(err error, fromParent bool) {
   262  	b.logger.Warnf("Received error: %v", err)
   263  	if fromParent && resource.ErrType(err) == resource.ErrorTypeResourceNotFound {
   264  		// This is an error from the parent ClientConn (can be the parent CDS
   265  		// balancer), and is a resource-not-found error. This means the resource
   266  		// (can be either LDS or CDS) was removed. Stop the EDS watch.
   267  		b.resourceWatcher.stop()
   268  	}
   269  	if b.child != nil {
   270  		b.child.ResolverError(err)
   271  	} else {
   272  		// If eds balancer was never created, fail the RPCs with errors.
   273  		b.cc.UpdateState(balancer.State{
   274  			ConnectivityState: connectivity.TransientFailure,
   275  			Picker:            base.NewErrPicker(err),
   276  		})
   277  	}
   278  
   279  }
   280  
   281  // run is a long-running goroutine which handles all updates from gRPC and
   282  // client. All methods which are invoked directly by gRPC or xdsClient simply
   283  // push an update onto a channel which is read and acted upon right here.
   284  func (b *clusterResolverBalancer) run() {
   285  	for {
   286  		select {
   287  		case u := <-b.updateCh.Get():
   288  			b.updateCh.Load()
   289  			switch update := u.(type) {
   290  			case *ccUpdate:
   291  				b.handleClientConnUpdate(update)
   292  			case *scUpdate:
   293  				// SubConn updates are simply handed over to the underlying
   294  				// child balancer.
   295  				if b.child == nil {
   296  					b.logger.Errorf("xds: received scUpdate {%+v} with no child balancer", update)
   297  					break
   298  				}
   299  				b.child.UpdateSubConnState(update.subConn, update.state)
   300  			case exitIdle:
   301  				if b.child == nil {
   302  					b.logger.Errorf("xds: received ExitIdle with no child balancer")
   303  					break
   304  				}
   305  				// This implementation assumes the child balancer supports
   306  				// ExitIdle (but still checks for the interface's existence to
   307  				// avoid a panic if not).  If the child does not, no subconns
   308  				// will be connected.
   309  				if ei, ok := b.child.(balancer.ExitIdler); ok {
   310  					ei.ExitIdle()
   311  				}
   312  			}
   313  		case u := <-b.resourceWatcher.updateChannel:
   314  			b.handleWatchUpdate(u)
   315  
   316  		// Close results in cancellation of the EDS watch and closing of the
   317  		// underlying child policy and is the only way to exit this goroutine.
   318  		case <-b.closed.Done():
   319  			b.resourceWatcher.stop()
   320  
   321  			if b.child != nil {
   322  				b.child.Close()
   323  				b.child = nil
   324  			}
   325  			// This is the *ONLY* point of return from this function.
   326  			b.logger.Infof("Shutdown")
   327  			b.done.Fire()
   328  			return
   329  		}
   330  	}
   331  }
   332  
   333  // Following are methods to implement the balancer interface.
   334  
   335  // UpdateClientConnState receives the serviceConfig (which contains the
   336  // clusterName to watch for in CDS) and the xdsClient object from the
   337  // xdsResolver.
   338  func (b *clusterResolverBalancer) UpdateClientConnState(state balancer.ClientConnState) error {
   339  	if b.closed.HasFired() {
   340  		b.logger.Warnf("xds: received ClientConnState {%+v} after clusterResolverBalancer was closed", state)
   341  		return errBalancerClosed
   342  	}
   343  
   344  	if b.xdsClient == nil {
   345  		c := client.FromResolverState(state.ResolverState)
   346  		if c == nil {
   347  			return balancer.ErrBadResolverState
   348  		}
   349  		b.xdsClient = c
   350  		b.attrsWithClient = state.ResolverState.Attributes
   351  	}
   352  
   353  	b.updateCh.Put(&ccUpdate{state: state})
   354  	return nil
   355  }
   356  
   357  // ResolverError handles errors reported by the xdsResolver.
   358  func (b *clusterResolverBalancer) ResolverError(err error) {
   359  	if b.closed.HasFired() {
   360  		b.logger.Warnf("xds: received resolver error {%v} after clusterResolverBalancer was closed", err)
   361  		return
   362  	}
   363  	b.updateCh.Put(&ccUpdate{err: err})
   364  }
   365  
   366  // UpdateSubConnState handles subConn updates from gRPC.
   367  func (b *clusterResolverBalancer) UpdateSubConnState(sc balancer.SubConn, state balancer.SubConnState) {
   368  	if b.closed.HasFired() {
   369  		b.logger.Warnf("xds: received subConn update {%v, %v} after clusterResolverBalancer was closed", sc, state)
   370  		return
   371  	}
   372  	b.updateCh.Put(&scUpdate{subConn: sc, state: state})
   373  }
   374  
   375  // Close closes the cdsBalancer and the underlying child balancer.
   376  func (b *clusterResolverBalancer) Close() {
   377  	b.closed.Fire()
   378  	<-b.done.Done()
   379  }
   380  
   381  func (b *clusterResolverBalancer) ExitIdle() {
   382  	b.updateCh.Put(exitIdle{})
   383  }
   384  
   385  // ccWrapper overrides ResolveNow(), so that re-resolution from the child
   386  // policies will trigger the DNS resolver in cluster_resolver balancer.
   387  type ccWrapper struct {
   388  	balancer.ClientConn
   389  	resourceWatcher *resourceResolver
   390  }
   391  
   392  func (c *ccWrapper) ResolveNow(resolver.ResolveNowOptions) {
   393  	c.resourceWatcher.resolveNow()
   394  }