google.golang.org/grpc@v1.72.2/xds/internal/server/rds_handler.go (about)

     1  /*
     2   *
     3   * Copyright 2021 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 server
    20  
    21  import (
    22  	"fmt"
    23  	"sync"
    24  
    25  	igrpclog "google.golang.org/grpc/internal/grpclog"
    26  	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
    27  )
    28  
    29  // rdsHandler handles any RDS queries that need to be started for a given server
    30  // side listeners Filter Chains (i.e. not inline). It persists rdsWatcher
    31  // updates for later use and also determines whether all the rdsWatcher updates
    32  // needed have been received or not.
    33  type rdsHandler struct {
    34  	xdsC      XDSClient
    35  	xdsNodeID string
    36  	logger    *igrpclog.PrefixLogger
    37  
    38  	callback func(string, rdsWatcherUpdate)
    39  
    40  	// updates is a map from routeName to rdsWatcher update, including
    41  	// RouteConfiguration resources and any errors received. If not written in
    42  	// this map, no RouteConfiguration or error for that route name yet. If
    43  	// update set in value, use that as valid route configuration, otherwise
    44  	// treat as an error case and fail at L7 level.
    45  	updates map[string]rdsWatcherUpdate
    46  
    47  	mu      sync.Mutex
    48  	cancels map[string]func()
    49  }
    50  
    51  // newRDSHandler creates a new rdsHandler to watch for RouteConfiguration
    52  // resources. listenerWrapper updates the list of route names to watch by
    53  // calling updateRouteNamesToWatch() upon receipt of new Listener configuration.
    54  func newRDSHandler(cb func(string, rdsWatcherUpdate), xdsC XDSClient, logger *igrpclog.PrefixLogger) *rdsHandler {
    55  	r := &rdsHandler{
    56  		xdsC:     xdsC,
    57  		logger:   logger,
    58  		callback: cb,
    59  		updates:  make(map[string]rdsWatcherUpdate),
    60  		cancels:  make(map[string]func()),
    61  	}
    62  	r.xdsNodeID = xdsC.BootstrapConfig().Node().GetId()
    63  	return r
    64  }
    65  
    66  // updateRouteNamesToWatch handles a list of route names to watch for a given
    67  // server side listener (if a filter chain specifies dynamic
    68  // RouteConfiguration). This function handles all the logic with respect to any
    69  // routes that may have been added or deleted as compared to what was previously
    70  // present. Must be called within an xDS Client callback.
    71  func (rh *rdsHandler) updateRouteNamesToWatch(routeNamesToWatch map[string]bool) {
    72  	rh.mu.Lock()
    73  	defer rh.mu.Unlock()
    74  	// Add and start watches for any new routes in routeNamesToWatch.
    75  	for routeName := range routeNamesToWatch {
    76  		if _, ok := rh.cancels[routeName]; !ok {
    77  			// The xDS client keeps a reference to the watcher until the cancel
    78  			// func is invoked. So, we don't need to keep a reference for fear
    79  			// of it being garbage collected.
    80  			w := &rdsWatcher{parent: rh, routeName: routeName}
    81  			cancel := xdsresource.WatchRouteConfig(rh.xdsC, routeName, w)
    82  			// Set bit on cancel function to eat any RouteConfiguration calls
    83  			// for this watcher after it has been canceled.
    84  			rh.cancels[routeName] = func() {
    85  				w.mu.Lock()
    86  				w.canceled = true
    87  				w.mu.Unlock()
    88  				cancel()
    89  			}
    90  		}
    91  	}
    92  
    93  	// Delete and cancel watches for any routes from persisted routeNamesToWatch
    94  	// that are no longer present.
    95  	for routeName := range rh.cancels {
    96  		if _, ok := routeNamesToWatch[routeName]; !ok {
    97  			rh.cancels[routeName]()
    98  			delete(rh.cancels, routeName)
    99  			delete(rh.updates, routeName)
   100  		}
   101  	}
   102  }
   103  
   104  // determines if all dynamic RouteConfiguration needed has received
   105  // configuration or update. Must be called from an xDS Client Callback.
   106  func (rh *rdsHandler) determineRouteConfigurationReady() bool {
   107  	// Safe to read cancels because only written to in other parts of xDS Client
   108  	// Callbacks, which are sync.
   109  	return len(rh.updates) == len(rh.cancels)
   110  }
   111  
   112  // Must be called from an xDS Client Callback.
   113  func (rh *rdsHandler) handleRouteUpdate(routeName string, update rdsWatcherUpdate) {
   114  	rwu := rh.updates[routeName]
   115  
   116  	// Accept the new update if any of the following are true:
   117  	// 1. we had no valid update data.
   118  	// 2. the update is valid.
   119  	// 3. the update error is ResourceNotFound.
   120  	if rwu.data == nil || update.err == nil || xdsresource.ErrType(update.err) == xdsresource.ErrorTypeResourceNotFound {
   121  		rwu = update
   122  	}
   123  	rh.updates[routeName] = rwu
   124  	rh.callback(routeName, rwu)
   125  }
   126  
   127  // close() is meant to be called by wrapped listener when the wrapped listener
   128  // is closed, and it cleans up resources by canceling all the active RDS
   129  // watches.
   130  func (rh *rdsHandler) close() {
   131  	rh.mu.Lock()
   132  	defer rh.mu.Unlock()
   133  	for _, cancel := range rh.cancels {
   134  		cancel()
   135  	}
   136  }
   137  
   138  type rdsWatcherUpdate struct {
   139  	data *xdsresource.RouteConfigUpdate
   140  	err  error
   141  }
   142  
   143  // rdsWatcher implements the xdsresource.RouteConfigWatcher interface and is
   144  // passed to the WatchRouteConfig API.
   145  type rdsWatcher struct {
   146  	parent    *rdsHandler
   147  	logger    *igrpclog.PrefixLogger
   148  	routeName string
   149  
   150  	mu       sync.Mutex
   151  	canceled bool // eats callbacks if true
   152  }
   153  
   154  func (rw *rdsWatcher) OnUpdate(update *xdsresource.RouteConfigResourceData, onDone xdsresource.OnDoneFunc) {
   155  	defer onDone()
   156  	rw.mu.Lock()
   157  	if rw.canceled {
   158  		rw.mu.Unlock()
   159  		return
   160  	}
   161  	rw.mu.Unlock()
   162  	if rw.logger.V(2) {
   163  		rw.logger.Infof("RDS watch for resource %q received update: %#v", rw.routeName, update.Resource)
   164  	}
   165  	rw.parent.handleRouteUpdate(rw.routeName, rdsWatcherUpdate{data: &update.Resource})
   166  }
   167  
   168  func (rw *rdsWatcher) OnError(err error, onDone xdsresource.OnDoneFunc) {
   169  	defer onDone()
   170  	rw.mu.Lock()
   171  	if rw.canceled {
   172  		rw.mu.Unlock()
   173  		return
   174  	}
   175  	rw.mu.Unlock()
   176  	if rw.logger.V(2) {
   177  		rw.logger.Infof("RDS watch for resource %q reported error: %v", rw.routeName, err)
   178  	}
   179  	rw.parent.handleRouteUpdate(rw.routeName, rdsWatcherUpdate{err: err})
   180  }
   181  
   182  func (rw *rdsWatcher) OnResourceDoesNotExist(onDone xdsresource.OnDoneFunc) {
   183  	defer onDone()
   184  	rw.mu.Lock()
   185  	if rw.canceled {
   186  		rw.mu.Unlock()
   187  		return
   188  	}
   189  	rw.mu.Unlock()
   190  	rw.logger.Warningf("RDS watch for resource %q reported resource-does-not-exist error", rw.routeName)
   191  	err := xdsresource.NewErrorf(xdsresource.ErrorTypeResourceNotFound, "resource name %q of type RouteConfiguration not found in received response", rw.routeName)
   192  	err = fmt.Errorf("[xDS node id: %v]: %w", rw.parent.xdsNodeID, err)
   193  	rw.parent.handleRouteUpdate(rw.routeName, rdsWatcherUpdate{err: err})
   194  }