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

grpc / grpc-java / #20297

01 Jun 2026 04:04AM UTC coverage: 88.886% (-0.002%) from 88.888%
#20297

push

github

web-flow
xds: Hold parsed service config in CdsUpdate

This avoids re-parsing the config within CdsLB, as the providers could
have changed and the config may no longer be valid.

Many usages of ServiceConfigUtil.unwrapLoadBalancingConfig() were
replaced with public API, which should be less brittle to internal
changes. Similarly, config.equals() was added for LBs least_request,
ring_hash, wrr to use more public APIs in testing. But I've gone out of
my way to avoid using equals for XdsClient change detection, by
preserving the original "JSON" config.

This fixes a bug in WRR config parsing which prevented it from parsing
errorUtilizationPenalty as it assumed it would be a Float, not a Double
like our parser actually generates and our API requires.
JsonUtil.getNumberAsFloat() was added specifically for WRR and has never
worked as JSON Numbers will always be Doubles.

In XdsClusterResource.CdsUpdate, the LB-specific fields like minRingSize
were already not used at all, so this commit deletes them as it seems a
relevant cleanup.

Fixes #12733

36493 of 41056 relevant lines covered (88.89%)

0.89 hits per line

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

93.46
/../xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java
1
/*
2
 * Copyright 2020 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
import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
21
import static io.grpc.xds.XdsLbPolicies.CDS_POLICY_NAME;
22
import static io.grpc.xds.XdsLbPolicies.PRIORITY_POLICY_NAME;
23

24
import com.google.common.collect.ImmutableMap;
25
import com.google.common.primitives.UnsignedInts;
26
import com.google.errorprone.annotations.CheckReturnValue;
27
import io.grpc.Attributes;
28
import io.grpc.EquivalentAddressGroup;
29
import io.grpc.HttpConnectProxiedSocketAddress;
30
import io.grpc.InternalEquivalentAddressGroup;
31
import io.grpc.InternalLogId;
32
import io.grpc.LoadBalancer;
33
import io.grpc.LoadBalancerProvider;
34
import io.grpc.LoadBalancerRegistry;
35
import io.grpc.Status;
36
import io.grpc.StatusOr;
37
import io.grpc.internal.GrpcUtil;
38
import io.grpc.util.GracefulSwitchLoadBalancer;
39
import io.grpc.util.OutlierDetectionLoadBalancer.OutlierDetectionLoadBalancerConfig;
40
import io.grpc.xds.CdsLoadBalancerProvider.CdsConfig;
41
import io.grpc.xds.ClusterImplLoadBalancerProvider.ClusterImplConfig;
42
import io.grpc.xds.Endpoints.DropOverload;
43
import io.grpc.xds.Endpoints.LbEndpoint;
44
import io.grpc.xds.Endpoints.LocalityLbEndpoints;
45
import io.grpc.xds.EnvoyServerProtoData.FailurePercentageEjection;
46
import io.grpc.xds.EnvoyServerProtoData.OutlierDetection;
47
import io.grpc.xds.EnvoyServerProtoData.SuccessRateEjection;
48
import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig;
49
import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig.PriorityChildConfig;
50
import io.grpc.xds.XdsClusterResource.CdsUpdate;
51
import io.grpc.xds.XdsClusterResource.CdsUpdate.ClusterType;
52
import io.grpc.xds.XdsConfig.Subscription;
53
import io.grpc.xds.XdsConfig.XdsClusterConfig;
54
import io.grpc.xds.XdsConfig.XdsClusterConfig.AggregateConfig;
55
import io.grpc.xds.XdsConfig.XdsClusterConfig.EndpointConfig;
56
import io.grpc.xds.XdsEndpointResource.EdsUpdate;
57
import io.grpc.xds.client.Locality;
58
import io.grpc.xds.client.XdsLogger;
59
import io.grpc.xds.client.XdsLogger.XdsLogLevel;
60
import io.grpc.xds.internal.XdsInternalAttributes;
61
import java.net.InetSocketAddress;
62
import java.net.SocketAddress;
63
import java.util.ArrayList;
64
import java.util.Arrays;
65
import java.util.Collections;
66
import java.util.HashMap;
67
import java.util.HashSet;
68
import java.util.List;
69
import java.util.Map;
70
import java.util.Set;
71
import java.util.TreeMap;
72

73
/**
74
 * Load balancer for cds_experimental LB policy. One instance per top-level cluster.
75
 * The top-level cluster may be a plain EDS/logical-DNS cluster or an aggregate cluster formed
76
 * by a group of sub-clusters in a tree hierarchy.
77
 */
