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