snapshot of https://github.com/grpc/grpc-go.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / vet-proto (push) Failing after 1s
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
Stale bot / stale (push) Failing after 1s
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / vet-proto (push) Failing after 1s
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
Stale bot / stale (push) Failing after 1s
This commit is contained in:
294
internal/xds/xdsclient/xdsresource/unmarshal_eds.go
Normal file
294
internal/xds/xdsclient/xdsresource/unmarshal_eds.go
Normal file
@@ -0,0 +1,294 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2021 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package xdsresource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||
v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
|
||||
v3typepb "github.com/envoyproxy/go-control-plane/envoy/type/v3"
|
||||
"google.golang.org/grpc/experimental/balancer/hostname"
|
||||
"google.golang.org/grpc/internal/envconfig"
|
||||
"google.golang.org/grpc/internal/pretty"
|
||||
xdsinternal "google.golang.org/grpc/internal/xds"
|
||||
"google.golang.org/grpc/internal/xds/clients"
|
||||
"google.golang.org/grpc/resolver"
|
||||
"google.golang.org/grpc/resolver/ringhash"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/anypb"
|
||||
)
|
||||
|
||||
// Hostname returns the hostname from the BalancerAttributes of the
|
||||
// given Address. If this attribute is not set, it returns the empty
|
||||
// string.
|
||||
func Hostname(addr resolver.Address) string {
|
||||
ep := resolver.Endpoint{Attributes: addr.BalancerAttributes}
|
||||
return hostname.FromEndpoint(ep)
|
||||
}
|
||||
|
||||
func unmarshalEndpointsResource(r *anypb.Any) (string, EndpointsUpdate, error) {
|
||||
r, err := UnwrapResource(r)
|
||||
if err != nil {
|
||||
return "", EndpointsUpdate{}, fmt.Errorf("failed to unwrap resource: %v", err)
|
||||
}
|
||||
|
||||
if !IsEndpointsResource(r.GetTypeUrl()) {
|
||||
return "", EndpointsUpdate{}, fmt.Errorf("unexpected resource type: %q ", r.GetTypeUrl())
|
||||
}
|
||||
|
||||
cla := &v3endpointpb.ClusterLoadAssignment{}
|
||||
if err := proto.Unmarshal(r.GetValue(), cla); err != nil {
|
||||
return "", EndpointsUpdate{}, fmt.Errorf("failed to unmarshal resource: %v", err)
|
||||
}
|
||||
|
||||
if cla.GetClusterName() == "" {
|
||||
return "", EndpointsUpdate{}, fmt.Errorf("empty resource name in endpoints resource")
|
||||
}
|
||||
|
||||
u, err := parseEDSRespProto(cla)
|
||||
if err != nil {
|
||||
return cla.GetClusterName(), EndpointsUpdate{}, err
|
||||
}
|
||||
u.Raw = r
|
||||
|
||||
return cla.GetClusterName(), u, nil
|
||||
}
|
||||
|
||||
func parseAddress(socketAddress *v3corepb.SocketAddress) string {
|
||||
return net.JoinHostPort(socketAddress.GetAddress(), strconv.Itoa(int(socketAddress.GetPortValue())))
|
||||
}
|
||||
|
||||
func parseDropPolicy(dropPolicy *v3endpointpb.ClusterLoadAssignment_Policy_DropOverload) (OverloadDropConfig, error) {
|
||||
percentage := dropPolicy.GetDropPercentage()
|
||||
var (
|
||||
numerator = percentage.GetNumerator()
|
||||
denominator uint32
|
||||
)
|
||||
switch percentage.GetDenominator() {
|
||||
case v3typepb.FractionalPercent_HUNDRED:
|
||||
denominator = 100
|
||||
case v3typepb.FractionalPercent_TEN_THOUSAND:
|
||||
denominator = 10000
|
||||
case v3typepb.FractionalPercent_MILLION:
|
||||
denominator = 1000000
|
||||
default:
|
||||
return OverloadDropConfig{}, fmt.Errorf("EDS response contains a drop policy with unsupported denominator: %v", percentage.GetDenominator())
|
||||
}
|
||||
return OverloadDropConfig{
|
||||
Category: dropPolicy.GetCategory(),
|
||||
Numerator: numerator,
|
||||
Denominator: denominator,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseEndpoints(lbEndpoints []*v3endpointpb.LbEndpoint, uniqueEndpointAddrs map[string]bool) ([]Endpoint, error) {
|
||||
endpoints := make([]Endpoint, 0, len(lbEndpoints))
|
||||
var totalWeight uint64
|
||||
for _, lbEndpoint := range lbEndpoints {
|
||||
// If the load_balancing_weight field is specified, it must be set to a
|
||||
// value of at least 1. If unspecified, each host is presumed to have
|
||||
// equal weight in a locality.
|
||||
weight := uint32(1)
|
||||
if w := lbEndpoint.GetLoadBalancingWeight(); w != nil {
|
||||
if w.GetValue() == 0 {
|
||||
return nil, fmt.Errorf("EDS response contains an endpoint with zero weight: %+v", lbEndpoint)
|
||||
}
|
||||
weight = w.GetValue()
|
||||
}
|
||||
|
||||
totalWeight += uint64(weight)
|
||||
if totalWeight > math.MaxUint32 {
|
||||
return nil, fmt.Errorf("sum of weights of endpoints in the same locality exceeds maximum value %d", uint64(math.MaxUint32))
|
||||
}
|
||||
|
||||
addrs := []string{parseAddress(lbEndpoint.GetEndpoint().GetAddress().GetSocketAddress())}
|
||||
if envconfig.XDSDualstackEndpointsEnabled {
|
||||
for _, sa := range lbEndpoint.GetEndpoint().GetAdditionalAddresses() {
|
||||
addrs = append(addrs, parseAddress(sa.GetAddress().GetSocketAddress()))
|
||||
}
|
||||
}
|
||||
|
||||
address := []resolver.Address{}
|
||||
for _, a := range addrs {
|
||||
address = append(address, resolver.Address{Addr: a})
|
||||
if uniqueEndpointAddrs[a] {
|
||||
return nil, fmt.Errorf("duplicate endpoint with the same address %s", a)
|
||||
}
|
||||
uniqueEndpointAddrs[a] = true
|
||||
}
|
||||
|
||||
var endpointMetadata map[string]any
|
||||
var hashKey string
|
||||
if envconfig.XDSHTTPConnectEnabled || !envconfig.XDSEndpointHashKeyBackwardCompat {
|
||||
var err error
|
||||
endpointMetadata, err = validateAndConstructMetadata(lbEndpoint.GetMetadata())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// "The xDS resolver, described in A74, will be changed to set the hash_key
|
||||
// endpoint attribute to the value of LbEndpoint.Metadata envoy.lb hash_key
|
||||
// field, as described in Envoy's documentation for the ring hash load
|
||||
// balancer." - A76
|
||||
if !envconfig.XDSEndpointHashKeyBackwardCompat {
|
||||
hashKey = hashKeyFromMetadata(endpointMetadata)
|
||||
}
|
||||
}
|
||||
endpoint := resolver.Endpoint{Addresses: address}
|
||||
endpoint = hostname.Set(endpoint, lbEndpoint.GetEndpoint().GetHostname())
|
||||
endpoint = ringhash.SetHashKey(endpoint, hashKey)
|
||||
endpoints = append(endpoints, Endpoint{
|
||||
ResolverEndpoint: endpoint,
|
||||
HealthStatus: EndpointHealthStatus(lbEndpoint.GetHealthStatus()),
|
||||
Weight: weight,
|
||||
Metadata: endpointMetadata,
|
||||
})
|
||||
}
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
// hashKey extracts and returns the hash key from the given endpoint metadata.
|
||||
// If no hash key is found, it returns an empty string.
|
||||
func hashKeyFromMetadata(metadata map[string]any) string {
|
||||
envoyLB, ok := metadata["envoy.lb"].(StructMetadataValue)
|
||||
if ok {
|
||||
if h, ok := envoyLB.Data["hash_key"].(string); ok {
|
||||
return h
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func parseEDSRespProto(m *v3endpointpb.ClusterLoadAssignment) (EndpointsUpdate, error) {
|
||||
ret := EndpointsUpdate{}
|
||||
for _, dropPolicy := range m.GetPolicy().GetDropOverloads() {
|
||||
drop, err := parseDropPolicy(dropPolicy)
|
||||
if err != nil {
|
||||
return EndpointsUpdate{}, err
|
||||
}
|
||||
ret.Drops = append(ret.Drops, drop)
|
||||
}
|
||||
priorities := make(map[uint32]map[string]bool)
|
||||
sumOfWeights := make(map[uint32]uint64)
|
||||
uniqueEndpointAddrs := make(map[string]bool)
|
||||
for _, locality := range m.Endpoints {
|
||||
l := locality.GetLocality()
|
||||
if l == nil {
|
||||
return EndpointsUpdate{}, fmt.Errorf("EDS response contains a locality without ID, locality: %+v", locality)
|
||||
}
|
||||
weight := locality.GetLoadBalancingWeight().GetValue()
|
||||
if weight == 0 {
|
||||
logger.Warningf("Ignoring locality %s with weight 0", pretty.ToJSON(l))
|
||||
continue
|
||||
}
|
||||
priority := locality.GetPriority()
|
||||
sumOfWeights[priority] += uint64(weight)
|
||||
if sumOfWeights[priority] > math.MaxUint32 {
|
||||
return EndpointsUpdate{}, fmt.Errorf("sum of weights of localities at the same priority %d exceeded maximal value", priority)
|
||||
}
|
||||
localitiesWithPriority := priorities[priority]
|
||||
if localitiesWithPriority == nil {
|
||||
localitiesWithPriority = make(map[string]bool)
|
||||
priorities[priority] = localitiesWithPriority
|
||||
}
|
||||
lid := clients.Locality{
|
||||
Region: l.Region,
|
||||
Zone: l.Zone,
|
||||
SubZone: l.SubZone,
|
||||
}
|
||||
lidStr := xdsinternal.LocalityString(lid)
|
||||
|
||||
// "Since an xDS configuration can place a given locality under multiple
|
||||
// priorities, it is possible to see locality weight attributes with
|
||||
// different values for the same locality." - A52
|
||||
//
|
||||
// This is handled in the client by emitting the locality weight
|
||||
// specified for the priority it is specified in. If the same locality
|
||||
// has a different weight in two priorities, each priority will specify
|
||||
// a locality with the locality weight specified for that priority, and
|
||||
// thus the subsequent tree of balancers linked to that priority will
|
||||
// use that locality weight as well.
|
||||
if localitiesWithPriority[lidStr] {
|
||||
return EndpointsUpdate{}, fmt.Errorf("duplicate locality %s with the same priority %v", lidStr, priority)
|
||||
}
|
||||
localitiesWithPriority[lidStr] = true
|
||||
endpoints, err := parseEndpoints(locality.GetLbEndpoints(), uniqueEndpointAddrs)
|
||||
if err != nil {
|
||||
return EndpointsUpdate{}, err
|
||||
}
|
||||
var localityMetadata map[string]any
|
||||
if envconfig.XDSHTTPConnectEnabled {
|
||||
var err error
|
||||
localityMetadata, err = validateAndConstructMetadata(locality.GetMetadata())
|
||||
if err != nil {
|
||||
return EndpointsUpdate{}, err
|
||||
}
|
||||
}
|
||||
|
||||
ret.Localities = append(ret.Localities, Locality{
|
||||
ID: lid,
|
||||
Endpoints: endpoints,
|
||||
Weight: weight,
|
||||
Priority: priority,
|
||||
Metadata: localityMetadata,
|
||||
})
|
||||
}
|
||||
for i := 0; i < len(priorities); i++ {
|
||||
if _, ok := priorities[uint32(i)]; !ok {
|
||||
return EndpointsUpdate{}, fmt.Errorf("priority %v missing (with different priorities %v received)", i, priorities)
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// validateAndConstructMetadata processes the metadata from the xDS resource
|
||||
// and returns a map of parsed metadata values.
|
||||
func validateAndConstructMetadata(metadataProto *v3corepb.Metadata) (map[string]any, error) {
|
||||
if metadataProto == nil {
|
||||
return nil, nil
|
||||
}
|
||||
metadata := make(map[string]any)
|
||||
// First go through TypedFilterMetadata.
|
||||
for key, anyProto := range metadataProto.GetTypedFilterMetadata() {
|
||||
converter := metadataConverterForType(anyProto.GetTypeUrl())
|
||||
// Ignore types we don't have a converter for.
|
||||
if converter == nil {
|
||||
continue
|
||||
}
|
||||
val, err := converter.convert(anyProto)
|
||||
if err != nil {
|
||||
// If the converter fails, nack the whole resource.
|
||||
return nil, fmt.Errorf("metadata conversion for key %q and type %q failed: %v", key, anyProto.GetTypeUrl(), err)
|
||||
}
|
||||
metadata[key] = val
|
||||
}
|
||||
|
||||
// Process FilterMetadata for any keys not already handled.
|
||||
for key, structProto := range metadataProto.GetFilterMetadata() {
|
||||
// Skip keys already added from TypedFilterMetadata.
|
||||
if metadata[key] != nil {
|
||||
continue
|
||||
}
|
||||
metadata[key] = StructMetadataValue{Data: structProto.AsMap()}
|
||||
}
|
||||
return metadata, nil
|
||||
}
|
||||
Reference in New Issue
Block a user