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