google.golang.org/grpc@v1.62.1/xds/internal/xdsclient/tests/rds_watchers_test.go (about)

     1  /*
     2   *
     3   * Copyright 2022 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 xdsclient_test
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"strings"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/google/go-cmp/cmp"
    29  	"github.com/google/go-cmp/cmp/cmpopts"
    30  	"github.com/google/uuid"
    31  	"google.golang.org/grpc/internal/grpcsync"
    32  	"google.golang.org/grpc/internal/testutils"
    33  	"google.golang.org/grpc/internal/testutils/xds/e2e"
    34  	xdstestutils "google.golang.org/grpc/xds/internal/testutils"
    35  	"google.golang.org/grpc/xds/internal/xdsclient"
    36  	"google.golang.org/grpc/xds/internal/xdsclient/bootstrap"
    37  	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
    38  	"google.golang.org/protobuf/types/known/wrapperspb"
    39  
    40  	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
    41  	v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
    42  	v3discoverypb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
    43  )
    44  
    45  type noopRouteConfigWatcher struct{}
    46  
    47  func (noopRouteConfigWatcher) OnUpdate(update *xdsresource.RouteConfigResourceData) {}
    48  func (noopRouteConfigWatcher) OnError(err error)                                    {}
    49  func (noopRouteConfigWatcher) OnResourceDoesNotExist()                              {}
    50  
    51  type routeConfigUpdateErrTuple struct {
    52  	update xdsresource.RouteConfigUpdate
    53  	err    error
    54  }
    55  
    56  type routeConfigWatcher struct {
    57  	updateCh *testutils.Channel
    58  }
    59  
    60  func newRouteConfigWatcher() *routeConfigWatcher {
    61  	return &routeConfigWatcher{updateCh: testutils.NewChannel()}
    62  }
    63  
    64  func (rw *routeConfigWatcher) OnUpdate(update *xdsresource.RouteConfigResourceData) {
    65  	rw.updateCh.Send(routeConfigUpdateErrTuple{update: update.Resource})
    66  }
    67  
    68  func (rw *routeConfigWatcher) OnError(err error) {
    69  	// When used with a go-control-plane management server that continuously
    70  	// resends resources which are NACKed by the xDS client, using a `Replace()`
    71  	// here and in OnResourceDoesNotExist() simplifies tests which will have
    72  	// access to the most recently received error.
    73  	rw.updateCh.Replace(routeConfigUpdateErrTuple{err: err})
    74  }
    75  
    76  func (rw *routeConfigWatcher) OnResourceDoesNotExist() {
    77  	rw.updateCh.Replace(routeConfigUpdateErrTuple{err: xdsresource.NewErrorf(xdsresource.ErrorTypeResourceNotFound, "RouteConfiguration not found in received response")})
    78  }
    79  
    80  // badRouteConfigResource returns a RouteConfiguration resource for the given
    81  // routeName which contains a retry config with num_retries set to `0`. This is
    82  // expected to be NACK'ed by the xDS client.
    83  func badRouteConfigResource(routeName, ldsTarget, clusterName string) *v3routepb.RouteConfiguration {
    84  	return &v3routepb.RouteConfiguration{
    85  		Name: routeName,
    86  		VirtualHosts: []*v3routepb.VirtualHost{{
    87  			Domains: []string{ldsTarget},
    88  			Routes: []*v3routepb.Route{{
    89  				Match: &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"}},
    90  				Action: &v3routepb.Route_Route{Route: &v3routepb.RouteAction{
    91  					ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: clusterName},
    92  				}}}},
    93  			RetryPolicy: &v3routepb.RetryPolicy{
    94  				NumRetries: &wrapperspb.UInt32Value{Value: 0},
    95  			},
    96  		}},
    97  	}
    98  }
    99  
   100  // xdsClient is expected to produce an error containing this string when an
   101  // update is received containing a route configuration resource created using
   102  // `badRouteConfigResource`.
   103  const wantRouteConfigNACKErr = "received route is invalid: retry_policy.num_retries = 0; must be >= 1"
   104  
   105  // verifyRouteConfigUpdate waits for an update to be received on the provided
   106  // update channel and verifies that it matches the expected update.
   107  //
   108  // Returns an error if no update is received before the context deadline expires
   109  // or the received update does not match the expected one.
   110  func verifyRouteConfigUpdate(ctx context.Context, updateCh *testutils.Channel, wantUpdate routeConfigUpdateErrTuple) error {
   111  	u, err := updateCh.Receive(ctx)
   112  	if err != nil {
   113  		return fmt.Errorf("timeout when waiting for a route configuration resource from the management server: %v", err)
   114  	}
   115  	got := u.(routeConfigUpdateErrTuple)
   116  	if wantUpdate.err != nil {
   117  		if gotType, wantType := xdsresource.ErrType(got.err), xdsresource.ErrType(wantUpdate.err); gotType != wantType {
   118  			return fmt.Errorf("received update with error type %v, want %v", gotType, wantType)
   119  		}
   120  	}
   121  	cmpOpts := []cmp.Option{cmpopts.EquateEmpty(), cmpopts.IgnoreFields(xdsresource.RouteConfigUpdate{}, "Raw")}
   122  	if diff := cmp.Diff(wantUpdate.update, got.update, cmpOpts...); diff != "" {
   123  		return fmt.Errorf("received unepected diff in the route configuration resource update: (-want, got):\n%s", diff)
   124  	}
   125  	return nil
   126  }
   127  
   128  // verifyNoRouteConfigUpdate verifies that no route configuration update is
   129  // received on the provided update channel, and returns an error if an update is
   130  // received.
   131  //
   132  // A very short deadline is used while waiting for the update, as this function
   133  // is intended to be used when an update is not expected.
   134  func verifyNoRouteConfigUpdate(ctx context.Context, updateCh *testutils.Channel) error {
   135  	sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout)
   136  	defer sCancel()
   137  	if u, err := updateCh.Receive(sCtx); err != context.DeadlineExceeded {
   138  		return fmt.Errorf("unexpected RouteConfigUpdate: %v", u)
   139  	}
   140  	return nil
   141  }
   142  
   143  // TestRDSWatch covers the case where a single watcher exists for a single route
   144  // configuration resource. The test verifies the following scenarios:
   145  //  1. An update from the management server containing the resource being
   146  //     watched should result in the invocation of the watch callback.
   147  //  2. An update from the management server containing a resource *not* being
   148  //     watched should not result in the invocation of the watch callback.
   149  //  3. After the watch is cancelled, an update from the management server
   150  //     containing the resource that was being watched should not result in the
   151  //     invocation of the watch callback.
   152  //
   153  // The test is run for old and new style names.
   154  func (s) TestRDSWatch(t *testing.T) {
   155  	tests := []struct {
   156  		desc                   string
   157  		resourceName           string
   158  		watchedResource        *v3routepb.RouteConfiguration // The resource being watched.
   159  		updatedWatchedResource *v3routepb.RouteConfiguration // The watched resource after an update.
   160  		notWatchedResource     *v3routepb.RouteConfiguration // A resource which is not being watched.
   161  		wantUpdate             routeConfigUpdateErrTuple
   162  	}{
   163  		{
   164  			desc:                   "old style resource",
   165  			resourceName:           rdsName,
   166  			watchedResource:        e2e.DefaultRouteConfig(rdsName, ldsName, cdsName),
   167  			updatedWatchedResource: e2e.DefaultRouteConfig(rdsName, ldsName, "new-cds-resource"),
   168  			notWatchedResource:     e2e.DefaultRouteConfig("unsubscribed-rds-resource", ldsName, cdsName),
   169  			wantUpdate: routeConfigUpdateErrTuple{
   170  				update: xdsresource.RouteConfigUpdate{
   171  					VirtualHosts: []*xdsresource.VirtualHost{
   172  						{
   173  							Domains: []string{ldsName},
   174  							Routes: []*xdsresource.Route{
   175  								{
   176  									Prefix:           newStringP("/"),
   177  									ActionType:       xdsresource.RouteActionRoute,
   178  									WeightedClusters: map[string]xdsresource.WeightedCluster{cdsName: {Weight: 100}},
   179  								},
   180  							},
   181  						},
   182  					},
   183  				},
   184  			},
   185  		},
   186  		{
   187  			desc:                   "new style resource",
   188  			resourceName:           rdsNameNewStyle,
   189  			watchedResource:        e2e.DefaultRouteConfig(rdsNameNewStyle, ldsNameNewStyle, cdsNameNewStyle),
   190  			updatedWatchedResource: e2e.DefaultRouteConfig(rdsNameNewStyle, ldsNameNewStyle, "new-cds-resource"),
   191  			notWatchedResource:     e2e.DefaultRouteConfig("unsubscribed-rds-resource", ldsNameNewStyle, cdsNameNewStyle),
   192  			wantUpdate: routeConfigUpdateErrTuple{
   193  				update: xdsresource.RouteConfigUpdate{
   194  					VirtualHosts: []*xdsresource.VirtualHost{
   195  						{
   196  							Domains: []string{ldsNameNewStyle},
   197  							Routes: []*xdsresource.Route{
   198  								{
   199  									Prefix:           newStringP("/"),
   200  									ActionType:       xdsresource.RouteActionRoute,
   201  									WeightedClusters: map[string]xdsresource.WeightedCluster{cdsNameNewStyle: {Weight: 100}},
   202  								},
   203  							},
   204  						},
   205  					},
   206  				},
   207  			},
   208  		},
   209  	}
   210  
   211  	for _, test := range tests {
   212  		t.Run(test.desc, func(t *testing.T) {
   213  			mgmtServer, nodeID, bootstrapContents, _, cleanup := e2e.SetupManagementServer(t, e2e.ManagementServerOptions{})
   214  			defer cleanup()
   215  
   216  			// Create an xDS client with the above bootstrap contents.
   217  			client, close, err := xdsclient.NewWithBootstrapContentsForTesting(bootstrapContents)
   218  			if err != nil {
   219  				t.Fatalf("Failed to create xDS client: %v", err)
   220  			}
   221  			defer close()
   222  
   223  			// Register a watch for a route configuration resource and have the
   224  			// watch callback push the received update on to a channel.
   225  			rw := newRouteConfigWatcher()
   226  			rdsCancel := xdsresource.WatchRouteConfig(client, test.resourceName, rw)
   227  
   228  			// Configure the management server to return a single route
   229  			// configuration resource, corresponding to the one being watched.
   230  			resources := e2e.UpdateOptions{
   231  				NodeID:         nodeID,
   232  				Routes:         []*v3routepb.RouteConfiguration{test.watchedResource},
   233  				SkipValidation: true,
   234  			}
   235  			ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   236  			defer cancel()
   237  			if err := mgmtServer.Update(ctx, resources); err != nil {
   238  				t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err)
   239  			}
   240  
   241  			// Verify the contents of the received update.
   242  			if err := verifyRouteConfigUpdate(ctx, rw.updateCh, test.wantUpdate); err != nil {
   243  				t.Fatal(err)
   244  			}
   245  
   246  			// Configure the management server to return an additional route
   247  			// configuration resource, one that we are not interested in.
   248  			resources = e2e.UpdateOptions{
   249  				NodeID:         nodeID,
   250  				Routes:         []*v3routepb.RouteConfiguration{test.watchedResource, test.notWatchedResource},
   251  				SkipValidation: true,
   252  			}
   253  			if err := mgmtServer.Update(ctx, resources); err != nil {
   254  				t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err)
   255  			}
   256  			if err := verifyNoRouteConfigUpdate(ctx, rw.updateCh); err != nil {
   257  				t.Fatal(err)
   258  			}
   259  
   260  			// Cancel the watch and update the resource corresponding to the original
   261  			// watch.  Ensure that the cancelled watch callback is not invoked.
   262  			rdsCancel()
   263  			resources = e2e.UpdateOptions{
   264  				NodeID:         nodeID,
   265  				Routes:         []*v3routepb.RouteConfiguration{test.updatedWatchedResource, test.notWatchedResource},
   266  				SkipValidation: true,
   267  			}
   268  			if err := mgmtServer.Update(ctx, resources); err != nil {
   269  				t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err)
   270  			}
   271  			if err := verifyNoRouteConfigUpdate(ctx, rw.updateCh); err != nil {
   272  				t.Fatal(err)
   273  			}
   274  		})
   275  	}
   276  }
   277  
   278  // TestRDSWatch_TwoWatchesForSameResourceName covers the case where two watchers
   279  // exist for a single route configuration resource.  The test verifies the
   280  // following scenarios:
   281  //  1. An update from the management server containing the resource being
   282  //     watched should result in the invocation of both watch callbacks.
   283  //  2. After one of the watches is cancelled, a redundant update from the
   284  //     management server should not result in the invocation of either of the
   285  //     watch callbacks.
   286  //  3. An update from the management server containing the resource being
   287  //     watched should result in the invocation of the un-cancelled watch
   288  //     callback.
   289  //
   290  // The test is run for old and new style names.
   291  func (s) TestRDSWatch_TwoWatchesForSameResourceName(t *testing.T) {
   292  	tests := []struct {
   293  		desc                   string
   294  		resourceName           string
   295  		watchedResource        *v3routepb.RouteConfiguration // The resource being watched.
   296  		updatedWatchedResource *v3routepb.RouteConfiguration // The watched resource after an update.
   297  		wantUpdateV1           routeConfigUpdateErrTuple
   298  		wantUpdateV2           routeConfigUpdateErrTuple
   299  	}{
   300  		{
   301  			desc:                   "old style resource",
   302  			resourceName:           rdsName,
   303  			watchedResource:        e2e.DefaultRouteConfig(rdsName, ldsName, cdsName),
   304  			updatedWatchedResource: e2e.DefaultRouteConfig(rdsName, ldsName, "new-cds-resource"),
   305  			wantUpdateV1: routeConfigUpdateErrTuple{
   306  				update: xdsresource.RouteConfigUpdate{
   307  					VirtualHosts: []*xdsresource.VirtualHost{
   308  						{
   309  							Domains: []string{ldsName},
   310  							Routes: []*xdsresource.Route{
   311  								{
   312  									Prefix:           newStringP("/"),
   313  									ActionType:       xdsresource.RouteActionRoute,
   314  									WeightedClusters: map[string]xdsresource.WeightedCluster{cdsName: {Weight: 100}},
   315  								},
   316  							},
   317  						},
   318  					},
   319  				},
   320  			},
   321  			wantUpdateV2: routeConfigUpdateErrTuple{
   322  				update: xdsresource.RouteConfigUpdate{
   323  					VirtualHosts: []*xdsresource.VirtualHost{
   324  						{
   325  							Domains: []string{ldsName},
   326  							Routes: []*xdsresource.Route{
   327  								{
   328  									Prefix:           newStringP("/"),
   329  									ActionType:       xdsresource.RouteActionRoute,
   330  									WeightedClusters: map[string]xdsresource.WeightedCluster{"new-cds-resource": {Weight: 100}},
   331  								},
   332  							},
   333  						},
   334  					},
   335  				},
   336  			},
   337  		},
   338  		{
   339  			desc:                   "new style resource",
   340  			resourceName:           rdsNameNewStyle,
   341  			watchedResource:        e2e.DefaultRouteConfig(rdsNameNewStyle, ldsNameNewStyle, cdsNameNewStyle),
   342  			updatedWatchedResource: e2e.DefaultRouteConfig(rdsNameNewStyle, ldsNameNewStyle, "new-cds-resource"),
   343  			wantUpdateV1: routeConfigUpdateErrTuple{
   344  				update: xdsresource.RouteConfigUpdate{
   345  					VirtualHosts: []*xdsresource.VirtualHost{
   346  						{
   347  							Domains: []string{ldsNameNewStyle},
   348  							Routes: []*xdsresource.Route{
   349  								{
   350  									Prefix:           newStringP("/"),
   351  									ActionType:       xdsresource.RouteActionRoute,
   352  									WeightedClusters: map[string]xdsresource.WeightedCluster{cdsNameNewStyle: {Weight: 100}},
   353  								},
   354  							},
   355  						},
   356  					},
   357  				},
   358  			},
   359  			wantUpdateV2: routeConfigUpdateErrTuple{
   360  				update: xdsresource.RouteConfigUpdate{
   361  					VirtualHosts: []*xdsresource.VirtualHost{
   362  						{
   363  							Domains: []string{ldsNameNewStyle},
   364  							Routes: []*xdsresource.Route{
   365  								{
   366  									Prefix:           newStringP("/"),
   367  									ActionType:       xdsresource.RouteActionRoute,
   368  									WeightedClusters: map[string]xdsresource.WeightedCluster{"new-cds-resource": {Weight: 100}},
   369  								},
   370  							},
   371  						},
   372  					},
   373  				},
   374  			},
   375  		},
   376  	}
   377  
   378  	for _, test := range tests {
   379  		t.Run(test.desc, func(t *testing.T) {
   380  			mgmtServer, nodeID, bootstrapContents, _, cleanup := e2e.SetupManagementServer(t, e2e.ManagementServerOptions{})
   381  			defer cleanup()
   382  
   383  			// Create an xDS client with the above bootstrap contents.
   384  			client, close, err := xdsclient.NewWithBootstrapContentsForTesting(bootstrapContents)
   385  			if err != nil {
   386  				t.Fatalf("Failed to create xDS client: %v", err)
   387  			}
   388  			defer close()
   389  
   390  			// Register two watches for the same route configuration resource
   391  			// and have the callbacks push the received updates on to a channel.
   392  			rw1 := newRouteConfigWatcher()
   393  			rdsCancel1 := xdsresource.WatchRouteConfig(client, test.resourceName, rw1)
   394  			defer rdsCancel1()
   395  			rw2 := newRouteConfigWatcher()
   396  			rdsCancel2 := xdsresource.WatchRouteConfig(client, test.resourceName, rw2)
   397  
   398  			// Configure the management server to return a single route
   399  			// configuration resource, corresponding to the one being watched.
   400  			resources := e2e.UpdateOptions{
   401  				NodeID:         nodeID,
   402  				Routes:         []*v3routepb.RouteConfiguration{test.watchedResource},
   403  				SkipValidation: true,
   404  			}
   405  			ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   406  			defer cancel()
   407  			if err := mgmtServer.Update(ctx, resources); err != nil {
   408  				t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err)
   409  			}
   410  
   411  			// Verify the contents of the received update.
   412  			if err := verifyRouteConfigUpdate(ctx, rw1.updateCh, test.wantUpdateV1); err != nil {
   413  				t.Fatal(err)
   414  			}
   415  			if err := verifyRouteConfigUpdate(ctx, rw2.updateCh, test.wantUpdateV1); err != nil {
   416  				t.Fatal(err)
   417  			}
   418  
   419  			// Cancel the second watch and force the management server to push a
   420  			// redundant update for the resource being watched. Neither of the
   421  			// two watch callbacks should be invoked.
   422  			rdsCancel2()
   423  			if err := mgmtServer.Update(ctx, resources); err != nil {
   424  				t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err)
   425  			}
   426  			if err := verifyNoRouteConfigUpdate(ctx, rw1.updateCh); err != nil {
   427  				t.Fatal(err)
   428  			}
   429  			if err := verifyNoRouteConfigUpdate(ctx, rw2.updateCh); err != nil {
   430  				t.Fatal(err)
   431  			}
   432  
   433  			// Update to the resource being watched. The un-cancelled callback
   434  			// should be invoked while the cancelled one should not be.
   435  			resources = e2e.UpdateOptions{
   436  				NodeID:         nodeID,
   437  				Routes:         []*v3routepb.RouteConfiguration{test.updatedWatchedResource},
   438  				SkipValidation: true,
   439  			}
   440  			if err := mgmtServer.Update(ctx, resources); err != nil {
   441  				t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err)
   442  			}
   443  			if err := verifyRouteConfigUpdate(ctx, rw1.updateCh, test.wantUpdateV2); err != nil {
   444  				t.Fatal(err)
   445  			}
   446  			if err := verifyNoRouteConfigUpdate(ctx, rw2.updateCh); err != nil {
   447  				t.Fatal(err)
   448  			}
   449  		})
   450  	}
   451  }
   452  
   453  // TestRDSWatch_ThreeWatchesForDifferentResourceNames covers the case with three
   454  // watchers (two watchers for one resource, and the third watcher for another
   455  // resource), exist across two route configuration resources.  The test verifies
   456  // that an update from the management server containing both resources results
   457  // in the invocation of all watch callbacks.
   458  //
   459  // The test is run with both old and new style names.
   460  func (s) TestRDSWatch_ThreeWatchesForDifferentResourceNames(t *testing.T) {
   461  	mgmtServer, nodeID, bootstrapContents, _, cleanup := e2e.SetupManagementServer(t, e2e.ManagementServerOptions{})
   462  	defer cleanup()
   463  
   464  	// Create an xDS client with the above bootstrap contents.
   465  	client, close, err := xdsclient.NewWithBootstrapContentsForTesting(bootstrapContents)
   466  	if err != nil {
   467  		t.Fatalf("Failed to create xDS client: %v", err)
   468  	}
   469  	defer close()
   470  
   471  	// Register two watches for the same route configuration resource
   472  	// and have the callbacks push the received updates on to a channel.
   473  	rw1 := newRouteConfigWatcher()
   474  	rdsCancel1 := xdsresource.WatchRouteConfig(client, rdsName, rw1)
   475  	defer rdsCancel1()
   476  	rw2 := newRouteConfigWatcher()
   477  	rdsCancel2 := xdsresource.WatchRouteConfig(client, rdsName, rw2)
   478  	defer rdsCancel2()
   479  
   480  	// Register the third watch for a different route configuration resource.
   481  	rw3 := newRouteConfigWatcher()
   482  	rdsCancel3 := xdsresource.WatchRouteConfig(client, rdsNameNewStyle, rw3)
   483  	defer rdsCancel3()
   484  
   485  	// Configure the management server to return two route configuration
   486  	// resources, corresponding to the registered watches.
   487  	resources := e2e.UpdateOptions{
   488  		NodeID: nodeID,
   489  		Routes: []*v3routepb.RouteConfiguration{
   490  			e2e.DefaultRouteConfig(rdsName, ldsName, cdsName),
   491  			e2e.DefaultRouteConfig(rdsNameNewStyle, ldsName, cdsName),
   492  		},
   493  		SkipValidation: true,
   494  	}
   495  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   496  	defer cancel()
   497  	if err := mgmtServer.Update(ctx, resources); err != nil {
   498  		t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err)
   499  	}
   500  
   501  	// Verify the contents of the received update for the all watchers. The two
   502  	// resources returned differ only in the resource name. Therefore the
   503  	// expected update is the same for all the watchers.
   504  	wantUpdate := routeConfigUpdateErrTuple{
   505  		update: xdsresource.RouteConfigUpdate{
   506  			VirtualHosts: []*xdsresource.VirtualHost{
   507  				{
   508  					Domains: []string{ldsName},
   509  					Routes: []*xdsresource.Route{
   510  						{
   511  							Prefix:           newStringP("/"),
   512  							ActionType:       xdsresource.RouteActionRoute,
   513  							WeightedClusters: map[string]xdsresource.WeightedCluster{cdsName: {Weight: 100}},
   514  						},
   515  					},
   516  				},
   517  			},
   518  		},
   519  	}
   520  	if err := verifyRouteConfigUpdate(ctx, rw1.updateCh, wantUpdate); err != nil {
   521  		t.Fatal(err)
   522  	}
   523  	if err := verifyRouteConfigUpdate(ctx, rw2.updateCh, wantUpdate); err != nil {
   524  		t.Fatal(err)
   525  	}
   526  	if err := verifyRouteConfigUpdate(ctx, rw3.updateCh, wantUpdate); err != nil {
   527  		t.Fatal(err)
   528  	}
   529  }
   530  
   531  // TestRDSWatch_ResourceCaching covers the case where a watch is registered for
   532  // a resource which is already present in the cache.  The test verifies that the
   533  // watch callback is invoked with the contents from the cache, instead of a
   534  // request being sent to the management server.
   535  func (s) TestRDSWatch_ResourceCaching(t *testing.T) {
   536  	firstRequestReceived := false
   537  	firstAckReceived := grpcsync.NewEvent()
   538  	secondRequestReceived := grpcsync.NewEvent()
   539  
   540  	mgmtServer, nodeID, bootstrapContents, _, cleanup := e2e.SetupManagementServer(t, e2e.ManagementServerOptions{
   541  		OnStreamRequest: func(id int64, req *v3discoverypb.DiscoveryRequest) error {
   542  			// The first request has an empty version string.
   543  			if !firstRequestReceived && req.GetVersionInfo() == "" {
   544  				firstRequestReceived = true
   545  				return nil
   546  			}
   547  			// The first ack has a non-empty version string.
   548  			if !firstAckReceived.HasFired() && req.GetVersionInfo() != "" {
   549  				firstAckReceived.Fire()
   550  				return nil
   551  			}
   552  			// Any requests after the first request and ack, are not expected.
   553  			secondRequestReceived.Fire()
   554  			return nil
   555  		},
   556  	})
   557  	defer cleanup()
   558  
   559  	// Create an xDS client with the above bootstrap contents.
   560  	client, close, err := xdsclient.NewWithBootstrapContentsForTesting(bootstrapContents)
   561  	if err != nil {
   562  		t.Fatalf("Failed to create xDS client: %v", err)
   563  	}
   564  	defer close()
   565  
   566  	// Register a watch for a route configuration resource and have the watch
   567  	// callback push the received update on to a channel.
   568  	rw1 := newRouteConfigWatcher()
   569  	rdsCancel1 := xdsresource.WatchRouteConfig(client, rdsName, rw1)
   570  	defer rdsCancel1()
   571  
   572  	// Configure the management server to return a single route configuration
   573  	// resource, corresponding to the one we registered a watch for.
   574  	resources := e2e.UpdateOptions{
   575  		NodeID:         nodeID,
   576  		Routes:         []*v3routepb.RouteConfiguration{e2e.DefaultRouteConfig(rdsName, ldsName, cdsName)},
   577  		SkipValidation: true,
   578  	}
   579  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   580  	defer cancel()
   581  	if err := mgmtServer.Update(ctx, resources); err != nil {
   582  		t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err)
   583  	}
   584  
   585  	// Verify the contents of the received update.
   586  	wantUpdate := routeConfigUpdateErrTuple{
   587  		update: xdsresource.RouteConfigUpdate{
   588  			VirtualHosts: []*xdsresource.VirtualHost{
   589  				{
   590  					Domains: []string{ldsName},
   591  					Routes: []*xdsresource.Route{
   592  						{
   593  							Prefix:           newStringP("/"),
   594  							ActionType:       xdsresource.RouteActionRoute,
   595  							WeightedClusters: map[string]xdsresource.WeightedCluster{cdsName: {Weight: 100}},
   596  						},
   597  					},
   598  				},
   599  			},
   600  		},
   601  	}
   602  	if err := verifyRouteConfigUpdate(ctx, rw1.updateCh, wantUpdate); err != nil {
   603  		t.Fatal(err)
   604  	}
   605  	select {
   606  	case <-ctx.Done():
   607  		t.Fatal("timeout when waiting for receipt of ACK at the management server")
   608  	case <-firstAckReceived.Done():
   609  	}
   610  
   611  	// Register another watch for the same resource. This should get the update
   612  	// from the cache.
   613  	rw2 := newRouteConfigWatcher()
   614  	rdsCancel2 := xdsresource.WatchRouteConfig(client, rdsName, rw2)
   615  	defer rdsCancel2()
   616  	if err := verifyRouteConfigUpdate(ctx, rw2.updateCh, wantUpdate); err != nil {
   617  		t.Fatal(err)
   618  	}
   619  	// No request should get sent out as part of this watch.
   620  	sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout)
   621  	defer sCancel()
   622  	select {
   623  	case <-sCtx.Done():
   624  	case <-secondRequestReceived.Done():
   625  		t.Fatal("xdsClient sent out request instead of using update from cache")
   626  	}
   627  }
   628  
   629  // TestRDSWatch_ExpiryTimerFiresBeforeResponse tests the case where the client
   630  // does not receive an RDS response for the request that it sends. The test
   631  // verifies that the watch callback is invoked with an error once the
   632  // watchExpiryTimer fires.
   633  func (s) TestRDSWatch_ExpiryTimerFiresBeforeResponse(t *testing.T) {
   634  	mgmtServer, err := e2e.StartManagementServer(e2e.ManagementServerOptions{})
   635  	if err != nil {
   636  		t.Fatalf("Failed to spin up the xDS management server: %v", err)
   637  	}
   638  	defer mgmtServer.Stop()
   639  
   640  	// Create an xDS client talking to a non-existent management server.
   641  	client, close, err := xdsclient.NewWithConfigForTesting(&bootstrap.Config{
   642  		XDSServer: xdstestutils.ServerConfigForAddress(t, mgmtServer.Address),
   643  		NodeProto: &v3corepb.Node{},
   644  	}, defaultTestWatchExpiryTimeout, time.Duration(0))
   645  	if err != nil {
   646  		t.Fatalf("failed to create xds client: %v", err)
   647  	}
   648  	defer close()
   649  
   650  	// Register a watch for a resource which is expected to fail with an error
   651  	// after the watch expiry timer fires.
   652  	rw := newRouteConfigWatcher()
   653  	rdsCancel := xdsresource.WatchRouteConfig(client, rdsName, rw)
   654  	defer rdsCancel()
   655  
   656  	// Wait for the watch expiry timer to fire.
   657  	<-time.After(defaultTestWatchExpiryTimeout)
   658  
   659  	// Verify that an empty update with the expected error is received.
   660  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   661  	defer cancel()
   662  	wantErr := xdsresource.NewErrorf(xdsresource.ErrorTypeResourceNotFound, "")
   663  	if err := verifyRouteConfigUpdate(ctx, rw.updateCh, routeConfigUpdateErrTuple{err: wantErr}); err != nil {
   664  		t.Fatal(err)
   665  	}
   666  }
   667  
   668  // TestRDSWatch_ValidResponseCancelsExpiryTimerBehavior tests the case where the
   669  // client receives a valid RDS response for the request that it sends. The test
   670  // verifies that the behavior associated with the expiry timer (i.e, callback
   671  // invocation with error) does not take place.
   672  func (s) TestRDSWatch_ValidResponseCancelsExpiryTimerBehavior(t *testing.T) {
   673  	mgmtServer, err := e2e.StartManagementServer(e2e.ManagementServerOptions{})
   674  	if err != nil {
   675  		t.Fatalf("Failed to spin up the xDS management server: %v", err)
   676  	}
   677  	defer mgmtServer.Stop()
   678  
   679  	// Create an xDS client talking to the above management server.
   680  	nodeID := uuid.New().String()
   681  	client, close, err := xdsclient.NewWithConfigForTesting(&bootstrap.Config{
   682  		XDSServer: xdstestutils.ServerConfigForAddress(t, mgmtServer.Address),
   683  		NodeProto: &v3corepb.Node{Id: nodeID},
   684  	}, defaultTestWatchExpiryTimeout, time.Duration(0))
   685  	if err != nil {
   686  		t.Fatalf("failed to create xds client: %v", err)
   687  	}
   688  	defer close()
   689  
   690  	// Register a watch for a route configuration resource and have the watch
   691  	// callback push the received update on to a channel.
   692  	rw := newRouteConfigWatcher()
   693  	rdsCancel := xdsresource.WatchRouteConfig(client, rdsName, rw)
   694  	defer rdsCancel()
   695  
   696  	// Configure the management server to return a single route configuration
   697  	// resource, corresponding to the one we registered a watch for.
   698  	resources := e2e.UpdateOptions{
   699  		NodeID:         nodeID,
   700  		Routes:         []*v3routepb.RouteConfiguration{e2e.DefaultRouteConfig(rdsName, ldsName, cdsName)},
   701  		SkipValidation: true,
   702  	}
   703  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   704  	defer cancel()
   705  	if err := mgmtServer.Update(ctx, resources); err != nil {
   706  		t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err)
   707  	}
   708  
   709  	// Verify the contents of the received update.
   710  	wantUpdate := routeConfigUpdateErrTuple{
   711  		update: xdsresource.RouteConfigUpdate{
   712  			VirtualHosts: []*xdsresource.VirtualHost{
   713  				{
   714  					Domains: []string{ldsName},
   715  					Routes: []*xdsresource.Route{
   716  						{
   717  							Prefix:           newStringP("/"),
   718  							ActionType:       xdsresource.RouteActionRoute,
   719  							WeightedClusters: map[string]xdsresource.WeightedCluster{cdsName: {Weight: 100}},
   720  						},
   721  					},
   722  				},
   723  			},
   724  		},
   725  	}
   726  	if err := verifyRouteConfigUpdate(ctx, rw.updateCh, wantUpdate); err != nil {
   727  		t.Fatal(err)
   728  	}
   729  
   730  	// Wait for the watch expiry timer to fire, and verify that the callback is
   731  	// not invoked.
   732  	<-time.After(defaultTestWatchExpiryTimeout)
   733  	if err := verifyNoRouteConfigUpdate(ctx, rw.updateCh); err != nil {
   734  		t.Fatal(err)
   735  	}
   736  }
   737  
   738  // TestRDSWatch_NACKError covers the case where an update from the management
   739  // server is NACK'ed by the xdsclient. The test verifies that the error is
   740  // propagated to the watcher.
   741  func (s) TestRDSWatch_NACKError(t *testing.T) {
   742  	mgmtServer, nodeID, bootstrapContents, _, cleanup := e2e.SetupManagementServer(t, e2e.ManagementServerOptions{})
   743  	defer cleanup()
   744  
   745  	// Create an xDS client with the above bootstrap contents.
   746  	client, close, err := xdsclient.NewWithBootstrapContentsForTesting(bootstrapContents)
   747  	if err != nil {
   748  		t.Fatalf("Failed to create xDS client: %v", err)
   749  	}
   750  	defer close()
   751  
   752  	// Register a watch for a route configuration resource and have the watch
   753  	// callback push the received update on to a channel.
   754  	rw := newRouteConfigWatcher()
   755  	rdsCancel := xdsresource.WatchRouteConfig(client, rdsName, rw)
   756  	defer rdsCancel()
   757  
   758  	// Configure the management server to return a single route configuration
   759  	// resource which is expected to be NACKed by the client.
   760  	resources := e2e.UpdateOptions{
   761  		NodeID:         nodeID,
   762  		Routes:         []*v3routepb.RouteConfiguration{badRouteConfigResource(rdsName, ldsName, cdsName)},
   763  		SkipValidation: true,
   764  	}
   765  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   766  	defer cancel()
   767  	if err := mgmtServer.Update(ctx, resources); err != nil {
   768  		t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err)
   769  	}
   770  
   771  	// Verify that the expected error is propagated to the watcher.
   772  	u, err := rw.updateCh.Receive(ctx)
   773  	if err != nil {
   774  		t.Fatalf("timeout when waiting for a route configuration resource from the management server: %v", err)
   775  	}
   776  	gotErr := u.(routeConfigUpdateErrTuple).err
   777  	if gotErr == nil || !strings.Contains(gotErr.Error(), wantRouteConfigNACKErr) {
   778  		t.Fatalf("update received with error: %v, want %q", gotErr, wantRouteConfigNACKErr)
   779  	}
   780  }
   781  
   782  // TestRDSWatch_PartialValid covers the case where a response from the
   783  // management server contains both valid and invalid resources and is expected
   784  // to be NACK'ed by the xdsclient. The test verifies that watchers corresponding
   785  // to the valid resource receive the update, while watchers corresponding to the
   786  // invalid resource receive an error.
   787  func (s) TestRDSWatch_PartialValid(t *testing.T) {
   788  	mgmtServer, nodeID, bootstrapContents, _, cleanup := e2e.SetupManagementServer(t, e2e.ManagementServerOptions{})
   789  	defer cleanup()
   790  
   791  	// Create an xDS client with the above bootstrap contents.
   792  	client, close, err := xdsclient.NewWithBootstrapContentsForTesting(bootstrapContents)
   793  	if err != nil {
   794  		t.Fatalf("Failed to create xDS client: %v", err)
   795  	}
   796  	defer close()
   797  
   798  	// Register two watches for route configuration resources. The first watch
   799  	// is expected to receive an error because the received resource is NACKed.
   800  	// The second watch is expected to get a good update.
   801  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   802  	defer cancel()
   803  	badResourceName := rdsName
   804  	rw1 := newRouteConfigWatcher()
   805  	rdsCancel1 := xdsresource.WatchRouteConfig(client, badResourceName, rw1)
   806  	defer rdsCancel1()
   807  	goodResourceName := rdsNameNewStyle
   808  	rw2 := newRouteConfigWatcher()
   809  	rdsCancel2 := xdsresource.WatchRouteConfig(client, goodResourceName, rw2)
   810  	defer rdsCancel2()
   811  
   812  	// Configure the management server to return two route configuration
   813  	// resources, corresponding to the registered watches.
   814  	resources := e2e.UpdateOptions{
   815  		NodeID: nodeID,
   816  		Routes: []*v3routepb.RouteConfiguration{
   817  			badRouteConfigResource(badResourceName, ldsName, cdsName),
   818  			e2e.DefaultRouteConfig(goodResourceName, ldsName, cdsName),
   819  		},
   820  		SkipValidation: true,
   821  	}
   822  	if err := mgmtServer.Update(ctx, resources); err != nil {
   823  		t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err)
   824  	}
   825  
   826  	// Verify that the expected error is propagated to the watcher which
   827  	// requested for the bad resource.
   828  	u, err := rw1.updateCh.Receive(ctx)
   829  	if err != nil {
   830  		t.Fatalf("timeout when waiting for a route configuration resource from the management server: %v", err)
   831  	}
   832  	gotErr := u.(routeConfigUpdateErrTuple).err
   833  	if gotErr == nil || !strings.Contains(gotErr.Error(), wantRouteConfigNACKErr) {
   834  		t.Fatalf("update received with error: %v, want %q", gotErr, wantRouteConfigNACKErr)
   835  	}
   836  
   837  	// Verify that the watcher watching the good resource receives a good
   838  	// update.
   839  	wantUpdate := routeConfigUpdateErrTuple{
   840  		update: xdsresource.RouteConfigUpdate{
   841  			VirtualHosts: []*xdsresource.VirtualHost{
   842  				{
   843  					Domains: []string{ldsName},
   844  					Routes: []*xdsresource.Route{
   845  						{
   846  							Prefix:           newStringP("/"),
   847  							ActionType:       xdsresource.RouteActionRoute,
   848  							WeightedClusters: map[string]xdsresource.WeightedCluster{cdsName: {Weight: 100}},
   849  						},
   850  					},
   851  				},
   852  			},
   853  		},
   854  	}
   855  	if err := verifyRouteConfigUpdate(ctx, rw2.updateCh, wantUpdate); err != nil {
   856  		t.Fatal(err)
   857  	}
   858  }