github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/go-control-plane/pkg/server/delta/v3/server.go (about)

     1  package delta
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"strconv"
     7  	"sync/atomic"
     8  
     9  	"github.com/hxx258456/ccgo/grpc/codes"
    10  	"github.com/hxx258456/ccgo/grpc/status"
    11  
    12  	core "github.com/hxx258456/ccgo/go-control-plane/envoy/config/core/v3"
    13  	discovery "github.com/hxx258456/ccgo/go-control-plane/envoy/service/discovery/v3"
    14  	"github.com/hxx258456/ccgo/go-control-plane/pkg/cache/v3"
    15  	"github.com/hxx258456/ccgo/go-control-plane/pkg/resource/v3"
    16  	"github.com/hxx258456/ccgo/go-control-plane/pkg/server/stream/v3"
    17  )
    18  
    19  // Server is a wrapper interface which is meant to hold the proper stream handler for each xDS protocol.
    20  type Server interface {
    21  	DeltaStreamHandler(stream stream.DeltaStream, typeURL string) error
    22  }
    23  
    24  type Callbacks interface {
    25  	// OnDeltaStreamOpen is called once an incremental xDS stream is open with a stream ID and the type URL (or "" for ADS).
    26  	// Returning an error will end processing and close the stream. OnStreamClosed will still be called.
    27  	OnDeltaStreamOpen(context.Context, int64, string) error
    28  	// OnDeltaStreamClosed is called immediately prior to closing an xDS stream with a stream ID.
    29  	OnDeltaStreamClosed(int64)
    30  	// OnStreamDeltaRequest is called once a request is received on a stream.
    31  	// Returning an error will end processing and close the stream. OnStreamClosed will still be called.
    32  	OnStreamDeltaRequest(int64, *discovery.DeltaDiscoveryRequest) error
    33  	// OnStreamDelatResponse is called immediately prior to sending a response on a stream.
    34  	OnStreamDeltaResponse(int64, *discovery.DeltaDiscoveryRequest, *discovery.DeltaDiscoveryResponse)
    35  }
    36  
    37  var deltaErrorResponse = &cache.RawDeltaResponse{}
    38  
    39  type server struct {
    40  	cache     cache.ConfigWatcher
    41  	callbacks Callbacks
    42  
    43  	// total stream count for counting bi-di streams
    44  	streamCount int64
    45  	ctx         context.Context
    46  }
    47  
    48  // NewServer creates a delta xDS specific server which utilizes a ConfigWatcher and delta Callbacks.
    49  func NewServer(ctx context.Context, config cache.ConfigWatcher, callbacks Callbacks) Server {
    50  	return &server{
    51  		cache:     config,
    52  		callbacks: callbacks,
    53  		ctx:       ctx,
    54  	}
    55  }
    56  
    57  func (s *server) processDelta(str stream.DeltaStream, reqCh <-chan *discovery.DeltaDiscoveryRequest, defaultTypeURL string) error {
    58  	streamID := atomic.AddInt64(&s.streamCount, 1)
    59  
    60  	// streamNonce holds a unique nonce for req-resp pairs per xDS stream.
    61  	var streamNonce int64
    62  
    63  	// a collection of stack allocated watches per request type
    64  	watches := newWatches()
    65  
    66  	defer func() {
    67  		watches.Cancel()
    68  		if s.callbacks != nil {
    69  			s.callbacks.OnDeltaStreamClosed(streamID)
    70  		}
    71  	}()
    72  
    73  	// Sends a response, returns the new stream nonce
    74  	send := func(resp cache.DeltaResponse) (string, error) {
    75  		if resp == nil {
    76  			return "", errors.New("missing response")
    77  		}
    78  
    79  		response, err := resp.GetDeltaDiscoveryResponse()
    80  		if err != nil {
    81  			return "", err
    82  		}
    83  
    84  		streamNonce = streamNonce + 1
    85  		response.Nonce = strconv.FormatInt(streamNonce, 10)
    86  		if s.callbacks != nil {
    87  			s.callbacks.OnStreamDeltaResponse(streamID, resp.GetDeltaRequest(), response)
    88  		}
    89  
    90  		return response.Nonce, str.Send(response)
    91  	}
    92  
    93  	if s.callbacks != nil {
    94  		if err := s.callbacks.OnDeltaStreamOpen(str.Context(), streamID, defaultTypeURL); err != nil {
    95  			return err
    96  		}
    97  	}
    98  
    99  	var node = &core.Node{}
   100  	for {
   101  		select {
   102  		case <-s.ctx.Done():
   103  			return nil
   104  		case resp, more := <-watches.deltaMuxedResponses:
   105  			if !more {
   106  				break
   107  			}
   108  
   109  			typ := resp.GetDeltaRequest().GetTypeUrl()
   110  			if resp == deltaErrorResponse {
   111  				return status.Errorf(codes.Unavailable, typ+" watch failed")
   112  			}
   113  
   114  			nonce, err := send(resp)
   115  			if err != nil {
   116  				return err
   117  			}
   118  
   119  			watch := watches.deltaWatches[typ]
   120  			watch.nonce = nonce
   121  
   122  			watch.state.SetResourceVersions(resp.GetNextVersionMap())
   123  			watches.deltaWatches[typ] = watch
   124  		case req, more := <-reqCh:
   125  			// input stream ended or errored out
   126  			if !more {
   127  				return nil
   128  			}
   129  			if req == nil {
   130  				return status.Errorf(codes.Unavailable, "empty request")
   131  			}
   132  
   133  			if s.callbacks != nil {
   134  				if err := s.callbacks.OnStreamDeltaRequest(streamID, req); err != nil {
   135  					return err
   136  				}
   137  			}
   138  
   139  			// The node information might only be set on the first incoming delta discovery request, so store it here so we can
   140  			// reset it on subsequent requests that omit it.
   141  			if req.Node != nil {
   142  				node = req.Node
   143  			} else {
   144  				req.Node = node
   145  			}
   146  
   147  			// type URL is required for ADS but is implicit for any other xDS stream
   148  			if defaultTypeURL == resource.AnyType {
   149  				if req.TypeUrl == "" {
   150  					return status.Errorf(codes.InvalidArgument, "type URL is required for ADS")
   151  				}
   152  			} else if req.TypeUrl == "" {
   153  				req.TypeUrl = defaultTypeURL
   154  			}
   155  
   156  			typeURL := req.GetTypeUrl()
   157  
   158  			// cancel existing watch to (re-)request a newer version
   159  			watch, ok := watches.deltaWatches[typeURL]
   160  			if !ok {
   161  				// Initialize the state of the stream.
   162  				// Since there was no previous state, we know we're handling the first request of this type
   163  				// so we set the initial resource versions if we have any, and also signal if this stream is in wildcard mode.
   164  				watch.state = stream.NewStreamState(len(req.GetResourceNamesSubscribe()) == 0, req.GetInitialResourceVersions())
   165  			} else {
   166  				watch.Cancel()
   167  			}
   168  
   169  			s.subscribe(req.GetResourceNamesSubscribe(), watch.state.GetResourceVersions())
   170  			s.unsubscribe(req.GetResourceNamesUnsubscribe(), watch.state.GetResourceVersions())
   171  
   172  			watch.responses = make(chan cache.DeltaResponse, 1)
   173  			watch.cancel = s.cache.CreateDeltaWatch(req, watch.state, watch.responses)
   174  			watches.deltaWatches[typeURL] = watch
   175  
   176  			go func() {
   177  				resp, more := <-watch.responses
   178  				if more {
   179  					watches.deltaMuxedResponses <- resp
   180  				}
   181  			}()
   182  		}
   183  	}
   184  }
   185  
   186  func (s *server) DeltaStreamHandler(str stream.DeltaStream, typeURL string) error {
   187  	// a channel for receiving incoming delta requests
   188  	reqCh := make(chan *discovery.DeltaDiscoveryRequest)
   189  
   190  	// we need to concurrently handle incoming requests since we kick off processDelta as a return
   191  	go func() {
   192  		for {
   193  			select {
   194  			case <-str.Context().Done():
   195  				close(reqCh)
   196  				return
   197  			default:
   198  				req, err := str.Recv()
   199  				if err != nil {
   200  					close(reqCh)
   201  					return
   202  				}
   203  
   204  				reqCh <- req
   205  			}
   206  		}
   207  	}()
   208  
   209  	return s.processDelta(str, reqCh, typeURL)
   210  }
   211  
   212  // When we subscribe, we just want to make the cache know we are subscribing to a resource.
   213  // Providing a name with an empty version is enough to make that happen.
   214  func (s *server) subscribe(resources []string, sv map[string]string) {
   215  	for _, resource := range resources {
   216  		sv[resource] = ""
   217  	}
   218  }
   219  
   220  // Unsubscriptions remove resources from the stream state to
   221  // indicate to the cache that we don't care about the resource anymore
   222  func (s *server) unsubscribe(resources []string, sv map[string]string) {
   223  	for _, resource := range resources {
   224  		delete(sv, resource)
   225  	}
   226  }