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  }