78
final class CdsLoadBalancer2 extends LoadBalancer {
79
  static boolean pickFirstWeightedShuffling =
1✔
80
      GrpcUtil.getFlag("GRPC_EXPERIMENTAL_PF_WEIGHTED_SHUFFLING", true);
1✔
81

82
  private final XdsLogger logger;
83
  private final Helper helper;
84
  private final LoadBalancerRegistry lbRegistry;
85
  private final ClusterState clusterState = new ClusterState();
1✔
86
  private GracefulSwitchLoadBalancer delegate;
87
  // Following fields are effectively final.
88
  private String clusterName;
89
  private Subscription clusterSubscription;
90

91
  CdsLoadBalancer2(Helper helper, LoadBalancerRegistry lbRegistry) {
1✔
92
    this.helper = checkNotNull(helper, "helper");
1✔
93
    this.lbRegistry = checkNotNull(lbRegistry, "lbRegistry");
1✔
94
    this.delegate = new GracefulSwitchLoadBalancer(helper);
1✔
95
    logger = XdsLogger.withLogId(InternalLogId.allocate("cds-lb", helper.getAuthority()));
1✔
96
    logger.log(XdsLogLevel.INFO, "Created");
1✔
97
  }
1✔
98

99
  @Override
100
  public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
101
    logger.log(XdsLogLevel.DEBUG, "Received resolution result: {0}", resolvedAddresses);
1✔
102
    if (this.clusterName == null) {
1✔
103
      CdsConfig config = (CdsConfig) resolvedAddresses.getLoadBalancingPolicyConfig();
1✔
104
      logger.log(XdsLogLevel.INFO, "Config: {0}", config);
1✔
105
      if (config.isDynamic) {
1✔
106
        clusterSubscription = resolvedAddresses.getAttributes()
1✔
107
            .get(XdsAttributes.XDS_CLUSTER_SUBSCRIPT_REGISTRY)
1✔
108
            .subscribeToCluster(config.name);
1✔
109
      }
110
      this.clusterName = config.name;
1✔
111
    }
112
    XdsConfig xdsConfig = resolvedAddresses.getAttributes().get(XdsAttributes.XDS_CONFIG);
1✔
113
    StatusOr<XdsClusterConfig> clusterConfigOr = xdsConfig.getClusters().get(clusterName);
1✔
114
    if (clusterConfigOr == null) {
1✔
115
      if (clusterSubscription == null) {
1✔
116
        // Should be impossible, because XdsDependencyManager wouldn't have generated this
117
        return fail(Status.INTERNAL.withDescription(
×
118
            errorPrefix() + "Unable to find non-dynamic cluster"));
×
119
      }
120
      // The dynamic cluster must not have loaded yet
121
      return Status.OK;
1✔
122
    }
123
    if (!clusterConfigOr.hasValue()) {
1✔
124
      return fail(clusterConfigOr.getStatus());
1✔
125
    }
126
    XdsClusterConfig clusterConfig = clusterConfigOr.getValue();
1✔
127

128
    if (clusterConfig.getChildren() instanceof EndpointConfig) {
1✔
129
      StatusOr<EdsUpdate> edsUpdate = getEdsUpdate(xdsConfig, clusterName);
1✔
130
      StatusOr<ClusterResolutionResult> statusOrResult = clusterState.edsUpdateToResult(
1✔
131
          clusterName,
132
          clusterConfig.getClusterResource(),
1✔
133
          clusterConfig.getClusterResource().lbPolicyConfig(),
1✔
134
          edsUpdate);
135
      if (!statusOrResult.hasValue()) {
1✔
136
        Status status = Status.UNAVAILABLE
1✔
137
            .withDescription(statusOrResult.getStatus().getDescription())
1✔
138
            .withCause(statusOrResult.getStatus().getCause());
1✔
139
        delegate.handleNameResolutionError(status);
1✔
140
        return status;
1✔
141
      }
142
      ClusterResolutionResult result = statusOrResult.getValue();
1✔
143
      List<EquivalentAddressGroup> addresses = result.addresses;
1✔
144
      if (addresses.isEmpty()) {
1✔
145
        Status status = Status.UNAVAILABLE
1✔
146
            .withDescription("No usable endpoint from cluster: " + clusterName);
1✔
147
        delegate.handleNameResolutionError(status);
1✔
148
        return status;
1✔
149
      }
150
      Object gracefulConfig = GracefulSwitchLoadBalancer.createLoadBalancingPolicyConfig(
1✔
151
          lbRegistry.getProvider(PRIORITY_POLICY_NAME),
1✔
152
          new PriorityLbConfig(
153
              Collections.unmodifiableMap(result.priorityChildConfigs),
1✔
154
              Collections.unmodifiableList(result.priorities)));
1✔
155
      return delegate.acceptResolvedAddresses(
1✔
156
          resolvedAddresses.toBuilder()
1✔
157
            .setLoadBalancingPolicyConfig(gracefulConfig)
1✔
158
            .setAddresses(Collections.unmodifiableList(addresses))
1✔
159
            .build());
1✔
160
    } else if (clusterConfig.getChildren() instanceof AggregateConfig) {
1✔
161
      Map<String, PriorityChildConfig> priorityChildConfigs = new HashMap<>();
1✔
162
      List<String> leafClusters = ((AggregateConfig) clusterConfig.getChildren()).getLeafNames();
1✔
163
      for (String childCluster: leafClusters) {
1✔
164
        priorityChildConfigs.put(childCluster,
1✔
165
                new PriorityChildConfig(
166
                        GracefulSwitchLoadBalancer.createLoadBalancingPolicyConfig(
1✔
167
                                lbRegistry.getProvider(CDS_POLICY_NAME),
1✔
168
                                new CdsConfig(childCluster)),
169
                        false));
170
      }
1✔
171
      Object gracefulConfig = GracefulSwitchLoadBalancer.createLoadBalancingPolicyConfig(
1✔
172
          lbRegistry.getProvider(PRIORITY_POLICY_NAME),
1✔
173
          new PriorityLoadBalancerProvider.PriorityLbConfig(
174
              Collections.unmodifiableMap(priorityChildConfigs), leafClusters));
1✔
175
      return delegate.acceptResolvedAddresses(
1✔
176
          resolvedAddresses.toBuilder().setLoadBalancingPolicyConfig(gracefulConfig).build());
1✔
177
    } else {
178
      return fail(Status.INTERNAL.withDescription(
×
179
              errorPrefix() + "Unexpected cluster children type: "
×
180
                      + clusterConfig.getChildren().getClass()));
×
181
    }
182
  }
