google.golang.org/grpc@v1.74.2/test/xds/xds_client_custom_lb_test.go (about) 1 /* 2 * 3 * Copyright 2023 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 "time" 26 27 "google.golang.org/grpc" 28 _ "google.golang.org/grpc/balancer/leastrequest" // To register least_request 29 _ "google.golang.org/grpc/balancer/weightedroundrobin" // To register weighted_round_robin 30 "google.golang.org/grpc/credentials/insecure" 31 "google.golang.org/grpc/internal/stubserver" 32 "google.golang.org/grpc/internal/testutils" 33 "google.golang.org/grpc/internal/testutils/roundrobin" 34 "google.golang.org/grpc/internal/testutils/xds/e2e" 35 "google.golang.org/grpc/internal/testutils/xds/e2e/setup" 36 "google.golang.org/grpc/resolver" 37 38 v3xdsxdstypepb "github.com/cncf/xds/go/xds/type/v3" 39 v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" 40 v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 41 v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3" 42 v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" 43 v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" 44 v3clientsideweightedroundrobinpb "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/client_side_weighted_round_robin/v3" 45 v3leastrequestpb "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/least_request/v3" 46 v3roundrobinpb "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/round_robin/v3" 47 v3wrrlocalitypb "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/wrr_locality/v3" 48 testgrpc "google.golang.org/grpc/interop/grpc_testing" 49 "google.golang.org/protobuf/proto" 50 "google.golang.org/protobuf/types/known/durationpb" 51 "google.golang.org/protobuf/types/known/structpb" 52 "google.golang.org/protobuf/types/known/wrapperspb" 53 ) 54 55 // wrrLocality is a helper that takes a proto message and returns a 56 // WrrLocalityProto with the proto message marshaled into a proto.Any as a 57 // child. 58 func wrrLocality(t *testing.T, m proto.Message) *v3wrrlocalitypb.WrrLocality { 59 return &v3wrrlocalitypb.WrrLocality{ 60 EndpointPickingPolicy: &v3clusterpb.LoadBalancingPolicy{ 61 Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{ 62 { 63 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{ 64 TypedConfig: testutils.MarshalAny(t, m), 65 }, 66 }, 67 }, 68 }, 69 } 70 } 71 72 // clusterWithLBConfiguration returns a cluster resource with the proto message 73 // passed Marshaled to an any and specified through the load_balancing_policy 74 // field. 75 func clusterWithLBConfiguration(t *testing.T, clusterName, edsServiceName string, secLevel e2e.SecurityLevel, m proto.Message) *v3clusterpb.Cluster { 76 cluster := e2e.DefaultCluster(clusterName, edsServiceName, secLevel) 77 cluster.LoadBalancingPolicy = &v3clusterpb.LoadBalancingPolicy{ 78 Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{ 79 { 80 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{ 81 TypedConfig: testutils.MarshalAny(t, m), 82 }, 83 }, 84 }, 85 } 86 return cluster 87 } 88 89 // TestWRRLocality tests RPC distribution across a scenario with 5 backends, 90 // with 2 backends in a locality with weight 1, and 3 backends in a second 91 // locality with weight 2. Through xDS, the test configures a 92 // wrr_locality_balancer with either a round robin or custom (specifying pick 93 // first) child load balancing policy, and asserts the correct distribution 94 // based on the locality weights and the endpoint picking policy specified. 95 func (s) TestWrrLocality(t *testing.T) { 96 backend1 := stubserver.StartTestService(t, nil) 97 port1 := testutils.ParsePort(t, backend1.Address) 98 defer backend1.Stop() 99 backend2 := stubserver.StartTestService(t, nil) 100 port2 := testutils.ParsePort(t, backend2.Address) 101 defer backend2.Stop() 102 backend3 := stubserver.StartTestService(t, nil) 103 port3 := testutils.ParsePort(t, backend3.Address) 104 defer backend3.Stop() 105 backend4 := stubserver.StartTestService(t, nil) 106 port4 := testutils.ParsePort(t, backend4.Address) 107 defer backend4.Stop() 108 backend5 := stubserver.StartTestService(t, nil) 109 port5 := testutils.ParsePort(t, backend5.Address) 110 defer backend5.Stop() 111 const serviceName = "my-service-client-side-xds" 112 113 tests := []struct { 114 name string 115 // Configuration will be specified through load_balancing_policy field. 116 wrrLocalityConfiguration *v3wrrlocalitypb.WrrLocality 117 addressDistributionWant []struct { 118 addr string 119 count int 120 } 121 }{ 122 { 123 name: "rr_child", 124 wrrLocalityConfiguration: wrrLocality(t, &v3roundrobinpb.RoundRobin{}), 125 // Each addresses expected probability is locality weight of 126 // locality / total locality weights multiplied by 1 / number of 127 // endpoints in each locality (due to round robin across endpoints 128 // in a locality). Thus, address 1 and address 2 have 1/3 * 1/2 129 // probability, and addresses 3 4 5 have 2/3 * 1/3 probability of 130 // being routed to. 131 addressDistributionWant: []struct { 132 addr string 133 count int 134 }{ 135 {addr: backend1.Address, count: 6}, 136 {addr: backend2.Address, count: 6}, 137 {addr: backend3.Address, count: 8}, 138 {addr: backend4.Address, count: 8}, 139 {addr: backend5.Address, count: 8}, 140 }, 141 }, 142 // This configures custom lb as the child of wrr_locality, which points 143 // to our pick_first implementation. Thus, the expected distribution of 144 // addresses is locality weight of locality / total locality weights as 145 // the probability of picking the first backend within the locality 146 // (e.g. Address 1 for locality 1, and Address 3 for locality 2). 147 { 148 name: "custom_lb_child_pick_first", 149 wrrLocalityConfiguration: wrrLocality(t, &v3xdsxdstypepb.TypedStruct{ 150 TypeUrl: "type.googleapis.com/pick_first", 151 Value: &structpb.Struct{}, 152 }), 153 addressDistributionWant: []struct { 154 addr string 155 count int 156 }{ 157 {addr: backend1.Address, count: 1}, 158 {addr: backend3.Address, count: 2}, 159 }, 160 }, 161 // Sanity check for weighted round robin. Don't need to test super 162 // specific behaviors, as that is covered in unit tests. Set up weighted 163 // round robin as the endpoint picking policy with per RPC load reports 164 // enabled. Due the server not sending trailers with load reports, the 165 // weighted round robin policy should essentially function as round 166 // robin, and thus should have the same distribution as round robin 167 // above. 168 { 169 name: "custom_lb_child_wrr/", 170 wrrLocalityConfiguration: wrrLocality(t, &v3clientsideweightedroundrobinpb.ClientSideWeightedRoundRobin{ 171 EnableOobLoadReport: &wrapperspb.BoolValue{ 172 Value: false, 173 }, 174 // BlackoutPeriod long enough to cause load report weights to 175 // trigger in the scope of test case, but no load reports 176 // configured anyway. 177 BlackoutPeriod: durationpb.New(10 * time.Second), 178 WeightExpirationPeriod: durationpb.New(10 * time.Second), 179 WeightUpdatePeriod: durationpb.New(time.Second), 180 ErrorUtilizationPenalty: &wrapperspb.FloatValue{Value: 1}, 181 }), 182 addressDistributionWant: []struct { 183 addr string 184 count int 185 }{ 186 {addr: backend1.Address, count: 6}, 187 {addr: backend2.Address, count: 6}, 188 {addr: backend3.Address, count: 8}, 189 {addr: backend4.Address, count: 8}, 190 {addr: backend5.Address, count: 8}, 191 }, 192 }, 193 { 194 name: "custom_lb_least_request", 195 wrrLocalityConfiguration: wrrLocality(t, &v3leastrequestpb.LeastRequest{ 196 ChoiceCount: wrapperspb.UInt32(2), 197 }), 198 // The test performs a Unary RPC, and blocks until the RPC returns, 199 // and then makes the next Unary RPC. Thus, over iterations, no RPC 200 // counts are present. This causes least request's randomness of 201 // indexes to sample to converge onto a round robin distribution per 202 // locality. Thus, expect the same distribution as round robin 203 // above. 204 addressDistributionWant: []struct { 205 addr string 206 count int 207 }{ 208 {addr: backend1.Address, count: 6}, 209 {addr: backend2.Address, count: 6}, 210 {addr: backend3.Address, count: 8}, 211 {addr: backend4.Address, count: 8}, 212 {addr: backend5.Address, count: 8}, 213 }, 214 }, 215 } 216 for _, test := range tests { 217 t.Run(test.name, func(t *testing.T) { 218 // Start an xDS management server. 219 managementServer, nodeID, _, xdsResolver := setup.ManagementServerAndResolver(t) 220 221 routeConfigName := "route-" + serviceName 222 clusterName := "cluster-" + serviceName 223 endpointsName := "endpoints-" + serviceName 224 resources := e2e.UpdateOptions{ 225 NodeID: nodeID, 226 Listeners: []*v3listenerpb.Listener{e2e.DefaultClientListener(serviceName, routeConfigName)}, 227 Routes: []*v3routepb.RouteConfiguration{e2e.DefaultRouteConfig(routeConfigName, serviceName, clusterName)}, 228 Clusters: []*v3clusterpb.Cluster{clusterWithLBConfiguration(t, clusterName, endpointsName, e2e.SecurityLevelNone, test.wrrLocalityConfiguration)}, 229 Endpoints: []*v3endpointpb.ClusterLoadAssignment{e2e.EndpointResourceWithOptions(e2e.EndpointOptions{ 230 ClusterName: endpointsName, 231 Host: "localhost", 232 Localities: []e2e.LocalityOptions{ 233 { 234 Backends: []e2e.BackendOptions{{Ports: []uint32{port1}}, {Ports: []uint32{port2}}}, 235 Weight: 1, 236 }, 237 { 238 Backends: []e2e.BackendOptions{{Ports: []uint32{port3}}, {Ports: []uint32{port4}}, {Ports: []uint32{port5}}}, 239 Weight: 2, 240 }, 241 }, 242 })}, 243 } 244 245 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 246 defer cancel() 247 if err := managementServer.Update(ctx, resources); err != nil { 248 t.Fatal(err) 249 } 250 251 cc, err := grpc.NewClient(fmt.Sprintf("xds:///%s", serviceName), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(xdsResolver)) 252 if err != nil { 253 t.Fatalf("Failed to dial local test server: %v", err) 254 } 255 defer cc.Close() 256 257 client := testgrpc.NewTestServiceClient(cc) 258 var addrDistWant []resolver.Address 259 for _, addrAndCount := range test.addressDistributionWant { 260 for i := 0; i < addrAndCount.count; i++ { 261 addrDistWant = append(addrDistWant, resolver.Address{Addr: addrAndCount.addr}) 262 } 263 } 264 if err := roundrobin.CheckWeightedRoundRobinRPCs(ctx, client, addrDistWant); err != nil { 265 t.Fatalf("Error in expected round robin: %v", err) 266 } 267 }) 268 } 269 }