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 }