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