183

184
  @Override
185
  public void handleNameResolutionError(Status error) {
186
    logger.log(XdsLogLevel.WARNING, "Received name resolution error: {0}", error);
1✔
187
    if (delegate != null) {
1✔
188
      delegate.handleNameResolutionError(error);
1✔
189
    } else {
190
      helper.updateBalancingState(
×
191
          TRANSIENT_FAILURE, new FixedResultPicker(PickResult.withError(error)));
×
192
    }
193
  }
1✔
194

195
  @Override
196
  public void shutdown() {
197
    logger.log(XdsLogLevel.INFO, "Shutdown");
1✔
198
    delegate.shutdown();
1✔
199
    delegate = new GracefulSwitchLoadBalancer(helper);
1✔
200
    if (clusterSubscription != null) {
1✔
201
      clusterSubscription.close();
1✔
202
      clusterSubscription = null;
1✔
203
    }
204
  }
1✔
205

206
  @CheckReturnValue // don't forget to return up the stack after the fail call
207
  private Status fail(Status error) {
208
    delegate.shutdown();
1✔
209
    helper.updateBalancingState(
1✔
210
        TRANSIENT_FAILURE, new FixedResultPicker(PickResult.withError(error)));
1✔
211
    return Status.OK; // XdsNameResolver isn't a polling NR, so this value doesn't matter
1✔
212
  }
213

214
  private String errorPrefix() {
215
    return "CdsLb for " + clusterName + ": ";
×
216
  }
217

218
  /**
219
   * The number of bits assigned to the fractional part of fixed-point values. We normalize weights
220
   * to a fixed-point number between 0 and 1, representing that item's proportion of traffic (1 ==
221
   * 100% of traffic). We reserve at least one bit for the whole number so that we don't need to
222
   * special case a single item, and so that we can round up very low values without risking uint32
223
   * overflow of the sum of weights.
224
   */
