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

     1  /*
     2   *
     3   * Copyright 2021 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  package xdsclient_test
    19  
    20  import (
    21  	"context"
    22  	"encoding/json"
    23  	"fmt"
    24  	"testing"
    25  
    26  	"github.com/google/uuid"
    27  	"google.golang.org/grpc/internal/testutils/xds/e2e"
    28  	"google.golang.org/grpc/internal/xds/bootstrap"
    29  	"google.golang.org/grpc/xds/internal"
    30  	"google.golang.org/grpc/xds/internal/xdsclient"
    31  	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
    32  
    33  	v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
    34  	v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
    35  	v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
    36  	v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
    37  )
    38  
    39  const testNonDefaultAuthority = "non-default-authority"
    40  
    41  // setupForFederationWatchersTest spins up two management servers, one for the
    42  // default (empty) authority and another for a non-default authority.
    43  //
    44  // Returns the management server associated with the non-default authority, the
    45  // nodeID to use, and the xDS client.
    46  func setupForFederationWatchersTest(t *testing.T) (*e2e.ManagementServer, string, xdsclient.XDSClient) {
    47  	// Start a management server as the default authority.
    48  	serverDefaultAuthority := e2e.StartManagementServer(t, e2e.ManagementServerOptions{})
    49  
    50  	// Start another management server as the other authority.
    51  	serverNonDefaultAuthority := e2e.StartManagementServer(t, e2e.ManagementServerOptions{})
    52  
    53  	nodeID := uuid.New().String()
    54  	bootstrapContents, err := bootstrap.NewContentsForTesting(bootstrap.ConfigOptionsForTesting{
    55  		Servers: []byte(fmt.Sprintf(`[{
    56  			"server_uri": %q,
    57  			"channel_creds": [{"type": "insecure"}]
    58  		}]`, serverDefaultAuthority.Address)),
    59  		Node: []byte(fmt.Sprintf(`{"id": "%s"}`, nodeID)),
    60  		Authorities: map[string]json.RawMessage{
    61  			testNonDefaultAuthority: []byte(fmt.Sprintf(`{
    62  				"xds_servers": [{
    63  					"server_uri": %q,
    64  					"channel_creds": [{"type": "insecure"}]
    65  				}]}`, serverNonDefaultAuthority.Address)),
    66  		},
    67  	})
    68  	if err != nil {
    69  		t.Fatalf("Failed to create bootstrap configuration: %v", err)
    70  	}
    71  	// Create an xDS client with the above bootstrap contents.
    72  	config, err := bootstrap.NewConfigFromContents(bootstrapContents)
    73  	if err != nil {
    74  		t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err)
    75  	}
    76  	pool := xdsclient.NewPool(config)
    77  	client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
    78  		Name: t.Name(),
    79  	})
    80  	if err != nil {
    81  		t.Fatalf("Failed to create xDS client: %v", err)
    82  	}
    83  	t.Cleanup(close)
    84  	return serverNonDefaultAuthority, nodeID, client
    85  }
    86  
    87  // TestFederation_ListenerResourceContextParamOrder covers the case of watching
    88  // a Listener resource with the new style resource name and context parameters.
    89  // The test registers watches for two resources which differ only in the order
    90  // of context parameters in their URI. The server is configured to respond with
    91  // a single resource with canonicalized context parameters. The test verifies
    92  // that both watchers are notified.
    93  func (s) TestFederation_ListenerResourceContextParamOrder(t *testing.T) {
    94  	serverNonDefaultAuthority, nodeID, client := setupForFederationWatchersTest(t)
    95  
    96  	var (
    97  		// Two resource names only differ in context parameter order.
    98  		resourceName1 = fmt.Sprintf("xdstp://%s/envoy.config.listener.v3.Listener/xdsclient-test-lds-resource?a=1&b=2", testNonDefaultAuthority)
    99  		resourceName2 = fmt.Sprintf("xdstp://%s/envoy.config.listener.v3.Listener/xdsclient-test-lds-resource?b=2&a=1", testNonDefaultAuthority)
   100  	)
   101  
   102  	// Register two watches for listener resources with the same query string,
   103  	// but context parameters in different order.
   104  	lw1 := newListenerWatcher()
   105  	ldsCancel1 := xdsresource.WatchListener(client, resourceName1, lw1)
   106  	defer ldsCancel1()
   107  	lw2 := newListenerWatcher()
   108  	ldsCancel2 := xdsresource.WatchListener(client, resourceName2, lw2)
   109  	defer ldsCancel2()
   110  
   111  	// Configure the management server for the non-default authority to return a
   112  	// single listener resource, corresponding to the watches registered above.
   113  	resources := e2e.UpdateOptions{
   114  		NodeID:         nodeID,
   115  		Listeners:      []*v3listenerpb.Listener{e2e.DefaultClientListener(resourceName1, "rds-resource")},
   116  		SkipValidation: true,
   117  	}
   118  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   119  	defer cancel()
   120  	if err := serverNonDefaultAuthority.Update(ctx, resources); err != nil {
   121  		t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err)
   122  	}
   123  
   124  	wantUpdate := listenerUpdateErrTuple{
   125  		update: xdsresource.ListenerUpdate{
   126  			RouteConfigName: "rds-resource",
   127  			HTTPFilters:     []xdsresource.HTTPFilter{{Name: "router"}},
   128  		},
   129  	}
   130  	// Verify the contents of the received update.
   131  	if err := verifyListenerUpdate(ctx, lw1.updateCh, wantUpdate); err != nil {
   132  		t.Fatal(err)
   133  	}
   134  	if err := verifyListenerUpdate(ctx, lw2.updateCh, wantUpdate); err != nil {
   135  		t.Fatal(err)
   136  	}
   137  }
   138  
   139  // TestFederation_RouteConfigResourceContextParamOrder covers the case of
   140  // watching a RouteConfiguration resource with the new style resource name and
   141  // context parameters. The test registers watches for two resources which
   142  // differ only in the order of context parameters in their URI. The server is
   143  // configured to respond with a single resource with canonicalized context
   144  // parameters. The test verifies that both watchers are notified.
   145  func (s) TestFederation_RouteConfigResourceContextParamOrder(t *testing.T) {
   146  	serverNonDefaultAuthority, nodeID, client := setupForFederationWatchersTest(t)
   147  
   148  	var (
   149  		// Two resource names only differ in context parameter order.
   150  		resourceName1 = fmt.Sprintf("xdstp://%s/envoy.config.route.v3.RouteConfiguration/xdsclient-test-rds-resource?a=1&b=2", testNonDefaultAuthority)
   151  		resourceName2 = fmt.Sprintf("xdstp://%s/envoy.config.route.v3.RouteConfiguration/xdsclient-test-rds-resource?b=2&a=1", testNonDefaultAuthority)
   152  	)
   153  
   154  	// Register two watches for route configuration resources with the same
   155  	// query string, but context parameters in different order.
   156  	rw1 := newRouteConfigWatcher()
   157  	rdsCancel1 := xdsresource.WatchRouteConfig(client, resourceName1, rw1)
   158  	defer rdsCancel1()
   159  	rw2 := newRouteConfigWatcher()
   160  	rdsCancel2 := xdsresource.WatchRouteConfig(client, resourceName2, rw2)
   161  	defer rdsCancel2()
   162  
   163  	// Configure the management server for the non-default authority to return a
   164  	// single route config resource, corresponding to the watches registered.
   165  	resources := e2e.UpdateOptions{
   166  		NodeID:         nodeID,
   167  		Routes:         []*v3routepb.RouteConfiguration{e2e.DefaultRouteConfig(resourceName1, "listener-resource", "cluster-resource")},
   168  		SkipValidation: true,
   169  	}
   170  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   171  	defer cancel()
   172  	if err := serverNonDefaultAuthority.Update(ctx, resources); err != nil {
   173  		t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err)
   174  	}
   175  
   176  	wantUpdate := routeConfigUpdateErrTuple{
   177  		update: xdsresource.RouteConfigUpdate{
   178  			VirtualHosts: []*xdsresource.VirtualHost{
   179  				{
   180  					Domains: []string{"listener-resource"},
   181  					Routes: []*xdsresource.Route{
   182  						{
   183  							Prefix:           newStringP("/"),
   184  							ActionType:       xdsresource.RouteActionRoute,
   185  							WeightedClusters: map[string]xdsresource.WeightedCluster{"cluster-resource": {Weight: 100}},
   186  						},
   187  					},
   188  				},
   189  			},
   190  		},
   191  	}
   192  	// Verify the contents of the received update.
   193  	if err := verifyRouteConfigUpdate(ctx, rw1.updateCh, wantUpdate); err != nil {
   194  		t.Fatal(err)
   195  	}
   196  	if err := verifyRouteConfigUpdate(ctx, rw2.updateCh, wantUpdate); err != nil {
   197  		t.Fatal(err)
   198  	}
   199  }
   200  
   201  // TestFederation_ClusterResourceContextParamOrder covers the case of watching a
   202  // Cluster resource with the new style resource name and context parameters.
   203  // The test registers watches for two resources which differ only in the order
   204  // of context parameters in their URI. The server is configured to respond with
   205  // a single resource with canonicalized context parameters. The test verifies
   206  // that both watchers are notified.
   207  func (s) TestFederation_ClusterResourceContextParamOrder(t *testing.T) {
   208  	serverNonDefaultAuthority, nodeID, client := setupForFederationWatchersTest(t)
   209  
   210  	var (
   211  		// Two resource names only differ in context parameter order.
   212  		resourceName1 = fmt.Sprintf("xdstp://%s/envoy.config.cluster.v3.Cluster/xdsclient-test-cds-resource?a=1&b=2", testNonDefaultAuthority)
   213  		resourceName2 = fmt.Sprintf("xdstp://%s/envoy.config.cluster.v3.Cluster/xdsclient-test-cds-resource?b=2&a=1", testNonDefaultAuthority)
   214  	)
   215  
   216  	// Register two watches for cluster resources with the same query string,
   217  	// but context parameters in different order.
   218  	cw1 := newClusterWatcher()
   219  	cdsCancel1 := xdsresource.WatchCluster(client, resourceName1, cw1)
   220  	defer cdsCancel1()
   221  	cw2 := newClusterWatcher()
   222  	cdsCancel2 := xdsresource.WatchCluster(client, resourceName2, cw2)
   223  	defer cdsCancel2()
   224  
   225  	// Configure the management server for the non-default authority to return a
   226  	// single cluster resource, corresponding to the watches registered.
   227  	resources := e2e.UpdateOptions{
   228  		NodeID:         nodeID,
   229  		Clusters:       []*v3clusterpb.Cluster{e2e.DefaultCluster(resourceName1, "eds-service-name", e2e.SecurityLevelNone)},
   230  		SkipValidation: true,
   231  	}
   232  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   233  	defer cancel()
   234  	if err := serverNonDefaultAuthority.Update(ctx, resources); err != nil {
   235  		t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err)
   236  	}
   237  
   238  	wantUpdate := clusterUpdateErrTuple{
   239  		update: xdsresource.ClusterUpdate{
   240  			ClusterName:    "xdstp://non-default-authority/envoy.config.cluster.v3.Cluster/xdsclient-test-cds-resource?a=1&b=2",
   241  			EDSServiceName: "eds-service-name",
   242  		},
   243  	}
   244  	// Verify the contents of the received update.
   245  	if err := verifyClusterUpdate(ctx, cw1.updateCh, wantUpdate); err != nil {
   246  		t.Fatal(err)
   247  	}
   248  	if err := verifyClusterUpdate(ctx, cw2.updateCh, wantUpdate); err != nil {
   249  		t.Fatal(err)
   250  	}
   251  }
   252  
   253  // TestFederation_EndpointsResourceContextParamOrder covers the case of watching
   254  // an Endpoints resource with the new style resource name and context parameters.
   255  // The test registers watches for two resources which differ only in the order
   256  // of context parameters in their URI. The server is configured to respond with
   257  // a single resource with canonicalized context parameters. The test verifies
   258  // that both watchers are notified.
   259  func (s) TestFederation_EndpointsResourceContextParamOrder(t *testing.T) {
   260  	serverNonDefaultAuthority, nodeID, client := setupForFederationWatchersTest(t)
   261  
   262  	var (
   263  		// Two resource names only differ in context parameter order.
   264  		resourceName1 = fmt.Sprintf("xdstp://%s/envoy.config.endpoint.v3.ClusterLoadAssignment/xdsclient-test-eds-resource?a=1&b=2", testNonDefaultAuthority)
   265  		resourceName2 = fmt.Sprintf("xdstp://%s/envoy.config.endpoint.v3.ClusterLoadAssignment/xdsclient-test-eds-resource?b=2&a=1", testNonDefaultAuthority)
   266  	)
   267  
   268  	// Register two watches for endpoint resources with the same query string,
   269  	// but context parameters in different order.
   270  	ew1 := newEndpointsWatcher()
   271  	edsCancel1 := xdsresource.WatchEndpoints(client, resourceName1, ew1)
   272  	defer edsCancel1()
   273  	ew2 := newEndpointsWatcher()
   274  	edsCancel2 := xdsresource.WatchEndpoints(client, resourceName2, ew2)
   275  	defer edsCancel2()
   276  
   277  	// Configure the management server for the non-default authority to return a
   278  	// single endpoints resource, corresponding to the watches registered.
   279  	resources := e2e.UpdateOptions{
   280  		NodeID:         nodeID,
   281  		Endpoints:      []*v3endpointpb.ClusterLoadAssignment{e2e.DefaultEndpoint(resourceName1, "localhost", []uint32{666})},
   282  		SkipValidation: true,
   283  	}
   284  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   285  	defer cancel()
   286  	if err := serverNonDefaultAuthority.Update(ctx, resources); err != nil {
   287  		t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err)
   288  	}
   289  
   290  	wantUpdate := endpointsUpdateErrTuple{
   291  		update: xdsresource.EndpointsUpdate{
   292  			Localities: []xdsresource.Locality{
   293  				{
   294  					Endpoints: []xdsresource.Endpoint{{Addresses: []string{"localhost:666"}, Weight: 1}},
   295  					Weight:    1,
   296  					ID: internal.LocalityID{
   297  						Region:  "region-1",
   298  						Zone:    "zone-1",
   299  						SubZone: "subzone-1",
   300  					},
   301  				},
   302  			},
   303  		},
   304  	}
   305  	// Verify the contents of the received update.
   306  	if err := verifyEndpointsUpdate(ctx, ew1.updateCh, wantUpdate); err != nil {
   307  		t.Fatal(err)
   308  	}
   309  	if err := verifyEndpointsUpdate(ctx, ew2.updateCh, wantUpdate); err != nil {
   310  		t.Fatal(err)
   311  	}
   312  }
   313  
   314  func newStringP(s string) *string {
   315  	return &s
   316  }