1
18
19
20 package xdslbregistry_test
21
22 import (
23 "encoding/json"
24 "strings"
25 "testing"
26
27 "github.com/google/go-cmp/cmp"
28 _ "google.golang.org/grpc/balancer/roundrobin"
29 "google.golang.org/grpc/internal/balancer/stub"
30 "google.golang.org/grpc/internal/envconfig"
31 "google.golang.org/grpc/internal/grpctest"
32 "google.golang.org/grpc/internal/pretty"
33 internalserviceconfig "google.golang.org/grpc/internal/serviceconfig"
34 "google.golang.org/grpc/internal/testutils"
35 _ "google.golang.org/grpc/xds"
36 "google.golang.org/grpc/xds/internal/balancer/wrrlocality"
37 "google.golang.org/grpc/xds/internal/xdsclient/xdslbregistry"
38 "google.golang.org/protobuf/proto"
39 "google.golang.org/protobuf/types/known/anypb"
40 "google.golang.org/protobuf/types/known/structpb"
41 "google.golang.org/protobuf/types/known/wrapperspb"
42
43 v1xdsudpatypepb "github.com/cncf/xds/go/udpa/type/v1"
44 v3xdsxdstypepb "github.com/cncf/xds/go/xds/type/v3"
45 v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
46 v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
47 v3leastrequestpb "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/least_request/v3"
48 v3pickfirstpb "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/pick_first/v3"
49 v3ringhashpb "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/ring_hash/v3"
50 v3roundrobinpb "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/round_robin/v3"
51 v3wrrlocalitypb "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/wrr_locality/v3"
52 )
53
54 type s struct {
55 grpctest.Tester
56 }
57
58 func Test(t *testing.T) {
59 grpctest.RunSubTests(t, s{})
60 }
61
62 func wrrLocalityBalancerConfig(childPolicy *internalserviceconfig.BalancerConfig) *internalserviceconfig.BalancerConfig {
63 return &internalserviceconfig.BalancerConfig{
64 Name: wrrlocality.Name,
65 Config: &wrrlocality.LBConfig{
66 ChildPolicy: childPolicy,
67 },
68 }
69 }
70
71 func (s) TestConvertToServiceConfigSuccess(t *testing.T) {
72 defer func(old bool) { envconfig.LeastRequestLB = old }(envconfig.LeastRequestLB)
73 envconfig.LeastRequestLB = false
74
75 const customLBPolicyName = "myorg.MyCustomLeastRequestPolicy"
76 stub.Register(customLBPolicyName, stub.BalancerFuncs{})
77
78 tests := []struct {
79 name string
80 policy *v3clusterpb.LoadBalancingPolicy
81 wantConfig string
82 lrEnabled bool
83 }{
84 {
85 name: "ring_hash",
86 policy: &v3clusterpb.LoadBalancingPolicy{
87 Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{
88 {
89 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
90 TypedConfig: testutils.MarshalAny(t, &v3ringhashpb.RingHash{
91 HashFunction: v3ringhashpb.RingHash_XX_HASH,
92 MinimumRingSize: wrapperspb.UInt64(10),
93 MaximumRingSize: wrapperspb.UInt64(100),
94 }),
95 },
96 },
97 },
98 },
99 wantConfig: `[{"ring_hash_experimental": { "minRingSize": 10, "maxRingSize": 100 }}]`,
100 },
101 {
102 name: "least_request",
103 policy: &v3clusterpb.LoadBalancingPolicy{
104 Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{
105 {
106 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
107 TypedConfig: testutils.MarshalAny(t, &v3leastrequestpb.LeastRequest{
108 ChoiceCount: wrapperspb.UInt32(3),
109 }),
110 },
111 },
112 },
113 },
114 wantConfig: `[{"least_request_experimental": { "choiceCount": 3 }}]`,
115 lrEnabled: true,
116 },
117 {
118 name: "pick_first_shuffle",
119 policy: &v3clusterpb.LoadBalancingPolicy{
120 Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{
121 {
122 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
123 TypedConfig: testutils.MarshalAny(t, &v3pickfirstpb.PickFirst{
124 ShuffleAddressList: true,
125 }),
126 },
127 },
128 },
129 },
130 wantConfig: `[{"pick_first": { "shuffleAddressList": true }}]`,
131 },
132 {
133 name: "pick_first",
134 policy: &v3clusterpb.LoadBalancingPolicy{
135 Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{
136 {
137 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
138 TypedConfig: testutils.MarshalAny(t, &v3pickfirstpb.PickFirst{}),
139 },
140 },
141 },
142 },
143 wantConfig: `[{"pick_first": { "shuffleAddressList": false }}]`,
144 },
145 {
146 name: "round_robin",
147 policy: &v3clusterpb.LoadBalancingPolicy{
148 Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{
149 {
150 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
151 TypedConfig: testutils.MarshalAny(t, &v3roundrobinpb.RoundRobin{}),
152 },
153 },
154 },
155 },
156 wantConfig: `[{"round_robin": {}}]`,
157 },
158 {
159 name: "round_robin_ring_hash_use_first_supported",
160 policy: &v3clusterpb.LoadBalancingPolicy{
161 Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{
162 {
163 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
164 TypedConfig: testutils.MarshalAny(t, &v3roundrobinpb.RoundRobin{}),
165 },
166 },
167 {
168 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
169 TypedConfig: testutils.MarshalAny(t, &v3ringhashpb.RingHash{
170 HashFunction: v3ringhashpb.RingHash_XX_HASH,
171 MinimumRingSize: wrapperspb.UInt64(10),
172 MaximumRingSize: wrapperspb.UInt64(100),
173 }),
174 },
175 },
176 },
177 },
178 wantConfig: `[{"round_robin": {}}]`,
179 },
180 {
181 name: "pf_rr_use_pick_first",
182 policy: &v3clusterpb.LoadBalancingPolicy{
183 Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{
184 {
185 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
186 TypedConfig: testutils.MarshalAny(t, &v3pickfirstpb.PickFirst{
187 ShuffleAddressList: true,
188 }),
189 },
190 },
191 {
192 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
193 TypedConfig: testutils.MarshalAny(t, &v3roundrobinpb.RoundRobin{}),
194 },
195 },
196 },
197 },
198 wantConfig: `[{"pick_first": { "shuffleAddressList": true }}]`,
199 },
200 {
201 name: "least_request_disabled_pf_rr_use_first_supported",
202 policy: &v3clusterpb.LoadBalancingPolicy{
203 Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{
204 {
205 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
206 TypedConfig: testutils.MarshalAny(t, &v3leastrequestpb.LeastRequest{
207 ChoiceCount: wrapperspb.UInt32(32),
208 }),
209 },
210 },
211 {
212 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
213 TypedConfig: testutils.MarshalAny(t, &v3roundrobinpb.RoundRobin{}),
214 },
215 },
216 },
217 },
218 wantConfig: `[{"round_robin": {}}]`,
219 },
220 {
221 name: "custom_lb_type_v3_struct",
222 policy: &v3clusterpb.LoadBalancingPolicy{
223 Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{
224 {
225 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
226
227
228 TypedConfig: testutils.MarshalAny(t, &v3xdsxdstypepb.TypedStruct{
229 TypeUrl: "type.googleapis.com/myorg.ThisTypeDoesNotExist",
230 Value: &structpb.Struct{},
231 }),
232 },
233 },
234 {
235 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
236 TypedConfig: testutils.MarshalAny(t, &v3xdsxdstypepb.TypedStruct{
237 TypeUrl: "type.googleapis.com/myorg.MyCustomLeastRequestPolicy",
238 Value: &structpb.Struct{},
239 }),
240 },
241 },
242 },
243 },
244 wantConfig: `[{"myorg.MyCustomLeastRequestPolicy": {}}]`,
245 },
246 {
247 name: "custom_lb_type_v1_struct",
248 policy: &v3clusterpb.LoadBalancingPolicy{
249 Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{
250 {
251 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
252 TypedConfig: testutils.MarshalAny(t, &v1xdsudpatypepb.TypedStruct{
253 TypeUrl: "type.googleapis.com/myorg.MyCustomLeastRequestPolicy",
254 Value: &structpb.Struct{},
255 }),
256 },
257 },
258 },
259 },
260 wantConfig: `[{"myorg.MyCustomLeastRequestPolicy": {}}]`,
261 },
262 {
263 name: "wrr_locality_child_round_robin",
264 policy: &v3clusterpb.LoadBalancingPolicy{
265 Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{
266 {
267 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
268 TypedConfig: wrrLocalityAny(t, &v3roundrobinpb.RoundRobin{}),
269 },
270 },
271 },
272 },
273 wantConfig: `[{"xds_wrr_locality_experimental": { "childPolicy": [{"round_robin": {}}] }}]`,
274 },
275 {
276 name: "wrr_locality_child_custom_lb_type_v3_struct",
277 policy: &v3clusterpb.LoadBalancingPolicy{
278 Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{
279 {
280 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
281 TypedConfig: wrrLocalityAny(t, &v3xdsxdstypepb.TypedStruct{
282 TypeUrl: "type.googleapis.com/myorg.MyCustomLeastRequestPolicy",
283 Value: &structpb.Struct{},
284 }),
285 },
286 },
287 },
288 },
289 wantConfig: `[{"xds_wrr_locality_experimental": { "childPolicy": [{"myorg.MyCustomLeastRequestPolicy": {}}] }}]`,
290 },
291 {
292 name: "on-the-boundary-of-recursive-limit",
293 policy: &v3clusterpb.LoadBalancingPolicy{
294 Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{
295 {
296 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
297 TypedConfig: wrrLocalityAny(t, wrrLocality(t, wrrLocality(t, wrrLocality(t, wrrLocality(t, wrrLocality(t, wrrLocality(t, wrrLocality(t, wrrLocality(t, wrrLocality(t, wrrLocality(t, wrrLocality(t, wrrLocality(t, wrrLocality(t, wrrLocality(t, &v3roundrobinpb.RoundRobin{}))))))))))))))),
298 },
299 },
300 },
301 },
302 wantConfig: jsonMarshal(t, wrrLocalityBalancerConfig(wrrLocalityBalancerConfig(wrrLocalityBalancerConfig(wrrLocalityBalancerConfig(wrrLocalityBalancerConfig(wrrLocalityBalancerConfig(wrrLocalityBalancerConfig(wrrLocalityBalancerConfig(wrrLocalityBalancerConfig(wrrLocalityBalancerConfig(wrrLocalityBalancerConfig(wrrLocalityBalancerConfig(wrrLocalityBalancerConfig(wrrLocalityBalancerConfig(wrrLocalityBalancerConfig(&internalserviceconfig.BalancerConfig{
303 Name: "round_robin",
304 })))))))))))))))),
305 },
306 }
307
308 for _, test := range tests {
309 t.Run(test.name, func(t *testing.T) {
310 if test.lrEnabled {
311 defer func(old bool) { envconfig.LeastRequestLB = old }(envconfig.LeastRequestLB)
312 envconfig.LeastRequestLB = true
313 }
314 rawJSON, err := xdslbregistry.ConvertToServiceConfig(test.policy, 0)
315 if err != nil {
316 t.Fatalf("ConvertToServiceConfig(%s) failed: %v", pretty.ToJSON(test.policy), err)
317 }
318
319
320 var got []map[string]any
321 if err := json.Unmarshal(rawJSON, &got); err != nil {
322 t.Fatalf("Error unmarshalling rawJSON (%q): %v", rawJSON, err)
323 }
324 var want []map[string]any
325 if err := json.Unmarshal(json.RawMessage(test.wantConfig), &want); err != nil {
326 t.Fatalf("Error unmarshalling wantConfig (%q): %v", test.wantConfig, err)
327 }
328 if diff := cmp.Diff(got, want); diff != "" {
329 t.Fatalf("ConvertToServiceConfig() got unexpected output, diff (-got +want): %v", diff)
330 }
331 })
332 }
333 }
334
335 func jsonMarshal(t *testing.T, x any) string {
336 t.Helper()
337 js, err := json.Marshal(x)
338 if err != nil {
339 t.Fatalf("Error marshalling to JSON (%+v): %v", x, err)
340 }
341 return string(js)
342 }
343
344
345
346 func (s) TestConvertToServiceConfigFailure(t *testing.T) {
347 tests := []struct {
348 name string
349 policy *v3clusterpb.LoadBalancingPolicy
350 wantErr string
351 }{
352 {
353 name: "not xx_hash function",
354 policy: &v3clusterpb.LoadBalancingPolicy{
355 Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{
356 {
357 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
358 TypedConfig: testutils.MarshalAny(t, &v3ringhashpb.RingHash{
359 HashFunction: v3ringhashpb.RingHash_MURMUR_HASH_2,
360 MinimumRingSize: wrapperspb.UInt64(10),
361 MaximumRingSize: wrapperspb.UInt64(100),
362 }),
363 },
364 },
365 },
366 },
367 wantErr: "unsupported ring_hash hash function",
368 },
369 {
370 name: "no-supported-policy",
371 policy: &v3clusterpb.LoadBalancingPolicy{
372 Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{
373 {
374 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
375
376 TypedConfig: testutils.MarshalAny(t, &v3xdsxdstypepb.TypedStruct{
377 TypeUrl: "type.googleapis.com/myorg.ThisTypeDoesNotExist",
378 Value: &structpb.Struct{},
379 }),
380 },
381 },
382 {
383 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
384
385 TypedConfig: testutils.MarshalAny(t, &v3leastrequestpb.LeastRequest{}),
386 },
387 },
388 },
389 },
390 wantErr: "no supported policy found in policy list",
391 },
392 {
393 name: "exceeds-boundary-of-recursive-limit-by-1",
394 policy: &v3clusterpb.LoadBalancingPolicy{
395 Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{
396 {
397 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
398 TypedConfig: wrrLocalityAny(t, wrrLocality(t, wrrLocality(t, wrrLocality(t, wrrLocality(t, wrrLocality(t, wrrLocality(t, wrrLocality(t, wrrLocality(t, wrrLocality(t, wrrLocality(t, wrrLocality(t, wrrLocality(t, wrrLocality(t, wrrLocality(t, wrrLocality(t, &v3roundrobinpb.RoundRobin{})))))))))))))))),
399 },
400 },
401 },
402 },
403 wantErr: "exceeds max depth",
404 },
405 }
406
407 for _, test := range tests {
408 t.Run(test.name, func(t *testing.T) {
409 _, gotErr := xdslbregistry.ConvertToServiceConfig(test.policy, 0)
410
411
412
413
414
415 if gotErr == nil || !strings.Contains(gotErr.Error(), test.wantErr) {
416 t.Fatalf("ConvertToServiceConfig() = %v, wantErr %v", gotErr, test.wantErr)
417 }
418 })
419 }
420 }
421
422
423
424
425 func wrrLocality(t *testing.T, m proto.Message) *v3wrrlocalitypb.WrrLocality {
426 return &v3wrrlocalitypb.WrrLocality{
427 EndpointPickingPolicy: &v3clusterpb.LoadBalancingPolicy{
428 Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{
429 {
430 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
431 TypedConfig: testutils.MarshalAny(t, m),
432 },
433 },
434 },
435 },
436 }
437 }
438
439
440
441 func wrrLocalityAny(t *testing.T, m proto.Message) *anypb.Any {
442 return testutils.MarshalAny(t, wrrLocality(t, m))
443 }
444
View as plain text