google.golang.org/grpc@v1.62.1/internal/testutils/xds/e2e/server.go (about)

     1  /*
     2   *
     3   * Copyright 2020 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 e2e provides utilities for end2end testing of xDS functionality.
    20  package e2e
    21  
    22  import (
    23  	"context"
    24  	"fmt"
    25  	"net"
    26  	"reflect"
    27  	"strconv"
    28  
    29  	"github.com/envoyproxy/go-control-plane/pkg/cache/types"
    30  	"google.golang.org/grpc"
    31  	"google.golang.org/grpc/internal/testutils/xds/fakeserver"
    32  
    33  	v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
    34  	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
    35  	v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
    36  	v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
    37  	v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
    38  	v3discoverygrpc "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
    39  	v3discoverypb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
    40  	v3lrsgrpc "github.com/envoyproxy/go-control-plane/envoy/service/load_stats/v3"
    41  	v3cache "github.com/envoyproxy/go-control-plane/pkg/cache/v3"
    42  	v3resource "github.com/envoyproxy/go-control-plane/pkg/resource/v3"
    43  	v3server "github.com/envoyproxy/go-control-plane/pkg/server/v3"
    44  )
    45  
    46  // ManagementServer is a thin wrapper around the xDS control plane
    47  // implementation provided by envoyproxy/go-control-plane.
    48  type ManagementServer struct {
    49  	// Address is the host:port on which the management server is listening for
    50  	// new connections.
    51  	Address string
    52  
    53  	// LRSServer points to the fake LRS server implementation. Set only if the
    54  	// SupportLoadReportingService option was set to true when creating this
    55  	// management server.
    56  	LRSServer *fakeserver.Server
    57  
    58  	cancel  context.CancelFunc    // To stop the v3 ADS service.
    59  	xs      v3server.Server       // v3 implementation of ADS.
    60  	gs      *grpc.Server          // gRPC server which exports the ADS service.
    61  	cache   v3cache.SnapshotCache // Resource snapshot.
    62  	version int                   // Version of resource snapshot.
    63  }
    64  
    65  // ManagementServerOptions contains options to be passed to the management
    66  // server during creation.
    67  type ManagementServerOptions struct {
    68  	// Listener to accept connections on. If nil, a TPC listener on a local port
    69  	// will be created and used.
    70  	Listener net.Listener
    71  
    72  	// SupportLoadReportingService, if set, results in the load reporting
    73  	// service being registered on the same port as that of ADS.
    74  	SupportLoadReportingService bool
    75  
    76  	// AllowResourceSubSet allows the management server to respond to requests
    77  	// before all configured resources are explicitly named in the request. The
    78  	// default behavior that we want is for the management server to wait for
    79  	// all configured resources to be requested before responding to any of
    80  	// them, since this is how we have run our tests historically, and should be
    81  	// set to true only for tests which explicitly require the other behavior.
    82  	AllowResourceSubset bool
    83  
    84  	// ServerFeaturesIgnoreResourceDeletion, if set, results in a bootstrap config
    85  	// where the server features list contains `ignore_resource_deletion`. This
    86  	// results in gRPC ignoring resource deletions from the management server, as
    87  	// per A53.
    88  	ServerFeaturesIgnoreResourceDeletion bool
    89  
    90  	// The callbacks defined below correspond to the state of the world (sotw)
    91  	// version of the xDS API on the management server.
    92  
    93  	// OnStreamOpen is called when an xDS stream is opened. The callback is
    94  	// invoked with the assigned stream ID and the type URL from the incoming
    95  	// request (or "" for ADS).
    96  	//
    97  	// Returning an error from this callback will end processing and close the
    98  	// stream. OnStreamClosed will still be called.
    99  	OnStreamOpen func(context.Context, int64, string) error
   100  
   101  	// OnStreamClosed is called immediately prior to closing an xDS stream. The
   102  	// callback is invoked with the stream ID of the stream being closed.
   103  	OnStreamClosed func(int64, *v3corepb.Node)
   104  
   105  	// OnStreamRequest is called when a request is received on the stream. The
   106  	// callback is invoked with the stream ID of the stream on which the request
   107  	// was received and the received request.
   108  	//
   109  	// Returning an error from this callback will end processing and close the
   110  	// stream. OnStreamClosed will still be called.
   111  	OnStreamRequest func(int64, *v3discoverypb.DiscoveryRequest) error
   112  
   113  	// OnStreamResponse is called immediately prior to sending a response on the
   114  	// stream. The callback is invoked with the stream ID of the stream on which
   115  	// the response is being sent along with the incoming request and the outgoing
   116  	// response.
   117  	OnStreamResponse func(context.Context, int64, *v3discoverypb.DiscoveryRequest, *v3discoverypb.DiscoveryResponse)
   118  }
   119  
   120  // StartManagementServer initializes a management server which implements the
   121  // AggregatedDiscoveryService endpoint. The management server is initialized
   122  // with no resources. Tests should call the Update() method to change the
   123  // resource snapshot held by the management server, as required by the test
   124  // logic. When the test is done, it should call the Stop() method to cleanup
   125  // resources allocated by the management server.
   126  func StartManagementServer(opts ManagementServerOptions) (*ManagementServer, error) {
   127  	// Create a snapshot cache. The first parameter to NewSnapshotCache()
   128  	// controls whether the server should wait for all resources to be
   129  	// explicitly named in the request before responding to any of them.
   130  	wait := !opts.AllowResourceSubset
   131  	cache := v3cache.NewSnapshotCache(wait, v3cache.IDHash{}, serverLogger{})
   132  	logger.Infof("Created new snapshot cache...")
   133  
   134  	lis := opts.Listener
   135  	if lis == nil {
   136  		var err error
   137  		lis, err = net.Listen("tcp", "localhost:0")
   138  		if err != nil {
   139  			return nil, fmt.Errorf("listening on local host and port: %v", err)
   140  		}
   141  	}
   142  
   143  	// Cancelling the context passed to the server is the only way of stopping it
   144  	// at the end of the test.
   145  	ctx, cancel := context.WithCancel(context.Background())
   146  	callbacks := v3server.CallbackFuncs{
   147  		StreamOpenFunc:     opts.OnStreamOpen,
   148  		StreamClosedFunc:   opts.OnStreamClosed,
   149  		StreamRequestFunc:  opts.OnStreamRequest,
   150  		StreamResponseFunc: opts.OnStreamResponse,
   151  	}
   152  
   153  	// Create an xDS management server and register the ADS implementation
   154  	// provided by it on a gRPC server.
   155  	xs := v3server.NewServer(ctx, cache, callbacks)
   156  	gs := grpc.NewServer()
   157  	v3discoverygrpc.RegisterAggregatedDiscoveryServiceServer(gs, xs)
   158  	logger.Infof("Registered Aggregated Discovery Service (ADS)...")
   159  
   160  	mgmtServer := &ManagementServer{
   161  		Address: lis.Addr().String(),
   162  		cancel:  cancel,
   163  		version: 0,
   164  		gs:      gs,
   165  		xs:      xs,
   166  		cache:   cache,
   167  	}
   168  	if opts.SupportLoadReportingService {
   169  		lrs := fakeserver.NewServer(lis.Addr().String())
   170  		v3lrsgrpc.RegisterLoadReportingServiceServer(gs, lrs)
   171  		mgmtServer.LRSServer = lrs
   172  		logger.Infof("Registered Load Reporting Service (LRS)...")
   173  	}
   174  
   175  	// Start serving.
   176  	go gs.Serve(lis)
   177  	logger.Infof("xDS management server serving at: %v...", lis.Addr().String())
   178  
   179  	return mgmtServer, nil
   180  }
   181  
   182  // UpdateOptions wraps parameters to be passed to the Update() method.
   183  type UpdateOptions struct {
   184  	// NodeID is the id of the client to which this update is to be pushed.
   185  	NodeID string
   186  	// Endpoints, Clusters, Routes, and Listeners are the updated list of xds
   187  	// resources for the server.  All must be provided with each Update.
   188  	Endpoints []*v3endpointpb.ClusterLoadAssignment
   189  	Clusters  []*v3clusterpb.Cluster
   190  	Routes    []*v3routepb.RouteConfiguration
   191  	Listeners []*v3listenerpb.Listener
   192  	// SkipValidation indicates whether we want to skip validation (by not
   193  	// calling snapshot.Consistent()). It can be useful for negative tests,
   194  	// where we send updates that the client will NACK.
   195  	SkipValidation bool
   196  }
   197  
   198  // Update changes the resource snapshot held by the management server, which
   199  // updates connected clients as required.
   200  func (s *ManagementServer) Update(ctx context.Context, opts UpdateOptions) error {
   201  	s.version++
   202  
   203  	// Create a snapshot with the passed in resources.
   204  	resources := map[v3resource.Type][]types.Resource{
   205  		v3resource.ListenerType: resourceSlice(opts.Listeners),
   206  		v3resource.RouteType:    resourceSlice(opts.Routes),
   207  		v3resource.ClusterType:  resourceSlice(opts.Clusters),
   208  		v3resource.EndpointType: resourceSlice(opts.Endpoints),
   209  	}
   210  	snapshot, err := v3cache.NewSnapshot(strconv.Itoa(s.version), resources)
   211  	if err != nil {
   212  		return fmt.Errorf("failed to create new snapshot cache: %v", err)
   213  
   214  	}
   215  	if !opts.SkipValidation {
   216  		if err := snapshot.Consistent(); err != nil {
   217  			return fmt.Errorf("failed to create new resource snapshot: %v", err)
   218  		}
   219  	}
   220  	logger.Infof("Created new resource snapshot...")
   221  
   222  	// Update the cache with the new resource snapshot.
   223  	if err := s.cache.SetSnapshot(ctx, opts.NodeID, snapshot); err != nil {
   224  		return fmt.Errorf("failed to update resource snapshot in management server: %v", err)
   225  	}
   226  	logger.Infof("Updated snapshot cache with resource snapshot...")
   227  	return nil
   228  }
   229  
   230  // Stop stops the management server.
   231  func (s *ManagementServer) Stop() {
   232  	if s.cancel != nil {
   233  		s.cancel()
   234  	}
   235  	s.gs.Stop()
   236  }
   237  
   238  // resourceSlice accepts a slice of any type of proto messages and returns a
   239  // slice of types.Resource.  Will panic if there is an input type mismatch.
   240  func resourceSlice(i any) []types.Resource {
   241  	v := reflect.ValueOf(i)
   242  	rs := make([]types.Resource, v.Len())
   243  	for i := 0; i < v.Len(); i++ {
   244  		rs[i] = v.Index(i).Interface().(types.Resource)
   245  	}
   246  	return rs
   247  }