• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grpc / grpc-java / #19535

30 Oct 2024 03:41PM CUT coverage: 84.572% (-0.005%) from 84.577%
#19535

push

github

web-flow
xds: Per-rpc rewriting of the authority header based on the selected route. (#11631)

Implementation of A81.

33970 of 40167 relevant lines covered (84.57%)

0.85 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.37
/../xds/src/main/java/io/grpc/xds/XdsEndpointResource.java
1
/*
2
 * Copyright 2022 The gRPC Authors
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package io.grpc.xds;
18

19
import static com.google.common.base.Preconditions.checkNotNull;
20

21
import com.google.common.annotations.VisibleForTesting;
22
import com.google.common.base.MoreObjects;
23
import com.google.protobuf.Message;
24
import io.envoyproxy.envoy.config.core.v3.Address;
25
import io.envoyproxy.envoy.config.core.v3.HealthStatus;
26
import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment;
27
import io.envoyproxy.envoy.config.endpoint.v3.Endpoint;
28
import io.envoyproxy.envoy.type.v3.FractionalPercent;
29
import io.grpc.EquivalentAddressGroup;
30
import io.grpc.internal.GrpcUtil;
31
import io.grpc.xds.Endpoints.DropOverload;
32
import io.grpc.xds.Endpoints.LocalityLbEndpoints;
33
import io.grpc.xds.XdsEndpointResource.EdsUpdate;
34
import io.grpc.xds.client.Locality;
35
import io.grpc.xds.client.XdsClient.ResourceUpdate;
36
import io.grpc.xds.client.XdsResourceType;
37
import java.net.InetSocketAddress;
38
import java.util.ArrayList;
39
import java.util.Collections;
40
import java.util.HashMap;
41
import java.util.HashSet;
42
import java.util.LinkedHashMap;
43
import java.util.List;
44
import java.util.Map;
45
import java.util.Objects;
46
import java.util.Set;
47
import javax.annotation.Nullable;
48

49
class XdsEndpointResource extends XdsResourceType<EdsUpdate> {
1✔
50
  static final String ADS_TYPE_URL_EDS =
51
      "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment";
52

53
  public static final String GRPC_EXPERIMENTAL_XDS_DUALSTACK_ENDPOINTS =
54
      "GRPC_EXPERIMENTAL_XDS_DUALSTACK_ENDPOINTS";
55

56
  private static final XdsEndpointResource instance = new XdsEndpointResource();
1✔
57

58
  static XdsEndpointResource getInstance() {
59
    return instance;
1✔
60
  }
61

62
  @Override
63
  @Nullable
64
  protected String extractResourceName(Message unpackedResource) {
65
    if (!(unpackedResource instanceof ClusterLoadAssignment)) {
1✔
66
      return null;
×
67
    }
68
    return ((ClusterLoadAssignment) unpackedResource).getClusterName();
1✔
69
  }
70

71
  @Override
72
  public String typeName() {
73
    return "EDS";
1✔
74
  }
75

76
  @Override
77
  public String typeUrl() {
78
    return ADS_TYPE_URL_EDS;
1✔
79
  }
80

81
  @Override
82
  public boolean shouldRetrieveResourceKeysForArgs() {
83
    return true;
1✔
84
  }
85

86
  @Override
87
  protected boolean isFullStateOfTheWorld() {
88
    return false;
1✔
89
  }
90

91
  @Override
92
  protected Class<ClusterLoadAssignment> unpackedClassName() {
93
    return ClusterLoadAssignment.class;
1✔
94
  }
95

96
  @Override
97
  protected EdsUpdate doParse(Args args, Message unpackedMessage) throws ResourceInvalidException {
98
    if (!(unpackedMessage instanceof ClusterLoadAssignment)) {
1✔
99
      throw new ResourceInvalidException("Invalid message type: " + unpackedMessage.getClass());
×
100
    }
101
    return processClusterLoadAssignment((ClusterLoadAssignment) unpackedMessage);
1✔
102
  }
103

104
  private static boolean isEnabledXdsDualStack() {
105
    return GrpcUtil.getFlag(GRPC_EXPERIMENTAL_XDS_DUALSTACK_ENDPOINTS, false);
1✔
106
  }
107

108
  private static EdsUpdate processClusterLoadAssignment(ClusterLoadAssignment assignment)
109
      throws ResourceInvalidException {
110
    Map<Integer, Set<Locality>> priorities = new HashMap<>();
1✔
111
    Map<Locality, LocalityLbEndpoints> localityLbEndpointsMap = new LinkedHashMap<>();
1✔
112
    List<Endpoints.DropOverload> dropOverloads = new ArrayList<>();
1✔
113
    int maxPriority = -1;
1✔
114
    for (io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints localityLbEndpointsProto
115
        : assignment.getEndpointsList()) {
1✔
116
      StructOrError<LocalityLbEndpoints> structOrError =
1✔
117
          parseLocalityLbEndpoints(localityLbEndpointsProto);
1✔
118
      if (structOrError == null) {
1✔
119
        continue;
1✔
120
      }
121
      if (structOrError.getErrorDetail() != null) {
1✔
122
        throw new ResourceInvalidException(structOrError.getErrorDetail());
1✔
123
      }
124

125
      LocalityLbEndpoints localityLbEndpoints = structOrError.getStruct();
1✔
126
      int priority = localityLbEndpoints.priority();
1✔
127
      maxPriority = Math.max(maxPriority, priority);
1✔
128
      // Note endpoints with health status other than HEALTHY and UNKNOWN are still
129
      // handed over to watching parties. It is watching parties' responsibility to
130
      // filter out unhealthy endpoints. See EnvoyProtoData.LbEndpoint#isHealthy().
131
      Locality locality =  parseLocality(localityLbEndpointsProto.getLocality());
1✔
132
      localityLbEndpointsMap.put(locality, localityLbEndpoints);
1✔
133
      if (!priorities.containsKey(priority)) {
1✔
134
        priorities.put(priority, new HashSet<>());
1✔
135
      }
136
      if (!priorities.get(priority).add(locality)) {
1✔
137
        throw new ResourceInvalidException("ClusterLoadAssignment has duplicate locality:"
1✔
138
            + locality + " for priority:" + priority);
139
      }
140
    }
1✔
141
    if (priorities.size() != maxPriority + 1) {
1✔
142
      throw new ResourceInvalidException("ClusterLoadAssignment has sparse priorities");
×
143
    }
144

145
    for (ClusterLoadAssignment.Policy.DropOverload dropOverloadProto
146
        : assignment.getPolicy().getDropOverloadsList()) {
1✔
147
      dropOverloads.add(parseDropOverload(dropOverloadProto));
1✔
148
    }
1✔
149
    return new EdsUpdate(assignment.getClusterName(), localityLbEndpointsMap, dropOverloads);
1✔
150
  }
151

152
  private static Locality parseLocality(io.envoyproxy.envoy.config.core.v3.Locality proto) {
153
    return Locality.create(proto.getRegion(), proto.getZone(), proto.getSubZone());
1✔
154
  }
155

156
  private static DropOverload parseDropOverload(
157
      io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment.Policy.DropOverload proto) {
158
    return DropOverload.create(proto.getCategory(), getRatePerMillion(proto.getDropPercentage()));
1✔
159
  }
160

161
  private static int getRatePerMillion(FractionalPercent percent) {
162
    int numerator = percent.getNumerator();
1✔
163
    FractionalPercent.DenominatorType type = percent.getDenominator();
1✔
164
    switch (type) {
1✔
165
      case TEN_THOUSAND:
166
        numerator *= 100;
×
167
        break;
×
168
      case HUNDRED:
169
        numerator *= 10_000;
×
170
        break;
×
171
      case MILLION:
172
        break;
1✔
173
      case UNRECOGNIZED:
174
      default:
175
        throw new IllegalArgumentException("Unknown denominator type of " + percent);
×
176
    }
177

178
    if (numerator > 1_000_000 || numerator < 0) {
1✔
179
      numerator = 1_000_000;
×
180
    }
181
    return numerator;
1✔
182
  }
183

184

185
  @VisibleForTesting
186
  @Nullable
187
  static StructOrError<LocalityLbEndpoints> parseLocalityLbEndpoints(
188
      io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints proto) {
189
    // Filter out localities without or with 0 weight.
190
    if (!proto.hasLoadBalancingWeight() || proto.getLoadBalancingWeight().getValue() < 1) {
1✔
191
      return null;
1✔
192
    }
193
    if (proto.getPriority() < 0) {
1✔
194
      return StructOrError.fromError("negative priority");
1✔
195
    }
196
    List<Endpoints.LbEndpoint> endpoints = new ArrayList<>(proto.getLbEndpointsCount());
1✔
197
    for (io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint endpoint : proto.getLbEndpointsList()) {
1✔
198
      // The endpoint field of each lb_endpoints must be set.
199
      // Inside of it: the address field must be set.
200
      if (!endpoint.hasEndpoint() || !endpoint.getEndpoint().hasAddress()) {
1✔
201
        return StructOrError.fromError("LbEndpoint with no endpoint/address");
×
202
      }
203
      List<java.net.SocketAddress> addresses = new ArrayList<>();
1✔
204
      addresses.add(getInetSocketAddress(endpoint.getEndpoint().getAddress()));
1✔
205

206
      if (isEnabledXdsDualStack()) {
1✔
207
        for (Endpoint.AdditionalAddress additionalAddress
208
            : endpoint.getEndpoint().getAdditionalAddressesList()) {
1✔
209
          addresses.add(getInetSocketAddress(additionalAddress.getAddress()));
1✔
210
        }
1✔
211
      }
212
      boolean isHealthy = (endpoint.getHealthStatus() == HealthStatus.HEALTHY)
1✔
213
              || (endpoint.getHealthStatus() == HealthStatus.UNKNOWN);
1✔
214
      endpoints.add(Endpoints.LbEndpoint.create(
1✔
215
          new EquivalentAddressGroup(addresses),
216
          endpoint.getLoadBalancingWeight().getValue(), isHealthy,
1✔
217
          endpoint.getEndpoint().getHostname()));
1✔
218
    }
1✔
219
    return StructOrError.fromStruct(Endpoints.LocalityLbEndpoints.create(
1✔
220
        endpoints, proto.getLoadBalancingWeight().getValue(), proto.getPriority()));
1✔
221
  }
222

223
  private static InetSocketAddress getInetSocketAddress(Address address) {
224
    io.envoyproxy.envoy.config.core.v3.SocketAddress socketAddress = address.getSocketAddress();
1✔
225

226
    return new InetSocketAddress(socketAddress.getAddress(), socketAddress.getPortValue());
1✔
227
  }
228

229
  static final class EdsUpdate implements ResourceUpdate {
230
    final String clusterName;
231
    final Map<Locality, LocalityLbEndpoints> localityLbEndpointsMap;
232
    final List<DropOverload> dropPolicies;
233

234
    EdsUpdate(String clusterName, Map<Locality, LocalityLbEndpoints> localityLbEndpoints,
235
              List<DropOverload> dropPolicies) {
1✔
236
      this.clusterName = checkNotNull(clusterName, "clusterName");
1✔
237
      this.localityLbEndpointsMap = Collections.unmodifiableMap(
1✔
238
          new LinkedHashMap<>(checkNotNull(localityLbEndpoints, "localityLbEndpoints")));
1✔
239
      this.dropPolicies = Collections.unmodifiableList(
1✔
240
          new ArrayList<>(checkNotNull(dropPolicies, "dropPolicies")));
1✔
241
    }
1✔
242

243
    @Override
244
    public boolean equals(Object o) {
245
      if (this == o) {
1✔
246
        return true;
×
247
      }
248
      if (o == null || getClass() != o.getClass()) {
1✔
249
        return false;
×
250
      }
251
      EdsUpdate that = (EdsUpdate) o;
1✔
252
      return Objects.equals(clusterName, that.clusterName)
1✔
253
          && Objects.equals(localityLbEndpointsMap, that.localityLbEndpointsMap)
1✔
254
          && Objects.equals(dropPolicies, that.dropPolicies);
1✔
255
    }
256

257
    @Override
258
    public int hashCode() {
259
      return Objects.hash(clusterName, localityLbEndpointsMap, dropPolicies);
×
260
    }
261

262
    @Override
263
    public String toString() {
264
      return
×
265
          MoreObjects
266
              .toStringHelper(this)
×
267
              .add("clusterName", clusterName)
×
268
              .add("localityLbEndpointsMap", localityLbEndpointsMap)
×
269
              .add("dropPolicies", dropPolicies)
×
270
              .toString();
×
271
    }
272
  }
273
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc