google.golang.org/grpc@v1.74.2/xds/internal/clients/xdsclient/clientimpl_watchers.go (about)

     1  /*
     2   *
     3   * Copyright 2025 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 xdsclient
    20  
    21  import (
    22  	"fmt"
    23  
    24  	"google.golang.org/grpc/xds/internal/clients/xdsclient/internal/xdsresource"
    25  )
    26  
    27  // wrappingWatcher is a wrapper around an xdsresource.ResourceWatcher that adds
    28  // the node ID to the error messages reported to the watcher.
    29  type wrappingWatcher struct {
    30  	ResourceWatcher
    31  	nodeID string
    32  }
    33  
    34  func (w *wrappingWatcher) AmbientError(err error, done func()) {
    35  	w.ResourceWatcher.AmbientError(fmt.Errorf("[xDS node id: %v]: %w", w.nodeID, err), done)
    36  }
    37  
    38  func (w *wrappingWatcher) ResourceError(err error, done func()) {
    39  	w.ResourceWatcher.ResourceError(fmt.Errorf("[xDS node id: %v]: %w", w.nodeID, err), done)
    40  }
    41  
    42  // WatchResource starts watching the specified resource.
    43  //
    44  // typeURL specifies the resource type implementation to use. The watch fails
    45  // if there is no resource type implementation for the given typeURL. See the
    46  // ResourceTypes field in the Config struct used to create the XDSClient.
    47  //
    48  // The returned function cancels the watch and prevents future calls to the
    49  // watcher.
    50  func (c *XDSClient) WatchResource(typeURL, resourceName string, watcher ResourceWatcher) (cancel func()) {
    51  	// Return early if the client is already closed.
    52  	if c.done.HasFired() {
    53  		logger.Warningf("Watch registered for type %q, but client is closed", typeURL)
    54  		return func() {}
    55  	}
    56  
    57  	watcher = &wrappingWatcher{
    58  		ResourceWatcher: watcher,
    59  		nodeID:          c.config.Node.ID,
    60  	}
    61  
    62  	rType, ok := c.config.ResourceTypes[typeURL]
    63  	if !ok {
    64  		logger.Warningf("ResourceType implementation for resource type url %v is not found", rType.TypeURL)
    65  		watcher.ResourceError(fmt.Errorf("ResourceType implementation for resource type url %v is not found", rType.TypeURL), func() {})
    66  		return func() {}
    67  	}
    68  
    69  	n := xdsresource.ParseName(resourceName)
    70  	a := c.getAuthorityForResource(n)
    71  	if a == nil {
    72  		logger.Warningf("Watch registered for name %q of type %q, authority %q is not found", rType.TypeName, resourceName, n.Authority)
    73  		watcher.ResourceError(fmt.Errorf("authority %q not found in bootstrap config for resource %q", n.Authority, resourceName), func() {})
    74  		return func() {}
    75  	}
    76  	// The watchResource method on the authority is invoked with n.String()
    77  	// instead of resourceName because n.String() canonicalizes the given name.
    78  	// So, two resource names which don't differ in the query string, but only
    79  	// differ in the order of context params will result in the same resource
    80  	// being watched by the authority.
    81  	return a.watchResource(rType, n.String(), watcher)
    82  }
    83  
    84  // Gets the authority for the given resource name.
    85  //
    86  // See examples in this section of the gRFC:
    87  // https://github.com/grpc/proposal/blob/master/A47-xds-federation.md#bootstrap-config-changes
    88  func (c *XDSClient) getAuthorityForResource(name *xdsresource.Name) *authority {
    89  	// For new-style resource names, always lookup the authorities map. If the
    90  	// name does not specify an authority, we will end up looking for an entry
    91  	// in the map with the empty string as the key.
    92  	if name.Scheme == xdsresource.FederationScheme {
    93  		return c.authorities[name.Authority]
    94  	}
    95  
    96  	// For old-style resource names, we use the top-level authority if the name
    97  	// does not specify an authority.
    98  	if name.Authority == "" {
    99  		return c.topLevelAuthority
   100  	}
   101  	return c.authorities[name.Authority]
   102  }