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