1
18
19
20 package tests_test
21
22 import (
23 "encoding/json"
24 "testing"
25
26 "github.com/google/go-cmp/cmp"
27 "github.com/google/go-cmp/cmp/cmpopts"
28 "google.golang.org/grpc/balancer/leastrequest"
29 "google.golang.org/grpc/internal/balancer/stub"
30 "google.golang.org/grpc/internal/envconfig"
31 "google.golang.org/grpc/internal/grpctest"
32 iserviceconfig "google.golang.org/grpc/internal/serviceconfig"
33 "google.golang.org/grpc/internal/testutils"
34 "google.golang.org/grpc/internal/testutils/xds/e2e"
35 "google.golang.org/grpc/internal/xds/bootstrap"
36 "google.golang.org/grpc/serviceconfig"
37 "google.golang.org/grpc/xds/internal/balancer/ringhash"
38 "google.golang.org/grpc/xds/internal/balancer/wrrlocality"
39 "google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
40 "google.golang.org/protobuf/proto"
41 "google.golang.org/protobuf/types/known/anypb"
42 "google.golang.org/protobuf/types/known/structpb"
43 "google.golang.org/protobuf/types/known/wrapperspb"
44
45 v3xdsxdstypepb "github.com/cncf/xds/go/xds/type/v3"
46 v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
47 v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
48 v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
49 v3aggregateclusterpb "github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/aggregate/v3"
50 v3ringhashpb "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/ring_hash/v3"
51 v3roundrobinpb "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/round_robin/v3"
52 v3wrrlocalitypb "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/wrr_locality/v3"
53
54 _ "google.golang.org/grpc/balancer/roundrobin"
55 _ "google.golang.org/grpc/xds"
56 )
57
58 type s struct {
59 grpctest.Tester
60 }
61
62 func Test(t *testing.T) {
63 grpctest.RunSubTests(t, s{})
64 }
65
66 const (
67 clusterName = "clusterName"
68 serviceName = "service"
69 )
70
71 func wrrLocality(t *testing.T, m proto.Message) *v3wrrlocalitypb.WrrLocality {
72 return &v3wrrlocalitypb.WrrLocality{
73 EndpointPickingPolicy: &v3clusterpb.LoadBalancingPolicy{
74 Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{
75 {
76 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
77 TypedConfig: testutils.MarshalAny(t, m),
78 },
79 },
80 },
81 },
82 }
83 }
84
85 func wrrLocalityAny(t *testing.T, m proto.Message) *anypb.Any {
86 return testutils.MarshalAny(t, wrrLocality(t, m))
87 }
88
89 type customLBConfig struct {
90 serviceconfig.LoadBalancingConfig
91 }
92
93
94
95 func (s) TestValidateCluster_Success(t *testing.T) {
96 const customLBPolicyName = "myorg.MyCustomLeastRequestPolicy"
97 stub.Register(customLBPolicyName, stub.BalancerFuncs{
98 ParseConfig: func(json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
99 return customLBConfig{}, nil
100 },
101 })
102
103 defer func(old bool) { envconfig.LeastRequestLB = old }(envconfig.LeastRequestLB)
104 envconfig.LeastRequestLB = true
105 tests := []struct {
106 name string
107 cluster *v3clusterpb.Cluster
108 serverCfg *bootstrap.ServerConfig
109 wantUpdate xdsresource.ClusterUpdate
110 wantLBConfig *iserviceconfig.BalancerConfig
111 }{
112 {
113 name: "happy-case-logical-dns",
114 cluster: &v3clusterpb.Cluster{
115 Name: clusterName,
116 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_LOGICAL_DNS},
117 LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
118 LoadAssignment: &v3endpointpb.ClusterLoadAssignment{
119 Endpoints: []*v3endpointpb.LocalityLbEndpoints{{
120 LbEndpoints: []*v3endpointpb.LbEndpoint{{
121 HostIdentifier: &v3endpointpb.LbEndpoint_Endpoint{
122 Endpoint: &v3endpointpb.Endpoint{
123 Address: &v3corepb.Address{
124 Address: &v3corepb.Address_SocketAddress{
125 SocketAddress: &v3corepb.SocketAddress{
126 Address: "dns_host",
127 PortSpecifier: &v3corepb.SocketAddress_PortValue{
128 PortValue: 8080,
129 },
130 },
131 },
132 },
133 },
134 },
135 }},
136 }},
137 },
138 },
139 wantUpdate: xdsresource.ClusterUpdate{
140 ClusterName: clusterName,
141 ClusterType: xdsresource.ClusterTypeLogicalDNS,
142 DNSHostName: "dns_host:8080",
143 },
144 wantLBConfig: &iserviceconfig.BalancerConfig{
145 Name: wrrlocality.Name,
146 Config: &wrrlocality.LBConfig{
147 ChildPolicy: &iserviceconfig.BalancerConfig{
148 Name: "round_robin",
149 },
150 },
151 },
152 },
153 {
154 name: "happy-case-aggregate-v3",
155 cluster: &v3clusterpb.Cluster{
156 Name: clusterName,
157 ClusterDiscoveryType: &v3clusterpb.Cluster_ClusterType{
158 ClusterType: &v3clusterpb.Cluster_CustomClusterType{
159 Name: "envoy.clusters.aggregate",
160 TypedConfig: testutils.MarshalAny(t, &v3aggregateclusterpb.ClusterConfig{
161 Clusters: []string{"a", "b", "c"},
162 }),
163 },
164 },
165 LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
166 },
167 wantUpdate: xdsresource.ClusterUpdate{
168 ClusterName: clusterName,
169 ClusterType: xdsresource.ClusterTypeAggregate,
170 PrioritizedClusterNames: []string{"a", "b", "c"},
171 },
172 wantLBConfig: &iserviceconfig.BalancerConfig{
173 Name: wrrlocality.Name,
174 Config: &wrrlocality.LBConfig{
175 ChildPolicy: &iserviceconfig.BalancerConfig{
176 Name: "round_robin",
177 },
178 },
179 },
180 },
181 {
182 name: "happy-case-no-service-name-no-lrs",
183 cluster: e2e.DefaultCluster(clusterName, "", e2e.SecurityLevelNone),
184 wantUpdate: xdsresource.ClusterUpdate{ClusterName: clusterName},
185 wantLBConfig: &iserviceconfig.BalancerConfig{
186 Name: wrrlocality.Name,
187 Config: &wrrlocality.LBConfig{
188 ChildPolicy: &iserviceconfig.BalancerConfig{
189 Name: "round_robin",
190 },
191 },
192 },
193 },
194 {
195 name: "happy-case-no-lrs",
196 cluster: e2e.DefaultCluster(clusterName, serviceName, e2e.SecurityLevelNone),
197 wantUpdate: xdsresource.ClusterUpdate{
198 ClusterName: clusterName,
199 EDSServiceName: serviceName,
200 },
201 wantLBConfig: &iserviceconfig.BalancerConfig{
202 Name: wrrlocality.Name,
203 Config: &wrrlocality.LBConfig{
204 ChildPolicy: &iserviceconfig.BalancerConfig{
205 Name: "round_robin",
206 },
207 },
208 },
209 },
210 {
211 name: "happiest-case-with-lrs",
212 cluster: e2e.ClusterResourceWithOptions(e2e.ClusterOptions{
213 ClusterName: clusterName,
214 ServiceName: serviceName,
215 EnableLRS: true,
216 }),
217 serverCfg: &bootstrap.ServerConfig{ServerURI: "test-server-uri"},
218 wantUpdate: xdsresource.ClusterUpdate{
219 ClusterName: clusterName,
220 EDSServiceName: serviceName,
221 LRSServerConfig: &bootstrap.ServerConfig{ServerURI: "test-server-uri"},
222 },
223 wantLBConfig: &iserviceconfig.BalancerConfig{
224 Name: wrrlocality.Name,
225 Config: &wrrlocality.LBConfig{
226 ChildPolicy: &iserviceconfig.BalancerConfig{
227 Name: "round_robin",
228 },
229 },
230 },
231 },
232 {
233 name: "happiest-case-with-circuitbreakers",
234 cluster: func() *v3clusterpb.Cluster {
235 c := e2e.ClusterResourceWithOptions(e2e.ClusterOptions{
236 ClusterName: clusterName,
237 ServiceName: serviceName,
238 EnableLRS: true,
239 })
240 c.CircuitBreakers = &v3clusterpb.CircuitBreakers{
241 Thresholds: []*v3clusterpb.CircuitBreakers_Thresholds{
242 {
243 Priority: v3corepb.RoutingPriority_DEFAULT,
244 MaxRequests: wrapperspb.UInt32(512),
245 },
246 {
247 Priority: v3corepb.RoutingPriority_HIGH,
248 MaxRequests: nil,
249 },
250 },
251 }
252 return c
253 }(),
254 serverCfg: &bootstrap.ServerConfig{ServerURI: "test-server-uri"},
255 wantUpdate: xdsresource.ClusterUpdate{
256 ClusterName: clusterName,
257 EDSServiceName: serviceName,
258 LRSServerConfig: &bootstrap.ServerConfig{ServerURI: "test-server-uri"},
259 MaxRequests: func() *uint32 { i := uint32(512); return &i }(),
260 },
261 wantLBConfig: &iserviceconfig.BalancerConfig{
262 Name: wrrlocality.Name,
263 Config: &wrrlocality.LBConfig{
264 ChildPolicy: &iserviceconfig.BalancerConfig{
265 Name: "round_robin",
266 },
267 },
268 },
269 },
270 {
271 name: "happiest-case-with-ring-hash-lb-policy-with-default-config",
272 cluster: func() *v3clusterpb.Cluster {
273 c := e2e.DefaultCluster(clusterName, serviceName, e2e.SecurityLevelNone)
274 c.LbPolicy = v3clusterpb.Cluster_RING_HASH
275 return c
276 }(),
277 wantUpdate: xdsresource.ClusterUpdate{
278 ClusterName: clusterName,
279 EDSServiceName: serviceName,
280 },
281 wantLBConfig: &iserviceconfig.BalancerConfig{
282 Name: "ring_hash_experimental",
283 Config: &ringhash.LBConfig{
284 MinRingSize: 1024,
285 MaxRingSize: 4096,
286 },
287 },
288 },
289 {
290 name: "happiest-case-with-least-request-lb-policy-with-default-config",
291 cluster: &v3clusterpb.Cluster{
292 Name: clusterName,
293 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
294 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
295 EdsConfig: &v3corepb.ConfigSource{
296 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
297 Ads: &v3corepb.AggregatedConfigSource{},
298 },
299 },
300 ServiceName: serviceName,
301 },
302 LbPolicy: v3clusterpb.Cluster_LEAST_REQUEST,
303 },
304 wantUpdate: xdsresource.ClusterUpdate{
305 ClusterName: clusterName,
306 EDSServiceName: serviceName,
307 },
308 wantLBConfig: &iserviceconfig.BalancerConfig{
309 Name: "least_request_experimental",
310 Config: &leastrequest.LBConfig{
311 ChoiceCount: 2,
312 },
313 },
314 },
315 {
316 name: "happiest-case-with-ring-hash-lb-policy-with-none-default-config",
317 cluster: func() *v3clusterpb.Cluster {
318 c := e2e.DefaultCluster(clusterName, serviceName, e2e.SecurityLevelNone)
319 c.LbPolicy = v3clusterpb.Cluster_RING_HASH
320 c.LbConfig = &v3clusterpb.Cluster_RingHashLbConfig_{
321 RingHashLbConfig: &v3clusterpb.Cluster_RingHashLbConfig{
322 MinimumRingSize: wrapperspb.UInt64(10),
323 MaximumRingSize: wrapperspb.UInt64(100),
324 },
325 }
326 return c
327 }(),
328 wantUpdate: xdsresource.ClusterUpdate{
329 ClusterName: clusterName,
330 EDSServiceName: serviceName,
331 },
332 wantLBConfig: &iserviceconfig.BalancerConfig{
333 Name: "ring_hash_experimental",
334 Config: &ringhash.LBConfig{
335 MinRingSize: 10,
336 MaxRingSize: 100,
337 },
338 },
339 },
340 {
341 name: "happiest-case-with-least-request-lb-policy-with-none-default-config",
342 cluster: &v3clusterpb.Cluster{
343 Name: clusterName,
344 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
345 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
346 EdsConfig: &v3corepb.ConfigSource{
347 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
348 Ads: &v3corepb.AggregatedConfigSource{},
349 },
350 },
351 ServiceName: serviceName,
352 },
353 LbPolicy: v3clusterpb.Cluster_LEAST_REQUEST,
354 LbConfig: &v3clusterpb.Cluster_LeastRequestLbConfig_{
355 LeastRequestLbConfig: &v3clusterpb.Cluster_LeastRequestLbConfig{
356 ChoiceCount: wrapperspb.UInt32(3),
357 },
358 },
359 },
360 wantUpdate: xdsresource.ClusterUpdate{
361 ClusterName: clusterName,
362 EDSServiceName: serviceName,
363 },
364 wantLBConfig: &iserviceconfig.BalancerConfig{
365 Name: "least_request_experimental",
366 Config: &leastrequest.LBConfig{
367 ChoiceCount: 3,
368 },
369 },
370 },
371 {
372 name: "happiest-case-with-ring-hash-lb-policy-configured-through-LoadBalancingPolicy",
373 cluster: &v3clusterpb.Cluster{
374 Name: clusterName,
375 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
376 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
377 EdsConfig: &v3corepb.ConfigSource{
378 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
379 Ads: &v3corepb.AggregatedConfigSource{},
380 },
381 },
382 ServiceName: serviceName,
383 },
384 LoadBalancingPolicy: &v3clusterpb.LoadBalancingPolicy{
385 Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{
386 {
387 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
388 TypedConfig: testutils.MarshalAny(t, &v3ringhashpb.RingHash{
389 HashFunction: v3ringhashpb.RingHash_XX_HASH,
390 MinimumRingSize: wrapperspb.UInt64(10),
391 MaximumRingSize: wrapperspb.UInt64(100),
392 }),
393 },
394 },
395 },
396 },
397 },
398 wantUpdate: xdsresource.ClusterUpdate{
399 ClusterName: clusterName,
400 EDSServiceName: serviceName,
401 },
402 wantLBConfig: &iserviceconfig.BalancerConfig{
403 Name: "ring_hash_experimental",
404 Config: &ringhash.LBConfig{
405 MinRingSize: 10,
406 MaxRingSize: 100,
407 },
408 },
409 },
410 {
411 name: "happiest-case-with-wrrlocality-rr-child-configured-through-LoadBalancingPolicy",
412 cluster: &v3clusterpb.Cluster{
413 Name: clusterName,
414 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
415 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
416 EdsConfig: &v3corepb.ConfigSource{
417 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
418 Ads: &v3corepb.AggregatedConfigSource{},
419 },
420 },
421 ServiceName: serviceName,
422 },
423 LoadBalancingPolicy: &v3clusterpb.LoadBalancingPolicy{
424 Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{
425 {
426 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
427 TypedConfig: wrrLocalityAny(t, &v3roundrobinpb.RoundRobin{}),
428 },
429 },
430 },
431 },
432 },
433 wantUpdate: xdsresource.ClusterUpdate{
434 ClusterName: clusterName,
435 EDSServiceName: serviceName,
436 },
437 wantLBConfig: &iserviceconfig.BalancerConfig{
438 Name: wrrlocality.Name,
439 Config: &wrrlocality.LBConfig{
440 ChildPolicy: &iserviceconfig.BalancerConfig{
441 Name: "round_robin",
442 },
443 },
444 },
445 },
446 {
447 name: "happiest-case-with-custom-lb-configured-through-LoadBalancingPolicy",
448 cluster: &v3clusterpb.Cluster{
449 Name: clusterName,
450 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
451 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
452 EdsConfig: &v3corepb.ConfigSource{
453 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
454 Ads: &v3corepb.AggregatedConfigSource{},
455 },
456 },
457 ServiceName: serviceName,
458 },
459 LoadBalancingPolicy: &v3clusterpb.LoadBalancingPolicy{
460 Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{
461 {
462 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
463 TypedConfig: wrrLocalityAny(t, &v3xdsxdstypepb.TypedStruct{
464 TypeUrl: "type.googleapis.com/myorg.MyCustomLeastRequestPolicy",
465 Value: &structpb.Struct{},
466 }),
467 },
468 },
469 },
470 },
471 },
472 wantUpdate: xdsresource.ClusterUpdate{
473 ClusterName: clusterName,
474 EDSServiceName: serviceName,
475 },
476 wantLBConfig: &iserviceconfig.BalancerConfig{
477 Name: wrrlocality.Name,
478 Config: &wrrlocality.LBConfig{
479 ChildPolicy: &iserviceconfig.BalancerConfig{
480 Name: "myorg.MyCustomLeastRequestPolicy",
481 Config: customLBConfig{},
482 },
483 },
484 },
485 },
486 {
487 name: "load-balancing-policy-takes-precedence-over-lb-policy-and-enum",
488 cluster: &v3clusterpb.Cluster{
489 Name: clusterName,
490 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
491 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
492 EdsConfig: &v3corepb.ConfigSource{
493 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
494 Ads: &v3corepb.AggregatedConfigSource{},
495 },
496 },
497 ServiceName: serviceName,
498 },
499 LbPolicy: v3clusterpb.Cluster_RING_HASH,
500 LbConfig: &v3clusterpb.Cluster_RingHashLbConfig_{
501 RingHashLbConfig: &v3clusterpb.Cluster_RingHashLbConfig{
502 MinimumRingSize: wrapperspb.UInt64(20),
503 MaximumRingSize: wrapperspb.UInt64(200),
504 },
505 },
506 LoadBalancingPolicy: &v3clusterpb.LoadBalancingPolicy{
507 Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{
508 {
509 TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
510 TypedConfig: testutils.MarshalAny(t, &v3ringhashpb.RingHash{
511 HashFunction: v3ringhashpb.RingHash_XX_HASH,
512 MinimumRingSize: wrapperspb.UInt64(10),
513 MaximumRingSize: wrapperspb.UInt64(100),
514 }),
515 },
516 },
517 },
518 },
519 },
520 wantUpdate: xdsresource.ClusterUpdate{
521 ClusterName: clusterName,
522 EDSServiceName: serviceName,
523 },
524 wantLBConfig: &iserviceconfig.BalancerConfig{
525 Name: "ring_hash_experimental",
526 Config: &ringhash.LBConfig{
527 MinRingSize: 10,
528 MaxRingSize: 100,
529 },
530 },
531 },
532 }
533
534 for _, test := range tests {
535 t.Run(test.name, func(t *testing.T) {
536 update, err := xdsresource.ValidateClusterAndConstructClusterUpdateForTesting(test.cluster, test.serverCfg)
537 if err != nil {
538 t.Errorf("validateClusterAndConstructClusterUpdate(%+v) failed: %v", test.cluster, err)
539 }
540
541
542
543
544
545 if diff := cmp.Diff(update, test.wantUpdate, cmpopts.EquateEmpty(), cmpopts.IgnoreFields(xdsresource.ClusterUpdate{}, "LBPolicy")); diff != "" {
546 t.Errorf("validateClusterAndConstructClusterUpdate(%+v) got diff: %v (-got, +want)", test.cluster, diff)
547 }
548 bc := &iserviceconfig.BalancerConfig{}
549 if err := json.Unmarshal(update.LBPolicy, bc); err != nil {
550 t.Fatalf("failed to unmarshal JSON: %v", err)
551 }
552 if diff := cmp.Diff(bc, test.wantLBConfig); diff != "" {
553 t.Fatalf("update.LBConfig got unexpected output, diff (-got +want): %v", diff)
554 }
555 })
556 }
557 }
558
View as plain text