225
  private static final int FIXED_POINT_FRACTIONAL_BITS = 31;
226

227
  /** Divide two uint32s and produce a fixed-point uint32 result. */
228
  private static long fractionToFixedPoint(long numerator, long denominator) {
229
    long one = 1L << FIXED_POINT_FRACTIONAL_BITS;
1✔
230
    return numerator * one / denominator;
1✔
231
  }
232

233
  /** Multiply two uint32 fixed-point numbers, returning a uint32 fixed-point. */
234
  private static long fixedPointMultiply(long a, long b) {
235
    return (a * b) >> FIXED_POINT_FRACTIONAL_BITS;
1✔
236
  }
237

238
  private static StatusOr<EdsUpdate> getEdsUpdate(XdsConfig xdsConfig, String cluster) {
239
    StatusOr<XdsClusterConfig> clusterConfig = xdsConfig.getClusters().get(cluster);
1✔
240
    if (clusterConfig == null) {
1✔
241
      return StatusOr.fromStatus(Status.INTERNAL
×
242
          .withDescription("BUG: cluster resolver could not find cluster in xdsConfig"));
×
243
    }
244
    if (!clusterConfig.hasValue()) {
1✔
245
      return StatusOr.fromStatus(clusterConfig.getStatus());
×
246
    }
247
    if (!(clusterConfig.getValue().getChildren() instanceof XdsClusterConfig.EndpointConfig)) {
1✔
248
      return StatusOr.fromStatus(Status.INTERNAL
×
249
          .withDescription("BUG: cluster resolver cluster with children of unknown type"));
×
250
    }
251
    XdsClusterConfig.EndpointConfig endpointConfig =
1✔
252
        (XdsClusterConfig.EndpointConfig) clusterConfig.getValue().getChildren();
1✔
253
    return endpointConfig.getEndpoint();
1✔
254
  }
255

256
  /**
257
   * Generates a string that represents the priority in the LB policy config. The string is unique
258
   * across priorities in all clusters and priorityName(c, p1) < priorityName(c, p2) iff p1 < p2.
259
   * The ordering is undefined for priorities in different clusters.
260
   */
261
  private static String priorityName(String cluster, int priority) {
262
    return cluster + "[child" + priority + "]";
1✔
263
  }
264

265
  /**
266
   * Generates a string that represents the locality in the LB policy config. The string is unique
267
   * across all localities in all clusters.
268
   */
269
  private static String localityName(Locality locality) {
270
    return "{region=\"" + locality.region()
1✔
271
        + "\", zone=\"" + locality.zone()
1✔
272
        + "\", sub_zone=\"" + locality.subZone()
1✔
273
        + "\"}";
274
  }
275

276
  private final class ClusterState {
1✔
277
    private Map<Locality, String> localityPriorityNames = Collections.emptyMap();
1✔
278
    int priorityNameGenId = 1;
1✔
279

280
    StatusOr<ClusterResolutionResult> edsUpdateToResult(
281
        String clusterName,
282
        CdsUpdate discovery,
283
        Object lbConfig,
284
        StatusOr<EdsUpdate> updateOr) {
285
      if (!updateOr.hasValue()) {
1✔
286
        return StatusOr.fromStatus(updateOr.getStatus());
1✔
287
      }
288
      EdsUpdate update = updateOr.getValue();
1✔
289
      logger.log(XdsLogLevel.DEBUG, "Received endpoint update {0}", update);
1✔
290
      if (logger.isLoggable(XdsLogLevel.INFO)) {
1✔
291
        logger.log(XdsLogLevel.INFO, "Cluster {0}: {1} localities, {2} drop categories",
×
292
            clusterName, update.localityLbEndpointsMap.size(),
×
293
            update.dropPolicies.size());
×
294
      }
295
      Map<Locality, LocalityLbEndpoints> localityLbEndpoints =
1✔
296
          update.localityLbEndpointsMap;
297
      List<DropOverload> dropOverloads = update.dropPolicies;
1✔
298
      List<EquivalentAddressGroup> addresses = new ArrayList<>();
1✔
299
      Map<String, Map<Locality, Integer>> prioritizedLocalityWeights = new HashMap<>();
1✔
300
      List<String> sortedPriorityNames =
1✔
301
          generatePriorityNames(clusterName, localityLbEndpoints);
1✔
302
      Map<String, Long> priorityLocalityWeightSums;
303
      if (pickFirstWeightedShuffling) {
1✔
304
        priorityLocalityWeightSums = new HashMap<>(sortedPriorityNames.size() * 2);
1✔
305
        for (Locality locality : localityLbEndpoints.keySet()) {
1✔
306
          LocalityLbEndpoints localityLbInfo = localityLbEndpoints.get(locality);
1✔
307
          String priorityName = localityPriorityNames.get(locality);
1✔
308
          Long sum = priorityLocalityWeightSums.get(priorityName);
1✔
309
          if (sum == null) {
1✔
310
            sum = 0L;
1✔
311
          }
312
          long weight = UnsignedInts.toLong(localityLbInfo.localityWeight());
1✔
313
          priorityLocalityWeightSums.put(priorityName, sum + weight);
1✔
314
        }
1✔
315
      } else {
316
        priorityLocalityWeightSums = null;
1✔
317
      }
318

319
      for (Locality locality : localityLbEndpoints.keySet()) {
1✔
320
        LocalityLbEndpoints localityLbInfo = localityLbEndpoints.get(locality);
1✔
321
        String priorityName = localityPriorityNames.get(locality);
1✔
322
        String localityName = localityName(locality);
1✔
323
        AddressFilter.PathChain pathChain =
1✔
324
            AddressFilter.createPathChain(Arrays.asList(priorityName, localityName));
1✔
325

326
        boolean discard = true;
1✔
327
        // These sums _should_ fit in uint32, but XdsEndpointResource isn't actually verifying that
328
        // is true today. Since we are using long to avoid signedness trouble, the math happens to
329
        // still work if it turns out the sums exceed uint32.
330
        long localityWeightSum = 0;
1✔
331
        long endpointWeightSum = 0;
1✔
332
        if (pickFirstWeightedShuffling) {
1✔
333
          localityWeightSum = priorityLocalityWeightSums.get(priorityName);
1✔
334
          for (LbEndpoint endpoint : localityLbInfo.endpoints()) {
1✔
335
            if (endpoint.isHealthy()) {
1✔
336
              endpointWeightSum += UnsignedInts.toLong(endpoint.loadBalancingWeight());
1✔
337
            }
338
          }
1✔
339
        }
340
        for (LbEndpoint endpoint : localityLbInfo.endpoints()) {
1✔
341
          if (endpoint.isHealthy()) {
1✔
342
            discard = false;
1✔
343
            long weight;
344
            if (pickFirstWeightedShuffling) {
1✔
345
              // Combine locality and endpoint weights as defined by gRFC A113
346
              long localityWeight = fractionToFixedPoint(
1✔
347
                  UnsignedInts.toLong(localityLbInfo.localityWeight()), localityWeightSum);
1✔
348
              long endpointWeight = fractionToFixedPoint(
1✔
349
                  UnsignedInts.toLong(endpoint.loadBalancingWeight()), endpointWeightSum);
1✔
350
              weight = fixedPointMultiply(localityWeight, endpointWeight);
1✔
351
              if (weight == 0) {
1✔
352
                weight = 1;
×
353
              }
354
            } else {
1✔
355
              weight = localityLbInfo.localityWeight();
1✔
356
              if (endpoint.loadBalancingWeight() != 0) {
1✔
357
                weight *= endpoint.loadBalancingWeight();
1✔
358
              }
359
            }
360

361
            Attributes attr =
1✔
362
                endpoint.eag().getAttributes().toBuilder()
1✔
363
                    .set(InternalEquivalentAddressGroup.ATTR_BACKEND_SERVICE, clusterName)
1✔
364
                    .set(io.grpc.xds.XdsAttributes.ATTR_LOCALITY, locality)
1✔
365
                    .set(EquivalentAddressGroup.ATTR_LOCALITY_NAME, localityName)
1✔
366
                    .set(io.grpc.xds.XdsAttributes.ATTR_LOCALITY_WEIGHT,
1✔
367
                        localityLbInfo.localityWeight())
1✔
368
                    .set(io.grpc.xds.XdsAttributes.ATTR_SERVER_WEIGHT, weight)
1✔
369
                    .set(XdsInternalAttributes.ATTR_ADDRESS_NAME, endpoint.hostname())
1✔
370
                    .set(AddressFilter.PATH_CHAIN_KEY, pathChain)
1✔
371
                    .build();
1✔
372
            EquivalentAddressGroup eag;
373
            if (discovery.isHttp11ProxyAvailable()) {
1✔
374
              List<SocketAddress> rewrittenAddresses = new ArrayList<>();
1✔
375
              for (SocketAddress addr : endpoint.eag().getAddresses()) {
1✔
376
                rewrittenAddresses.add(rewriteAddress(
1✔
377
                    addr, endpoint.endpointMetadata(), localityLbInfo.localityMetadata()));
1✔
378
              }
1✔
379
              eag = new EquivalentAddressGroup(rewrittenAddresses, attr);
1✔
380
            } else {
1✔
381
              eag = new EquivalentAddressGroup(endpoint.eag().getAddresses(), attr);
1✔
382
            }
383
            addresses.add(eag);
1✔
384
          }
385
        }
1✔
386
        if (discard) {
1✔
387
          logger.log(XdsLogLevel.INFO,
1✔
388
              "Discard locality {0} with 0 healthy endpoints", locality);
389
          continue;
1✔
390
        }
391
        if (!prioritizedLocalityWeights.containsKey(priorityName)) {
1✔
392
          prioritizedLocalityWeights.put(priorityName, new HashMap<Locality, Integer>());
1✔
393
        }
394
        prioritizedLocalityWeights.get(priorityName).put(
1✔
395
            locality, localityLbInfo.localityWeight());
1✔
396
      }
1✔
397
      if (prioritizedLocalityWeights.isEmpty()) {
1✔
398
        // Will still update the result, as if the cluster resource is revoked.
399
        logger.log(XdsLogLevel.INFO,
1✔
400
            "Cluster {0} has no usable priority/locality/endpoint", clusterName);
401
      }
402
      sortedPriorityNames.retainAll(prioritizedLocalityWeights.keySet());
1✔
403
      Map<String, PriorityChildConfig> priorityChildConfigs =
1✔
404
          generatePriorityChildConfigs(
1✔
405
              clusterName, discovery, lbConfig, lbRegistry,
1✔
406
              prioritizedLocalityWeights, dropOverloads);
407
      return StatusOr.fromValue(new ClusterResolutionResult(addresses, priorityChildConfigs,
1✔
408
          sortedPriorityNames));
409
    }
410

411
    private SocketAddress rewriteAddress(SocketAddress addr,
412
        ImmutableMap<String, Object> endpointMetadata,
413
        ImmutableMap<String, Object> localityMetadata) {
414
      if (!(addr instanceof InetSocketAddress)) {
1✔
415
        return addr;
×
416
      }
417

418
      SocketAddress proxyAddress;
419
      try {
420
        proxyAddress = (SocketAddress) endpointMetadata.get(
1✔
421
            "envoy.http11_proxy_transport_socket.proxy_address");
422
        if (proxyAddress == null) {
1✔
423
          proxyAddress = (SocketAddress) localityMetadata.get(
1✔
424
              "envoy.http11_proxy_transport_socket.proxy_address");
425
        }
426
      } catch (ClassCastException e) {
×
427
        return addr;
×
428
      }
1✔
429

430
      if (proxyAddress == null) {
1✔
431
        return addr;
1✔
432
      }
433

434
      return HttpConnectProxiedSocketAddress.newBuilder()
1✔
435
          .setTargetAddress((InetSocketAddress) addr)
1✔
436
          .setProxyAddress(proxyAddress)
1✔
437
          .build();
1✔
438
    }
439

440
    private List<String> generatePriorityNames(String name,
441
        Map<Locality, LocalityLbEndpoints> localityLbEndpoints) {
442
      TreeMap<Integer, List<Locality>> todo = new TreeMap<>();
1✔
443
      for (Locality locality : localityLbEndpoints.keySet()) {
1✔
444
        int priority = localityLbEndpoints.get(locality).priority();
1✔
445
        if (!todo.containsKey(priority)) {
1✔
446
          todo.put(priority, new ArrayList<>());
1✔
447
        }
448
        todo.get(priority).add(locality);
1✔
449
      }
1✔
450
      Map<Locality, String> newNames = new HashMap<>();
1✔
451
      Set<String> usedNames = new HashSet<>();
1✔
452
      List<String> ret = new ArrayList<>();
1✔
453
      for (Integer priority: todo.keySet()) {
1✔
454
        String foundName = "";
1✔
455
        for (Locality locality : todo.get(priority)) {
1✔
456
          if (localityPriorityNames.containsKey(locality)
1✔
457
              && usedNames.add(localityPriorityNames.get(locality))) {
1✔
458
            foundName = localityPriorityNames.get(locality);
1✔
459
            break;
1✔
460
          }
461
        }
1✔
462
        if ("".equals(foundName)) {
1✔
463
          foundName = priorityName(name, priorityNameGenId++);
1✔
464
        }
465
        for (Locality locality : todo.get(priority)) {
1✔
466
          newNames.put(locality, foundName);
1✔
467
        }
1✔
468
        ret.add(foundName);
1✔
469
      }
1✔
470
      localityPriorityNames = newNames;
1✔
471
      return ret;
1✔
472
    }
473
  }
