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