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 }