google.golang.org/grpc@v1.72.2/xds/server_serving_mode_ext_test.go (about) 1 /* 2 * 3 * Copyright 2025 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 "io" 24 "strings" 25 "testing" 26 27 "github.com/google/uuid" 28 "google.golang.org/grpc" 29 "google.golang.org/grpc/codes" 30 "google.golang.org/grpc/connectivity" 31 "google.golang.org/grpc/credentials/insecure" 32 "google.golang.org/grpc/internal/testutils" 33 "google.golang.org/grpc/internal/testutils/xds/e2e" 34 "google.golang.org/grpc/internal/testutils/xds/e2e/setup" 35 "google.golang.org/grpc/internal/xds/bootstrap" 36 "google.golang.org/grpc/status" 37 "google.golang.org/grpc/xds" 38 "google.golang.org/grpc/xds/internal/xdsclient" 39 40 v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" 41 v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" 42 testgrpc "google.golang.org/grpc/interop/grpc_testing" 43 testpb "google.golang.org/grpc/interop/grpc_testing" 44 ) 45 46 // Tests the Server's logic as it transitions from NOT_SERVING to SERVING, then 47 // to NOT_SERVING again. Before it goes to SERVING, connections should be 48 // accepted and closed. After it goes SERVING, RPC's should proceed as normal 49 // according to matched route configuration. After it transitions back into 50 // NOT_SERVING, (through an explicit LDS Resource Not Found), previously running 51 // RPC's should be gracefully closed and still work, and new RPC's should fail. 52 func (s) TestServer_ServingModeChanges_SingleServer(t *testing.T) { 53 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 54 defer cancel() 55 managementServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{AllowResourceSubset: true}) 56 57 // Create bootstrap configuration pointing to the above management server. 58 nodeID := uuid.New().String() 59 bootstrapContents := e2e.DefaultBootstrapContents(t, nodeID, managementServer.Address) 60 61 // Setup the management server to respond with a listener resource that 62 // specifies a route name to watch. Due to not having received the full 63 // configuration, this should cause the server to be in mode NOT_SERVING. 64 lis, err := testutils.LocalTCPListener() 65 if err != nil { 66 t.Fatalf("testutils.LocalTCPListener() failed: %v", err) 67 } 68 host, port, err := hostPortFromListener(lis) 69 if err != nil { 70 t.Fatalf("Failed to retrieve host and port of server: %v", err) 71 } 72 listener := e2e.DefaultServerListenerWithRouteConfigName(host, port, e2e.SecurityLevelNone, "routeName") 73 resources := e2e.UpdateOptions{ 74 NodeID: nodeID, 75 Listeners: []*v3listenerpb.Listener{listener}, 76 SkipValidation: true, 77 } 78 if err := managementServer.Update(ctx, resources); err != nil { 79 t.Fatal(err) 80 } 81 82 // Start an xDS-enabled gRPC server with the above bootstrap configuration. 83 config, err := bootstrap.NewConfigFromContents(bootstrapContents) 84 if err != nil { 85 t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err) 86 } 87 pool := xdsclient.NewPool(config) 88 modeChangeHandler := newServingModeChangeHandler(t) 89 modeChangeOpt := xds.ServingModeCallback(modeChangeHandler.modeChangeCallback) 90 createStubServer(t, lis, modeChangeOpt, xds.ClientPoolForTesting(pool)) 91 92 // Start a gRPC channel to the above server. The server is yet to receive 93 // route configuration, and therefore RPCs must fail at this time. 94 cc, err := grpc.NewClient(lis.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials())) 95 if err != nil { 96 t.Fatalf("Failed to dial local test server: %v", err) 97 } 98 defer cc.Close() 99 waitForFailedRPCWithStatus(ctx, t, cc, codes.Unavailable, "", "") 100 101 // Setup the route configuration resource on the management server. This 102 // should cause the xDS-enabled gRPC server to move to SERVING mode. 103 routeConfig := e2e.RouteConfigNonForwardingAction("routeName") 104 resources = e2e.UpdateOptions{ 105 NodeID: nodeID, 106 Listeners: []*v3listenerpb.Listener{listener}, 107 Routes: []*v3routepb.RouteConfiguration{routeConfig}, 108 SkipValidation: true, 109 } 110 defer cancel() 111 if err := managementServer.Update(ctx, resources); err != nil { 112 t.Fatal(err) 113 } 114 select { 115 case <-ctx.Done(): 116 t.Fatal("Timeout waiting for the xDS-enabled gRPC server to go SERVING") 117 case gotMode := <-modeChangeHandler.modeCh: 118 if gotMode != connectivity.ServingModeServing { 119 t.Fatalf("Mode changed to %v, want %v", gotMode, connectivity.ServingModeServing) 120 } 121 } 122 waitForSuccessfulRPC(ctx, t, cc) 123 124 // Start a stream before switching the server to not serving. Due to the 125 // stream being created before the graceful stop of the underlying 126 // connection, it should be able to continue even after the server switches 127 // to not serving. 128 c := testgrpc.NewTestServiceClient(cc) 129 stream, err := c.FullDuplexCall(ctx) 130 if err != nil { 131 t.Fatalf("cc.FullDuplexCall failed: %f", err) 132 } 133 134 // Remove the listener resource from the management server. 135 resources.Listeners = nil 136 if err := managementServer.Update(ctx, resources); err != nil { 137 t.Fatal(err) 138 } 139 140 // Ensure the server is in NOT_SERVING mode. 141 select { 142 case <-ctx.Done(): 143 t.Fatal("Timeout waiting for the xDS-enabled gRPC server to go NOT_SERVING") 144 case gotMode := <-modeChangeHandler.modeCh: 145 if gotMode != connectivity.ServingModeNotServing { 146 t.Fatalf("Mode changed to %v, want %v", gotMode, connectivity.ServingModeNotServing) 147 } 148 gotErr := <-modeChangeHandler.errCh 149 if gotErr == nil || !strings.Contains(gotErr.Error(), nodeID) { 150 t.Fatalf("Unexpected error: %v, want xDS Node id: %s", gotErr, nodeID) 151 } 152 } 153 154 // Due to graceful stop, any started streams continue to work. 155 if err = stream.Send(&testpb.StreamingOutputCallRequest{}); err != nil { 156 t.Fatalf("stream.Send() failed: %v, should continue to work due to graceful stop", err) 157 } 158 if err = stream.CloseSend(); err != nil { 159 t.Fatalf("stream.CloseSend() failed: %v, should continue to work due to graceful stop", err) 160 } 161 if _, err = stream.Recv(); err != io.EOF { 162 t.Fatalf("stream.Recv() failed with %v, want io.EOF", err) 163 } 164 165 // New RPCs on that connection should eventually start failing. 166 waitForFailedRPCWithStatus(ctx, t, cc, codes.Unavailable, "", "") 167 } 168 169 // Tests the serving mode functionality with multiple xDS enabled gRPC servers. 170 func (s) TestServer_ServingModeChanges_MultipleServers(t *testing.T) { 171 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 172 defer cancel() 173 managementServer, nodeID, bootstrapContents, _ := setup.ManagementServerAndResolver(t) 174 175 // Create two local listeners and pass it to Serve(). 176 lis1, err := testutils.LocalTCPListener() 177 if err != nil { 178 t.Fatalf("testutils.LocalTCPListener() failed: %v", err) 179 } 180 lis2, err := testutils.LocalTCPListener() 181 if err != nil { 182 t.Fatalf("testutils.LocalTCPListener() failed: %v", err) 183 } 184 185 // Create a server option to get notified about serving mode changes. 186 modeChangeHandler1 := newServingModeChangeHandler(t) 187 modeChangeOpt1 := xds.ServingModeCallback(modeChangeHandler1.modeChangeCallback) 188 modeChangeHandler2 := newServingModeChangeHandler(t) 189 modeChangeOpt2 := xds.ServingModeCallback(modeChangeHandler2.modeChangeCallback) 190 191 // Start two xDS-enabled gRPC servers with the above bootstrap configuration. 192 config, err := bootstrap.NewConfigFromContents(bootstrapContents) 193 if err != nil { 194 t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err) 195 } 196 pool := xdsclient.NewPool(config) 197 createStubServer(t, lis1, modeChangeOpt1, xds.ClientPoolForTesting(pool)) 198 createStubServer(t, lis2, modeChangeOpt2, xds.ClientPoolForTesting(pool)) 199 200 // Setup the management server to respond with server-side Listener 201 // resources for both listeners. 202 host1, port1, err := hostPortFromListener(lis1) 203 if err != nil { 204 t.Fatalf("Failed to retrieve host and port of server: %v", err) 205 } 206 listener1 := e2e.DefaultServerListener(host1, port1, e2e.SecurityLevelNone, "routeName") 207 host2, port2, err := hostPortFromListener(lis2) 208 if err != nil { 209 t.Fatalf("Failed to retrieve host and port of server: %v", err) 210 } 211 listener2 := e2e.DefaultServerListener(host2, port2, e2e.SecurityLevelNone, "routeName") 212 resources := e2e.UpdateOptions{ 213 NodeID: nodeID, 214 Listeners: []*v3listenerpb.Listener{listener1, listener2}, 215 } 216 if err := managementServer.Update(ctx, resources); err != nil { 217 t.Fatal(err) 218 } 219 220 // Wait for both listeners to move to "serving" mode. 221 select { 222 case <-ctx.Done(): 223 t.Fatalf("Timed out waiting for a mode change update: %v", err) 224 case mode := <-modeChangeHandler1.modeCh: 225 if mode != connectivity.ServingModeServing { 226 t.Fatalf("Listener 1 received new mode %v, want %v", mode, connectivity.ServingModeServing) 227 } 228 } 229 select { 230 case <-ctx.Done(): 231 t.Fatalf("Timed out waiting for a mode change update: %v", err) 232 case mode := <-modeChangeHandler2.modeCh: 233 if mode != connectivity.ServingModeServing { 234 t.Fatalf("Listener 2 received new mode %v, want %v", mode, connectivity.ServingModeServing) 235 } 236 } 237 238 // Create a ClientConn to the first listener and make a successful RPCs. 239 cc1, err := grpc.NewClient(lis1.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials())) 240 if err != nil { 241 t.Fatalf("grpc.NewClient() failed: %v", err) 242 } 243 defer cc1.Close() 244 waitForSuccessfulRPC(ctx, t, cc1) 245 246 // Create a ClientConn to the second listener and make a successful RPCs. 247 cc2, err := grpc.NewClient(lis2.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials())) 248 if err != nil { 249 t.Fatalf("grpc.NewClient() failed: %v", err) 250 } 251 defer cc2.Close() 252 waitForSuccessfulRPC(ctx, t, cc2) 253 254 // Update the management server to remove the second listener resource. This 255 // should push only the second listener into "not-serving" mode. 256 if err := managementServer.Update(ctx, e2e.UpdateOptions{ 257 NodeID: nodeID, 258 Listeners: []*v3listenerpb.Listener{listener1}, 259 }); err != nil { 260 t.Fatal(err) 261 } 262 263 // Wait for lis2 to move to "not-serving" mode. 264 select { 265 case <-ctx.Done(): 266 t.Fatalf("Timed out waiting for a mode change update: %v", err) 267 case mode := <-modeChangeHandler2.modeCh: 268 if mode != connectivity.ServingModeNotServing { 269 t.Fatalf("Listener received new mode %v, want %v", mode, connectivity.ServingModeNotServing) 270 } 271 gotErr := <-modeChangeHandler2.errCh 272 if gotErr == nil || !strings.Contains(gotErr.Error(), nodeID) { 273 t.Fatalf("Unexpected error: %v, want xDS Node id: %s", gotErr, nodeID) 274 } 275 } 276 277 // Make sure RPCs succeed on cc1 and fail on cc2. 278 waitForSuccessfulRPC(ctx, t, cc1) 279 waitForFailedRPCWithStatus(ctx, t, cc2, codes.Unavailable, "", "") 280 281 // Update the management server to remove the first listener resource as 282 // well. This should push the first listener into "not-serving" mode. Second 283 // listener is already in "not-serving" mode. 284 if err := managementServer.Update(ctx, e2e.UpdateOptions{ 285 NodeID: nodeID, 286 Listeners: []*v3listenerpb.Listener{}, 287 }); err != nil { 288 t.Fatal(err) 289 } 290 291 // Wait for lis1 to move to "not-serving" mode. lis2 was already removed 292 // from the xdsclient's resource cache. So, lis2's callback will not be 293 // invoked this time around. 294 select { 295 case <-ctx.Done(): 296 t.Fatalf("Timed out waiting for a mode change update: %v", err) 297 case mode := <-modeChangeHandler1.modeCh: 298 if mode != connectivity.ServingModeNotServing { 299 t.Fatalf("Listener received new mode %v, want %v", mode, connectivity.ServingModeNotServing) 300 } 301 gotErr := <-modeChangeHandler1.errCh 302 if gotErr == nil || !strings.Contains(gotErr.Error(), nodeID) { 303 t.Fatalf("Unexpected error: %v, want xDS Node id: %s", gotErr, nodeID) 304 } 305 } 306 307 // Make sure RPCs fail on both. 308 waitForFailedRPCWithStatus(ctx, t, cc1, codes.Unavailable, "", "") 309 waitForFailedRPCWithStatus(ctx, t, cc2, codes.Unavailable, "", "") 310 311 // Make sure new connection attempts to "not-serving" servers fail. 312 if cc1, err = grpc.NewClient(lis1.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials())); err != nil { 313 t.Fatal("Failed to create clientConn to a server in \"not-serving\" state") 314 } 315 defer cc1.Close() 316 if _, err := testgrpc.NewTestServiceClient(cc1).FullDuplexCall(ctx); status.Code(err) != codes.Unavailable { 317 t.Fatalf("FullDuplexCall failed with status code: %v, want: Unavailable", status.Code(err)) 318 } 319 320 // Update the management server with both listener resources. 321 if err := managementServer.Update(ctx, e2e.UpdateOptions{ 322 NodeID: nodeID, 323 Listeners: []*v3listenerpb.Listener{listener1, listener2}, 324 }); err != nil { 325 t.Fatal(err) 326 } 327 328 // Wait for both listeners to move to "serving" mode. 329 select { 330 case <-ctx.Done(): 331 t.Fatalf("Timed out waiting for a mode change update: %v", err) 332 case mode := <-modeChangeHandler1.modeCh: 333 if mode != connectivity.ServingModeServing { 334 t.Fatalf("Listener received new mode %v, want %v", mode, connectivity.ServingModeServing) 335 } 336 } 337 select { 338 case <-ctx.Done(): 339 t.Fatalf("Timed out waiting for a mode change update: %v", err) 340 case mode := <-modeChangeHandler2.modeCh: 341 if mode != connectivity.ServingModeServing { 342 t.Fatalf("Listener received new mode %v, want %v", mode, connectivity.ServingModeServing) 343 } 344 } 345 346 // The clientConns created earlier should be able to make RPCs now. 347 waitForSuccessfulRPC(ctx, t, cc1) 348 waitForSuccessfulRPC(ctx, t, cc2) 349 }