google.golang.org/grpc@v1.74.2/xds/server_resource_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 "fmt" 24 "io" 25 "net" 26 "strings" 27 "testing" 28 "time" 29 30 "github.com/google/go-cmp/cmp" 31 "github.com/google/uuid" 32 "google.golang.org/grpc" 33 "google.golang.org/grpc/codes" 34 "google.golang.org/grpc/connectivity" 35 "google.golang.org/grpc/credentials/insecure" 36 "google.golang.org/grpc/internal/testutils" 37 "google.golang.org/grpc/internal/testutils/xds/e2e" 38 "google.golang.org/grpc/internal/xds/bootstrap" 39 "google.golang.org/grpc/xds" 40 "google.golang.org/grpc/xds/internal/xdsclient" 41 "google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version" 42 "google.golang.org/protobuf/types/known/wrapperspb" 43 44 v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 45 v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" 46 v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" 47 v3routerpb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/router/v3" 48 v3httppb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" 49 v3discoverypb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" 50 testgrpc "google.golang.org/grpc/interop/grpc_testing" 51 testpb "google.golang.org/grpc/interop/grpc_testing" 52 ) 53 54 // Tests the case where an LDS points to an RDS which returns resource not 55 // found. Before getting the resource not found, the xDS Server has not received 56 // all configuration needed, so it should Accept and Close any new connections. 57 // After it has received the resource not found error (due to short watch 58 // expiry), the server should move to serving, successfully Accept Connections, 59 // and fail at the L7 level with resource not found specified. 60 func (s) TestServer_RouteConfiguration_ResourceNotFound(t *testing.T) { 61 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 62 defer cancel() 63 64 routeConfigNamesCh := make(chan []string, 1) 65 managementServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{ 66 OnStreamRequest: func(_ int64, req *v3discoverypb.DiscoveryRequest) error { 67 if req.TypeUrl == version.V3RouteConfigURL { 68 select { 69 case routeConfigNamesCh <- req.GetResourceNames(): 70 case <-ctx.Done(): 71 } 72 } 73 return nil 74 }, 75 AllowResourceSubset: true, 76 }) 77 78 // Create bootstrap configuration pointing to the above management server. 79 nodeID := uuid.New().String() 80 bootstrapContents := e2e.DefaultBootstrapContents(t, nodeID, managementServer.Address) 81 82 // Setup the management server to respond with a listener resource that 83 // specifies a route name to watch, and no RDS resource corresponding to 84 // this route name. 85 lis, err := testutils.LocalTCPListener() 86 if err != nil { 87 t.Fatalf("testutils.LocalTCPListener() failed: %v", err) 88 } 89 host, port, err := hostPortFromListener(lis) 90 if err != nil { 91 t.Fatalf("Failed to retrieve host and port of server: %v", err) 92 } 93 const routeConfigResourceName = "routeName" 94 listener := e2e.DefaultServerListenerWithRouteConfigName(host, port, e2e.SecurityLevelNone, routeConfigResourceName) 95 resources := e2e.UpdateOptions{ 96 NodeID: nodeID, 97 Listeners: []*v3listenerpb.Listener{listener}, 98 SkipValidation: true, 99 } 100 if err := managementServer.Update(ctx, resources); err != nil { 101 t.Fatal(err) 102 } 103 104 modeChangeHandler := newServingModeChangeHandler(t) 105 modeChangeOpt := xds.ServingModeCallback(modeChangeHandler.modeChangeCallback) 106 107 config, err := bootstrap.NewConfigFromContents(bootstrapContents) 108 if err != nil { 109 t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err) 110 } 111 // Create a specific xDS client instance within that pool for the server, 112 // configuring it with a short WatchExpiryTimeout. 113 pool := xdsclient.NewPool(config) 114 _, serverXDSClientClose, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{ 115 Name: xdsclient.NameForServer, 116 WatchExpiryTimeout: 500 * time.Millisecond, 117 }) 118 if err != nil { 119 t.Fatalf("Failed to create xDS client for server: %v", err) 120 } 121 defer serverXDSClientClose() 122 // Start an xDS-enabled gRPC server using the above client from the pool. 123 createStubServer(t, lis, modeChangeOpt, xds.ClientPoolForTesting(pool)) 124 125 // Wait for the route configuration resource to be requested from the 126 // management server. 127 select { 128 case gotNames := <-routeConfigNamesCh: 129 if !cmp.Equal(gotNames, []string{routeConfigResourceName}) { 130 t.Fatalf("Requested route config resource names: %v, want %v", gotNames, []string{routeConfigResourceName}) 131 } 132 case <-ctx.Done(): 133 t.Fatal("Timeout waiting for route config resource to be requested") 134 } 135 136 // Do NOT send the RDS resource. The xDS client's watch expiry timer will 137 // fire. After the RDS resource is deemed "not found" (due to the short 138 // watch expiry), the server will transition to SERVING mode. 139 140 cc, err := grpc.NewClient(lis.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials())) 141 if err != nil { 142 t.Fatalf("failed to dial local test server: %v", err) 143 } 144 defer cc.Close() 145 // Before the watch expiry, the server is NOT_SERVING, RPCs should fail with UNAVAILABLE. 146 waitForFailedRPCWithStatus(ctx, t, cc, codes.Unavailable, "", "") 147 148 // Wait for the xDS-enabled gRPC server to go SERVING. This should happen 149 // after the RDS watch expiry timer fires. 150 select { 151 case <-ctx.Done(): 152 t.Fatal("Timeout waiting for the xDS-enabled gRPC server to go SERVING") 153 case gotMode := <-modeChangeHandler.modeCh: 154 if gotMode != connectivity.ServingModeServing { 155 t.Fatalf("Mode changed to %v, want %v", gotMode, connectivity.ServingModeServing) 156 } 157 } 158 // After watch expiry, the server should be SERVING, but RPCs should fail 159 // at the L7 level with resource not found. 160 waitForFailedRPCWithStatus(ctx, t, cc, codes.Unavailable, "error from xDS configuration for matched route configuration", nodeID) 161 } 162 163 // Tests the scenario where the control plane sends the same resource update. It 164 // verifies that the mode change callback is not invoked and client connections 165 // to the server are not recycled. 166 func (s) TestServer_RedundantUpdateSuppression(t *testing.T) { 167 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 168 defer cancel() 169 managementServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{AllowResourceSubset: true}) 170 171 // Create bootstrap configuration pointing to the above management server. 172 nodeID := uuid.New().String() 173 bootstrapContents := e2e.DefaultBootstrapContents(t, nodeID, managementServer.Address) 174 175 // Setup the management server to respond with the listener resources. 176 lis, err := testutils.LocalTCPListener() 177 if err != nil { 178 t.Fatalf("testutils.LocalTCPListener() failed: %v", err) 179 } 180 host, port, err := hostPortFromListener(lis) 181 if err != nil { 182 t.Fatalf("Failed to retrieve host and port of server: %v", err) 183 } 184 listener := e2e.DefaultServerListener(host, port, e2e.SecurityLevelNone, "routeName") 185 resources := e2e.UpdateOptions{ 186 NodeID: nodeID, 187 Listeners: []*v3listenerpb.Listener{listener}, 188 } 189 if err := managementServer.Update(ctx, resources); err != nil { 190 t.Fatal(err) 191 } 192 193 // Start an xDS-enabled gRPC server with the above bootstrap configuration. 194 config, err := bootstrap.NewConfigFromContents(bootstrapContents) 195 if err != nil { 196 t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err) 197 } 198 pool := xdsclient.NewPool(config) 199 modeChangeHandler := newServingModeChangeHandler(t) 200 modeChangeOpt := xds.ServingModeCallback(modeChangeHandler.modeChangeCallback) 201 createStubServer(t, lis, modeChangeOpt, xds.ClientPoolForTesting(pool)) 202 203 select { 204 case <-ctx.Done(): 205 t.Fatalf("Timed out waiting for a mode change update: %v", err) 206 case mode := <-modeChangeHandler.modeCh: 207 if mode != connectivity.ServingModeServing { 208 t.Fatalf("Listener received new mode %v, want %v", mode, connectivity.ServingModeServing) 209 } 210 } 211 212 // Create a ClientConn and make a successful RPCs. 213 cc, err := grpc.NewClient(lis.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials())) 214 if err != nil { 215 t.Fatalf("grpc.NewClient(%q) failed: %v", lis.Addr(), err) 216 } 217 defer cc.Close() 218 waitForSuccessfulRPC(ctx, t, cc) 219 220 // Start a goroutine to make sure that we do not see any connectivity state 221 // changes on the client connection. If redundant updates are not 222 // suppressed, server will recycle client connections. 223 errCh := make(chan error, 1) 224 go func() { 225 prev := connectivity.Ready // We know we are READY since we just did an RPC. 226 for { 227 curr := cc.GetState() 228 if !(curr == connectivity.Ready || curr == connectivity.Idle) { 229 errCh <- fmt.Errorf("unexpected connectivity state change {%s --> %s} on the client connection", prev, curr) 230 return 231 } 232 if !cc.WaitForStateChange(ctx, curr) { 233 // Break out of the for loop when the context has been cancelled. 234 break 235 } 236 prev = curr 237 } 238 errCh <- nil 239 }() 240 241 // Update the management server with the same listener resource. This will 242 // update the resource version though, and should result in a the management 243 // server sending the same resource to the xDS-enabled gRPC server. 244 if err := managementServer.Update(ctx, e2e.UpdateOptions{ 245 NodeID: nodeID, 246 Listeners: []*v3listenerpb.Listener{listener}, 247 }); err != nil { 248 t.Fatal(err) 249 } 250 251 // Since redundant resource updates are suppressed, we should not see the 252 // mode change callback being invoked. 253 sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout) 254 defer sCancel() 255 select { 256 case <-sCtx.Done(): 257 case mode := <-modeChangeHandler.modeCh: 258 t.Fatalf("Unexpected mode change callback with new mode %v", mode) 259 } 260 261 // Make sure RPCs continue to succeed. 262 waitForSuccessfulRPC(ctx, t, cc) 263 264 // Cancel the context to ensure that the WaitForStateChange call exits early 265 // and returns false. 266 cancel() 267 if err := <-errCh; err != nil { 268 t.Fatal(err) 269 } 270 } 271 272 // Tests the case where the route configuration contains an unsupported route 273 // action. Verifies that RPCs fail with UNAVAILABLE. 274 func (s) TestServer_FailWithRouteActionRoute(t *testing.T) { 275 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 276 defer cancel() 277 278 // Start an xDS management server. 279 managementServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 280 281 // Create bootstrap configuration pointing to the above management server. 282 nodeID := uuid.New().String() 283 bootstrapContents := e2e.DefaultBootstrapContents(t, nodeID, managementServer.Address) 284 285 // Configure the managegement server with a listener and route configuration 286 // resource for the above xDS enabled gRPC server. 287 lis, err := testutils.LocalTCPListener() 288 if err != nil { 289 t.Fatalf("Failed to listen to local port: %v", err) 290 } 291 host, port, err := hostPortFromListener(lis) 292 if err != nil { 293 t.Fatalf("Failed to retrieve host and port of server: %v", err) 294 } 295 const routeConfigName = "routeName" 296 resources := e2e.UpdateOptions{ 297 NodeID: nodeID, 298 Listeners: []*v3listenerpb.Listener{e2e.DefaultServerListenerWithRouteConfigName(host, port, e2e.SecurityLevelNone, "routeName")}, 299 Routes: []*v3routepb.RouteConfiguration{e2e.RouteConfigNonForwardingAction(routeConfigName)}, 300 } 301 if err := managementServer.Update(ctx, resources); err != nil { 302 t.Fatal(err) 303 } 304 305 // Start an xDS-enabled gRPC server with the above bootstrap configuration. 306 config, err := bootstrap.NewConfigFromContents(bootstrapContents) 307 if err != nil { 308 t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err) 309 } 310 pool := xdsclient.NewPool(config) 311 modeChangeOpt := xds.ServingModeCallback(func(addr net.Addr, args xds.ServingModeChangeArgs) { 312 t.Logf("Serving mode for listener %q changed to %q, err: %v", addr.String(), args.Mode, args.Err) 313 }) 314 createStubServer(t, lis, modeChangeOpt, xds.ClientPoolForTesting(pool)) 315 316 // Create a gRPC channel and verify that RPCs succeed. 317 cc, err := grpc.NewClient(lis.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials())) 318 if err != nil { 319 t.Fatalf("grpc.NewClient(%q) failed: %v", lis.Addr(), err) 320 } 321 defer cc.Close() 322 waitForSuccessfulRPC(ctx, t, cc) 323 324 // Update the route config resource to contain an unsupported action. 325 // 326 // "NonForwardingAction is expected for all Routes used on server-side; a 327 // route with an inappropriate action causes RPCs matching that route to 328 // fail with UNAVAILABLE." - A36 329 resources.Routes = []*v3routepb.RouteConfiguration{e2e.RouteConfigFilterAction("routeName")} 330 if err := managementServer.Update(ctx, resources); err != nil { 331 t.Fatal(err) 332 } 333 waitForFailedRPCWithStatus(ctx, t, cc, codes.Unavailable, "the incoming RPC matched to a route that was not of action type non forwarding", nodeID) 334 } 335 336 // Tests the case where the listener resource is removed from the management 337 // server. This should cause the xDS server to transition to NOT_SERVING mode, 338 // and the error message should contain the xDS node ID. 339 func (s) TestServer_ListenerResourceRemoved(t *testing.T) { 340 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 341 defer cancel() 342 343 // Start an xDS management server. 344 managementServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 345 346 // Create bootstrap configuration pointing to the above management server. 347 nodeID := uuid.New().String() 348 bootstrapContents := e2e.DefaultBootstrapContents(t, nodeID, managementServer.Address) 349 350 // Configure the managegement server with a listener and route configuration 351 // resource for the above xDS enabled gRPC server. 352 lis, err := testutils.LocalTCPListener() 353 if err != nil { 354 t.Fatalf("Failed to listen to local port: %v", err) 355 } 356 host, port, err := hostPortFromListener(lis) 357 if err != nil { 358 t.Fatalf("Failed to retrieve host and port of server: %v", err) 359 } 360 const routeConfigName = "routeName" 361 resources := e2e.UpdateOptions{ 362 NodeID: nodeID, 363 Listeners: []*v3listenerpb.Listener{e2e.DefaultServerListenerWithRouteConfigName(host, port, e2e.SecurityLevelNone, "routeName")}, 364 Routes: []*v3routepb.RouteConfiguration{e2e.RouteConfigNonForwardingAction(routeConfigName)}, 365 SkipValidation: true, 366 } 367 if err := managementServer.Update(ctx, resources); err != nil { 368 t.Fatal(err) 369 } 370 371 // Start an xDS-enabled gRPC server with the above bootstrap configuration. 372 config, err := bootstrap.NewConfigFromContents(bootstrapContents) 373 if err != nil { 374 t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err) 375 } 376 pool := xdsclient.NewPool(config) 377 modeChangeHandler := newServingModeChangeHandler(t) 378 modeChangeOpt := xds.ServingModeCallback(modeChangeHandler.modeChangeCallback) 379 createStubServer(t, lis, modeChangeOpt, xds.ClientPoolForTesting(pool)) 380 381 select { 382 case <-ctx.Done(): 383 t.Fatal("Timeout waiting for the xDS-enabled gRPC server to go SERVING") 384 case gotMode := <-modeChangeHandler.modeCh: 385 if gotMode != connectivity.ServingModeServing { 386 t.Fatalf("Mode changed to %v, want %v", gotMode, connectivity.ServingModeServing) 387 } 388 } 389 390 // Create a gRPC channel and verify that RPCs succeed. 391 cc, err := grpc.NewClient(lis.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials())) 392 if err != nil { 393 t.Fatalf("grpc.NewClient(%q) failed: %v", lis.Addr(), err) 394 } 395 defer cc.Close() 396 waitForSuccessfulRPC(ctx, t, cc) 397 398 // Remove the listener resource from the management server. This should 399 // cause the server to go NOT_SERVING, and the error message should contain 400 // the xDS node ID. 401 resources.Listeners = nil 402 if err := managementServer.Update(ctx, resources); err != nil { 403 t.Fatal(err) 404 } 405 select { 406 case <-ctx.Done(): 407 t.Fatalf("Timed out waiting for server to go NOT_SERVING") 408 case gotMode := <-modeChangeHandler.modeCh: 409 if gotMode != connectivity.ServingModeNotServing { 410 t.Fatalf("Mode changed to %v, want %v", gotMode, connectivity.ServingModeNotServing) 411 } 412 gotErr := <-modeChangeHandler.errCh 413 if gotErr == nil || !strings.Contains(gotErr.Error(), nodeID) { 414 t.Fatalf("Unexpected error: %v, want xDS Node id: %s", gotErr, nodeID) 415 } 416 } 417 } 418 419 // Tests the case where the listener resource points to a route configuration 420 // name that is NACKed. This should trigger the server to move to SERVING, 421 // successfully accept connections, and fail at RPCs with an expected error 422 // message. 423 func (s) TestServer_RouteConfiguration_ResourceNACK(t *testing.T) { 424 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 425 defer cancel() 426 427 // Start an xDS management server. 428 managementServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 429 430 // Create bootstrap configuration pointing to the above management server. 431 nodeID := uuid.New().String() 432 bootstrapContents := e2e.DefaultBootstrapContents(t, nodeID, managementServer.Address) 433 434 // Configure the managegement server with a listener and route configuration 435 // resource (that will be NACKed) for the above xDS enabled gRPC server. 436 lis, err := testutils.LocalTCPListener() 437 if err != nil { 438 t.Fatalf("Failed to listen to local port: %v", err) 439 } 440 host, port, err := hostPortFromListener(lis) 441 if err != nil { 442 t.Fatalf("Failed to retrieve host and port of server: %v", err) 443 } 444 const routeConfigName = "routeName" 445 resources := e2e.UpdateOptions{ 446 NodeID: nodeID, 447 Listeners: []*v3listenerpb.Listener{e2e.DefaultServerListenerWithRouteConfigName(host, port, e2e.SecurityLevelNone, "routeName")}, 448 Routes: []*v3routepb.RouteConfiguration{e2e.RouteConfigNoRouteMatch(routeConfigName)}, 449 SkipValidation: true, 450 } 451 if err := managementServer.Update(ctx, resources); err != nil { 452 t.Fatal(err) 453 } 454 455 // Start an xDS-enabled gRPC server with the above bootstrap configuration. 456 config, err := bootstrap.NewConfigFromContents(bootstrapContents) 457 if err != nil { 458 t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err) 459 } 460 pool := xdsclient.NewPool(config) 461 modeChangeHandler := newServingModeChangeHandler(t) 462 modeChangeOpt := xds.ServingModeCallback(modeChangeHandler.modeChangeCallback) 463 createStubServer(t, lis, modeChangeOpt, xds.ClientPoolForTesting(pool)) 464 465 // Create a gRPC channel and verify that RPCs succeed. 466 cc, err := grpc.NewClient(lis.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials())) 467 if err != nil { 468 t.Fatalf("grpc.NewClient(%q) failed: %v", lis.Addr(), err) 469 } 470 defer cc.Close() 471 472 select { 473 case <-ctx.Done(): 474 t.Fatal("Timeout waiting for the server to start serving RPCs") 475 case gotMode := <-modeChangeHandler.modeCh: 476 if gotMode != connectivity.ServingModeServing { 477 t.Fatalf("Mode changed to %v, want %v", gotMode, connectivity.ServingModeServing) 478 } 479 } 480 waitForFailedRPCWithStatus(ctx, t, cc, codes.Unavailable, "error from xDS configuration for matched route configuration", nodeID) 481 } 482 483 // Tests the case where the listener resource points to multiple route 484 // configuration resources. 485 // 486 // - Initially the listener resource points to three route configuration 487 // resources (A, B and C). The filter chain in the listener matches incoming 488 // connections to route A, and RPCs are expected to succeed. 489 // - A streaming RPC is also kept open at this point. 490 // - The listener resource is then updated to point to two route configuration 491 // resources (A and B). The filter chain in the listener resource does not 492 // match to any of the configured routes. The default filter chain though 493 // matches to route B, which contains a route action of type "Route", and this 494 // is not supported on the server side. New RPCs are expected to fail, while 495 // any ongoing RPCs should be allowed to complete. 496 // - The listener resource is then updated to point to a single route 497 // configuration (A), and the filter chain in the listener matches to route A. 498 // New RPCs are expected to succeed at this point. 499 func (s) TestServer_MultipleRouteConfigurations(t *testing.T) { 500 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 501 defer cancel() 502 503 // Start an xDS management server. 504 managementServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 505 506 // Create bootstrap configuration pointing to the above management server. 507 nodeID := uuid.New().String() 508 bootstrapContents := e2e.DefaultBootstrapContents(t, nodeID, managementServer.Address) 509 510 // Create a listener on a local port to act as the xDS enabled gRPC server. 511 lis, err := testutils.LocalTCPListener() 512 if err != nil { 513 t.Fatalf("Failed to listen to local port: %v", err) 514 } 515 host, port, err := hostPortFromListener(lis) 516 if err != nil { 517 t.Fatalf("Failed to retrieve host and port of server: %v", err) 518 } 519 520 // Setup the management server to respond with a listener resource that 521 // specifies three route names to watch, and the corresponding route 522 // configuration resources. 523 const routeConfigNameA = "routeName-A" 524 const routeConfigNameB = "routeName-B" 525 const routeConfigNameC = "routeName-C" 526 ldsResource := e2e.DefaultServerListenerWithRouteConfigName(host, port, e2e.SecurityLevelNone, routeConfigNameA) 527 ldsResource.FilterChains = append(ldsResource.FilterChains, 528 filterChainWontMatch(t, routeConfigNameB, "1.1.1.1", []uint32{1}), 529 filterChainWontMatch(t, routeConfigNameC, "2.2.2.2", []uint32{2}), 530 ) 531 routeConfigA := e2e.RouteConfigNonForwardingAction(routeConfigNameA) 532 routeConfigB := e2e.RouteConfigFilterAction(routeConfigNameB) // Unsupported route action on server. 533 routeConfigC := e2e.RouteConfigFilterAction(routeConfigNameC) // Unsupported route action on server. 534 resources := e2e.UpdateOptions{ 535 NodeID: nodeID, 536 Listeners: []*v3listenerpb.Listener{ldsResource}, 537 Routes: []*v3routepb.RouteConfiguration{routeConfigA, routeConfigB, routeConfigC}, 538 SkipValidation: true, 539 } 540 if err := managementServer.Update(ctx, resources); err != nil { 541 t.Fatal(err) 542 } 543 544 // Start an xDS-enabled gRPC server with the above bootstrap configuration. 545 config, err := bootstrap.NewConfigFromContents(bootstrapContents) 546 if err != nil { 547 t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err) 548 } 549 pool := xdsclient.NewPool(config) 550 modeChangeOpt := xds.ServingModeCallback(func(addr net.Addr, args xds.ServingModeChangeArgs) { 551 t.Logf("Serving mode for listener %q changed to %q, err: %v", addr.String(), args.Mode, args.Err) 552 }) 553 createStubServer(t, lis, modeChangeOpt, xds.ClientPoolForTesting(pool)) 554 555 // Create a gRPC channel and verify that RPCs succeed. 556 cc, err := grpc.NewClient(lis.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials())) 557 if err != nil { 558 t.Fatalf("grpc.NewClient(%q) failed: %v", lis.Addr(), err) 559 } 560 defer cc.Close() 561 waitForSuccessfulRPC(ctx, t, cc) 562 563 // Start a streaming RPC and keep the stream open. 564 client := testgrpc.NewTestServiceClient(cc) 565 stream, err := client.FullDuplexCall(ctx) 566 if err != nil { 567 t.Fatalf("FullDuplexCall failed: %v", err) 568 } 569 if err = stream.Send(&testpb.StreamingOutputCallRequest{}); err != nil { 570 t.Fatalf("stream.Send() failed: %v, should continue to work due to graceful stop", err) 571 } 572 573 // Update the listener resource such that the filter chain does not match 574 // incoming connections to route A. Instead a default filter chain matches 575 // to route B, which contains an unsupported route action. 576 ldsResource = e2e.DefaultServerListenerWithRouteConfigName(host, port, e2e.SecurityLevelNone, routeConfigNameA) 577 ldsResource.FilterChains = []*v3listenerpb.FilterChain{filterChainWontMatch(t, routeConfigNameA, "1.1.1.1", []uint32{1})} 578 ldsResource.DefaultFilterChain = filterChainWontMatch(t, routeConfigNameB, "2.2.2.2", []uint32{2}) 579 resources.Listeners = []*v3listenerpb.Listener{ldsResource} 580 if err := managementServer.Update(ctx, resources); err != nil { 581 t.Fatal(err) 582 } 583 584 // xDS is eventually consistent. So simply poll for the new change to be 585 // reflected. 586 // "NonForwardingAction is expected for all Routes used on server-side; a 587 // route with an inappropriate action causes RPCs matching that route to 588 // fail with UNAVAILABLE." - A36 589 waitForFailedRPCWithStatus(ctx, t, cc, codes.Unavailable, "the incoming RPC matched to a route that was not of action type non forwarding", nodeID) 590 591 // Stream should be allowed to continue on the old working configuration - 592 // as it on a connection that is gracefully closed (old FCM/LDS 593 // Configuration which is allowed to continue). 594 if err = stream.CloseSend(); err != nil { 595 t.Fatalf("stream.CloseSend() failed: %v, should continue to work due to graceful stop", err) 596 } 597 if _, err = stream.Recv(); err != io.EOF { 598 t.Fatalf("unexpected error: %v, expected an EOF error", err) 599 } 600 601 // Update the listener resource to point to a single route configuration 602 // that is expected to match and verify that RPCs succeed. 603 resources.Listeners = []*v3listenerpb.Listener{e2e.DefaultServerListener(host, port, e2e.SecurityLevelNone, routeConfigNameA)} 604 if err := managementServer.Update(ctx, resources); err != nil { 605 t.Fatal(err) 606 } 607 waitForSuccessfulRPC(ctx, t, cc) 608 } 609 610 // filterChainWontMatch returns a filter chain that won't match if running the 611 // test locally. 612 func filterChainWontMatch(t *testing.T, routeName string, addressPrefix string, srcPorts []uint32) *v3listenerpb.FilterChain { 613 hcm := &v3httppb.HttpConnectionManager{ 614 RouteSpecifier: &v3httppb.HttpConnectionManager_Rds{ 615 Rds: &v3httppb.Rds{ 616 ConfigSource: &v3corepb.ConfigSource{ 617 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{Ads: &v3corepb.AggregatedConfigSource{}}, 618 }, 619 RouteConfigName: routeName, 620 }, 621 }, 622 HttpFilters: []*v3httppb.HttpFilter{e2e.HTTPFilter("router", &v3routerpb.Router{})}, 623 } 624 return &v3listenerpb.FilterChain{ 625 Name: routeName + "-wont-match", 626 FilterChainMatch: &v3listenerpb.FilterChainMatch{ 627 PrefixRanges: []*v3corepb.CidrRange{ 628 { 629 AddressPrefix: addressPrefix, 630 PrefixLen: &wrapperspb.UInt32Value{ 631 Value: uint32(0), 632 }, 633 }, 634 }, 635 SourceType: v3listenerpb.FilterChainMatch_SAME_IP_OR_LOOPBACK, 636 SourcePorts: srcPorts, 637 SourcePrefixRanges: []*v3corepb.CidrRange{ 638 { 639 AddressPrefix: addressPrefix, 640 PrefixLen: &wrapperspb.UInt32Value{ 641 Value: uint32(0), 642 }, 643 }, 644 }, 645 }, 646 Filters: []*v3listenerpb.Filter{ 647 { 648 Name: "filter-1", 649 ConfigType: &v3listenerpb.Filter_TypedConfig{TypedConfig: testutils.MarshalAny(t, hcm)}, 650 }, 651 }, 652 } 653 }