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