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