google.golang.org/grpc@v1.72.2/test/xds/xds_client_priority_locality_test.go (about)

     1  /*
     2   *
     3   * Copyright 2024 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 xds_test
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"testing"
    25  
    26  	"google.golang.org/grpc"
    27  	"google.golang.org/grpc/credentials/insecure"
    28  	"google.golang.org/grpc/internal/stubserver"
    29  	"google.golang.org/grpc/internal/testutils"
    30  	rrutil "google.golang.org/grpc/internal/testutils/roundrobin"
    31  	"google.golang.org/grpc/internal/testutils/xds/e2e"
    32  	"google.golang.org/grpc/internal/testutils/xds/e2e/setup"
    33  	"google.golang.org/grpc/resolver"
    34  
    35  	v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
    36  	v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
    37  	v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
    38  	v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
    39  	testgrpc "google.golang.org/grpc/interop/grpc_testing"
    40  )
    41  
    42  // backendAddressesAndPorts extracts the address and port of each of the
    43  // StubServers passed in and returns them. Fails the test if any of the
    44  // StubServers passed have an invalid address.
    45  func backendAddressesAndPorts(t *testing.T, servers []*stubserver.StubServer) ([]resolver.Address, []uint32) {
    46  	addrs := make([]resolver.Address, len(servers))
    47  	ports := make([]uint32, len(servers))
    48  	for i := 0; i < len(servers); i++ {
    49  		addrs[i] = resolver.Address{Addr: servers[i].Address}
    50  		ports[i] = testutils.ParsePort(t, servers[i].Address)
    51  	}
    52  	return addrs, ports
    53  }
    54  
    55  // Tests scenarios involving localities moving between priorities.
    56  //   - The test starts off with a cluster that contains two priorities, one
    57  //     locality in each, and one endpoint in each. Verifies that traffic reaches
    58  //     the endpoint in the higher priority.
    59  //   - The test then moves the locality in the lower priority over to the higher
    60  //     priority. At that point, we would have a cluster with a single priority,
    61  //     but two localities, and one endpoint in each. Verifies that traffic is
    62  //     split between the endpoints.
    63  //   - The test then deletes the locality that was originally in the higher
    64  //     priority.Verifies that all traffic is now reaching the only remaining
    65  //     endpoint.
    66  func (s) TestClientSideXDS_LocalityChangesPriority(t *testing.T) {
    67  	// Spin up a management server and two test service backends.
    68  	managementServer, nodeID, _, xdsResolver := setup.ManagementServerAndResolver(t)
    69  	backend0 := stubserver.StartTestService(t, nil)
    70  	defer backend0.Stop()
    71  	backend1 := stubserver.StartTestService(t, nil)
    72  	defer backend1.Stop()
    73  	addrs, ports := backendAddressesAndPorts(t, []*stubserver.StubServer{backend0, backend1})
    74  
    75  	// Configure resources on the management server. We use default client side
    76  	// resources for listener, route configuration and cluster. For the
    77  	// endpoints resource though, we create one with two priorities, and one
    78  	// locality each, and one endpoint each.
    79  	const serviceName = "my-service-client-side-xds"
    80  	const routeConfigName = "route-" + serviceName
    81  	const clusterName = "cluster-" + serviceName
    82  	const endpointsName = "endpoints-" + serviceName
    83  	locality1 := e2e.LocalityID{Region: "my-region-1", Zone: "my-zone-1", SubZone: "my-subzone-1"}
    84  	locality2 := e2e.LocalityID{Region: "my-region-2", Zone: "my-zone-2", SubZone: "my-subzone-2"}
    85  	resources := e2e.UpdateOptions{
    86  		NodeID:    nodeID,
    87  		Listeners: []*v3listenerpb.Listener{e2e.DefaultClientListener(serviceName, routeConfigName)},
    88  		Routes:    []*v3routepb.RouteConfiguration{e2e.DefaultRouteConfig(routeConfigName, serviceName, clusterName)},
    89  		Clusters:  []*v3clusterpb.Cluster{e2e.DefaultCluster(clusterName, endpointsName, e2e.SecurityLevelNone)},
    90  		Endpoints: []*v3endpointpb.ClusterLoadAssignment{e2e.EndpointResourceWithOptions(e2e.EndpointOptions{
    91  			ClusterName: endpointsName,
    92  			Host:        "localhost",
    93  			Localities: []e2e.LocalityOptions{
    94  				{
    95  					Name:     "my-locality-1",
    96  					Weight:   1000000,
    97  					Priority: 0,
    98  					Backends: []e2e.BackendOptions{{Ports: []uint32{ports[0]}}},
    99  					Locality: locality1,
   100  				},
   101  				{
   102  					Name:     "my-locality-2",
   103  					Weight:   1000000,
   104  					Priority: 1,
   105  					Backends: []e2e.BackendOptions{{Ports: []uint32{ports[1]}}},
   106  					Locality: locality2,
   107  				},
   108  			},
   109  		})},
   110  	}
   111  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   112  	defer cancel()
   113  	if err := managementServer.Update(ctx, resources); err != nil {
   114  		t.Fatal(err)
   115  	}
   116  
   117  	// Create a ClientConn and make a successful RPC.
   118  	cc, err := grpc.NewClient(fmt.Sprintf("xds:///%s", serviceName), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(xdsResolver))
   119  	if err != nil {
   120  		t.Fatalf("failed to dial local test server: %v", err)
   121  	}
   122  	defer cc.Close()
   123  
   124  	// // Ensure that RPCs get routed to the backend in the higher priority.
   125  	client := testgrpc.NewTestServiceClient(cc)
   126  	if err := rrutil.CheckRoundRobinRPCs(ctx, client, addrs[:1]); err != nil {
   127  		t.Fatal(err)
   128  	}
   129  
   130  	// Update the endpoints resource to contain a single priority with two
   131  	// localities, and one endpoint each. The locality weights are equal at this
   132  	// point, and we expect RPCs to be round-robined across the two localities.
   133  	resources.Endpoints = []*v3endpointpb.ClusterLoadAssignment{e2e.EndpointResourceWithOptions(e2e.EndpointOptions{
   134  		ClusterName: endpointsName,
   135  		Host:        "localhost",
   136  		Localities: []e2e.LocalityOptions{
   137  			{
   138  				Name:     "my-locality-1",
   139  				Weight:   500000,
   140  				Priority: 0,
   141  				Backends: []e2e.BackendOptions{{Ports: []uint32{testutils.ParsePort(t, backend0.Address)}}},
   142  				Locality: locality1,
   143  			},
   144  			{
   145  				Name:     "my-locality-2",
   146  				Weight:   500000,
   147  				Priority: 0,
   148  				Backends: []e2e.BackendOptions{{Ports: []uint32{testutils.ParsePort(t, backend1.Address)}}},
   149  				Locality: locality2,
   150  			},
   151  		},
   152  	})}
   153  	if err := managementServer.Update(ctx, resources); err != nil {
   154  		t.Fatal(err)
   155  	}
   156  	if err := rrutil.CheckRoundRobinRPCs(ctx, client, addrs); err != nil {
   157  		t.Fatal(err)
   158  	}
   159  
   160  	// Update the locality weights ever so slightly. We still expect RPCs to be
   161  	// round-robined across the two localities.
   162  	resources.Endpoints = []*v3endpointpb.ClusterLoadAssignment{e2e.EndpointResourceWithOptions(e2e.EndpointOptions{
   163  		ClusterName: endpointsName,
   164  		Host:        "localhost",
   165  		Localities: []e2e.LocalityOptions{
   166  			{
   167  				Name:     "my-locality-1",
   168  				Weight:   499884,
   169  				Priority: 0,
   170  				Backends: []e2e.BackendOptions{{Ports: []uint32{testutils.ParsePort(t, backend0.Address)}}},
   171  				Locality: locality1,
   172  			},
   173  			{
   174  				Name:     "my-locality-2",
   175  				Weight:   500115,
   176  				Priority: 0,
   177  				Backends: []e2e.BackendOptions{{Ports: []uint32{testutils.ParsePort(t, backend1.Address)}}},
   178  				Locality: locality2,
   179  			},
   180  		},
   181  	})}
   182  	if err := managementServer.Update(ctx, resources); err != nil {
   183  		t.Fatal(err)
   184  	}
   185  	if err := rrutil.CheckRoundRobinRPCs(ctx, client, addrs); err != nil {
   186  		t.Fatal(err)
   187  	}
   188  
   189  	// Update the endpoints resource to contain a single priority with one
   190  	// locality. The locality which was originally in the higher priority is now
   191  	// dropped.
   192  	resources.Endpoints = []*v3endpointpb.ClusterLoadAssignment{e2e.EndpointResourceWithOptions(e2e.EndpointOptions{
   193  		ClusterName: endpointsName,
   194  		Host:        "localhost",
   195  		Localities: []e2e.LocalityOptions{
   196  			{
   197  				Name:     "my-locality-2",
   198  				Weight:   1000000,
   199  				Priority: 0,
   200  				Backends: []e2e.BackendOptions{{Ports: []uint32{testutils.ParsePort(t, backend1.Address)}}},
   201  				Locality: locality2,
   202  			},
   203  		},
   204  	})}
   205  	if err := managementServer.Update(ctx, resources); err != nil {
   206  		t.Fatal(err)
   207  	}
   208  	if err := rrutil.CheckRoundRobinRPCs(ctx, client, addrs[1:]); err != nil {
   209  		t.Fatal(err)
   210  	}
   211  }