474

475
  private static class ClusterResolutionResult {
476
    // Endpoint addresses.
477
    private final List<EquivalentAddressGroup> addresses;
478
    // Config (include load balancing policy/config) for each priority in the cluster.
479
    private final Map<String, PriorityChildConfig> priorityChildConfigs;
480
    // List of priority names ordered in descending priorities.
481
    private final List<String> priorities;
482

483
    ClusterResolutionResult(List<EquivalentAddressGroup> addresses,
484
        Map<String, PriorityChildConfig> configs, List<String> priorities) {
1✔
485
      this.addresses = addresses;
1✔
486
      this.priorityChildConfigs = configs;
1✔
487
      this.priorities = priorities;
1✔
488
    }
1✔
489
  }
490

491
  /**
492
   * Generates configs to be used in the priority LB policy for priorities in a cluster.
493
   *
494
   * <p>priority LB -> cluster_impl LB (one per priority) -> (weighted_target LB
495
   * -> round_robin / least_request_experimental (one per locality)) / ring_hash_experimental
496
   */
497
  private static Map<String, PriorityChildConfig> generatePriorityChildConfigs(
498
      String clusterName,
499
      CdsUpdate discovery,
500
      Object endpointLbConfig,
501
      LoadBalancerRegistry lbRegistry,
502
      Map<String, Map<Locality, Integer>> prioritizedLocalityWeights,
503
      List<DropOverload> dropOverloads) {
504
    Map<String, PriorityChildConfig> configs = new HashMap<>();
1✔
505
    for (String priority : prioritizedLocalityWeights.keySet()) {
1✔
506
      ClusterImplConfig clusterImplConfig =
1✔
507
          new ClusterImplConfig(
508
              clusterName, discovery.edsServiceName(), discovery.lrsServerInfo(),
1✔
509
              discovery.maxConcurrentRequests(), dropOverloads, endpointLbConfig,
1✔
510
              discovery.upstreamTlsContext(), discovery.filterMetadata(),
1✔
511
              discovery.backendMetricPropagation());
1✔
512
      LoadBalancerProvider clusterImplLbProvider =
1✔
513
          lbRegistry.getProvider(XdsLbPolicies.CLUSTER_IMPL_POLICY_NAME);
1✔
514
      Object priorityChildPolicy = GracefulSwitchLoadBalancer.createLoadBalancingPolicyConfig(
1✔
515
          clusterImplLbProvider, clusterImplConfig);
516

517
      // If outlier detection has been configured we wrap the child policy in the outlier detection
518
      // load balancer.
519
      if (discovery.outlierDetection() != null) {
1✔
520
        LoadBalancerProvider outlierDetectionProvider = lbRegistry.getProvider(
1✔
521
            "outlier_detection_experimental");
522
        priorityChildPolicy = GracefulSwitchLoadBalancer.createLoadBalancingPolicyConfig(
1✔
523
            outlierDetectionProvider,
524
            buildOutlierDetectionLbConfig(discovery.outlierDetection(), priorityChildPolicy));
1✔
525
      }
526

527
      boolean isEds = discovery.clusterType() == ClusterType.EDS;
1✔
528
      PriorityChildConfig priorityChildConfig =
1✔
529
          new PriorityChildConfig(priorityChildPolicy, isEds /* ignoreReresolution */);
530
      configs.put(priority, priorityChildConfig);
1✔
531
    }
1✔
532
    return configs;
1✔
533
  }
