dubbo.apache.org/dubbo-go/v3@v3.1.1/xds/resolver/watch_service.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  	"fmt"
    28  	"sync"
    29  	"time"
    30  )
    31  
    32  import (
    33  	dubbogoLogger "github.com/dubbogo/gost/log/logger"
    34  )
    35  
    36  import (
    37  	"dubbo.apache.org/dubbo-go/v3/xds/client"
    38  	"dubbo.apache.org/dubbo-go/v3/xds/client/resource"
    39  	"dubbo.apache.org/dubbo-go/v3/xds/clusterspecifier"
    40  	"dubbo.apache.org/dubbo-go/v3/xds/utils/pretty"
    41  )
    42  
    43  // serviceUpdate contains information received from the LDS/RDS responses which
    44  // are of interest to the xds resolver. The RDS request is built by first
    45  // making a LDS to get the RouteConfig name.
    46  type serviceUpdate struct {
    47  	// virtualHost contains routes and other configuration to route RPCs.
    48  	virtualHost *resource.VirtualHost
    49  	// clusterSpecifierPlugins contains the configurations for any cluster
    50  	// specifier plugins emitted by the client.
    51  	clusterSpecifierPlugins map[string]clusterspecifier.BalancerConfig
    52  	// ldsConfig contains configuration that applies to all routes.
    53  	ldsConfig ldsConfig
    54  }
    55  
    56  // ldsConfig contains information received from the LDS responses which are of
    57  // interest to the xds resolver.
    58  type ldsConfig struct {
    59  	// maxStreamDuration is from the HTTP connection manager's
    60  	// common_http_protocol_options field.
    61  	maxStreamDuration time.Duration
    62  	httpFilterConfig  []resource.HTTPFilter
    63  }
    64  
    65  // watchService uses LDS and RDS to discover information about the provided
    66  // serviceName.
    67  //
    68  // Note that during race (e.g. an xDS response is received while the user is
    69  // calling cancel()), there's a small window where the callback can be called
    70  // after the watcher is canceled. The caller needs to handle this case.
    71  func watchService(c client.XDSClient, serviceName string, cb func(serviceUpdate, error), logger dubbogoLogger.Logger) (cancel func()) {
    72  	w := &serviceUpdateWatcher{
    73  		logger:      logger,
    74  		c:           c,
    75  		serviceName: serviceName,
    76  		serviceCb:   cb,
    77  	}
    78  	w.ldsCancel = c.WatchListener(serviceName, w.handleLDSResp)
    79  
    80  	return w.close
    81  }
    82  
    83  // serviceUpdateWatcher handles LDS and RDS response, and calls the service
    84  // callback at the right time.
    85  type serviceUpdateWatcher struct {
    86  	logger      dubbogoLogger.Logger
    87  	c           client.XDSClient
    88  	serviceName string
    89  	ldsCancel   func()
    90  	serviceCb   func(serviceUpdate, error)
    91  	lastUpdate  serviceUpdate
    92  
    93  	mu        sync.Mutex
    94  	closed    bool
    95  	rdsName   string
    96  	rdsCancel func()
    97  }
    98  
    99  func (w *serviceUpdateWatcher) handleLDSResp(update resource.ListenerUpdate, err error) {
   100  	w.logger.Infof("received LDS update: %+v, err: %v", pretty.ToJSON(update), err)
   101  	w.mu.Lock()
   102  	defer w.mu.Unlock()
   103  	if w.closed {
   104  		return
   105  	}
   106  	if err != nil {
   107  		// We check the error type and do different things. For now, the only
   108  		// type we check is ResourceNotFound, which indicates the LDS resource
   109  		// was removed, and besides sending the error to callback, we also
   110  		// cancel the RDS watch.
   111  		if resource.ErrType(err) == resource.ErrorTypeResourceNotFound && w.rdsCancel != nil {
   112  			w.rdsCancel()
   113  			w.rdsName = ""
   114  			w.rdsCancel = nil
   115  			w.lastUpdate = serviceUpdate{}
   116  		}
   117  		// The other error cases still return early without canceling the
   118  		// existing RDS watch.
   119  		w.serviceCb(serviceUpdate{}, err)
   120  		return
   121  	}
   122  
   123  	w.lastUpdate.ldsConfig = ldsConfig{
   124  		maxStreamDuration: update.MaxStreamDuration,
   125  		httpFilterConfig:  update.HTTPFilters,
   126  	}
   127  
   128  	if update.InlineRouteConfig != nil {
   129  		// If there was an RDS watch, cancel it.
   130  		w.rdsName = ""
   131  		if w.rdsCancel != nil {
   132  			w.rdsCancel()
   133  			w.rdsCancel = nil
   134  		}
   135  
   136  		// Handle the inline RDS update as if it's from an RDS watch.
   137  		w.applyRouteConfigUpdate(*update.InlineRouteConfig)
   138  		return
   139  	}
   140  
   141  	// RDS name from update is not an empty string, need RDS to fetch the
   142  	// routes.
   143  
   144  	if w.rdsName == update.RouteConfigName {
   145  		// If the new RouteConfigName is same as the previous, don't cancel and
   146  		// restart the RDS watch.
   147  		//
   148  		// If the route name did change, then we must wait until the first RDS
   149  		// update before reporting this LDS config.
   150  		if w.lastUpdate.virtualHost != nil {
   151  			// We want to send an update with the new fields from the new LDS
   152  			// (e.g. max stream duration), and old fields from the the previous
   153  			// RDS.
   154  			//
   155  			// But note that this should only happen when virtual host is set,
   156  			// which means an RDS was received.
   157  			w.serviceCb(w.lastUpdate, nil)
   158  		}
   159  		return
   160  	}
   161  	w.rdsName = update.RouteConfigName
   162  	if w.rdsCancel != nil {
   163  		w.rdsCancel()
   164  	}
   165  	w.rdsCancel = w.c.WatchRouteConfig(update.RouteConfigName, w.handleRDSResp)
   166  }
   167  
   168  func (w *serviceUpdateWatcher) applyRouteConfigUpdate(update resource.RouteConfigUpdate) {
   169  	matchVh := resource.FindBestMatchingVirtualHost(w.serviceName, update.VirtualHosts)
   170  	if matchVh == nil {
   171  		// No matching virtual host found.
   172  		w.serviceCb(serviceUpdate{}, fmt.Errorf("no matching virtual host found for %q", w.serviceName))
   173  		return
   174  	}
   175  
   176  	w.lastUpdate.virtualHost = matchVh
   177  	w.lastUpdate.clusterSpecifierPlugins = update.ClusterSpecifierPlugins
   178  	w.serviceCb(w.lastUpdate, nil)
   179  }
   180  
   181  func (w *serviceUpdateWatcher) handleRDSResp(update resource.RouteConfigUpdate, err error) {
   182  	w.logger.Infof("received RDS update: %+v, err: %v", pretty.ToJSON(update), err)
   183  	w.mu.Lock()
   184  	defer w.mu.Unlock()
   185  	if w.closed {
   186  		return
   187  	}
   188  	if w.rdsCancel == nil {
   189  		// This mean only the RDS watch is canceled, can happen if the LDS
   190  		// resource is removed.
   191  		return
   192  	}
   193  	if err != nil {
   194  		w.serviceCb(serviceUpdate{}, err)
   195  		return
   196  	}
   197  	w.applyRouteConfigUpdate(update)
   198  }
   199  
   200  func (w *serviceUpdateWatcher) close() {
   201  	w.mu.Lock()
   202  	defer w.mu.Unlock()
   203  	w.closed = true
   204  	w.ldsCancel()
   205  	if w.rdsCancel != nil {
   206  		w.rdsCancel()
   207  		w.rdsCancel = nil
   208  	}
   209  }