google.golang.org/grpc@v1.72.2/xds/internal/xdsclient/tests/authority_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  	"testing"
    26  
    27  	"github.com/google/go-cmp/cmp"
    28  	"github.com/google/go-cmp/cmp/cmpopts"
    29  	"github.com/google/uuid"
    30  	"google.golang.org/grpc/internal/testutils"
    31  	"google.golang.org/grpc/internal/testutils/xds/e2e"
    32  	"google.golang.org/grpc/internal/xds/bootstrap"
    33  	xdstestutils "google.golang.org/grpc/xds/internal/testutils"
    34  	"google.golang.org/grpc/xds/internal/xdsclient"
    35  	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
    36  
    37  	v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
    38  )
    39  
    40  const (
    41  	testAuthority1 = "test-authority1"
    42  	testAuthority2 = "test-authority2"
    43  	testAuthority3 = "test-authority3"
    44  )
    45  
    46  var (
    47  	// These two resources use `testAuthority1`, which contains an empty server
    48  	// config in the bootstrap file, and therefore will use the default
    49  	// management server.
    50  	authorityTestResourceName11 = xdstestutils.BuildResourceName(xdsresource.ClusterResourceTypeName, testAuthority1, cdsName+"1", nil)
    51  	authorityTestResourceName12 = xdstestutils.BuildResourceName(xdsresource.ClusterResourceTypeName, testAuthority1, cdsName+"2", nil)
    52  	// This resource uses `testAuthority2`, which contains an empty server
    53  	// config in the bootstrap file, and therefore will use the default
    54  	// management server.
    55  	authorityTestResourceName2 = xdstestutils.BuildResourceName(xdsresource.ClusterResourceTypeName, testAuthority2, cdsName+"3", nil)
    56  	// This resource uses `testAuthority3`, which contains a non-empty server
    57  	// config in the bootstrap file, and therefore will use the non-default
    58  	// management server.
    59  	authorityTestResourceName3 = xdstestutils.BuildResourceName(xdsresource.ClusterResourceTypeName, testAuthority3, cdsName+"3", nil)
    60  )
    61  
    62  // setupForAuthorityTests spins up two management servers, one to act as the
    63  // default and the other to act as the non-default. It also generates a
    64  // bootstrap configuration with three authorities (the first two pointing to the
    65  // default and the third one pointing to the non-default).
    66  //
    67  // Returns two listeners used by the default and non-default management servers
    68  // respectively, and the xDS client and its close function.
    69  func setupForAuthorityTests(ctx context.Context, t *testing.T) (*testutils.ListenerWrapper, *testutils.ListenerWrapper, xdsclient.XDSClient, func()) {
    70  	// Create listener wrappers which notify on to a channel whenever a new
    71  	// connection is accepted. We use this to track the number of transports
    72  	// used by the xDS client.
    73  	lisDefault := testutils.NewListenerWrapper(t, nil)
    74  	lisNonDefault := testutils.NewListenerWrapper(t, nil)
    75  
    76  	// Start a management server to act as the default authority.
    77  	defaultAuthorityServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{Listener: lisDefault})
    78  
    79  	// Start a management server to act as the non-default authority.
    80  	nonDefaultAuthorityServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{Listener: lisNonDefault})
    81  
    82  	// Create a bootstrap configuration with two non-default authorities which
    83  	// have empty server configs, and therefore end up using the default server
    84  	// config, which points to the above management server.
    85  	nodeID := uuid.New().String()
    86  	bootstrapContents, err := bootstrap.NewContentsForTesting(bootstrap.ConfigOptionsForTesting{
    87  		Servers: []byte(fmt.Sprintf(`[{
    88  			"server_uri": %q,
    89  			"channel_creds": [{"type": "insecure"}]
    90  		}]`, defaultAuthorityServer.Address)),
    91  		Node: []byte(fmt.Sprintf(`{"id": "%s"}`, nodeID)),
    92  		Authorities: map[string]json.RawMessage{
    93  			testAuthority1: []byte(`{}`),
    94  			testAuthority2: []byte(`{}`),
    95  			testAuthority3: []byte(fmt.Sprintf(`{
    96  				"xds_servers": [{
    97  					"server_uri": %q,
    98  					"channel_creds": [{"type": "insecure"}]
    99  				}]}`, nonDefaultAuthorityServer.Address)),
   100  		},
   101  	})
   102  	if err != nil {
   103  		t.Fatalf("Failed to create bootstrap configuration: %v", err)
   104  	}
   105  	config, err := bootstrap.NewConfigFromContents(bootstrapContents)
   106  	if err != nil {
   107  		t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err)
   108  	}
   109  	pool := xdsclient.NewPool(config)
   110  	client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
   111  		Name:               t.Name(),
   112  		WatchExpiryTimeout: defaultTestWatchExpiryTimeout,
   113  	})
   114  	if err != nil {
   115  		t.Fatalf("Failed to create an xDS client: %v", err)
   116  	}
   117  
   118  	resources := e2e.UpdateOptions{
   119  		NodeID: nodeID,
   120  		Clusters: []*v3clusterpb.Cluster{
   121  			e2e.DefaultCluster(authorityTestResourceName11, edsName, e2e.SecurityLevelNone),
   122  			e2e.DefaultCluster(authorityTestResourceName12, edsName, e2e.SecurityLevelNone),
   123  			e2e.DefaultCluster(authorityTestResourceName2, edsName, e2e.SecurityLevelNone),
   124  			e2e.DefaultCluster(authorityTestResourceName3, edsName, e2e.SecurityLevelNone),
   125  		},
   126  		SkipValidation: true,
   127  	}
   128  	if err := defaultAuthorityServer.Update(ctx, resources); err != nil {
   129  		t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err)
   130  	}
   131  	return lisDefault, lisNonDefault, client, close
   132  }
   133  
   134  // Tests the xdsChannel sharing logic among authorities. The test verifies the
   135  // following scenarios:
   136  //   - A watch for a resource name with an authority matching an existing watch
   137  //     should not result in a new transport being created.
   138  //   - A watch for a resource name with different authority name but same
   139  //     authority config as an existing watch should not result in a new transport
   140  //     being created.
   141  func (s) TestAuthority_XDSChannelSharing(t *testing.T) {
   142  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   143  	defer cancel()
   144  	lis, _, client, close := setupForAuthorityTests(ctx, t)
   145  	defer close()
   146  
   147  	// Verify that no connection is established to the management server at this
   148  	// point. A transport is created only when a resource (which belongs to that
   149  	// authority) is requested.
   150  	sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout)
   151  	defer sCancel()
   152  	if _, err := lis.NewConnCh.Receive(sCtx); err != context.DeadlineExceeded {
   153  		t.Fatal("Unexpected new transport created to management server")
   154  	}
   155  
   156  	// Request the first resource. Verify that a new transport is created.
   157  	watcher := noopClusterWatcher{}
   158  	cdsCancel1 := xdsresource.WatchCluster(client, authorityTestResourceName11, watcher)
   159  	defer cdsCancel1()
   160  	if _, err := lis.NewConnCh.Receive(ctx); err != nil {
   161  		t.Fatalf("Timed out when waiting for a new transport to be created to the management server: %v", err)
   162  	}
   163  
   164  	// Request the second resource. Verify that no new transport is created.
   165  	cdsCancel2 := xdsresource.WatchCluster(client, authorityTestResourceName12, watcher)
   166  	defer cdsCancel2()
   167  	sCtx, sCancel = context.WithTimeout(ctx, defaultTestShortTimeout)
   168  	defer sCancel()
   169  	if _, err := lis.NewConnCh.Receive(sCtx); err != context.DeadlineExceeded {
   170  		t.Fatal("Unexpected new transport created to management server")
   171  	}
   172  
   173  	// Request the third resource. Verify that no new transport is created.
   174  	cdsCancel3 := xdsresource.WatchCluster(client, authorityTestResourceName2, watcher)
   175  	defer cdsCancel3()
   176  	sCtx, sCancel = context.WithTimeout(ctx, defaultTestShortTimeout)
   177  	defer sCancel()
   178  	if _, err := lis.NewConnCh.Receive(sCtx); err != context.DeadlineExceeded {
   179  		t.Fatal("Unexpected new transport created to management server")
   180  	}
   181  }
   182  
   183  // Test the xdsChannel close logic. The test verifies that the xDS client
   184  // closes an xdsChannel immediately after the last watch is canceled.
   185  func (s) TestAuthority_XDSChannelClose(t *testing.T) {
   186  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   187  	defer cancel()
   188  	lis, _, client, close := setupForAuthorityTests(ctx, t)
   189  	defer close()
   190  
   191  	// Request the first resource. Verify that a new transport is created.
   192  	watcher := noopClusterWatcher{}
   193  	cdsCancel1 := xdsresource.WatchCluster(client, authorityTestResourceName11, watcher)
   194  	val, err := lis.NewConnCh.Receive(ctx)
   195  	if err != nil {
   196  		t.Fatalf("Timed out when waiting for a new transport to be created to the management server: %v", err)
   197  	}
   198  	conn := val.(*testutils.ConnWrapper)
   199  
   200  	// Request the second resource. Verify that no new transport is created.
   201  	cdsCancel2 := xdsresource.WatchCluster(client, authorityTestResourceName12, watcher)
   202  	sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout)
   203  	defer sCancel()
   204  	if _, err := lis.NewConnCh.Receive(sCtx); err != context.DeadlineExceeded {
   205  		t.Fatal("Unexpected new transport created to management server")
   206  	}
   207  
   208  	// Cancel both watches, and verify that the connection to the management
   209  	// server is closed.
   210  	cdsCancel1()
   211  	cdsCancel2()
   212  	if _, err := conn.CloseCh.Receive(ctx); err != nil {
   213  		t.Fatal("Timeout when waiting for connection to management server to be closed")
   214  	}
   215  }
   216  
   217  // Tests the scenario where the primary management server is unavailable at
   218  // startup and the xDS client falls back to the secondary.  The test verifies
   219  // that the resource watcher is not notifified of the connectivity failure until
   220  // all servers have failed.
   221  func (s) TestAuthority_Fallback(t *testing.T) {
   222  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   223  	defer cancel()
   224  
   225  	// Create primary and secondary management servers with restartable
   226  	// listeners.
   227  	l, err := testutils.LocalTCPListener()
   228  	if err != nil {
   229  		t.Fatalf("testutils.LocalTCPListener() failed: %v", err)
   230  	}
   231  	primaryLis := testutils.NewRestartableListener(l)
   232  	primaryMgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{Listener: primaryLis})
   233  	l, err = testutils.LocalTCPListener()
   234  	if err != nil {
   235  		t.Fatalf("testutils.LocalTCPListener() failed: %v", err)
   236  	}
   237  	secondaryLis := testutils.NewRestartableListener(l)
   238  	secondaryMgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{Listener: secondaryLis})
   239  
   240  	// Create bootstrap configuration with the above primary and fallback
   241  	// management servers, and an xDS client with that configuration.
   242  	nodeID := uuid.New().String()
   243  	bootstrapContents, err := bootstrap.NewContentsForTesting(bootstrap.ConfigOptionsForTesting{
   244  		Servers: []byte(fmt.Sprintf(`
   245  		[
   246  			{
   247  				"server_uri": %q,
   248  				"channel_creds": [{"type": "insecure"}]
   249  			},
   250  			{
   251  				"server_uri": %q,
   252  				"channel_creds": [{"type": "insecure"}]
   253  			}
   254  		]`, primaryMgmtServer.Address, secondaryMgmtServer.Address)),
   255  		Node: []byte(fmt.Sprintf(`{"id": "%s"}`, nodeID)),
   256  	})
   257  	if err != nil {
   258  		t.Fatalf("Failed to create bootstrap configuration: %v", err)
   259  	}
   260  	config, err := bootstrap.NewConfigFromContents(bootstrapContents)
   261  	if err != nil {
   262  		t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err)
   263  	}
   264  	pool := xdsclient.NewPool(config)
   265  	xdsC, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{Name: t.Name()})
   266  	if err != nil {
   267  		t.Fatalf("Failed to create an xDS client: %v", err)
   268  	}
   269  	defer close()
   270  
   271  	const clusterName = "cluster"
   272  	const edsPrimaryName = "eds-primary"
   273  	const edsSecondaryName = "eds-secondary"
   274  
   275  	// Create a Cluster resource on the primary.
   276  	resources := e2e.UpdateOptions{
   277  		NodeID: nodeID,
   278  		Clusters: []*v3clusterpb.Cluster{
   279  			e2e.DefaultCluster(clusterName, edsPrimaryName, e2e.SecurityLevelNone),
   280  		},
   281  		SkipValidation: true,
   282  	}
   283  	if err := primaryMgmtServer.Update(ctx, resources); err != nil {
   284  		t.Fatalf("Failed to update primary management server with resources: %v, err: %v", resources, err)
   285  	}
   286  
   287  	// Create a Cluster resource on the secondary .
   288  	resources = e2e.UpdateOptions{
   289  		NodeID: nodeID,
   290  		Clusters: []*v3clusterpb.Cluster{
   291  			e2e.DefaultCluster(clusterName, edsSecondaryName, e2e.SecurityLevelNone),
   292  		},
   293  		SkipValidation: true,
   294  	}
   295  	if err := secondaryMgmtServer.Update(ctx, resources); err != nil {
   296  		t.Fatalf("Failed to update primary management server with resources: %v, err: %v", resources, err)
   297  	}
   298  
   299  	// Stop the primary.
   300  	primaryLis.Close()
   301  
   302  	// Register a watch.
   303  	watcher := newClusterWatcherV2()
   304  	cdsCancel := xdsresource.WatchCluster(xdsC, clusterName, watcher)
   305  	defer cdsCancel()
   306  
   307  	// Ensure that the connectivity error callback is not called.
   308  	sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout)
   309  	defer sCancel()
   310  	if v, err := watcher.errCh.Receive(sCtx); err != context.DeadlineExceeded {
   311  		t.Fatalf("Error callback on the watcher with error:  %v", v.(error))
   312  	}
   313  
   314  	// Ensure that the resource update callback is invoked.
   315  	v, err := watcher.updateCh.Receive(ctx)
   316  	if err != nil {
   317  		t.Fatalf("Error when waiting for a resource update callback:  %v", err)
   318  	}
   319  	gotUpdate := v.(xdsresource.ClusterUpdate)
   320  	wantUpdate := xdsresource.ClusterUpdate{
   321  		ClusterName:    clusterName,
   322  		EDSServiceName: edsSecondaryName,
   323  	}
   324  	cmpOpts := []cmp.Option{cmpopts.EquateEmpty(), cmpopts.IgnoreFields(xdsresource.ClusterUpdate{}, "Raw", "LBPolicy", "TelemetryLabels")}
   325  	if diff := cmp.Diff(wantUpdate, gotUpdate, cmpOpts...); diff != "" {
   326  		t.Fatalf("Diff in the cluster resource update: (-want, got):\n%s", diff)
   327  	}
   328  
   329  	// Stop the secondary.
   330  	secondaryLis.Close()
   331  
   332  	// Ensure that the connectivity error callback is called.
   333  	if _, err := watcher.errCh.Receive(ctx); err != nil {
   334  		t.Fatal("Timeout when waiting for error callback on the watcher")
   335  	}
   336  }
   337  
   338  // TODO: Get rid of the clusterWatcher type in cds_watchers_test.go and use this
   339  // one instead. Also, rename this to clusterWatcher as part of that refactor.
   340  type clusterWatcherV2 struct {
   341  	updateCh           *testutils.Channel // Messages of type xdsresource.ClusterUpdate
   342  	errCh              *testutils.Channel // Messages of type error
   343  	resourceNotFoundCh *testutils.Channel // Messages of type error
   344  }
   345  
   346  func newClusterWatcherV2() *clusterWatcherV2 {
   347  	return &clusterWatcherV2{
   348  		updateCh:           testutils.NewChannel(),
   349  		errCh:              testutils.NewChannel(),
   350  		resourceNotFoundCh: testutils.NewChannel(),
   351  	}
   352  }
   353  
   354  func (cw *clusterWatcherV2) OnUpdate(update *xdsresource.ClusterResourceData, onDone xdsresource.OnDoneFunc) {
   355  	cw.updateCh.Send(update.Resource)
   356  	onDone()
   357  }
   358  
   359  func (cw *clusterWatcherV2) OnError(err error, onDone xdsresource.OnDoneFunc) {
   360  	// When used with a go-control-plane management server that continuously
   361  	// resends resources which are NACKed by the xDS client, using a `Replace()`
   362  	// here simplifies tests that want access to the most recently received
   363  	// error.
   364  	cw.errCh.Replace(err)
   365  	onDone()
   366  }
   367  
   368  func (cw *clusterWatcherV2) OnResourceDoesNotExist(onDone xdsresource.OnDoneFunc) {
   369  	// When used with a go-control-plane management server that continuously
   370  	// resends resources which are NACKed by the xDS client, using a `Replace()`
   371  	// here simplifies tests that want access to the most recently received
   372  	// error.
   373  	cw.resourceNotFoundCh.Replace(xdsresource.NewError(xdsresource.ErrorTypeResourceNotFound, "Cluster not found in received response"))
   374  	onDone()
   375  }