534

535
  /**
536
   * Converts {@link OutlierDetection} that represents the xDS configuration to {@link
537
   * OutlierDetectionLoadBalancerConfig} that the {@link io.grpc.util.OutlierDetectionLoadBalancer}
538
   * understands.
539
   */
540
  private static OutlierDetectionLoadBalancerConfig buildOutlierDetectionLbConfig(
541
      OutlierDetection outlierDetection, Object childConfig) {
542
    OutlierDetectionLoadBalancerConfig.Builder configBuilder
1✔
543
        = new OutlierDetectionLoadBalancerConfig.Builder();
544

545
    configBuilder.setChildConfig(childConfig);
1✔
546

547
    if (outlierDetection.intervalNanos() != null) {
1✔
548
      configBuilder.setIntervalNanos(outlierDetection.intervalNanos());
1✔
549
    }
550
    if (outlierDetection.baseEjectionTimeNanos() != null) {
1✔
551
      configBuilder.setBaseEjectionTimeNanos(outlierDetection.baseEjectionTimeNanos());
1✔
552
    }
553
    if (outlierDetection.maxEjectionTimeNanos() != null) {
1✔
554
      configBuilder.setMaxEjectionTimeNanos(outlierDetection.maxEjectionTimeNanos());
1✔
555
    }
556
    if (outlierDetection.maxEjectionPercent() != null) {
1✔
557
      configBuilder.setMaxEjectionPercent(outlierDetection.maxEjectionPercent());
1✔
558
    }
559

560
    SuccessRateEjection successRate = outlierDetection.successRateEjection();
1✔
561
    if (successRate != null) {
1✔
562
      OutlierDetectionLoadBalancerConfig.SuccessRateEjection.Builder
563
          successRateConfigBuilder = new OutlierDetectionLoadBalancerConfig
1✔
564
          .SuccessRateEjection.Builder();
565

566
      if (successRate.stdevFactor() != null) {
1✔
567
        successRateConfigBuilder.setStdevFactor(successRate.stdevFactor());
1✔
568
      }
569
      if (successRate.enforcementPercentage() != null) {
1✔
570
        successRateConfigBuilder.setEnforcementPercentage(successRate.enforcementPercentage());
1✔
571
      }
572
      if (successRate.minimumHosts() != null) {
1✔
573
        successRateConfigBuilder.setMinimumHosts(successRate.minimumHosts());
1✔
574
      }
575
      if (successRate.requestVolume() != null) {
1✔
576
        successRateConfigBuilder.setRequestVolume(successRate.requestVolume());
1✔
577
      }
578

579
      configBuilder.setSuccessRateEjection(successRateConfigBuilder.build());
1✔
580
    }
581

582
    FailurePercentageEjection failurePercentage = outlierDetection.failurePercentageEjection();
1✔
583
    if (failurePercentage != null) {
1✔
584
      OutlierDetectionLoadBalancerConfig.FailurePercentageEjection.Builder
585
          failurePercentageConfigBuilder = new OutlierDetectionLoadBalancerConfig
1✔
586
          .FailurePercentageEjection.Builder();
587

588
      if (failurePercentage.threshold() != null) {
1✔
589
        failurePercentageConfigBuilder.setThreshold(failurePercentage.threshold());
1✔
590
      }
591
      if (failurePercentage.enforcementPercentage() != null) {
1✔
592
        failurePercentageConfigBuilder.setEnforcementPercentage(
1✔
593
            failurePercentage.enforcementPercentage());
1✔
594
      }
595
      if (failurePercentage.minimumHosts() != null) {
1✔
596
        failurePercentageConfigBuilder.setMinimumHosts(failurePercentage.minimumHosts());
1✔
597
      }
598
      if (failurePercentage.requestVolume() != null) {
1✔
599
        failurePercentageConfigBuilder.setRequestVolume(failurePercentage.requestVolume());
1✔
600
      }
601

602
      configBuilder.setFailurePercentageEjection(failurePercentageConfigBuilder.build());
1✔
603
    }
604

605
    return configBuilder.build();
1✔
606
  }
607
}
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

© 2026 Coveralls, Inc