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  }