google.golang.org/grpc@v1.72.2/xds/internal/xdsclient/xdsresource/unmarshal_eds_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 package xdsresource 19 20 import ( 21 "fmt" 22 "net" 23 "strconv" 24 "testing" 25 26 v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 27 v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3" 28 v3discoverypb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" 29 v3typepb "github.com/envoyproxy/go-control-plane/envoy/type/v3" 30 "github.com/google/go-cmp/cmp" 31 "github.com/google/go-cmp/cmp/cmpopts" 32 "google.golang.org/grpc/internal/envconfig" 33 "google.golang.org/grpc/internal/pretty" 34 "google.golang.org/grpc/internal/testutils" 35 "google.golang.org/grpc/xds/internal" 36 "google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version" 37 "google.golang.org/protobuf/proto" 38 "google.golang.org/protobuf/types/known/anypb" 39 "google.golang.org/protobuf/types/known/structpb" 40 "google.golang.org/protobuf/types/known/wrapperspb" 41 ) 42 43 func (s) TestEDSParseRespProto(t *testing.T) { 44 tests := []struct { 45 name string 46 m *v3endpointpb.ClusterLoadAssignment 47 want EndpointsUpdate 48 wantErr bool 49 }{ 50 { 51 name: "missing-priority", 52 m: func() *v3endpointpb.ClusterLoadAssignment { 53 clab0 := newClaBuilder("test", nil) 54 clab0.addLocality("locality-1", 1, 0, []endpointOpts{{addrWithPort: "addr1:314"}}, nil) 55 clab0.addLocality("locality-2", 1, 2, []endpointOpts{{addrWithPort: "addr2:159"}}, nil) 56 return clab0.Build() 57 }(), 58 want: EndpointsUpdate{}, 59 wantErr: true, 60 }, 61 { 62 name: "missing-locality-ID", 63 m: func() *v3endpointpb.ClusterLoadAssignment { 64 clab0 := newClaBuilder("test", nil) 65 clab0.addLocality("", 1, 0, []endpointOpts{{addrWithPort: "addr1:314"}}, nil) 66 return clab0.Build() 67 }(), 68 want: EndpointsUpdate{}, 69 wantErr: true, 70 }, 71 { 72 name: "zero-endpoint-weight", 73 m: func() *v3endpointpb.ClusterLoadAssignment { 74 clab0 := newClaBuilder("test", nil) 75 clab0.addLocality("locality-0", 1, 0, []endpointOpts{{addrWithPort: "addr1:314"}}, &addLocalityOptions{Weight: []uint32{0}}) 76 return clab0.Build() 77 }(), 78 want: EndpointsUpdate{}, 79 wantErr: true, 80 }, 81 { 82 name: "duplicate-locality-in-the-same-priority", 83 m: func() *v3endpointpb.ClusterLoadAssignment { 84 clab0 := newClaBuilder("test", nil) 85 clab0.addLocality("locality-0", 1, 0, []endpointOpts{{addrWithPort: "addr1:314"}}, nil) 86 clab0.addLocality("locality-0", 1, 0, []endpointOpts{{addrWithPort: "addr1:314"}}, nil) // Duplicate locality with the same priority. 87 return clab0.Build() 88 }(), 89 want: EndpointsUpdate{}, 90 wantErr: true, 91 }, 92 { 93 name: "missing locality weight", 94 m: func() *v3endpointpb.ClusterLoadAssignment { 95 clab0 := newClaBuilder("test", nil) 96 clab0.addLocality("locality-1", 0, 1, []endpointOpts{{addrWithPort: "addr1:314"}}, &addLocalityOptions{ 97 Health: []v3corepb.HealthStatus{v3corepb.HealthStatus_HEALTHY}, 98 }) 99 clab0.addLocality("locality-2", 0, 0, []endpointOpts{{addrWithPort: "addr2:159"}}, &addLocalityOptions{ 100 Health: []v3corepb.HealthStatus{v3corepb.HealthStatus_HEALTHY}, 101 }) 102 return clab0.Build() 103 }(), 104 want: EndpointsUpdate{}, 105 }, 106 { 107 name: "max sum of weights at the same priority exceeded", 108 m: func() *v3endpointpb.ClusterLoadAssignment { 109 clab0 := newClaBuilder("test", nil) 110 clab0.addLocality("locality-1", 1, 0, []endpointOpts{{addrWithPort: "addr1:314"}}, nil) 111 clab0.addLocality("locality-2", 4294967295, 1, []endpointOpts{{addrWithPort: "addr2:159"}}, nil) 112 clab0.addLocality("locality-3", 1, 1, []endpointOpts{{addrWithPort: "addr2:88"}}, nil) 113 return clab0.Build() 114 }(), 115 want: EndpointsUpdate{}, 116 wantErr: true, 117 }, 118 { 119 name: "duplicate endpoint address", 120 m: func() *v3endpointpb.ClusterLoadAssignment { 121 clab0 := newClaBuilder("test", nil) 122 clab0.addLocality("locality-1", 1, 1, []endpointOpts{{addrWithPort: "addr:997"}}, nil) 123 clab0.addLocality("locality-2", 1, 0, []endpointOpts{{addrWithPort: "addr:997"}}, nil) 124 return clab0.Build() 125 }(), 126 want: EndpointsUpdate{}, 127 wantErr: true, 128 }, 129 { 130 name: "good", 131 m: func() *v3endpointpb.ClusterLoadAssignment { 132 clab0 := newClaBuilder("test", nil) 133 clab0.addLocality("locality-1", 1, 1, []endpointOpts{{addrWithPort: "addr1:314"}}, &addLocalityOptions{ 134 Health: []v3corepb.HealthStatus{v3corepb.HealthStatus_UNHEALTHY}, 135 Weight: []uint32{271}, 136 }) 137 clab0.addLocality("locality-2", 1, 0, []endpointOpts{{addrWithPort: "addr2:159"}}, &addLocalityOptions{ 138 Health: []v3corepb.HealthStatus{v3corepb.HealthStatus_DRAINING}, 139 Weight: []uint32{828}, 140 }) 141 return clab0.Build() 142 }(), 143 want: EndpointsUpdate{ 144 Drops: nil, 145 Localities: []Locality{ 146 { 147 Endpoints: []Endpoint{{ 148 Addresses: []string{"addr1:314"}, 149 HealthStatus: EndpointHealthStatusUnhealthy, 150 Weight: 271, 151 }}, 152 ID: internal.LocalityID{SubZone: "locality-1"}, 153 Priority: 1, 154 Weight: 1, 155 }, 156 { 157 Endpoints: []Endpoint{{ 158 Addresses: []string{"addr2:159"}, 159 HealthStatus: EndpointHealthStatusDraining, 160 Weight: 828, 161 }}, 162 ID: internal.LocalityID{SubZone: "locality-2"}, 163 Priority: 0, 164 Weight: 1, 165 }, 166 }, 167 }, 168 wantErr: false, 169 }, 170 { 171 name: "good duplicate locality with different priority", 172 m: func() *v3endpointpb.ClusterLoadAssignment { 173 clab0 := newClaBuilder("test", nil) 174 clab0.addLocality("locality-1", 1, 1, []endpointOpts{{addrWithPort: "addr1:314"}}, &addLocalityOptions{ 175 Health: []v3corepb.HealthStatus{v3corepb.HealthStatus_UNHEALTHY}, 176 Weight: []uint32{271}, 177 }) 178 // Same locality name, but with different priority. 179 clab0.addLocality("locality-1", 1, 0, []endpointOpts{{addrWithPort: "addr2:159"}}, &addLocalityOptions{ 180 Health: []v3corepb.HealthStatus{v3corepb.HealthStatus_DRAINING}, 181 Weight: []uint32{828}, 182 }) 183 return clab0.Build() 184 }(), 185 want: EndpointsUpdate{ 186 Drops: nil, 187 Localities: []Locality{ 188 { 189 Endpoints: []Endpoint{{ 190 Addresses: []string{"addr1:314"}, 191 HealthStatus: EndpointHealthStatusUnhealthy, 192 Weight: 271, 193 }}, 194 ID: internal.LocalityID{SubZone: "locality-1"}, 195 Priority: 1, 196 Weight: 1, 197 }, 198 { 199 Endpoints: []Endpoint{{ 200 Addresses: []string{"addr2:159"}, 201 HealthStatus: EndpointHealthStatusDraining, 202 Weight: 828, 203 }}, 204 ID: internal.LocalityID{SubZone: "locality-1"}, 205 Priority: 0, 206 Weight: 1, 207 }, 208 }, 209 }, 210 wantErr: false, 211 }, 212 } 213 for _, tt := range tests { 214 t.Run(tt.name, func(t *testing.T) { 215 got, err := parseEDSRespProto(tt.m) 216 if (err != nil) != tt.wantErr { 217 t.Errorf("parseEDSRespProto() error = %v, wantErr %v", err, tt.wantErr) 218 return 219 } 220 if d := cmp.Diff(got, tt.want, cmpopts.EquateEmpty()); d != "" { 221 t.Errorf("parseEDSRespProto() got = %v, want %v, diff: %v", got, tt.want, d) 222 } 223 }) 224 } 225 } 226 227 func (s) TestEDSParseRespProtoAdditionalAddrs(t *testing.T) { 228 origDualstackEndpointsEnabled := envconfig.XDSDualstackEndpointsEnabled 229 defer func() { 230 envconfig.XDSDualstackEndpointsEnabled = origDualstackEndpointsEnabled 231 }() 232 envconfig.XDSDualstackEndpointsEnabled = true 233 234 tests := []struct { 235 name string 236 m *v3endpointpb.ClusterLoadAssignment 237 want EndpointsUpdate 238 wantErr bool 239 }{ 240 { 241 name: "duplicate primary address in self additional addresses", 242 m: func() *v3endpointpb.ClusterLoadAssignment { 243 clab0 := newClaBuilder("test", nil) 244 clab0.addLocality("locality-1", 1, 0, []endpointOpts{{addrWithPort: "addr:998", additionalAddrWithPorts: []string{"addr:998"}}}, nil) 245 return clab0.Build() 246 }(), 247 want: EndpointsUpdate{}, 248 wantErr: true, 249 }, 250 { 251 name: "duplicate primary address in other locality additional addresses", 252 m: func() *v3endpointpb.ClusterLoadAssignment { 253 clab0 := newClaBuilder("test", nil) 254 clab0.addLocality("locality-1", 1, 1, []endpointOpts{{addrWithPort: "addr:997"}}, nil) 255 clab0.addLocality("locality-2", 1, 0, []endpointOpts{{addrWithPort: "addr:998", additionalAddrWithPorts: []string{"addr:997"}}}, nil) 256 return clab0.Build() 257 }(), 258 want: EndpointsUpdate{}, 259 wantErr: true, 260 }, 261 { 262 name: "duplicate additional address in self additional addresses", 263 m: func() *v3endpointpb.ClusterLoadAssignment { 264 clab0 := newClaBuilder("test", nil) 265 clab0.addLocality("locality-1", 1, 0, []endpointOpts{{addrWithPort: "addr:998", additionalAddrWithPorts: []string{"addr:999", "addr:999"}}}, nil) 266 return clab0.Build() 267 }(), 268 want: EndpointsUpdate{}, 269 wantErr: true, 270 }, 271 { 272 name: "duplicate additional address in other locality additional addresses", 273 m: func() *v3endpointpb.ClusterLoadAssignment { 274 clab0 := newClaBuilder("test", nil) 275 clab0.addLocality("locality-1", 1, 1, []endpointOpts{{addrWithPort: "addr:997", additionalAddrWithPorts: []string{"addr:1000"}}}, nil) 276 clab0.addLocality("locality-2", 1, 0, []endpointOpts{{addrWithPort: "addr:998", additionalAddrWithPorts: []string{"addr:1000"}}}, nil) 277 return clab0.Build() 278 }(), 279 want: EndpointsUpdate{}, 280 wantErr: true, 281 }, 282 { 283 name: "multiple localities", 284 m: func() *v3endpointpb.ClusterLoadAssignment { 285 clab0 := newClaBuilder("test", nil) 286 clab0.addLocality("locality-1", 1, 1, []endpointOpts{{addrWithPort: "addr1:997", additionalAddrWithPorts: []string{"addr1:1000"}}}, &addLocalityOptions{ 287 Health: []v3corepb.HealthStatus{v3corepb.HealthStatus_UNHEALTHY}, 288 Weight: []uint32{271}, 289 }) 290 clab0.addLocality("locality-2", 1, 0, []endpointOpts{{addrWithPort: "addr2:998", additionalAddrWithPorts: []string{"addr2:1000"}}}, &addLocalityOptions{ 291 Health: []v3corepb.HealthStatus{v3corepb.HealthStatus_HEALTHY}, 292 Weight: []uint32{828}, 293 }) 294 return clab0.Build() 295 }(), 296 want: EndpointsUpdate{ 297 Drops: nil, 298 Localities: []Locality{ 299 { 300 Endpoints: []Endpoint{{ 301 Addresses: []string{"addr1:997", "addr1:1000"}, 302 HealthStatus: EndpointHealthStatusUnhealthy, 303 Weight: 271, 304 }}, 305 ID: internal.LocalityID{SubZone: "locality-1"}, 306 Priority: 1, 307 Weight: 1, 308 }, 309 { 310 Endpoints: []Endpoint{{ 311 Addresses: []string{"addr2:998", "addr2:1000"}, 312 HealthStatus: EndpointHealthStatusHealthy, 313 Weight: 828, 314 }}, 315 ID: internal.LocalityID{SubZone: "locality-2"}, 316 Priority: 0, 317 Weight: 1, 318 }, 319 }, 320 }, 321 }, 322 } 323 324 for _, tt := range tests { 325 t.Run(tt.name, func(t *testing.T) { 326 got, err := parseEDSRespProto(tt.m) 327 if (err != nil) != tt.wantErr { 328 t.Errorf("parseEDSRespProto() error = %v, wantErr %v", err, tt.wantErr) 329 return 330 } 331 if d := cmp.Diff(got, tt.want, cmpopts.EquateEmpty()); d != "" { 332 t.Errorf("parseEDSRespProto() got = %v, want %v, diff: %v", got, tt.want, d) 333 } 334 }) 335 } 336 } 337 338 func (s) TestUnmarshalEndpointHashKey(t *testing.T) { 339 baseCLA := &v3endpointpb.ClusterLoadAssignment{ 340 Endpoints: []*v3endpointpb.LocalityLbEndpoints{ 341 { 342 Locality: &v3corepb.Locality{Region: "r"}, 343 LbEndpoints: []*v3endpointpb.LbEndpoint{ 344 { 345 HostIdentifier: &v3endpointpb.LbEndpoint_Endpoint{ 346 Endpoint: &v3endpointpb.Endpoint{ 347 Address: &v3corepb.Address{ 348 Address: &v3corepb.Address_SocketAddress{ 349 SocketAddress: &v3corepb.SocketAddress{ 350 Address: "test-address", 351 PortSpecifier: &v3corepb.SocketAddress_PortValue{ 352 PortValue: 8080, 353 }, 354 }, 355 }, 356 }, 357 }, 358 }, 359 }, 360 }, 361 LoadBalancingWeight: &wrapperspb.UInt32Value{Value: 1}, 362 }, 363 }, 364 } 365 366 tests := []struct { 367 name string 368 metadata *v3corepb.Metadata 369 wantHashKey string 370 compatEnvVar bool 371 }{ 372 { 373 name: "no metadata", 374 metadata: nil, 375 wantHashKey: "", 376 }, 377 { 378 name: "empty metadata", 379 metadata: &v3corepb.Metadata{}, 380 wantHashKey: "", 381 }, 382 { 383 name: "filter metadata without envoy.lb", 384 metadata: &v3corepb.Metadata{ 385 FilterMetadata: map[string]*structpb.Struct{ 386 "test-filter": {}, 387 }, 388 }, 389 wantHashKey: "", 390 }, 391 { 392 name: "nil envoy.lb", 393 metadata: &v3corepb.Metadata{ 394 FilterMetadata: map[string]*structpb.Struct{ 395 "envoy.lb": nil, 396 }, 397 }, 398 wantHashKey: "", 399 }, 400 { 401 name: "envoy.lb without hash key", 402 metadata: &v3corepb.Metadata{ 403 FilterMetadata: map[string]*structpb.Struct{ 404 "envoy.lb": { 405 Fields: map[string]*structpb.Value{ 406 "hash_key": { 407 Kind: &structpb.Value_NumberValue{NumberValue: 123.0}, 408 }, 409 }, 410 }, 411 }, 412 }, 413 wantHashKey: "", 414 }, 415 { 416 name: "envoy.lb with hash key, compat mode off", 417 metadata: &v3corepb.Metadata{ 418 FilterMetadata: map[string]*structpb.Struct{ 419 "envoy.lb": { 420 Fields: map[string]*structpb.Value{ 421 "hash_key": { 422 Kind: &structpb.Value_StringValue{StringValue: "test-hash-key"}, 423 }, 424 }, 425 }, 426 }, 427 }, 428 wantHashKey: "test-hash-key", 429 }, 430 { 431 name: "envoy.lb with hash key, compat mode on", 432 metadata: &v3corepb.Metadata{ 433 FilterMetadata: map[string]*structpb.Struct{ 434 "envoy.lb": { 435 Fields: map[string]*structpb.Value{ 436 "hash_key": { 437 Kind: &structpb.Value_StringValue{StringValue: "test-hash-key"}, 438 }, 439 }, 440 }, 441 }, 442 }, 443 wantHashKey: "", 444 compatEnvVar: true, 445 }, 446 } 447 448 for _, test := range tests { 449 t.Run(test.name, func(t *testing.T) { 450 testutils.SetEnvConfig(t, &envconfig.XDSEndpointHashKeyBackwardCompat, test.compatEnvVar) 451 452 cla := proto.Clone(baseCLA).(*v3endpointpb.ClusterLoadAssignment) 453 cla.Endpoints[0].LbEndpoints[0].Metadata = test.metadata 454 marshalledCLA := testutils.MarshalAny(t, cla) 455 _, update, err := unmarshalEndpointsResource(marshalledCLA) 456 if err != nil { 457 t.Fatalf("unmarshalEndpointsResource() got error = %v, want success", err) 458 } 459 got := update.Localities[0].Endpoints[0].HashKey 460 if got != test.wantHashKey { 461 t.Errorf("unmarshalEndpointResource() endpoint hash key: got %s, want %s", got, test.wantHashKey) 462 } 463 }) 464 } 465 } 466 467 func (s) TestUnmarshalEndpoints(t *testing.T) { 468 var v3EndpointsAny = testutils.MarshalAny(t, func() *v3endpointpb.ClusterLoadAssignment { 469 clab0 := newClaBuilder("test", nil) 470 clab0.addLocality("locality-1", 1, 1, []endpointOpts{{addrWithPort: "addr1:314"}}, &addLocalityOptions{ 471 Health: []v3corepb.HealthStatus{v3corepb.HealthStatus_UNHEALTHY}, 472 Weight: []uint32{271}, 473 }) 474 clab0.addLocality("locality-2", 1, 0, []endpointOpts{{addrWithPort: "addr2:159"}}, &addLocalityOptions{ 475 Health: []v3corepb.HealthStatus{v3corepb.HealthStatus_DRAINING}, 476 Weight: []uint32{828}, 477 }) 478 return clab0.Build() 479 }()) 480 481 tests := []struct { 482 name string 483 resource *anypb.Any 484 wantName string 485 wantUpdate EndpointsUpdate 486 wantErr bool 487 }{ 488 { 489 name: "non-clusterLoadAssignment resource type", 490 resource: &anypb.Any{TypeUrl: version.V3HTTPConnManagerURL}, 491 wantErr: true, 492 }, 493 { 494 name: "badly marshaled clusterLoadAssignment resource", 495 resource: &anypb.Any{ 496 TypeUrl: version.V3EndpointsURL, 497 Value: []byte{1, 2, 3, 4}, 498 }, 499 wantErr: true, 500 }, 501 { 502 name: "bad endpoints resource", 503 resource: testutils.MarshalAny(t, func() *v3endpointpb.ClusterLoadAssignment { 504 clab0 := newClaBuilder("test", nil) 505 clab0.addLocality("locality-1", 1, 0, []endpointOpts{{addrWithPort: "addr1:314"}}, nil) 506 clab0.addLocality("locality-2", 1, 2, []endpointOpts{{addrWithPort: "addr2:159"}}, nil) 507 return clab0.Build() 508 }()), 509 wantName: "test", 510 wantErr: true, 511 }, 512 { 513 name: "v3 endpoints", 514 resource: v3EndpointsAny, 515 wantName: "test", 516 wantUpdate: EndpointsUpdate{ 517 Drops: nil, 518 Localities: []Locality{ 519 { 520 Endpoints: []Endpoint{{ 521 Addresses: []string{"addr1:314"}, 522 HealthStatus: EndpointHealthStatusUnhealthy, 523 Weight: 271, 524 }}, 525 ID: internal.LocalityID{SubZone: "locality-1"}, 526 Priority: 1, 527 Weight: 1, 528 }, 529 { 530 Endpoints: []Endpoint{{ 531 Addresses: []string{"addr2:159"}, 532 HealthStatus: EndpointHealthStatusDraining, 533 Weight: 828, 534 }}, 535 ID: internal.LocalityID{SubZone: "locality-2"}, 536 Priority: 0, 537 Weight: 1, 538 }, 539 }, 540 Raw: v3EndpointsAny, 541 }, 542 }, 543 { 544 name: "v3 endpoints wrapped", 545 resource: testutils.MarshalAny(t, &v3discoverypb.Resource{Resource: v3EndpointsAny}), 546 wantName: "test", 547 wantUpdate: EndpointsUpdate{ 548 Drops: nil, 549 Localities: []Locality{ 550 { 551 Endpoints: []Endpoint{{ 552 Addresses: []string{"addr1:314"}, 553 HealthStatus: EndpointHealthStatusUnhealthy, 554 Weight: 271, 555 }}, 556 ID: internal.LocalityID{SubZone: "locality-1"}, 557 Priority: 1, 558 Weight: 1, 559 }, 560 { 561 Endpoints: []Endpoint{{ 562 Addresses: []string{"addr2:159"}, 563 HealthStatus: EndpointHealthStatusDraining, 564 Weight: 828, 565 }}, 566 ID: internal.LocalityID{SubZone: "locality-2"}, 567 Priority: 0, 568 Weight: 1, 569 }, 570 }, 571 Raw: v3EndpointsAny, 572 }, 573 }, 574 } 575 for _, test := range tests { 576 t.Run(test.name, func(t *testing.T) { 577 name, update, err := unmarshalEndpointsResource(test.resource) 578 if (err != nil) != test.wantErr { 579 t.Fatalf("unmarshalEndpointsResource(%s), got err: %v, wantErr: %v", pretty.ToJSON(test.resource), err, test.wantErr) 580 } 581 if name != test.wantName { 582 t.Errorf("unmarshalEndpointsResource(%s), got name: %s, want: %s", pretty.ToJSON(test.resource), name, test.wantName) 583 } 584 if diff := cmp.Diff(update, test.wantUpdate, cmpOpts); diff != "" { 585 t.Errorf("unmarshalEndpointsResource(%s), got unexpected update, diff (-got +want): %v", pretty.ToJSON(test.resource), diff) 586 } 587 }) 588 } 589 } 590 591 // claBuilder builds a ClusterLoadAssignment, aka EDS 592 // response. 593 type claBuilder struct { 594 v *v3endpointpb.ClusterLoadAssignment 595 } 596 597 // newClaBuilder creates a claBuilder. 598 func newClaBuilder(clusterName string, dropPercents []uint32) *claBuilder { 599 var drops []*v3endpointpb.ClusterLoadAssignment_Policy_DropOverload 600 for i, d := range dropPercents { 601 drops = append(drops, &v3endpointpb.ClusterLoadAssignment_Policy_DropOverload{ 602 Category: fmt.Sprintf("test-drop-%d", i), 603 DropPercentage: &v3typepb.FractionalPercent{ 604 Numerator: d, 605 Denominator: v3typepb.FractionalPercent_HUNDRED, 606 }, 607 }) 608 } 609 610 return &claBuilder{ 611 v: &v3endpointpb.ClusterLoadAssignment{ 612 ClusterName: clusterName, 613 Policy: &v3endpointpb.ClusterLoadAssignment_Policy{ 614 DropOverloads: drops, 615 }, 616 }, 617 } 618 } 619 620 // addLocalityOptions contains options when adding locality to the builder. 621 type addLocalityOptions struct { 622 Health []v3corepb.HealthStatus 623 Weight []uint32 624 } 625 626 type endpointOpts struct { 627 addrWithPort string 628 additionalAddrWithPorts []string 629 } 630 631 func addressFromStr(addrWithPort string) *v3corepb.Address { 632 host, portStr, err := net.SplitHostPort(addrWithPort) 633 if err != nil { 634 panic("failed to split " + addrWithPort) 635 } 636 port, err := strconv.Atoi(portStr) 637 if err != nil { 638 panic("failed to atoi " + portStr) 639 } 640 641 return &v3corepb.Address{ 642 Address: &v3corepb.Address_SocketAddress{ 643 SocketAddress: &v3corepb.SocketAddress{ 644 Protocol: v3corepb.SocketAddress_TCP, 645 Address: host, 646 PortSpecifier: &v3corepb.SocketAddress_PortValue{PortValue: uint32(port)}, 647 }, 648 }, 649 } 650 } 651 652 // addLocality adds a locality to the builder. 653 func (clab *claBuilder) addLocality(subzone string, weight uint32, priority uint32, endpoints []endpointOpts, opts *addLocalityOptions) { 654 var lbEndPoints []*v3endpointpb.LbEndpoint 655 for i, e := range endpoints { 656 var additionalAddrs []*v3endpointpb.Endpoint_AdditionalAddress 657 for _, a := range e.additionalAddrWithPorts { 658 additionalAddrs = append(additionalAddrs, &v3endpointpb.Endpoint_AdditionalAddress{ 659 Address: addressFromStr(a), 660 }) 661 } 662 lbe := &v3endpointpb.LbEndpoint{ 663 HostIdentifier: &v3endpointpb.LbEndpoint_Endpoint{ 664 Endpoint: &v3endpointpb.Endpoint{ 665 Address: addressFromStr(e.addrWithPort), 666 AdditionalAddresses: additionalAddrs, 667 }, 668 }, 669 } 670 if opts != nil { 671 if i < len(opts.Health) { 672 lbe.HealthStatus = opts.Health[i] 673 } 674 if i < len(opts.Weight) { 675 lbe.LoadBalancingWeight = &wrapperspb.UInt32Value{Value: opts.Weight[i]} 676 } 677 } 678 lbEndPoints = append(lbEndPoints, lbe) 679 } 680 681 var localityID *v3corepb.Locality 682 if subzone != "" { 683 localityID = &v3corepb.Locality{ 684 Region: "", 685 Zone: "", 686 SubZone: subzone, 687 } 688 } 689 690 clab.v.Endpoints = append(clab.v.Endpoints, &v3endpointpb.LocalityLbEndpoints{ 691 Locality: localityID, 692 LbEndpoints: lbEndPoints, 693 LoadBalancingWeight: &wrapperspb.UInt32Value{Value: weight}, 694 Priority: priority, 695 }) 696 } 697 698 // Build builds ClusterLoadAssignment. 699 func (clab *claBuilder) Build() *v3endpointpb.ClusterLoadAssignment { 700 return clab.v 701 }