google.golang.org/grpc@v1.72.2/xds/server_ext_test.go (about) 1 /* 2 * 3 * Copyright 2024 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 xds_test 20 21 import ( 22 "context" 23 "fmt" 24 "io" 25 "net" 26 "strconv" 27 "strings" 28 "sync" 29 "testing" 30 "time" 31 32 "github.com/google/go-cmp/cmp" 33 "github.com/google/uuid" 34 "google.golang.org/grpc" 35 "google.golang.org/grpc/codes" 36 "google.golang.org/grpc/connectivity" 37 "google.golang.org/grpc/credentials/insecure" 38 "google.golang.org/grpc/internal/grpctest" 39 "google.golang.org/grpc/internal/stubserver" 40 "google.golang.org/grpc/internal/testutils" 41 "google.golang.org/grpc/internal/testutils/xds/e2e" 42 "google.golang.org/grpc/internal/xds/bootstrap" 43 "google.golang.org/grpc/peer" 44 "google.golang.org/grpc/status" 45 "google.golang.org/grpc/xds" 46 "google.golang.org/grpc/xds/internal/xdsclient" 47 48 v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" 49 v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" 50 v3discoverypb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" 51 testgrpc "google.golang.org/grpc/interop/grpc_testing" 52 testpb "google.golang.org/grpc/interop/grpc_testing" 53 ) 54 55 type s struct { 56 grpctest.Tester 57 } 58 59 func Test(t *testing.T) { 60 grpctest.RunSubTests(t, s{}) 61 } 62 63 const ( 64 defaultTestTimeout = 10 * time.Second 65 defaultTestShortTimeout = 10 * time.Millisecond // For events expected to *not* happen. 66 ) 67 68 func hostPortFromListener(lis net.Listener) (string, uint32, error) { 69 host, p, err := net.SplitHostPort(lis.Addr().String()) 70 if err != nil { 71 return "", 0, fmt.Errorf("net.SplitHostPort(%s) failed: %v", lis.Addr().String(), err) 72 } 73 port, err := strconv.ParseInt(p, 10, 32) 74 if err != nil { 75 return "", 0, fmt.Errorf("strconv.ParseInt(%s, 10, 32) failed: %v", p, err) 76 } 77 return host, uint32(port), nil 78 } 79 80 // servingModeChangeHandler handles changes to the serving mode of an 81 // xDS-enabled gRPC server. It logs the changes and sends the new mode and any 82 // errors on appropriate channels for the test to consume. 83 type servingModeChangeHandler struct { 84 logger interface { 85 Logf(format string, args ...any) 86 } 87 modeCh chan connectivity.ServingMode 88 errCh chan error 89 } 90 91 func newServingModeChangeHandler(t *testing.T) *servingModeChangeHandler { 92 return &servingModeChangeHandler{ 93 logger: t, 94 modeCh: make(chan connectivity.ServingMode, 1), 95 errCh: make(chan error, 1), 96 } 97 } 98 99 func (m *servingModeChangeHandler) modeChangeCallback(addr net.Addr, args xds.ServingModeChangeArgs) { 100 m.logger.Logf("Serving mode for listener %q changed to %q, err: %v", addr.String(), args.Mode, args.Err) 101 m.modeCh <- args.Mode 102 if args.Err != nil { 103 m.errCh <- args.Err 104 } 105 } 106 107 // createStubServer creates a new xDS-enabled gRPC server and returns a 108 // stubserver.StubServer that can be used for testing. The server is configured 109 // with the provided modeChangeOpt and xdsclient.Pool. 110 func createStubServer(t *testing.T, lis net.Listener, opts ...grpc.ServerOption) *stubserver.StubServer { 111 stub := &stubserver.StubServer{ 112 Listener: lis, 113 EmptyCallF: func(ctx context.Context, in *testpb.Empty) (*testpb.Empty, error) { 114 return &testpb.Empty{}, nil 115 }, 116 FullDuplexCallF: func(stream testgrpc.TestService_FullDuplexCallServer) error { 117 for { 118 if _, err := stream.Recv(); err == io.EOF { 119 return nil 120 } else if err != nil { 121 return err 122 } 123 } 124 }, 125 } 126 server, err := xds.NewGRPCServer(opts...) 127 if err != nil { 128 t.Fatalf("Failed to create an xDS enabled gRPC server: %v", err) 129 } 130 stub.S = server 131 stubserver.StartTestService(t, stub) 132 t.Cleanup(stub.Stop) 133 return stub 134 } 135 136 // waitForSuccessfulRPC waits for an RPC to succeed, repeatedly calling the 137 // EmptyCall RPC on the provided client connection until the call succeeds. 138 // If the context is canceled or the expected error is not before the context 139 // timeout expires, the test will fail. 140 func waitForSuccessfulRPC(ctx context.Context, t *testing.T, cc *grpc.ClientConn, opts ...grpc.CallOption) { 141 t.Helper() 142 143 client := testgrpc.NewTestServiceClient(cc) 144 for { 145 select { 146 case <-ctx.Done(): 147 t.Fatalf("Timeout waiting for RPCs to succeed") 148 case <-time.After(defaultTestShortTimeout): 149 if _, err := client.EmptyCall(ctx, &testpb.Empty{}, opts...); err == nil { 150 return 151 } 152 } 153 } 154 } 155 156 // waitForFailedRPCWithStatus waits for an RPC to fail with the expected status 157 // code, error message, and node ID. It repeatedly calls the EmptyCall RPC on 158 // the provided client connection until the error matches the expected values. 159 // If the context is canceled or the expected error is not before the context 160 // timeout expires, the test will fail. 161 func waitForFailedRPCWithStatus(ctx context.Context, t *testing.T, cc *grpc.ClientConn, wantCode codes.Code, wantErr, wantNodeID string) { 162 t.Helper() 163 164 client := testgrpc.NewTestServiceClient(cc) 165 var err error 166 for { 167 select { 168 case <-ctx.Done(): 169 t.Fatalf("RPCs failed with most recent error: %v. Want status code %v, error: %s, node id: %s", err, wantCode, wantErr, wantNodeID) 170 case <-time.After(defaultTestShortTimeout): 171 _, err = client.EmptyCall(ctx, &testpb.Empty{}) 172 if gotCode := status.Code(err); gotCode != wantCode { 173 continue 174 } 175 if gotErr := err.Error(); !strings.Contains(gotErr, wantErr) { 176 continue 177 } 178 if !strings.Contains(err.Error(), wantNodeID) { 179 continue 180 } 181 t.Logf("Most recent error happy case: %v", err.Error()) 182 return 183 } 184 } 185 } 186 187 // Tests the basic scenario for an xDS enabled gRPC server. 188 // 189 // - Verifies that the xDS enabled gRPC server requests for the expected 190 // listener resource. 191 // - Once the listener resource is received from the management server, it 192 // verifies that the xDS enabled gRPC server requests for the appropriate 193 // route configuration name. Also verifies that at this point, the server has 194 // not yet started serving RPCs 195 // - Once the route configuration is received from the management server, it 196 // verifies that the server can serve RPCs successfully. 197 func (s) TestServer_Basic(t *testing.T) { 198 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 199 defer cancel() 200 201 // Start an xDS management server. 202 listenerNamesCh := make(chan []string, 1) 203 routeNamesCh := make(chan []string, 1) 204 managementServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{ 205 OnStreamRequest: func(_ int64, req *v3discoverypb.DiscoveryRequest) error { 206 switch req.GetTypeUrl() { 207 case "type.googleapis.com/envoy.config.listener.v3.Listener": 208 select { 209 case listenerNamesCh <- req.GetResourceNames(): 210 case <-ctx.Done(): 211 } 212 case "type.googleapis.com/envoy.config.route.v3.RouteConfiguration": 213 select { 214 case routeNamesCh <- req.GetResourceNames(): 215 case <-ctx.Done(): 216 } 217 } 218 return nil 219 }, 220 AllowResourceSubset: true, 221 }) 222 223 // Create bootstrap configuration pointing to the above management server. 224 nodeID := uuid.New().String() 225 bootstrapContents := e2e.DefaultBootstrapContents(t, nodeID, managementServer.Address) 226 227 // Create a listener on a local port to act as the xDS enabled gRPC server. 228 lis, err := testutils.LocalTCPListener() 229 if err != nil { 230 t.Fatalf("Failed to listen to local port: %v", err) 231 } 232 host, port, err := hostPortFromListener(lis) 233 if err != nil { 234 t.Fatalf("Failed to retrieve host and port of server: %v", err) 235 } 236 237 // Configure the managegement server with a listener resource for the above 238 // xDS enabled gRPC server. 239 const routeConfigName = "routeName" 240 resources := e2e.UpdateOptions{ 241 NodeID: nodeID, 242 Listeners: []*v3listenerpb.Listener{e2e.DefaultServerListenerWithRouteConfigName(host, port, e2e.SecurityLevelNone, "routeName")}, 243 SkipValidation: true, 244 } 245 if err := managementServer.Update(ctx, resources); err != nil { 246 t.Fatal(err) 247 } 248 249 // Start an xDS-enabled gRPC server with the above bootstrap configuration. 250 config, err := bootstrap.NewConfigFromContents(bootstrapContents) 251 if err != nil { 252 t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err) 253 } 254 pool := xdsclient.NewPool(config) 255 modeChangeHandler := newServingModeChangeHandler(t) 256 modeChangeOpt := xds.ServingModeCallback(modeChangeHandler.modeChangeCallback) 257 createStubServer(t, lis, modeChangeOpt, xds.ClientPoolForTesting(pool)) 258 259 // Wait for the expected listener resource to be requested. 260 wantLisResourceNames := []string{fmt.Sprintf(e2e.ServerListenerResourceNameTemplate, net.JoinHostPort(host, strconv.Itoa(int(port))))} 261 select { 262 case <-ctx.Done(): 263 t.Fatal("Timeout waiting for the expected listener resource to be requested") 264 case gotLisResourceName := <-listenerNamesCh: 265 if !cmp.Equal(gotLisResourceName, wantLisResourceNames) { 266 t.Fatalf("Got unexpected listener resource names: %v, want %v", gotLisResourceName, wantLisResourceNames) 267 } 268 } 269 270 // Wait for the expected route config resource to be requested. 271 select { 272 case <-ctx.Done(): 273 t.Fatal("Timeout waiting for the expected route config resource to be requested") 274 case gotRouteNames := <-routeNamesCh: 275 if !cmp.Equal(gotRouteNames, []string{routeConfigName}) { 276 t.Fatalf("Got unexpected route config resource names: %v, want %v", gotRouteNames, []string{routeConfigName}) 277 } 278 } 279 280 // Ensure that the server is not serving RPCs yet. 281 sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout) 282 defer sCancel() 283 select { 284 case <-sCtx.Done(): 285 case <-modeChangeHandler.modeCh: 286 t.Fatal("Server started serving RPCs before the route config was received") 287 } 288 289 // Create a gRPC channel to the xDS enabled server. 290 cc, err := grpc.NewClient(lis.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials())) 291 if err != nil { 292 t.Fatalf("grpc.NewClient(%q) failed: %v", lis.Addr(), err) 293 } 294 defer cc.Close() 295 296 // Ensure that the server isnt't serving RPCs successfully. 297 client := testgrpc.NewTestServiceClient(cc) 298 if _, err := client.EmptyCall(ctx, &testpb.Empty{}); err == nil || status.Code(err) != codes.Unavailable { 299 t.Fatalf("EmptyCall() returned %v, want %v", err, codes.Unavailable) 300 } 301 302 // Configure the management server with the expected route config resource, 303 // and expext RPCs to succeed. 304 resources.Routes = []*v3routepb.RouteConfiguration{e2e.RouteConfigNonForwardingAction(routeConfigName)} 305 if err := managementServer.Update(ctx, resources); err != nil { 306 t.Fatal(err) 307 } 308 select { 309 case <-ctx.Done(): 310 t.Fatal("Timeout waiting for the server to start serving RPCs") 311 case gotMode := <-modeChangeHandler.modeCh: 312 if gotMode != connectivity.ServingModeServing { 313 t.Fatalf("Mode changed to %v, want %v", gotMode, connectivity.ServingModeServing) 314 } 315 } 316 waitForSuccessfulRPC(ctx, t, cc) 317 } 318 319 // Tests that the xDS-enabled gRPC server cleans up all its resources when all 320 // connections to it are closed. 321 func (s) TestServer_ConnectionCleanup(t *testing.T) { 322 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 323 defer cancel() 324 325 // Start an xDS management server. 326 managementServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 327 328 // Create bootstrap configuration pointing to the above management server. 329 nodeID := uuid.New().String() 330 bootstrapContents := e2e.DefaultBootstrapContents(t, nodeID, managementServer.Address) 331 332 // Create a listener on a local port to act as the xDS enabled gRPC server. 333 lis, err := testutils.LocalTCPListener() 334 if err != nil { 335 t.Fatalf("Failed to listen to local port: %v", err) 336 } 337 host, port, err := hostPortFromListener(lis) 338 if err != nil { 339 t.Fatalf("Failed to retrieve host and port of server: %v", err) 340 } 341 342 // Configure the managegement server with a listener and route configuration 343 // resource for the above xDS enabled gRPC server. 344 const routeConfigName = "routeName" 345 resources := e2e.UpdateOptions{ 346 NodeID: nodeID, 347 Listeners: []*v3listenerpb.Listener{e2e.DefaultServerListenerWithRouteConfigName(host, port, e2e.SecurityLevelNone, "routeName")}, 348 Routes: []*v3routepb.RouteConfiguration{e2e.RouteConfigNonForwardingAction(routeConfigName)}, 349 SkipValidation: true, 350 } 351 if err := managementServer.Update(ctx, resources); err != nil { 352 t.Fatal(err) 353 } 354 355 // Start an xDS-enabled gRPC server with the above bootstrap configuration. 356 config, err := bootstrap.NewConfigFromContents(bootstrapContents) 357 if err != nil { 358 t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err) 359 } 360 pool := xdsclient.NewPool(config) 361 modeChangeOpt := xds.ServingModeCallback(func(addr net.Addr, args xds.ServingModeChangeArgs) { 362 t.Logf("Serving mode for listener %q changed to %q, err: %v", addr.String(), args.Mode, args.Err) 363 }) 364 createStubServer(t, lis, modeChangeOpt, xds.ClientPoolForTesting(pool)) 365 366 // Create a gRPC channel and verify that RPCs succeed. 367 cc, err := grpc.NewClient(lis.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials())) 368 if err != nil { 369 t.Fatalf("grpc.NewClient(%q) failed: %v", lis.Addr(), err) 370 } 371 defer cc.Close() 372 waitForSuccessfulRPC(ctx, t, cc) 373 374 // Create multiple channels to the server, and make an RPC on each one. When 375 // everything is closed, the server should have cleaned up all its resources 376 // as well (and this will be verified by the leakchecker). 377 const numConns = 100 378 var wg sync.WaitGroup 379 wg.Add(numConns) 380 for range numConns { 381 go func() { 382 defer wg.Done() 383 cc, err := grpc.NewClient(lis.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials())) 384 if err != nil { 385 t.Errorf("grpc.NewClient failed with err: %v", err) 386 } 387 client := testgrpc.NewTestServiceClient(cc) 388 if _, err := client.EmptyCall(ctx, &testpb.Empty{}); err != nil { 389 t.Errorf("EmptyCall() failed: %v", err) 390 } 391 cc.Close() 392 }() 393 } 394 wg.Wait() 395 } 396 397 // Tests that multiple xDS-enabled gRPC servers can be created with different 398 // bootstrap configurations, and that they correctly request different LDS 399 // resources from the management server based on their respective listening 400 // ports. It also ensures that gRPC clients can connect to the intended server 401 // and that RPCs function correctly. The test uses the grpc.Peer() call option 402 // to validate that the client is connected to the correct server. 403 func (s) TestServer_MultipleServers_DifferentBootstrapConfigurations(t *testing.T) { 404 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 405 defer cancel() 406 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{AllowResourceSubset: true}) 407 408 // Create two bootstrap configurations pointing to the above management server. 409 nodeID1 := uuid.New().String() 410 bootstrapContents1 := e2e.DefaultBootstrapContents(t, nodeID1, mgmtServer.Address) 411 nodeID2 := uuid.New().String() 412 bootstrapContents2 := e2e.DefaultBootstrapContents(t, nodeID2, mgmtServer.Address) 413 414 // Create two xDS-enabled gRPC servers using the above bootstrap configs. 415 lis1, err := testutils.LocalTCPListener() 416 if err != nil { 417 t.Fatalf("testutils.LocalTCPListener() failed: %v", err) 418 } 419 lis2, err := testutils.LocalTCPListener() 420 if err != nil { 421 t.Fatalf("testutils.LocalTCPListener() failed: %v", err) 422 } 423 424 modeChangeHandler1 := newServingModeChangeHandler(t) 425 modeChangeOpt1 := xds.ServingModeCallback(modeChangeHandler1.modeChangeCallback) 426 modeChangeHandler2 := newServingModeChangeHandler(t) 427 modeChangeOpt2 := xds.ServingModeCallback(modeChangeHandler2.modeChangeCallback) 428 config1, err := bootstrap.NewConfigFromContents(bootstrapContents1) 429 if err != nil { 430 t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents1), err) 431 } 432 pool1 := xdsclient.NewPool(config1) 433 config2, err := bootstrap.NewConfigFromContents(bootstrapContents2) 434 if err != nil { 435 t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents2), err) 436 } 437 pool2 := xdsclient.NewPool(config2) 438 createStubServer(t, lis1, modeChangeOpt1, xds.ClientPoolForTesting(pool1)) 439 createStubServer(t, lis2, modeChangeOpt2, xds.ClientPoolForTesting(pool2)) 440 441 // Update the management server with the listener resources pointing to the 442 // corresponding gRPC servers. 443 host1, port1, err := hostPortFromListener(lis1) 444 if err != nil { 445 t.Fatalf("Failed to retrieve host and port of server: %v", err) 446 } 447 host2, port2, err := hostPortFromListener(lis2) 448 if err != nil { 449 t.Fatalf("Failed to retrieve host and port of server: %v", err) 450 } 451 452 resources1 := e2e.UpdateOptions{ 453 NodeID: nodeID1, 454 Listeners: []*v3listenerpb.Listener{e2e.DefaultServerListener(host1, port1, e2e.SecurityLevelNone, "routeName")}, 455 } 456 if err := mgmtServer.Update(ctx, resources1); err != nil { 457 t.Fatal(err) 458 } 459 460 resources2 := e2e.UpdateOptions{ 461 NodeID: nodeID2, 462 Listeners: []*v3listenerpb.Listener{e2e.DefaultServerListener(host2, port2, e2e.SecurityLevelNone, "routeName")}, 463 } 464 if err := mgmtServer.Update(ctx, resources2); err != nil { 465 t.Fatal(err) 466 } 467 468 select { 469 case <-ctx.Done(): 470 t.Fatal("Timeout waiting for the xDS-enabled gRPC server to go SERVING") 471 case gotMode := <-modeChangeHandler1.modeCh: 472 if gotMode != connectivity.ServingModeServing { 473 t.Fatalf("Mode changed to %v, want %v", gotMode, connectivity.ServingModeServing) 474 } 475 } 476 select { 477 case <-ctx.Done(): 478 t.Fatal("Timeout waiting for the xDS-enabled gRPC server to go SERVING") 479 case gotMode := <-modeChangeHandler2.modeCh: 480 if gotMode != connectivity.ServingModeServing { 481 t.Fatalf("Mode changed to %v, want %v", gotMode, connectivity.ServingModeServing) 482 } 483 } 484 485 // Create two gRPC clients, one for each server. 486 cc1, err := grpc.NewClient(lis1.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials())) 487 if err != nil { 488 t.Fatalf("Failed to create client for test server 1: %s, %v", lis1.Addr().String(), err) 489 } 490 defer cc1.Close() 491 492 cc2, err := grpc.NewClient(lis2.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials())) 493 if err != nil { 494 t.Fatalf("Failed to create client for test server 2: %s, %v", lis2.Addr().String(), err) 495 } 496 defer cc2.Close() 497 498 // Both unary RPCs should work once the servers transitions into serving. 499 var peer1 peer.Peer 500 waitForSuccessfulRPC(ctx, t, cc1, grpc.Peer(&peer1)) 501 if peer1.Addr.String() != lis1.Addr().String() { 502 t.Errorf("Connected to wrong peer: %s, want %s", peer1.Addr, lis1.Addr()) 503 } 504 505 var peer2 peer.Peer 506 waitForSuccessfulRPC(ctx, t, cc2, grpc.Peer(&peer2)) 507 if peer2.Addr.String() != lis2.Addr().String() { 508 t.Errorf("Connected to wrong peer: %s, want %s", peer2.Addr, lis2.Addr()) 509 } 510 }