google.golang.org/grpc@v1.74.2/xds/internal/xdsclient/xdsresource/unmarshal_eds.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  	"math"
    23  	"net"
    24  	"strconv"
    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  	v3typepb "github.com/envoyproxy/go-control-plane/envoy/type/v3"
    29  	"google.golang.org/grpc/internal/envconfig"
    30  	"google.golang.org/grpc/internal/pretty"
    31  	"google.golang.org/grpc/xds/internal"
    32  	"google.golang.org/grpc/xds/internal/clients"
    33  	"google.golang.org/protobuf/proto"
    34  	"google.golang.org/protobuf/types/known/anypb"
    35  )
    36  
    37  func unmarshalEndpointsResource(r *anypb.Any) (string, EndpointsUpdate, error) {
    38  	r, err := UnwrapResource(r)
    39  	if err != nil {
    40  		return "", EndpointsUpdate{}, fmt.Errorf("failed to unwrap resource: %v", err)
    41  	}
    42  
    43  	if !IsEndpointsResource(r.GetTypeUrl()) {
    44  		return "", EndpointsUpdate{}, fmt.Errorf("unexpected resource type: %q ", r.GetTypeUrl())
    45  	}
    46  
    47  	cla := &v3endpointpb.ClusterLoadAssignment{}
    48  	if err := proto.Unmarshal(r.GetValue(), cla); err != nil {
    49  		return "", EndpointsUpdate{}, fmt.Errorf("failed to unmarshal resource: %v", err)
    50  	}
    51  
    52  	u, err := parseEDSRespProto(cla)
    53  	if err != nil {
    54  		return cla.GetClusterName(), EndpointsUpdate{}, err
    55  	}
    56  	u.Raw = r
    57  	return cla.GetClusterName(), u, nil
    58  }
    59  
    60  func parseAddress(socketAddress *v3corepb.SocketAddress) string {
    61  	return net.JoinHostPort(socketAddress.GetAddress(), strconv.Itoa(int(socketAddress.GetPortValue())))
    62  }
    63  
    64  func parseDropPolicy(dropPolicy *v3endpointpb.ClusterLoadAssignment_Policy_DropOverload) OverloadDropConfig {
    65  	percentage := dropPolicy.GetDropPercentage()
    66  	var (
    67  		numerator   = percentage.GetNumerator()
    68  		denominator uint32
    69  	)
    70  	switch percentage.GetDenominator() {
    71  	case v3typepb.FractionalPercent_HUNDRED:
    72  		denominator = 100
    73  	case v3typepb.FractionalPercent_TEN_THOUSAND:
    74  		denominator = 10000
    75  	case v3typepb.FractionalPercent_MILLION:
    76  		denominator = 1000000
    77  	}
    78  	return OverloadDropConfig{
    79  		Category:    dropPolicy.GetCategory(),
    80  		Numerator:   numerator,
    81  		Denominator: denominator,
    82  	}
    83  }
    84  
    85  func parseEndpoints(lbEndpoints []*v3endpointpb.LbEndpoint, uniqueEndpointAddrs map[string]bool) ([]Endpoint, error) {
    86  	endpoints := make([]Endpoint, 0, len(lbEndpoints))
    87  	for _, lbEndpoint := range lbEndpoints {
    88  		// If the load_balancing_weight field is specified, it must be set to a
    89  		// value of at least 1.  If unspecified, each host is presumed to have
    90  		// equal weight in a locality.
    91  		weight := uint32(1)
    92  		if w := lbEndpoint.GetLoadBalancingWeight(); w != nil {
    93  			if w.GetValue() == 0 {
    94  				return nil, fmt.Errorf("EDS response contains an endpoint with zero weight: %+v", lbEndpoint)
    95  			}
    96  			weight = w.GetValue()
    97  		}
    98  		addrs := []string{parseAddress(lbEndpoint.GetEndpoint().GetAddress().GetSocketAddress())}
    99  		if envconfig.XDSDualstackEndpointsEnabled {
   100  			for _, sa := range lbEndpoint.GetEndpoint().GetAdditionalAddresses() {
   101  				addrs = append(addrs, parseAddress(sa.GetAddress().GetSocketAddress()))
   102  			}
   103  		}
   104  
   105  		for _, a := range addrs {
   106  			if uniqueEndpointAddrs[a] {
   107  				return nil, fmt.Errorf("duplicate endpoint with the same address %s", a)
   108  			}
   109  			uniqueEndpointAddrs[a] = true
   110  		}
   111  		endpoints = append(endpoints, Endpoint{
   112  			HealthStatus: EndpointHealthStatus(lbEndpoint.GetHealthStatus()),
   113  			Addresses:    addrs,
   114  			Weight:       weight,
   115  			HashKey:      hashKey(lbEndpoint),
   116  		})
   117  	}
   118  	return endpoints, nil
   119  }
   120  
   121  // hashKey extracts and returns the hash key from the given LbEndpoint. If no
   122  // hash key is found, it returns an empty string.
   123  func hashKey(lbEndpoint *v3endpointpb.LbEndpoint) string {
   124  	// "The xDS resolver, described in A74, will be changed to set the hash_key
   125  	// endpoint attribute to the value of LbEndpoint.Metadata envoy.lb hash_key
   126  	// field, as described in Envoy's documentation for the ring hash load
   127  	// balancer." - A76
   128  	if envconfig.XDSEndpointHashKeyBackwardCompat {
   129  		return ""
   130  	}
   131  	envoyLB := lbEndpoint.GetMetadata().GetFilterMetadata()["envoy.lb"]
   132  	if envoyLB != nil {
   133  		if h := envoyLB.GetFields()["hash_key"]; h != nil {
   134  			return h.GetStringValue()
   135  		}
   136  	}
   137  	return ""
   138  }
   139  
   140  func parseEDSRespProto(m *v3endpointpb.ClusterLoadAssignment) (EndpointsUpdate, error) {
   141  	ret := EndpointsUpdate{}
   142  	for _, dropPolicy := range m.GetPolicy().GetDropOverloads() {
   143  		ret.Drops = append(ret.Drops, parseDropPolicy(dropPolicy))
   144  	}
   145  	priorities := make(map[uint32]map[string]bool)
   146  	sumOfWeights := make(map[uint32]uint64)
   147  	uniqueEndpointAddrs := make(map[string]bool)
   148  	for _, locality := range m.Endpoints {
   149  		l := locality.GetLocality()
   150  		if l == nil {
   151  			return EndpointsUpdate{}, fmt.Errorf("EDS response contains a locality without ID, locality: %+v", locality)
   152  		}
   153  		weight := locality.GetLoadBalancingWeight().GetValue()
   154  		if weight == 0 {
   155  			logger.Warningf("Ignoring locality %s with weight 0", pretty.ToJSON(l))
   156  			continue
   157  		}
   158  		priority := locality.GetPriority()
   159  		sumOfWeights[priority] += uint64(weight)
   160  		if sumOfWeights[priority] > math.MaxUint32 {
   161  			return EndpointsUpdate{}, fmt.Errorf("sum of weights of localities at the same priority %d exceeded maximal value", priority)
   162  		}
   163  		localitiesWithPriority := priorities[priority]
   164  		if localitiesWithPriority == nil {
   165  			localitiesWithPriority = make(map[string]bool)
   166  			priorities[priority] = localitiesWithPriority
   167  		}
   168  		lid := clients.Locality{
   169  			Region:  l.Region,
   170  			Zone:    l.Zone,
   171  			SubZone: l.SubZone,
   172  		}
   173  		lidStr := internal.LocalityString(lid)
   174  
   175  		// "Since an xDS configuration can place a given locality under multiple
   176  		// priorities, it is possible to see locality weight attributes with
   177  		// different values for the same locality." - A52
   178  		//
   179  		// This is handled in the client by emitting the locality weight
   180  		// specified for the priority it is specified in. If the same locality
   181  		// has a different weight in two priorities, each priority will specify
   182  		// a locality with the locality weight specified for that priority, and
   183  		// thus the subsequent tree of balancers linked to that priority will
   184  		// use that locality weight as well.
   185  		if localitiesWithPriority[lidStr] {
   186  			return EndpointsUpdate{}, fmt.Errorf("duplicate locality %s with the same priority %v", lidStr, priority)
   187  		}
   188  		localitiesWithPriority[lidStr] = true
   189  		endpoints, err := parseEndpoints(locality.GetLbEndpoints(), uniqueEndpointAddrs)
   190  		if err != nil {
   191  			return EndpointsUpdate{}, err
   192  		}
   193  		ret.Localities = append(ret.Localities, Locality{
   194  			ID:        lid,
   195  			Endpoints: endpoints,
   196  			Weight:    weight,
   197  			Priority:  priority,
   198  		})
   199  	}
   200  	for i := 0; i < len(priorities); i++ {
   201  		if _, ok := priorities[uint32(i)]; !ok {
   202  			return EndpointsUpdate{}, fmt.Errorf("priority %v missing (with different priorities %v received)", i, priorities)
   203  		}
   204  	}
   205  	return ret, nil
   206  }