google.golang.org/grpc@v1.62.1/xds/internal/xdsclient/transport/transport.go (about)

     1  /*
     2   *
     3   * Copyright 2022 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  // Package transport implements the xDS transport protocol functionality
    19  // required by the xdsclient.
    20  package transport
    21  
    22  import (
    23  	"context"
    24  	"errors"
    25  	"fmt"
    26  	"sync"
    27  	"time"
    28  
    29  	"google.golang.org/grpc"
    30  	"google.golang.org/grpc/codes"
    31  	"google.golang.org/grpc/connectivity"
    32  	"google.golang.org/grpc/internal/backoff"
    33  	"google.golang.org/grpc/internal/buffer"
    34  	"google.golang.org/grpc/internal/grpclog"
    35  	"google.golang.org/grpc/internal/pretty"
    36  	"google.golang.org/grpc/keepalive"
    37  	"google.golang.org/grpc/xds/internal/xdsclient/bootstrap"
    38  	"google.golang.org/grpc/xds/internal/xdsclient/load"
    39  	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
    40  	"google.golang.org/protobuf/types/known/anypb"
    41  
    42  	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
    43  	v3adsgrpc "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
    44  	v3discoverypb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
    45  	statuspb "google.golang.org/genproto/googleapis/rpc/status"
    46  )
    47  
    48  // Any per-RPC level logs which print complete request or response messages
    49  // should be gated at this verbosity level. Other per-RPC level logs which print
    50  // terse output should be at `INFO` and verbosity 2, which corresponds to using
    51  // the `Debugf` method on the logger.
    52  const perRPCVerbosityLevel = 9
    53  
    54  type adsStream = v3adsgrpc.AggregatedDiscoveryService_StreamAggregatedResourcesClient
    55  
    56  // Transport provides a resource-type agnostic implementation of the xDS
    57  // transport protocol. At this layer, resource contents are supposed to be
    58  // opaque blobs which should be be meaningful only to the xDS data model layer
    59  // which is implemented by the `xdsresource` package.
    60  //
    61  // Under the hood, it owns the gRPC connection to a single management server and
    62  // manages the lifecycle of ADS/LRS streams. It uses the xDS v3 transport
    63  // protocol version.
    64  type Transport struct {
    65  	// These fields are initialized at creation time and are read-only afterwards.
    66  	cc              *grpc.ClientConn        // ClientConn to the mangement server.
    67  	serverURI       string                  // URI of the management server.
    68  	onRecvHandler   OnRecvHandlerFunc       // Resource update handler. xDS data model layer.
    69  	onErrorHandler  func(error)             // To report underlying stream errors.
    70  	onSendHandler   OnSendHandlerFunc       // To report resources requested on ADS stream.
    71  	lrsStore        *load.Store             // Store returned to user for pushing loads.
    72  	backoff         func(int) time.Duration // Backoff after stream failures.
    73  	nodeProto       *v3corepb.Node          // Identifies the gRPC application.
    74  	logger          *grpclog.PrefixLogger   // Prefix logger for transport logs.
    75  	adsRunnerCancel context.CancelFunc      // CancelFunc for the ADS goroutine.
    76  	adsRunnerDoneCh chan struct{}           // To notify exit of ADS goroutine.
    77  	lrsRunnerDoneCh chan struct{}           // To notify exit of LRS goroutine.
    78  
    79  	// These channels enable synchronization amongst the different goroutines
    80  	// spawned by the transport, and between asynchorous events resulting from
    81  	// receipt of responses from the management server.
    82  	adsStreamCh  chan adsStream    // New ADS streams are pushed here.
    83  	adsRequestCh *buffer.Unbounded // Resource and ack requests are pushed here.
    84  
    85  	// mu guards the following runtime state maintained by the transport.
    86  	mu sync.Mutex
    87  	// resources is map from resource type URL to the set of resource names
    88  	// being requested for that type. When the ADS stream is restarted, the
    89  	// transport requests all these resources again from the management server.
    90  	resources map[string]map[string]bool
    91  	// versions is a map from resource type URL to the most recently ACKed
    92  	// version for that resource. Resource versions are a property of the
    93  	// resource type and not the stream, and will not be reset upon stream
    94  	// restarts.
    95  	versions map[string]string
    96  	// nonces is a map from resource type URL to the most recently received
    97  	// nonce for that resource type. Nonces are a property of the ADS stream and
    98  	// will be reset upon stream restarts.
    99  	nonces map[string]string
   100  
   101  	lrsMu           sync.Mutex         // Protects all LRS state.
   102  	lrsCancelStream context.CancelFunc // CancelFunc for the LRS stream.
   103  	lrsRefCount     int                // Reference count on the load store.
   104  }
   105  
   106  // OnRecvHandlerFunc is the implementation at the xDS data model layer, which
   107  // determines if the configuration received from the management server can be
   108  // applied locally or not.
   109  //
   110  // A nil error is returned from this function when the data model layer believes
   111  // that the received configuration is good and can be applied locally. This will
   112  // cause the transport layer to send an ACK to the management server. A non-nil
   113  // error is returned from this function when the data model layer believes
   114  // otherwise, and this will cause the transport layer to send a NACK.
   115  type OnRecvHandlerFunc func(update ResourceUpdate) error
   116  
   117  // OnSendHandlerFunc is the implementation at the authority, which handles state
   118  // changes for the resource watch and stop watch timers accordingly.
   119  type OnSendHandlerFunc func(update *ResourceSendInfo)
   120  
   121  // ResourceUpdate is a representation of the configuration update received from
   122  // the management server. It only contains fields which are useful to the data
   123  // model layer, and layers above it.
   124  type ResourceUpdate struct {
   125  	// Resources is the list of resources received from the management server.
   126  	Resources []*anypb.Any
   127  	// URL is the resource type URL for the above resources.
   128  	URL string
   129  	// Version is the resource version, for the above resources, as specified by
   130  	// the management server.
   131  	Version string
   132  }
   133  
   134  // Options specifies configuration knobs used when creating a new Transport.
   135  type Options struct {
   136  	// ServerCfg contains all the configuration required to connect to the xDS
   137  	// management server.
   138  	ServerCfg bootstrap.ServerConfig
   139  	// OnRecvHandler is the component which makes ACK/NACK decisions based on
   140  	// the received resources.
   141  	//
   142  	// Invoked inline and implementations must not block.
   143  	OnRecvHandler OnRecvHandlerFunc
   144  	// OnErrorHandler provides a way for the transport layer to report
   145  	// underlying stream errors. These can be bubbled all the way up to the user
   146  	// of the xdsClient.
   147  	//
   148  	// Invoked inline and implementations must not block.
   149  	OnErrorHandler func(error)
   150  	// OnSendHandler provides a way for the transport layer to report underlying
   151  	// resource requests sent on the stream. However, Send() on the ADS stream will
   152  	// return successfully as long as:
   153  	//   1. there is enough flow control quota to send the message.
   154  	//   2. the message is added to the send buffer.
   155  	// However, the connection may fail after the callback is invoked and before
   156  	// the message is actually sent on the wire. This is accepted.
   157  	//
   158  	// Invoked inline and implementations must not block.
   159  	OnSendHandler func(*ResourceSendInfo)
   160  	// Backoff controls the amount of time to backoff before recreating failed
   161  	// ADS streams. If unspecified, a default exponential backoff implementation
   162  	// is used. For more details, see:
   163  	// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md.
   164  	Backoff func(retries int) time.Duration
   165  	// Logger does logging with a prefix.
   166  	Logger *grpclog.PrefixLogger
   167  	// NodeProto contains the Node proto to be used in xDS requests. This will be
   168  	// of type *v3corepb.Node.
   169  	NodeProto *v3corepb.Node
   170  }
   171  
   172  // For overriding in unit tests.
   173  var grpcDial = grpc.Dial
   174  
   175  // New creates a new Transport.
   176  func New(opts Options) (*Transport, error) {
   177  	switch {
   178  	case opts.ServerCfg.ServerURI == "":
   179  		return nil, errors.New("missing server URI when creating a new transport")
   180  	case opts.ServerCfg.CredsDialOption() == nil:
   181  		return nil, errors.New("missing credentials when creating a new transport")
   182  	case opts.OnRecvHandler == nil:
   183  		return nil, errors.New("missing OnRecv callback handler when creating a new transport")
   184  	case opts.OnErrorHandler == nil:
   185  		return nil, errors.New("missing OnError callback handler when creating a new transport")
   186  	case opts.OnSendHandler == nil:
   187  		return nil, errors.New("missing OnSend callback handler when creating a new transport")
   188  	}
   189  
   190  	// Dial the xDS management with the passed in credentials.
   191  	dopts := []grpc.DialOption{
   192  		opts.ServerCfg.CredsDialOption(),
   193  		grpc.WithKeepaliveParams(keepalive.ClientParameters{
   194  			// We decided to use these sane defaults in all languages, and
   195  			// kicked the can down the road as far making these configurable.
   196  			Time:    5 * time.Minute,
   197  			Timeout: 20 * time.Second,
   198  		}),
   199  	}
   200  	cc, err := grpcDial(opts.ServerCfg.ServerURI, dopts...)
   201  	if err != nil {
   202  		// An error from a non-blocking dial indicates something serious.
   203  		return nil, fmt.Errorf("failed to create a transport to the management server %q: %v", opts.ServerCfg.ServerURI, err)
   204  	}
   205  
   206  	boff := opts.Backoff
   207  	if boff == nil {
   208  		boff = backoff.DefaultExponential.Backoff
   209  	}
   210  	ret := &Transport{
   211  		cc:             cc,
   212  		serverURI:      opts.ServerCfg.ServerURI,
   213  		onRecvHandler:  opts.OnRecvHandler,
   214  		onErrorHandler: opts.OnErrorHandler,
   215  		onSendHandler:  opts.OnSendHandler,
   216  		lrsStore:       load.NewStore(),
   217  		backoff:        boff,
   218  		nodeProto:      opts.NodeProto,
   219  		logger:         opts.Logger,
   220  
   221  		adsStreamCh:     make(chan adsStream, 1),
   222  		adsRequestCh:    buffer.NewUnbounded(),
   223  		resources:       make(map[string]map[string]bool),
   224  		versions:        make(map[string]string),
   225  		nonces:          make(map[string]string),
   226  		adsRunnerDoneCh: make(chan struct{}),
   227  	}
   228  
   229  	// This context is used for sending and receiving RPC requests and
   230  	// responses. It is also used by all the goroutines spawned by this
   231  	// Transport. Therefore, cancelling this context when the transport is
   232  	// closed will essentially cancel any pending RPCs, and cause the goroutines
   233  	// to terminate.
   234  	ctx, cancel := context.WithCancel(context.Background())
   235  	ret.adsRunnerCancel = cancel
   236  	go ret.adsRunner(ctx)
   237  
   238  	ret.logger.Infof("Created transport to server %q", ret.serverURI)
   239  	return ret, nil
   240  }
   241  
   242  // resourceRequest wraps the resource type url and the resource names requested
   243  // by the user of this transport.
   244  type resourceRequest struct {
   245  	resources []string
   246  	url       string
   247  }
   248  
   249  // SendRequest sends out an ADS request for the provided resources of the
   250  // specified resource type.
   251  //
   252  // The request is sent out asynchronously. If no valid stream exists at the time
   253  // of processing this request, it is queued and will be sent out once a valid
   254  // stream exists.
   255  //
   256  // If a successful response is received, the update handler callback provided at
   257  // creation time is invoked. If an error is encountered, the stream error
   258  // handler callback provided at creation time is invoked.
   259  func (t *Transport) SendRequest(url string, resources []string) {
   260  	t.adsRequestCh.Put(&resourceRequest{
   261  		url:       url,
   262  		resources: resources,
   263  	})
   264  }
   265  
   266  func (t *Transport) newAggregatedDiscoveryServiceStream(ctx context.Context, cc *grpc.ClientConn) (adsStream, error) {
   267  	// The transport retries the stream with an exponential backoff whenever the
   268  	// stream breaks without ever having seen a response.
   269  	return v3adsgrpc.NewAggregatedDiscoveryServiceClient(cc).StreamAggregatedResources(ctx)
   270  }
   271  
   272  // ResourceSendInfo wraps the names and url of resources sent to the management
   273  // server. This is used by the `authority` type to start/stop the watch timer
   274  // associated with every resource in the update.
   275  type ResourceSendInfo struct {
   276  	ResourceNames []string
   277  	URL           string
   278  }
   279  
   280  func (t *Transport) sendAggregatedDiscoveryServiceRequest(stream adsStream, sendNodeProto bool, resourceNames []string, resourceURL, version, nonce string, nackErr error) error {
   281  	req := &v3discoverypb.DiscoveryRequest{
   282  		TypeUrl:       resourceURL,
   283  		ResourceNames: resourceNames,
   284  		VersionInfo:   version,
   285  		ResponseNonce: nonce,
   286  	}
   287  	if sendNodeProto {
   288  		req.Node = t.nodeProto
   289  	}
   290  	if nackErr != nil {
   291  		req.ErrorDetail = &statuspb.Status{
   292  			Code: int32(codes.InvalidArgument), Message: nackErr.Error(),
   293  		}
   294  	}
   295  	if err := stream.Send(req); err != nil {
   296  		return err
   297  	}
   298  	if t.logger.V(perRPCVerbosityLevel) {
   299  		t.logger.Infof("ADS request sent: %v", pretty.ToJSON(req))
   300  	} else {
   301  		t.logger.Debugf("ADS request sent for type %q, resources: %v, version %q, nonce %q", resourceURL, resourceNames, version, nonce)
   302  	}
   303  	t.onSendHandler(&ResourceSendInfo{URL: resourceURL, ResourceNames: resourceNames})
   304  	return nil
   305  }
   306  
   307  func (t *Transport) recvAggregatedDiscoveryServiceResponse(stream adsStream) (resources []*anypb.Any, resourceURL, version, nonce string, err error) {
   308  	resp, err := stream.Recv()
   309  	if err != nil {
   310  		return nil, "", "", "", err
   311  	}
   312  	if t.logger.V(perRPCVerbosityLevel) {
   313  		t.logger.Infof("ADS response received: %v", pretty.ToJSON(resp))
   314  	} else {
   315  		t.logger.Debugf("ADS response received for type %q, version %q, nonce %q", resp.GetTypeUrl(), resp.GetVersionInfo(), resp.GetNonce())
   316  	}
   317  	return resp.GetResources(), resp.GetTypeUrl(), resp.GetVersionInfo(), resp.GetNonce(), nil
   318  }
   319  
   320  // adsRunner starts an ADS stream (and backs off exponentially, if the previous
   321  // stream failed without receiving a single reply) and runs the sender and
   322  // receiver routines to send and receive data from the stream respectively.
   323  func (t *Transport) adsRunner(ctx context.Context) {
   324  	defer close(t.adsRunnerDoneCh)
   325  
   326  	go t.send(ctx)
   327  
   328  	// We reset backoff state when we successfully receive at least one
   329  	// message from the server.
   330  	runStreamWithBackoff := func() error {
   331  		stream, err := t.newAggregatedDiscoveryServiceStream(ctx, t.cc)
   332  		if err != nil {
   333  			t.onErrorHandler(err)
   334  			t.logger.Warningf("Creating new ADS stream failed: %v", err)
   335  			return nil
   336  		}
   337  		t.logger.Infof("ADS stream created")
   338  
   339  		select {
   340  		case <-t.adsStreamCh:
   341  		default:
   342  		}
   343  		t.adsStreamCh <- stream
   344  		msgReceived := t.recv(stream)
   345  		if msgReceived {
   346  			return backoff.ErrResetBackoff
   347  		}
   348  		return nil
   349  	}
   350  	backoff.RunF(ctx, runStreamWithBackoff, t.backoff)
   351  }
   352  
   353  // send is a separate goroutine for sending resource requests on the ADS stream.
   354  //
   355  // For every new stream received on the stream channel, all existing resources
   356  // are re-requested from the management server.
   357  //
   358  // For every new resource request received on the resources channel, the
   359  // resources map is updated (this ensures that resend will pick them up when
   360  // there are new streams) and the appropriate request is sent out.
   361  func (t *Transport) send(ctx context.Context) {
   362  	var stream adsStream
   363  	// The xDS protocol only requires that we send the node proto in the first
   364  	// discovery request on every stream. Sending the node proto in every
   365  	// request message wastes CPU resources on the client and the server.
   366  	sendNodeProto := true
   367  	for {
   368  		select {
   369  		case <-ctx.Done():
   370  			return
   371  		case stream = <-t.adsStreamCh:
   372  			// We have a new stream and we've to ensure that the node proto gets
   373  			// sent out in the first request on the stream. At this point, we
   374  			// might not have any registered watches. Setting this field to true
   375  			// here will ensure that the node proto gets sent out along with the
   376  			// discovery request when the first watch is registered.
   377  			if len(t.resources) == 0 {
   378  				sendNodeProto = true
   379  				continue
   380  			}
   381  
   382  			if !t.sendExisting(stream) {
   383  				// Send failed, clear the current stream. Attempt to resend will
   384  				// only be made after a new stream is created.
   385  				stream = nil
   386  				continue
   387  			}
   388  			sendNodeProto = false
   389  		case u, ok := <-t.adsRequestCh.Get():
   390  			if !ok {
   391  				// No requests will be sent after the adsRequestCh buffer is closed.
   392  				return
   393  			}
   394  			t.adsRequestCh.Load()
   395  
   396  			var (
   397  				resources           []string
   398  				url, version, nonce string
   399  				send                bool
   400  				nackErr             error
   401  			)
   402  			switch update := u.(type) {
   403  			case *resourceRequest:
   404  				resources, url, version, nonce = t.processResourceRequest(update)
   405  			case *ackRequest:
   406  				resources, url, version, nonce, send = t.processAckRequest(update, stream)
   407  				if !send {
   408  					continue
   409  				}
   410  				nackErr = update.nackErr
   411  			}
   412  			if stream == nil {
   413  				// There's no stream yet. Skip the request. This request
   414  				// will be resent to the new streams. If no stream is
   415  				// created, the watcher will timeout (same as server not
   416  				// sending response back).
   417  				continue
   418  			}
   419  			if err := t.sendAggregatedDiscoveryServiceRequest(stream, sendNodeProto, resources, url, version, nonce, nackErr); err != nil {
   420  				t.logger.Warningf("Sending ADS request for resources: %q, url: %q, version: %q, nonce: %q failed: %v", resources, url, version, nonce, err)
   421  				// Send failed, clear the current stream.
   422  				stream = nil
   423  			}
   424  			sendNodeProto = false
   425  		}
   426  	}
   427  }
   428  
   429  // sendExisting sends out xDS requests for existing resources when recovering
   430  // from a broken stream.
   431  //
   432  // We call stream.Send() here with the lock being held. It should be OK to do
   433  // that here because the stream has just started and Send() usually returns
   434  // quickly (once it pushes the message onto the transport layer) and is only
   435  // ever blocked if we don't have enough flow control quota.
   436  func (t *Transport) sendExisting(stream adsStream) bool {
   437  	t.mu.Lock()
   438  	defer t.mu.Unlock()
   439  
   440  	// Reset only the nonces map when the stream restarts.
   441  	//
   442  	// xDS spec says the following. See section:
   443  	// https://www.envoyproxy.io/docs/envoy/latest/api-docs/xds_protocol#ack-nack-and-resource-type-instance-version
   444  	//
   445  	// Note that the version for a resource type is not a property of an
   446  	// individual xDS stream but rather a property of the resources themselves. If
   447  	// the stream becomes broken and the client creates a new stream, the client’s
   448  	// initial request on the new stream should indicate the most recent version
   449  	// seen by the client on the previous stream
   450  	t.nonces = make(map[string]string)
   451  
   452  	// Send node proto only in the first request on the stream.
   453  	sendNodeProto := true
   454  	for url, resources := range t.resources {
   455  		if err := t.sendAggregatedDiscoveryServiceRequest(stream, sendNodeProto, mapToSlice(resources), url, t.versions[url], "", nil); err != nil {
   456  			t.logger.Warningf("Sending ADS request for resources: %q, url: %q, version: %q, nonce: %q failed: %v", resources, url, t.versions[url], "", err)
   457  			return false
   458  		}
   459  		sendNodeProto = false
   460  	}
   461  
   462  	return true
   463  }
   464  
   465  // recv receives xDS responses on the provided ADS stream and branches out to
   466  // message specific handlers. Returns true if at least one message was
   467  // successfully received.
   468  func (t *Transport) recv(stream adsStream) bool {
   469  	msgReceived := false
   470  	for {
   471  		resources, url, rVersion, nonce, err := t.recvAggregatedDiscoveryServiceResponse(stream)
   472  		if err != nil {
   473  			// Note that we do not consider it an error if the ADS stream was closed
   474  			// after having received a response on the stream. This is because there
   475  			// are legitimate reasons why the server may need to close the stream during
   476  			// normal operations, such as needing to rebalance load or the underlying
   477  			// connection hitting its max connection age limit.
   478  			// (see [gRFC A9](https://github.com/grpc/proposal/blob/master/A9-server-side-conn-mgt.md)).
   479  			if msgReceived {
   480  				err = xdsresource.NewErrorf(xdsresource.ErrTypeStreamFailedAfterRecv, err.Error())
   481  			}
   482  			t.onErrorHandler(err)
   483  			t.logger.Warningf("ADS stream closed: %v", err)
   484  			return msgReceived
   485  		}
   486  		msgReceived = true
   487  
   488  		err = t.onRecvHandler(ResourceUpdate{
   489  			Resources: resources,
   490  			URL:       url,
   491  			Version:   rVersion,
   492  		})
   493  		if xdsresource.ErrType(err) == xdsresource.ErrorTypeResourceTypeUnsupported {
   494  			t.logger.Warningf("%v", err)
   495  			continue
   496  		}
   497  		// If the data model layer returned an error, we need to NACK the
   498  		// response in which case we need to set the version to the most
   499  		// recently accepted version of this resource type.
   500  		if err != nil {
   501  			t.mu.Lock()
   502  			t.adsRequestCh.Put(&ackRequest{
   503  				url:     url,
   504  				nonce:   nonce,
   505  				stream:  stream,
   506  				version: t.versions[url],
   507  				nackErr: err,
   508  			})
   509  			t.mu.Unlock()
   510  			t.logger.Warningf("Sending NACK for resource type: %q, version: %q, nonce: %q, reason: %v", url, rVersion, nonce, err)
   511  			continue
   512  		}
   513  		t.adsRequestCh.Put(&ackRequest{
   514  			url:     url,
   515  			nonce:   nonce,
   516  			stream:  stream,
   517  			version: rVersion,
   518  		})
   519  		t.logger.Debugf("Sending ACK for resource type: %q, version: %q, nonce: %q", url, rVersion, nonce)
   520  	}
   521  }
   522  
   523  func mapToSlice(m map[string]bool) []string {
   524  	ret := make([]string, 0, len(m))
   525  	for i := range m {
   526  		ret = append(ret, i)
   527  	}
   528  	return ret
   529  }
   530  
   531  func sliceToMap(ss []string) map[string]bool {
   532  	ret := make(map[string]bool, len(ss))
   533  	for _, s := range ss {
   534  		ret[s] = true
   535  	}
   536  	return ret
   537  }
   538  
   539  // processResourceRequest pulls the fields needed to send out an ADS request.
   540  // The resource type and the list of resources to request are provided by the
   541  // user, while the version and nonce are maintained internally.
   542  //
   543  // The resources map, which keeps track of the resources being requested, is
   544  // updated here. Any subsequent stream failure will re-request resources stored
   545  // in this map.
   546  //
   547  // Returns the list of resources, resource type url, version and nonce.
   548  func (t *Transport) processResourceRequest(req *resourceRequest) ([]string, string, string, string) {
   549  	t.mu.Lock()
   550  	defer t.mu.Unlock()
   551  
   552  	resources := sliceToMap(req.resources)
   553  	t.resources[req.url] = resources
   554  	return req.resources, req.url, t.versions[req.url], t.nonces[req.url]
   555  }
   556  
   557  type ackRequest struct {
   558  	url     string // Resource type URL.
   559  	version string // NACK if version is an empty string.
   560  	nonce   string
   561  	nackErr error // nil for ACK, non-nil for NACK.
   562  	// ACK/NACK are tagged with the stream it's for. When the stream is down,
   563  	// all the ACK/NACK for this stream will be dropped, and the version/nonce
   564  	// won't be updated.
   565  	stream grpc.ClientStream
   566  }
   567  
   568  // processAckRequest pulls the fields needed to send out an ADS ACK. The nonces
   569  // and versions map is updated.
   570  //
   571  // Returns the list of resources, resource type url, version, nonce, and an
   572  // indication of whether an ACK should be sent on the wire or not.
   573  func (t *Transport) processAckRequest(ack *ackRequest, stream grpc.ClientStream) ([]string, string, string, string, bool) {
   574  	if ack.stream != stream {
   575  		// If ACK's stream isn't the current sending stream, this means the ACK
   576  		// was pushed to queue before the old stream broke, and a new stream has
   577  		// been started since. Return immediately here so we don't update the
   578  		// nonce for the new stream.
   579  		return nil, "", "", "", false
   580  	}
   581  
   582  	t.mu.Lock()
   583  	defer t.mu.Unlock()
   584  
   585  	// Update the nonce irrespective of whether we send the ACK request on wire.
   586  	// An up-to-date nonce is required for the next request.
   587  	nonce := ack.nonce
   588  	t.nonces[ack.url] = nonce
   589  
   590  	s, ok := t.resources[ack.url]
   591  	if !ok || len(s) == 0 {
   592  		// We don't send the ACK request if there are no resources of this type
   593  		// in our resources map. This can be either when the server sends
   594  		// responses before any request, or the resources are removed while the
   595  		// ackRequest was in queue). If we send a request with an empty
   596  		// resource name list, the server may treat it as a wild card and send
   597  		// us everything.
   598  		return nil, "", "", "", false
   599  	}
   600  	resources := mapToSlice(s)
   601  
   602  	// Update the versions map only when we plan to send an ACK.
   603  	if ack.nackErr == nil {
   604  		t.versions[ack.url] = ack.version
   605  	}
   606  
   607  	return resources, ack.url, ack.version, nonce, true
   608  }
   609  
   610  // Close closes the Transport and frees any associated resources.
   611  func (t *Transport) Close() {
   612  	t.adsRunnerCancel()
   613  	<-t.adsRunnerDoneCh
   614  	t.adsRequestCh.Close()
   615  	t.cc.Close()
   616  }
   617  
   618  // ChannelConnectivityStateForTesting returns the connectivity state of the gRPC
   619  // channel to the management server.
   620  //
   621  // Only for testing purposes.
   622  func (t *Transport) ChannelConnectivityStateForTesting() connectivity.State {
   623  	return t.cc.GetState()
   624  }