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 }