google.golang.org/grpc@v1.72.2/xds/internal/balancer/clusterresolver/configbuilder_test.go (about) 1 /* 2 * 3 * Copyright 2021 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 clusterresolver 20 21 import ( 22 "bytes" 23 "encoding/json" 24 "fmt" 25 "sort" 26 "testing" 27 "time" 28 29 "github.com/google/go-cmp/cmp" 30 "google.golang.org/grpc/attributes" 31 "google.golang.org/grpc/balancer" 32 "google.golang.org/grpc/balancer/roundrobin" 33 "google.golang.org/grpc/internal/balancer/weight" 34 "google.golang.org/grpc/internal/hierarchy" 35 iserviceconfig "google.golang.org/grpc/internal/serviceconfig" 36 "google.golang.org/grpc/internal/xds/bootstrap" 37 "google.golang.org/grpc/resolver" 38 "google.golang.org/grpc/xds/internal" 39 "google.golang.org/grpc/xds/internal/balancer/clusterimpl" 40 "google.golang.org/grpc/xds/internal/balancer/outlierdetection" 41 "google.golang.org/grpc/xds/internal/balancer/priority" 42 "google.golang.org/grpc/xds/internal/balancer/ringhash" 43 "google.golang.org/grpc/xds/internal/balancer/wrrlocality" 44 "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" 45 ) 46 47 const ( 48 testLRSServer = "test-lrs-server" 49 testMaxRequests = 314 50 testEDSServiceName = "service-name-from-parent" 51 testDropCategory = "test-drops" 52 testDropOverMillion = 1 53 54 localityCount = 5 55 endpointPerLocality = 2 56 ) 57 58 var ( 59 testLocalityIDs []internal.LocalityID 60 testResolverEndpoints [][]resolver.Endpoint 61 testEndpoints [][]xdsresource.Endpoint 62 63 testLocalitiesP0, testLocalitiesP1 []xdsresource.Locality 64 65 endpointCmpOpts = cmp.Options{ 66 cmp.AllowUnexported(attributes.Attributes{}), 67 cmp.Transformer("SortEndpoints", func(in []resolver.Endpoint) []resolver.Endpoint { 68 out := append([]resolver.Endpoint(nil), in...) // Copy input to avoid mutating it 69 sort.Slice(out, func(i, j int) bool { 70 return out[i].Addresses[0].Addr < out[j].Addresses[0].Addr 71 }) 72 return out 73 }), 74 } 75 76 noopODCfg = outlierdetection.LBConfig{ 77 Interval: iserviceconfig.Duration(10 * time.Second), // default interval 78 BaseEjectionTime: iserviceconfig.Duration(30 * time.Second), 79 MaxEjectionTime: iserviceconfig.Duration(300 * time.Second), 80 MaxEjectionPercent: 10, 81 } 82 ) 83 84 func init() { 85 for i := 0; i < localityCount; i++ { 86 testLocalityIDs = append(testLocalityIDs, internal.LocalityID{Zone: fmt.Sprintf("test-zone-%d", i)}) 87 var ( 88 endpoints []resolver.Endpoint 89 ends []xdsresource.Endpoint 90 ) 91 for j := 0; j < endpointPerLocality; j++ { 92 addr := fmt.Sprintf("addr-%d-%d", i, j) 93 endpoints = append(endpoints, resolver.Endpoint{Addresses: []resolver.Address{{Addr: addr}}}) 94 ends = append(ends, xdsresource.Endpoint{ 95 HealthStatus: xdsresource.EndpointHealthStatusHealthy, 96 Addresses: []string{ 97 addr, 98 fmt.Sprintf("addr-%d-%d-additional-1", i, j), 99 fmt.Sprintf("addr-%d-%d-additional-2", i, j), 100 }, 101 }) 102 } 103 testResolverEndpoints = append(testResolverEndpoints, endpoints) 104 testEndpoints = append(testEndpoints, ends) 105 } 106 107 testLocalitiesP0 = []xdsresource.Locality{ 108 { 109 Endpoints: testEndpoints[0], 110 ID: testLocalityIDs[0], 111 Weight: 20, 112 Priority: 0, 113 }, 114 { 115 Endpoints: testEndpoints[1], 116 ID: testLocalityIDs[1], 117 Weight: 80, 118 Priority: 0, 119 }, 120 } 121 testLocalitiesP1 = []xdsresource.Locality{ 122 { 123 Endpoints: testEndpoints[2], 124 ID: testLocalityIDs[2], 125 Weight: 20, 126 Priority: 1, 127 }, 128 { 129 Endpoints: testEndpoints[3], 130 ID: testLocalityIDs[3], 131 Weight: 80, 132 Priority: 1, 133 }, 134 } 135 } 136 137 // TestBuildPriorityConfigJSON is a sanity check that the built balancer config 138 // can be parsed. The behavior test is covered by TestBuildPriorityConfig. 139 func TestBuildPriorityConfigJSON(t *testing.T) { 140 testLRSServerConfig, err := bootstrap.ServerConfigForTesting(bootstrap.ServerConfigTestingOptions{ 141 URI: "trafficdirector.googleapis.com:443", 142 ChannelCreds: []bootstrap.ChannelCreds{{Type: "google_default"}}, 143 }) 144 if err != nil { 145 t.Fatalf("Failed to create LRS server config for testing: %v", err) 146 } 147 148 gotConfig, _, err := buildPriorityConfigJSON([]priorityConfig{ 149 { 150 mechanism: DiscoveryMechanism{ 151 Cluster: testClusterName, 152 LoadReportingServer: testLRSServerConfig, 153 MaxConcurrentRequests: newUint32(testMaxRequests), 154 Type: DiscoveryMechanismTypeEDS, 155 EDSServiceName: testEDSServiceName, 156 }, 157 edsResp: xdsresource.EndpointsUpdate{ 158 Drops: []xdsresource.OverloadDropConfig{ 159 { 160 Category: testDropCategory, 161 Numerator: testDropOverMillion, 162 Denominator: million, 163 }, 164 }, 165 Localities: []xdsresource.Locality{ 166 testLocalitiesP0[0], 167 testLocalitiesP0[1], 168 testLocalitiesP1[0], 169 testLocalitiesP1[1], 170 }, 171 }, 172 childNameGen: newNameGenerator(0), 173 }, 174 { 175 mechanism: DiscoveryMechanism{ 176 Type: DiscoveryMechanismTypeLogicalDNS, 177 }, 178 endpoints: testResolverEndpoints[4], 179 childNameGen: newNameGenerator(1), 180 }, 181 }, nil) 182 if err != nil { 183 t.Fatalf("buildPriorityConfigJSON(...) failed: %v", err) 184 } 185 186 var prettyGot bytes.Buffer 187 if err := json.Indent(&prettyGot, gotConfig, ">>> ", " "); err != nil { 188 t.Fatalf("json.Indent() failed: %v", err) 189 } 190 // Print the indented json if this test fails. 191 t.Log(prettyGot.String()) 192 193 priorityB := balancer.Get(priority.Name) 194 if _, err = priorityB.(balancer.ConfigParser).ParseConfig(gotConfig); err != nil { 195 t.Fatalf("ParseConfig(%+v) failed: %v", gotConfig, err) 196 } 197 } 198 199 // TestBuildPriorityConfig tests the priority config generation. Each top level 200 // balancer per priority should be an Outlier Detection balancer, with a Cluster 201 // Impl Balancer as a child. 202 func TestBuildPriorityConfig(t *testing.T) { 203 gotConfig, _, _ := buildPriorityConfig([]priorityConfig{ 204 { 205 // EDS - OD config should be the top level for both of the EDS 206 // priorities balancer This EDS priority will have multiple sub 207 // priorities. The Outlier Detection configuration specified in the 208 // Discovery Mechanism should be the top level for each sub 209 // priorities balancer. 210 mechanism: DiscoveryMechanism{ 211 Cluster: testClusterName, 212 Type: DiscoveryMechanismTypeEDS, 213 EDSServiceName: testEDSServiceName, 214 outlierDetection: noopODCfg, 215 }, 216 edsResp: xdsresource.EndpointsUpdate{ 217 Localities: []xdsresource.Locality{ 218 testLocalitiesP0[0], 219 testLocalitiesP0[1], 220 testLocalitiesP1[0], 221 testLocalitiesP1[1], 222 }, 223 }, 224 childNameGen: newNameGenerator(0), 225 }, 226 { 227 // This OD config should wrap the Logical DNS priorities balancer. 228 mechanism: DiscoveryMechanism{ 229 Cluster: testClusterName2, 230 Type: DiscoveryMechanismTypeLogicalDNS, 231 outlierDetection: noopODCfg, 232 }, 233 endpoints: testResolverEndpoints[4], 234 childNameGen: newNameGenerator(1), 235 }, 236 }, nil) 237 238 wantConfig := &priority.LBConfig{ 239 Children: map[string]*priority.Child{ 240 "priority-0-0": { 241 Config: &iserviceconfig.BalancerConfig{ 242 Name: outlierdetection.Name, 243 Config: &outlierdetection.LBConfig{ 244 Interval: iserviceconfig.Duration(10 * time.Second), // default interval 245 BaseEjectionTime: iserviceconfig.Duration(30 * time.Second), 246 MaxEjectionTime: iserviceconfig.Duration(300 * time.Second), 247 MaxEjectionPercent: 10, 248 ChildPolicy: &iserviceconfig.BalancerConfig{ 249 Name: clusterimpl.Name, 250 Config: &clusterimpl.LBConfig{ 251 Cluster: testClusterName, 252 EDSServiceName: testEDSServiceName, 253 DropCategories: []clusterimpl.DropConfig{}, 254 }, 255 }, 256 }, 257 }, 258 IgnoreReresolutionRequests: true, 259 }, 260 "priority-0-1": { 261 Config: &iserviceconfig.BalancerConfig{ 262 Name: outlierdetection.Name, 263 Config: &outlierdetection.LBConfig{ 264 Interval: iserviceconfig.Duration(10 * time.Second), // default interval 265 BaseEjectionTime: iserviceconfig.Duration(30 * time.Second), 266 MaxEjectionTime: iserviceconfig.Duration(300 * time.Second), 267 MaxEjectionPercent: 10, 268 ChildPolicy: &iserviceconfig.BalancerConfig{ 269 Name: clusterimpl.Name, 270 Config: &clusterimpl.LBConfig{ 271 Cluster: testClusterName, 272 EDSServiceName: testEDSServiceName, 273 DropCategories: []clusterimpl.DropConfig{}, 274 }, 275 }, 276 }, 277 }, 278 IgnoreReresolutionRequests: true, 279 }, 280 "priority-1": { 281 Config: &iserviceconfig.BalancerConfig{ 282 Name: outlierdetection.Name, 283 Config: &outlierdetection.LBConfig{ 284 Interval: iserviceconfig.Duration(10 * time.Second), // default interval 285 BaseEjectionTime: iserviceconfig.Duration(30 * time.Second), 286 MaxEjectionTime: iserviceconfig.Duration(300 * time.Second), 287 MaxEjectionPercent: 10, 288 ChildPolicy: &iserviceconfig.BalancerConfig{ 289 Name: clusterimpl.Name, 290 Config: &clusterimpl.LBConfig{ 291 Cluster: testClusterName2, 292 ChildPolicy: &iserviceconfig.BalancerConfig{Name: "pick_first"}, 293 }, 294 }, 295 }, 296 }, 297 IgnoreReresolutionRequests: false, 298 }, 299 }, 300 Priorities: []string{"priority-0-0", "priority-0-1", "priority-1"}, 301 } 302 if diff := cmp.Diff(gotConfig, wantConfig); diff != "" { 303 t.Errorf("buildPriorityConfig() diff (-got +want) %v", diff) 304 } 305 } 306 307 func TestBuildClusterImplConfigForDNS(t *testing.T) { 308 gotName, gotConfig, gotEndpoints := buildClusterImplConfigForDNS(newNameGenerator(3), testResolverEndpoints[0], DiscoveryMechanism{Cluster: testClusterName2, Type: DiscoveryMechanismTypeLogicalDNS}) 309 wantName := "priority-3" 310 wantConfig := &clusterimpl.LBConfig{ 311 Cluster: testClusterName2, 312 ChildPolicy: &iserviceconfig.BalancerConfig{ 313 Name: "pick_first", 314 }, 315 } 316 e1 := resolver.Endpoint{Addresses: []resolver.Address{{Addr: testEndpoints[0][0].Addresses[0]}}} 317 e2 := resolver.Endpoint{Addresses: []resolver.Address{{Addr: testEndpoints[0][1].Addresses[0]}}} 318 wantEndpoints := []resolver.Endpoint{ 319 hierarchy.SetInEndpoint(e1, []string{"priority-3"}), 320 hierarchy.SetInEndpoint(e2, []string{"priority-3"}), 321 } 322 323 if diff := cmp.Diff(gotName, wantName); diff != "" { 324 t.Errorf("buildClusterImplConfigForDNS() diff (-got +want) %v", diff) 325 } 326 if diff := cmp.Diff(gotConfig, wantConfig); diff != "" { 327 t.Errorf("buildClusterImplConfigForDNS() diff (-got +want) %v", diff) 328 } 329 if diff := cmp.Diff(gotEndpoints, wantEndpoints, endpointCmpOpts); diff != "" { 330 t.Errorf("buildClusterImplConfigForDNS() diff (-got +want) %v", diff) 331 } 332 } 333 334 func TestBuildClusterImplConfigForEDS(t *testing.T) { 335 testLRSServerConfig, err := bootstrap.ServerConfigForTesting(bootstrap.ServerConfigTestingOptions{ 336 URI: "trafficdirector.googleapis.com:443", 337 ChannelCreds: []bootstrap.ChannelCreds{{Type: "google_default"}}, 338 }) 339 if err != nil { 340 t.Fatalf("Failed to create LRS server config for testing: %v", err) 341 } 342 343 gotNames, gotConfigs, gotEndpoints, _ := buildClusterImplConfigForEDS( 344 newNameGenerator(2), 345 xdsresource.EndpointsUpdate{ 346 Drops: []xdsresource.OverloadDropConfig{ 347 { 348 Category: testDropCategory, 349 Numerator: testDropOverMillion, 350 Denominator: million, 351 }, 352 }, 353 Localities: []xdsresource.Locality{ 354 { 355 Endpoints: testEndpoints[3], 356 ID: testLocalityIDs[3], 357 Weight: 80, 358 Priority: 1, 359 }, { 360 Endpoints: testEndpoints[1], 361 ID: testLocalityIDs[1], 362 Weight: 80, 363 Priority: 0, 364 }, { 365 Endpoints: testEndpoints[2], 366 ID: testLocalityIDs[2], 367 Weight: 20, 368 Priority: 1, 369 }, { 370 Endpoints: testEndpoints[0], 371 ID: testLocalityIDs[0], 372 Weight: 20, 373 Priority: 0, 374 }, 375 }, 376 }, 377 DiscoveryMechanism{ 378 Cluster: testClusterName, 379 MaxConcurrentRequests: newUint32(testMaxRequests), 380 LoadReportingServer: testLRSServerConfig, 381 Type: DiscoveryMechanismTypeEDS, 382 EDSServiceName: testEDSServiceName, 383 }, 384 nil, 385 ) 386 387 wantNames := []string{ 388 fmt.Sprintf("priority-%v-%v", 2, 0), 389 fmt.Sprintf("priority-%v-%v", 2, 1), 390 } 391 wantConfigs := map[string]*clusterimpl.LBConfig{ 392 "priority-2-0": { 393 Cluster: testClusterName, 394 EDSServiceName: testEDSServiceName, 395 LoadReportingServer: testLRSServerConfig, 396 MaxConcurrentRequests: newUint32(testMaxRequests), 397 DropCategories: []clusterimpl.DropConfig{ 398 { 399 Category: testDropCategory, 400 RequestsPerMillion: testDropOverMillion, 401 }, 402 }, 403 }, 404 "priority-2-1": { 405 Cluster: testClusterName, 406 EDSServiceName: testEDSServiceName, 407 LoadReportingServer: testLRSServerConfig, 408 MaxConcurrentRequests: newUint32(testMaxRequests), 409 DropCategories: []clusterimpl.DropConfig{ 410 { 411 Category: testDropCategory, 412 RequestsPerMillion: testDropOverMillion, 413 }, 414 }, 415 }, 416 } 417 wantEndpoints := []resolver.Endpoint{ 418 testEndpointWithAttrs(testEndpoints[0][0].Addresses, 20, 1, "priority-2-0", &testLocalityIDs[0]), 419 testEndpointWithAttrs(testEndpoints[0][1].Addresses, 20, 1, "priority-2-0", &testLocalityIDs[0]), 420 testEndpointWithAttrs(testEndpoints[1][0].Addresses, 80, 1, "priority-2-0", &testLocalityIDs[1]), 421 testEndpointWithAttrs(testEndpoints[1][1].Addresses, 80, 1, "priority-2-0", &testLocalityIDs[1]), 422 testEndpointWithAttrs(testEndpoints[2][0].Addresses, 20, 1, "priority-2-1", &testLocalityIDs[2]), 423 testEndpointWithAttrs(testEndpoints[2][1].Addresses, 20, 1, "priority-2-1", &testLocalityIDs[2]), 424 testEndpointWithAttrs(testEndpoints[3][0].Addresses, 80, 1, "priority-2-1", &testLocalityIDs[3]), 425 testEndpointWithAttrs(testEndpoints[3][1].Addresses, 80, 1, "priority-2-1", &testLocalityIDs[3]), 426 } 427 428 if diff := cmp.Diff(gotNames, wantNames); diff != "" { 429 t.Errorf("buildClusterImplConfigForEDS() diff (-got +want) %v", diff) 430 } 431 if diff := cmp.Diff(gotConfigs, wantConfigs); diff != "" { 432 t.Errorf("buildClusterImplConfigForEDS() diff (-got +want) %v", diff) 433 } 434 if diff := cmp.Diff(gotEndpoints, wantEndpoints, endpointCmpOpts); diff != "" { 435 t.Errorf("buildClusterImplConfigForEDS() diff (-got +want) %v", diff) 436 } 437 438 } 439 440 func TestGroupLocalitiesByPriority(t *testing.T) { 441 tests := []struct { 442 name string 443 localities []xdsresource.Locality 444 wantLocalities [][]xdsresource.Locality 445 }{ 446 { 447 name: "1 locality 1 priority", 448 localities: []xdsresource.Locality{testLocalitiesP0[0]}, 449 wantLocalities: [][]xdsresource.Locality{ 450 {testLocalitiesP0[0]}, 451 }, 452 }, 453 { 454 name: "2 locality 1 priority", 455 localities: []xdsresource.Locality{testLocalitiesP0[0], testLocalitiesP0[1]}, 456 wantLocalities: [][]xdsresource.Locality{ 457 {testLocalitiesP0[0], testLocalitiesP0[1]}, 458 }, 459 }, 460 { 461 name: "1 locality in each", 462 localities: []xdsresource.Locality{testLocalitiesP0[0], testLocalitiesP1[0]}, 463 wantLocalities: [][]xdsresource.Locality{ 464 {testLocalitiesP0[0]}, 465 {testLocalitiesP1[0]}, 466 }, 467 }, 468 { 469 name: "2 localities in each sorted", 470 localities: []xdsresource.Locality{ 471 testLocalitiesP0[0], testLocalitiesP0[1], 472 testLocalitiesP1[0], testLocalitiesP1[1]}, 473 wantLocalities: [][]xdsresource.Locality{ 474 {testLocalitiesP0[0], testLocalitiesP0[1]}, 475 {testLocalitiesP1[0], testLocalitiesP1[1]}, 476 }, 477 }, 478 { 479 // The localities are given in order [p1, p0, p1, p0], but the 480 // returned priority list must be sorted [p0, p1], because the list 481 // order is the priority order. 482 name: "2 localities in each needs to sort", 483 localities: []xdsresource.Locality{ 484 testLocalitiesP1[1], testLocalitiesP0[1], 485 testLocalitiesP1[0], testLocalitiesP0[0]}, 486 wantLocalities: [][]xdsresource.Locality{ 487 {testLocalitiesP0[1], testLocalitiesP0[0]}, 488 {testLocalitiesP1[1], testLocalitiesP1[0]}, 489 }, 490 }, 491 } 492 for _, tt := range tests { 493 t.Run(tt.name, func(t *testing.T) { 494 gotLocalities := groupLocalitiesByPriority(tt.localities) 495 if diff := cmp.Diff(gotLocalities, tt.wantLocalities); diff != "" { 496 t.Errorf("groupLocalitiesByPriority() diff(-got +want) %v", diff) 497 } 498 }) 499 } 500 } 501 502 func TestDedupSortedIntSlice(t *testing.T) { 503 tests := []struct { 504 name string 505 a []int 506 want []int 507 }{ 508 { 509 name: "empty", 510 a: []int{}, 511 want: []int{}, 512 }, 513 { 514 name: "no dup", 515 a: []int{0, 1, 2, 3}, 516 want: []int{0, 1, 2, 3}, 517 }, 518 { 519 name: "with dup", 520 a: []int{0, 0, 1, 1, 1, 2, 3}, 521 want: []int{0, 1, 2, 3}, 522 }, 523 } 524 for _, tt := range tests { 525 t.Run(tt.name, func(t *testing.T) { 526 if got := dedupSortedIntSlice(tt.a); !cmp.Equal(got, tt.want) { 527 t.Errorf("dedupSortedIntSlice() = %v, want %v, diff %v", got, tt.want, cmp.Diff(got, tt.want)) 528 } 529 }) 530 } 531 } 532 533 func TestPriorityLocalitiesToClusterImpl(t *testing.T) { 534 tests := []struct { 535 name string 536 localities []xdsresource.Locality 537 priorityName string 538 mechanism DiscoveryMechanism 539 childPolicy *iserviceconfig.BalancerConfig 540 wantConfig *clusterimpl.LBConfig 541 wantEndpoints []resolver.Endpoint 542 wantErr bool 543 }{{ 544 name: "round robin as child, no LRS", 545 localities: []xdsresource.Locality{ 546 { 547 Endpoints: []xdsresource.Endpoint{ 548 {Addresses: []string{"addr-1-1"}, HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 90}, 549 {Addresses: []string{"addr-1-2"}, HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 10}, 550 }, 551 ID: internal.LocalityID{Zone: "test-zone-1"}, 552 Weight: 20, 553 }, 554 { 555 Endpoints: []xdsresource.Endpoint{ 556 {Addresses: []string{"addr-2-1"}, HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 90}, 557 {Addresses: []string{"addr-2-2"}, HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 10}, 558 }, 559 ID: internal.LocalityID{Zone: "test-zone-2"}, 560 Weight: 80, 561 }, 562 }, 563 priorityName: "test-priority", 564 childPolicy: &iserviceconfig.BalancerConfig{Name: roundrobin.Name}, 565 mechanism: DiscoveryMechanism{ 566 Cluster: testClusterName, 567 Type: DiscoveryMechanismTypeEDS, 568 EDSServiceName: testEDSService, 569 }, 570 // lrsServer is nil, so LRS policy will not be used. 571 wantConfig: &clusterimpl.LBConfig{ 572 Cluster: testClusterName, 573 EDSServiceName: testEDSService, 574 ChildPolicy: &iserviceconfig.BalancerConfig{Name: roundrobin.Name}, 575 }, 576 wantEndpoints: []resolver.Endpoint{ 577 testEndpointWithAttrs([]string{"addr-1-1"}, 20, 90, "test-priority", &internal.LocalityID{Zone: "test-zone-1"}), 578 testEndpointWithAttrs([]string{"addr-1-2"}, 20, 10, "test-priority", &internal.LocalityID{Zone: "test-zone-1"}), 579 testEndpointWithAttrs([]string{"addr-2-1"}, 80, 90, "test-priority", &internal.LocalityID{Zone: "test-zone-2"}), 580 testEndpointWithAttrs([]string{"addr-2-2"}, 80, 10, "test-priority", &internal.LocalityID{Zone: "test-zone-2"}), 581 }, 582 }, 583 { 584 name: "ring_hash as child", 585 localities: []xdsresource.Locality{ 586 { 587 Endpoints: []xdsresource.Endpoint{ 588 {Addresses: []string{"addr-1-1"}, HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 90}, 589 {Addresses: []string{"addr-1-2"}, HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 10}, 590 }, 591 ID: internal.LocalityID{Zone: "test-zone-1"}, 592 Weight: 20, 593 }, 594 { 595 Endpoints: []xdsresource.Endpoint{ 596 {Addresses: []string{"addr-2-1"}, HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 90}, 597 {Addresses: []string{"addr-2-2"}, HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 10}, 598 }, 599 ID: internal.LocalityID{Zone: "test-zone-2"}, 600 Weight: 80, 601 }, 602 }, 603 priorityName: "test-priority", 604 childPolicy: &iserviceconfig.BalancerConfig{Name: ringhash.Name, Config: &ringhash.LBConfig{MinRingSize: 1, MaxRingSize: 2}}, 605 // lrsServer is nil, so LRS policy will not be used. 606 wantConfig: &clusterimpl.LBConfig{ 607 ChildPolicy: &iserviceconfig.BalancerConfig{ 608 Name: ringhash.Name, 609 Config: &ringhash.LBConfig{MinRingSize: 1, MaxRingSize: 2}, 610 }, 611 }, 612 wantEndpoints: []resolver.Endpoint{ 613 testEndpointWithAttrs([]string{"addr-1-1"}, 20, 90, "test-priority", &internal.LocalityID{Zone: "test-zone-1"}), 614 testEndpointWithAttrs([]string{"addr-1-2"}, 20, 10, "test-priority", &internal.LocalityID{Zone: "test-zone-1"}), 615 testEndpointWithAttrs([]string{"addr-2-1"}, 80, 90, "test-priority", &internal.LocalityID{Zone: "test-zone-2"}), 616 testEndpointWithAttrs([]string{"addr-2-2"}, 80, 10, "test-priority", &internal.LocalityID{Zone: "test-zone-2"}), 617 }, 618 }, 619 } 620 for _, tt := range tests { 621 t.Run(tt.name, func(t *testing.T) { 622 got, got1, err := priorityLocalitiesToClusterImpl(tt.localities, tt.priorityName, tt.mechanism, nil, tt.childPolicy) 623 if (err != nil) != tt.wantErr { 624 t.Fatalf("priorityLocalitiesToClusterImpl() error = %v, wantErr %v", err, tt.wantErr) 625 } 626 if diff := cmp.Diff(got, tt.wantConfig); diff != "" { 627 t.Errorf("localitiesToWeightedTarget() diff (-got +want) %v", diff) 628 } 629 if diff := cmp.Diff(got1, tt.wantEndpoints, cmp.AllowUnexported(attributes.Attributes{})); diff != "" { 630 t.Errorf("localitiesToWeightedTarget() diff (-got +want) %v", diff) 631 } 632 }) 633 } 634 } 635 636 func assertString(f func() (string, error)) string { 637 s, err := f() 638 if err != nil { 639 panic(err.Error()) 640 } 641 return s 642 } 643 644 func testEndpointWithAttrs(addrStrs []string, localityWeight, endpointWeight uint32, priority string, lID *internal.LocalityID) resolver.Endpoint { 645 endpoint := resolver.Endpoint{} 646 for _, a := range addrStrs { 647 endpoint.Addresses = append(endpoint.Addresses, resolver.Address{Addr: a}) 648 } 649 path := []string{priority} 650 if lID != nil { 651 path = append(path, assertString(lID.ToString)) 652 endpoint = internal.SetLocalityIDInEndpoint(endpoint, *lID) 653 } 654 endpoint = hierarchy.SetInEndpoint(endpoint, path) 655 endpoint = wrrlocality.SetAddrInfoInEndpoint(endpoint, wrrlocality.AddrInfo{LocalityWeight: localityWeight}) 656 endpoint = weight.Set(endpoint, weight.EndpointInfo{Weight: localityWeight * endpointWeight}) 657 return endpoint 658 } 659 660 func TestConvertClusterImplMapToOutlierDetection(t *testing.T) { 661 tests := []struct { 662 name string 663 ciCfgsMap map[string]*clusterimpl.LBConfig 664 odCfg outlierdetection.LBConfig 665 wantODCfgs map[string]*outlierdetection.LBConfig 666 }{ 667 { 668 name: "single-entry-noop", 669 ciCfgsMap: map[string]*clusterimpl.LBConfig{ 670 "child1": { 671 Cluster: "cluster1", 672 }, 673 }, 674 odCfg: outlierdetection.LBConfig{ 675 Interval: 1<<63 - 1, 676 }, 677 wantODCfgs: map[string]*outlierdetection.LBConfig{ 678 "child1": { 679 Interval: 1<<63 - 1, 680 ChildPolicy: &iserviceconfig.BalancerConfig{ 681 Name: clusterimpl.Name, 682 Config: &clusterimpl.LBConfig{ 683 Cluster: "cluster1", 684 }, 685 }, 686 }, 687 }, 688 }, 689 { 690 name: "multiple-entries-noop", 691 ciCfgsMap: map[string]*clusterimpl.LBConfig{ 692 "child1": { 693 Cluster: "cluster1", 694 }, 695 "child2": { 696 Cluster: "cluster2", 697 }, 698 }, 699 odCfg: outlierdetection.LBConfig{ 700 Interval: 1<<63 - 1, 701 }, 702 wantODCfgs: map[string]*outlierdetection.LBConfig{ 703 "child1": { 704 Interval: 1<<63 - 1, 705 ChildPolicy: &iserviceconfig.BalancerConfig{ 706 Name: clusterimpl.Name, 707 Config: &clusterimpl.LBConfig{ 708 Cluster: "cluster1", 709 }, 710 }, 711 }, 712 "child2": { 713 Interval: 1<<63 - 1, 714 ChildPolicy: &iserviceconfig.BalancerConfig{ 715 Name: clusterimpl.Name, 716 Config: &clusterimpl.LBConfig{ 717 Cluster: "cluster2", 718 }, 719 }, 720 }, 721 }, 722 }, 723 } 724 for _, test := range tests { 725 t.Run(test.name, func(t *testing.T) { 726 got := convertClusterImplMapToOutlierDetection(test.ciCfgsMap, test.odCfg) 727 if diff := cmp.Diff(got, test.wantODCfgs); diff != "" { 728 t.Fatalf("convertClusterImplMapToOutlierDetection() diff(-got +want) %v", diff) 729 } 730 }) 731 } 732 }