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