dubbo.apache.org/dubbo-go/v3@v3.1.1/xds/resolver/xds_resolver.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 implements the xds resolver, that does LDS and RDS to find
    25  // the cluster to use.
    26  package resolver
    27  
    28  import (
    29  	"errors"
    30  	"fmt"
    31  	"strings"
    32  )
    33  
    34  import (
    35  	dubbogoLogger "github.com/dubbogo/gost/log/logger"
    36  
    37  	"google.golang.org/grpc/credentials"
    38  
    39  	"google.golang.org/grpc/resolver"
    40  )
    41  
    42  import (
    43  	"dubbo.apache.org/dubbo-go/v3/xds/client"
    44  	"dubbo.apache.org/dubbo-go/v3/xds/client/bootstrap"
    45  	"dubbo.apache.org/dubbo-go/v3/xds/client/resource"
    46  	"dubbo.apache.org/dubbo-go/v3/xds/utils/grpcsync"
    47  	"dubbo.apache.org/dubbo-go/v3/xds/utils/pretty"
    48  	iresolver "dubbo.apache.org/dubbo-go/v3/xds/utils/resolver"
    49  )
    50  
    51  const xdsScheme = "xds"
    52  
    53  // NewBuilder creates a new xds resolver builder using a specific xds bootstrap
    54  // config, so tests can use multiple xds clients in different ClientConns at
    55  // the same time.
    56  func NewBuilder(config []byte) (resolver.Builder, error) {
    57  	return &xdsResolverBuilder{
    58  		newXDSClient: func() (client.XDSClient, error) {
    59  			return client.NewClientWithBootstrapContents(config)
    60  		},
    61  	}, nil
    62  }
    63  
    64  // For overriding in unittests.
    65  var newXDSClient = func() (client.XDSClient, error) { return client.New() }
    66  
    67  func init() {
    68  	resolver.Register(&xdsResolverBuilder{})
    69  }
    70  
    71  type xdsResolverBuilder struct {
    72  	newXDSClient func() (client.XDSClient, error)
    73  }
    74  
    75  // Build helps implement the resolver.Builder interface.
    76  //
    77  // The xds bootstrap process is performed (and a new xds client is built) every
    78  // time an xds resolver is built.
    79  func (b *xdsResolverBuilder) Build(t resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (_ resolver.Resolver, retErr error) {
    80  	r := &xdsResolver{
    81  		target:         t,
    82  		cc:             cc,
    83  		closed:         grpcsync.NewEvent(),
    84  		updateCh:       make(chan suWithError, 1),
    85  		activeClusters: make(map[string]*clusterInfo),
    86  	}
    87  	defer func() {
    88  		if retErr != nil {
    89  			r.Close()
    90  		}
    91  	}()
    92  	r.logger = dubbogoLogger.GetLogger()
    93  	r.logger.Infof("Creating resolver for target: %+v", t)
    94  
    95  	newXDSClient := newXDSClient
    96  	if b.newXDSClient != nil {
    97  		newXDSClient = b.newXDSClient
    98  	}
    99  
   100  	client, err := newXDSClient()
   101  	if err != nil {
   102  		return nil, fmt.Errorf("xds: failed to create xds-client: %v", err)
   103  	}
   104  	r.client = client
   105  	bootstrapConfig := client.BootstrapConfig()
   106  	if bootstrapConfig == nil {
   107  		return nil, errors.New("bootstrap configuration is empty")
   108  	}
   109  
   110  	// If xds credentials were specified by the user, but bootstrap configs do
   111  	// not contain any certificate provider configuration, it is better to fail
   112  	// right now rather than failing when attempting to create certificate
   113  	// providers after receiving an CDS response with security configuration.
   114  	var creds credentials.TransportCredentials
   115  	switch {
   116  	case opts.DialCreds != nil:
   117  		creds = opts.DialCreds
   118  	case opts.CredsBundle != nil:
   119  		creds = opts.CredsBundle.TransportCredentials()
   120  	}
   121  	if xc, ok := creds.(interface{ UsesXDS() bool }); ok && xc.UsesXDS() {
   122  		if len(bootstrapConfig.CertProviderConfigs) == 0 {
   123  			return nil, errors.New("xds: xdsCreds specified but certificate_providers config missing in bootstrap file")
   124  		}
   125  	}
   126  
   127  	// Find the client listener template to use from the bootstrap config:
   128  	// - If authority is not set in the target, use the top level template
   129  	// - If authority is set, use the template from the authority map.
   130  	template := bootstrapConfig.ClientDefaultListenerResourceNameTemplate
   131  	if authority := r.target.URL.Host; authority != "" {
   132  		a := bootstrapConfig.Authorities[authority]
   133  		if a == nil {
   134  			return nil, fmt.Errorf("xds: authority %q is not found in the bootstrap file", authority)
   135  		}
   136  		if a.ClientListenerResourceNameTemplate != "" {
   137  			// This check will never be false, because
   138  			// ClientListenerResourceNameTemplate is required to start with
   139  			// xdstp://, and has a default value (not an empty string) if unset.
   140  			template = a.ClientListenerResourceNameTemplate
   141  		}
   142  	}
   143  	endpoint := r.target.URL.Path
   144  	if endpoint == "" {
   145  		endpoint = r.target.URL.Opaque
   146  	}
   147  	endpoint = strings.TrimPrefix(endpoint, "/")
   148  	resourceName := bootstrap.PopulateResourceTemplate(template, endpoint)
   149  
   150  	// Register a watch on the xdsClient for the user's dial target.
   151  	cancelWatch := watchService(r.client, resourceName, r.handleServiceUpdate, r.logger)
   152  	r.logger.Infof("Watch started on resource name %v with xds-client %p", r.target.Endpoint, r.client)
   153  	r.cancelWatch = func() {
   154  		cancelWatch()
   155  		r.logger.Infof("Watch cancel on resource name %v with xds-client %p", r.target.Endpoint, r.client)
   156  	}
   157  
   158  	go r.run()
   159  	return r, nil
   160  }
   161  
   162  // Name helps implement the resolver.Builder interface.
   163  func (*xdsResolverBuilder) Scheme() string {
   164  	return xdsScheme
   165  }
   166  
   167  // suWithError wraps the ServiceUpdate and error received through a watch API
   168  // callback, so that it can pushed onto the update channel as a single entity.
   169  type suWithError struct {
   170  	su          serviceUpdate
   171  	emptyUpdate bool
   172  	err         error
   173  }
   174  
   175  // xdsResolver implements the resolver.Resolver interface.
   176  //
   177  // It registers a watcher for ServiceConfig updates with the xdsClient object
   178  // (which performs LDS/RDS queries for the same), and passes the received
   179  // updates to the ClientConn.
   180  type xdsResolver struct {
   181  	target resolver.Target
   182  	cc     resolver.ClientConn
   183  	closed *grpcsync.Event
   184  
   185  	logger dubbogoLogger.Logger
   186  
   187  	// The underlying xdsClient which performs all xDS requests and responses.
   188  	client client.XDSClient
   189  	// A channel for the watch API callback to write service updates on to. The
   190  	// updates are read by the run goroutine and passed on to the ClientConn.
   191  	updateCh chan suWithError
   192  	// cancelWatch is the function to cancel the watcher.
   193  	cancelWatch func()
   194  
   195  	// activeClusters is a map from cluster name to a ref count.  Only read or
   196  	// written during a service update (synchronous).
   197  	activeClusters map[string]*clusterInfo
   198  
   199  	curConfigSelector *configSelector
   200  }
   201  
   202  // sendNewServiceConfig prunes active clusters, generates a new service config
   203  // based on the current set of active clusters, and sends an update to the
   204  // channel with that service config and the provided config selector.  Returns
   205  // false if an error occurs while generating the service config and the update
   206  // cannot be sent.
   207  func (r *xdsResolver) sendNewServiceConfig(cs *configSelector) bool {
   208  	// Delete entries from r.activeClusters with zero references;
   209  	// otherwise serviceConfigJSON will generate a config including
   210  	// them.
   211  	r.pruneActiveClusters()
   212  
   213  	if cs == nil && len(r.activeClusters) == 0 {
   214  		// There are no clusters and we are sending a failing configSelector.
   215  		// Send an empty config, which picks pick-first, with no address, and
   216  		// puts the ClientConn into transient failure.
   217  		r.cc.UpdateState(resolver.State{ServiceConfig: r.cc.ParseServiceConfig("{}")})
   218  		return true
   219  	}
   220  
   221  	sc, err := serviceConfigJSON(r.activeClusters)
   222  	if err != nil {
   223  		// JSON marshal error; should never happen.
   224  		r.logger.Errorf("%v", err)
   225  		r.cc.ReportError(err)
   226  		return false
   227  	}
   228  	r.logger.Infof("Received update on resource %v from xds-client %p, generated service config: %v", r.target.Endpoint, r.client, pretty.FormatJSON(sc))
   229  
   230  	// Send the update to the ClientConn.
   231  	state := iresolver.SetConfigSelector(resolver.State{
   232  		ServiceConfig: r.cc.ParseServiceConfig(string(sc)),
   233  	}, cs)
   234  	r.cc.UpdateState(client.SetClient(state, r.client))
   235  	return true
   236  }
   237  
   238  // run is a long running goroutine which blocks on receiving service updates
   239  // and passes it on the ClientConn.
   240  func (r *xdsResolver) run() {
   241  	for {
   242  		select {
   243  		case <-r.closed.Done():
   244  			return
   245  		case update := <-r.updateCh:
   246  			if update.err != nil {
   247  				r.logger.Warnf("Watch error on resource %v from xds-client %p, %v", r.target.Endpoint, r.client, update.err)
   248  				if resource.ErrType(update.err) == resource.ErrorTypeResourceNotFound {
   249  					// If error is resource-not-found, it means the LDS
   250  					// resource was removed. Ultimately send an empty service
   251  					// config, which picks pick-first, with no address, and
   252  					// puts the ClientConn into transient failure.  Before we
   253  					// can do that, we may need to send a normal service config
   254  					// along with an erroring (nil) config selector.
   255  					r.sendNewServiceConfig(nil)
   256  					// Stop and dereference the active config selector, if one exists.
   257  					r.curConfigSelector.stop()
   258  					r.curConfigSelector = nil
   259  					continue
   260  				}
   261  				// Send error to ClientConn, and balancers, if error is not
   262  				// resource not found.  No need to update resolver state if we
   263  				// can keep using the old config.
   264  				r.cc.ReportError(update.err)
   265  				continue
   266  			}
   267  			if update.emptyUpdate {
   268  				r.sendNewServiceConfig(r.curConfigSelector)
   269  				continue
   270  			}
   271  
   272  			// Create the config selector for this update.
   273  			cs, err := r.newConfigSelector(update.su)
   274  			if err != nil {
   275  				r.logger.Warnf("Error parsing update on resource %v from xds-client %p: %v", r.target.Endpoint, r.client, err)
   276  				r.cc.ReportError(err)
   277  				continue
   278  			}
   279  
   280  			if !r.sendNewServiceConfig(cs) {
   281  				// JSON error creating the service config (unexpected); erase
   282  				// this config selector and ignore this update, continuing with
   283  				// the previous config selector.
   284  				cs.stop()
   285  				continue
   286  			}
   287  
   288  			// Decrement references to the old config selector and assign the
   289  			// new one as the current one.
   290  			r.curConfigSelector.stop()
   291  			r.curConfigSelector = cs
   292  		}
   293  	}
   294  }
   295  
   296  // handleServiceUpdate is the callback which handles service updates. It writes
   297  // the received update to the update channel, which is picked by the run
   298  // goroutine.
   299  func (r *xdsResolver) handleServiceUpdate(su serviceUpdate, err error) {
   300  	if r.closed.HasFired() {
   301  		// Do not pass updates to the ClientConn once the resolver is closed.
   302  		return
   303  	}
   304  	// Remove any existing entry in updateCh and replace with the new one.
   305  	select {
   306  	case <-r.updateCh:
   307  	default:
   308  	}
   309  	r.updateCh <- suWithError{su: su, err: err}
   310  }
   311  
   312  // ResolveNow is a no-op at this point.
   313  func (*xdsResolver) ResolveNow(o resolver.ResolveNowOptions) {}
   314  
   315  // Close closes the resolver, and also closes the underlying client.
   316  func (r *xdsResolver) Close() {
   317  	// Note that Close needs to check for nils even if some of them are always
   318  	// set in the constructor. This is because the constructor defers Close() in
   319  	// error cases, and the fields might not be set when the error happens.
   320  	if r.cancelWatch != nil {
   321  		r.cancelWatch()
   322  	}
   323  	if r.client != nil {
   324  		r.client.Close()
   325  	}
   326  	r.closed.Fire()
   327  	r.logger.Infof("Shutdown")
   328  }