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

grpc / grpc-java / #20140

07 Jan 2026 02:11AM UTC coverage: 88.659% (-0.04%) from 88.696%
#20140

push

github

ejona86
xds: Merge ClusterResolverLB into CdsLB2

This is a cleanup from gRFC A74:

> The xds_cluster_resolver LB policy will be removed completely, as
> obtaining endpoint addresses will now be done in the xds resolver. The
> code for generating the child policy configs for the priority policy
> will now be done in the cds LB policy.

Since XdsDependencyManager handles the actual resolution,
ClusterResolverLB was only handling config conversion. We just do that
in CdsLB2 now.

The large blob of code from ClusterResolverLB was moved into CdsLB2
unchanged. The cluster resolver tests were left in-place, though, as
they did need changes and it'd be very hard to see the changes if moved
at the same time.

35312 of 39829 relevant lines covered (88.66%)

0.89 hits per line

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

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

71
/**
72
 * Load balancer for cds_experimental LB policy. One instance per top-level cluster.
73
 * The top-level cluster may be a plain EDS/logical-DNS cluster or an aggregate cluster formed
74
 * by a group of sub-clusters in a tree hierarchy.
75
 */
76
final class CdsLoadBalancer2 extends LoadBalancer {
77
  private final XdsLogger logger;
78
  private final Helper helper;
79
  private final LoadBalancerRegistry lbRegistry;
80
  private final ClusterState clusterState = new ClusterState();
1✔
81
  private GracefulSwitchLoadBalancer delegate;
82
  // Following fields are effectively final.
83
  private String clusterName;
84
  private Subscription clusterSubscription;
85

86
  CdsLoadBalancer2(Helper helper, LoadBalancerRegistry lbRegistry) {
1✔
87
    this.helper = checkNotNull(helper, "helper");
1✔
88
    this.lbRegistry = checkNotNull(lbRegistry, "lbRegistry");
1✔
89
    this.delegate = new GracefulSwitchLoadBalancer(helper);
1✔
90
    logger = XdsLogger.withLogId(InternalLogId.allocate("cds-lb", helper.getAuthority()));
1✔
91
    logger.log(XdsLogLevel.INFO, "Created");
1✔
92
  }
1✔
93

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

123
    NameResolver.ConfigOrError configOrError;
124
    if (clusterConfig.getChildren() instanceof EndpointConfig) {
1✔
125
      // The LB policy config is provided in service_config.proto/JSON format.
126
      configOrError =
1✔
127
              GracefulSwitchLoadBalancer.parseLoadBalancingPolicyConfig(
1✔
128
                      Arrays.asList(clusterConfig.getClusterResource().lbPolicyConfig()),
1✔
129
                      lbRegistry);
130
      if (configOrError.getError() != null) {
1✔
131
        // Should be impossible, because XdsClusterResource validated this
132
        return fail(Status.INTERNAL.withDescription(
×
133
                errorPrefix() + "Unable to parse the LB config: " + configOrError.getError()));
×
134
      }
135

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

191
  @Override
192
  public void handleNameResolutionError(Status error) {
193
    logger.log(XdsLogLevel.WARNING, "Received name resolution error: {0}", error);
1✔
194
    if (delegate != null) {
1✔
195
      delegate.handleNameResolutionError(error);
1✔
196
    } else {
197
      helper.updateBalancingState(
×
198
          TRANSIENT_FAILURE, new FixedResultPicker(PickResult.withError(error)));
×
199
    }
200
  }
1✔
201

202
  @Override
203
  public void shutdown() {
204
    logger.log(XdsLogLevel.INFO, "Shutdown");
1✔
205
    delegate.shutdown();
1✔
206
    delegate = new GracefulSwitchLoadBalancer(helper);
1✔
207
    if (clusterSubscription != null) {
1✔
208
      clusterSubscription.close();
1✔
209
      clusterSubscription = null;
1✔
210
    }
211
  }
1✔
212

213
  @CheckReturnValue // don't forget to return up the stack after the fail call
214
  private Status fail(Status error) {
215
    delegate.shutdown();
1✔
216
    helper.updateBalancingState(
1✔
217
        TRANSIENT_FAILURE, new FixedResultPicker(PickResult.withError(error)));
1✔
218
    return Status.OK; // XdsNameResolver isn't a polling NR, so this value doesn't matter
1✔
219
  }
220

221
  private String errorPrefix() {
222
    return "CdsLb for " + clusterName + ": ";
×
223
  }
224

225
  private static StatusOr<EdsUpdate> getEdsUpdate(XdsConfig xdsConfig, String cluster) {
226
    StatusOr<XdsClusterConfig> clusterConfig = xdsConfig.getClusters().get(cluster);
1✔
227
    if (clusterConfig == null) {
1✔
228
      return StatusOr.fromStatus(Status.INTERNAL
×
229
          .withDescription("BUG: cluster resolver could not find cluster in xdsConfig"));
×
230
    }
231
    if (!clusterConfig.hasValue()) {
1✔
232
      return StatusOr.fromStatus(clusterConfig.getStatus());
×
233
    }
234
    if (!(clusterConfig.getValue().getChildren() instanceof XdsClusterConfig.EndpointConfig)) {
1✔
235
      return StatusOr.fromStatus(Status.INTERNAL
×
236
          .withDescription("BUG: cluster resolver cluster with children of unknown type"));
×
237
    }
238
    XdsClusterConfig.EndpointConfig endpointConfig =
1✔
239
        (XdsClusterConfig.EndpointConfig) clusterConfig.getValue().getChildren();
1✔
240
    return endpointConfig.getEndpoint();
1✔
241
  }
242

243
  /**
244
   * Generates a string that represents the priority in the LB policy config. The string is unique
245
   * across priorities in all clusters and priorityName(c, p1) < priorityName(c, p2) iff p1 < p2.
246
   * The ordering is undefined for priorities in different clusters.
247
   */
248
  private static String priorityName(String cluster, int priority) {
249
    return cluster + "[child" + priority + "]";
1✔
250
  }
251

252
  /**
253
   * Generates a string that represents the locality in the LB policy config. The string is unique
254
   * across all localities in all clusters.
255
   */
256
  private static String localityName(Locality locality) {
257
    return "{region=\"" + locality.region()
1✔
258
        + "\", zone=\"" + locality.zone()
1✔
259
        + "\", sub_zone=\"" + locality.subZone()
1✔
260
        + "\"}";
261
  }
262

263
  private final class ClusterState {
1✔
264
    private Map<Locality, String> localityPriorityNames = Collections.emptyMap();
1✔
265
    int priorityNameGenId = 1;
1✔
266

267
    StatusOr<ClusterResolutionResult> edsUpdateToResult(
268
        String clusterName,
269
        CdsUpdate discovery,
270
        Object lbConfig,
271
        StatusOr<EdsUpdate> updateOr) {
272
      if (!updateOr.hasValue()) {
1✔
273
        return StatusOr.fromStatus(updateOr.getStatus());
1✔
274
      }
275
      EdsUpdate update = updateOr.getValue();
1✔
276
      logger.log(XdsLogLevel.DEBUG, "Received endpoint update {0}", update);
1✔
277
      if (logger.isLoggable(XdsLogLevel.INFO)) {
1✔
278
        logger.log(XdsLogLevel.INFO, "Cluster {0}: {1} localities, {2} drop categories",
×
279
            clusterName, update.localityLbEndpointsMap.size(),
×
280
            update.dropPolicies.size());
×
281
      }
282
      Map<Locality, LocalityLbEndpoints> localityLbEndpoints =
1✔
283
          update.localityLbEndpointsMap;
284
      List<DropOverload> dropOverloads = update.dropPolicies;
1✔
285
      List<EquivalentAddressGroup> addresses = new ArrayList<>();
1✔
286
      Map<String, Map<Locality, Integer>> prioritizedLocalityWeights = new HashMap<>();
1✔
287
      List<String> sortedPriorityNames =
1✔
288
          generatePriorityNames(clusterName, localityLbEndpoints);
1✔
289
      for (Locality locality : localityLbEndpoints.keySet()) {
1✔
290
        LocalityLbEndpoints localityLbInfo = localityLbEndpoints.get(locality);
1✔
291
        String priorityName = localityPriorityNames.get(locality);
1✔
292
        boolean discard = true;
1✔
293
        for (LbEndpoint endpoint : localityLbInfo.endpoints()) {
1✔
294
          if (endpoint.isHealthy()) {
1✔
295
            discard = false;
1✔
296
            long weight = localityLbInfo.localityWeight();
1✔
297
            if (endpoint.loadBalancingWeight() != 0) {
1✔
298
              weight *= endpoint.loadBalancingWeight();
1✔
299
            }
300
            String localityName = localityName(locality);
1✔
301
            Attributes attr =
1✔
302
                endpoint.eag().getAttributes().toBuilder()
1✔
303
                    .set(io.grpc.xds.XdsAttributes.ATTR_LOCALITY, locality)
1✔
304
                    .set(EquivalentAddressGroup.ATTR_LOCALITY_NAME, localityName)
1✔
305
                    .set(io.grpc.xds.XdsAttributes.ATTR_LOCALITY_WEIGHT,
1✔
306
                        localityLbInfo.localityWeight())
1✔
307
                    .set(io.grpc.xds.XdsAttributes.ATTR_SERVER_WEIGHT, weight)
1✔
308
                    .set(XdsInternalAttributes.ATTR_ADDRESS_NAME, endpoint.hostname())
1✔
309
                    .build();
1✔
310
            EquivalentAddressGroup eag;
311
            if (discovery.isHttp11ProxyAvailable()) {
1✔
312
              List<SocketAddress> rewrittenAddresses = new ArrayList<>();
1✔
313
              for (SocketAddress addr : endpoint.eag().getAddresses()) {
1✔
314
                rewrittenAddresses.add(rewriteAddress(
1✔
315
                    addr, endpoint.endpointMetadata(), localityLbInfo.localityMetadata()));
1✔
316
              }
1✔
317
              eag = new EquivalentAddressGroup(rewrittenAddresses, attr);
1✔
318
            } else {
1✔
319
              eag = new EquivalentAddressGroup(endpoint.eag().getAddresses(), attr);
1✔
320
            }
321
            eag = AddressFilter.setPathFilter(eag, Arrays.asList(priorityName, localityName));
1✔
322
            addresses.add(eag);
1✔
323
          }
324
        }
1✔
325
        if (discard) {
1✔
326
          logger.log(XdsLogLevel.INFO,
1✔
327
              "Discard locality {0} with 0 healthy endpoints", locality);
328
          continue;
1✔
329
        }
330
        if (!prioritizedLocalityWeights.containsKey(priorityName)) {
1✔
331
          prioritizedLocalityWeights.put(priorityName, new HashMap<Locality, Integer>());
1✔
332
        }
333
        prioritizedLocalityWeights.get(priorityName).put(
1✔
334
            locality, localityLbInfo.localityWeight());
1✔
335
      }
1✔
336
      if (prioritizedLocalityWeights.isEmpty()) {
1✔
337
        // Will still update the result, as if the cluster resource is revoked.
338
        logger.log(XdsLogLevel.INFO,
1✔
339
            "Cluster {0} has no usable priority/locality/endpoint", clusterName);
340
      }
341
      sortedPriorityNames.retainAll(prioritizedLocalityWeights.keySet());
1✔
342
      Map<String, PriorityChildConfig> priorityChildConfigs =
1✔
343
          generatePriorityChildConfigs(
1✔
344
              clusterName, discovery, lbConfig, lbRegistry,
1✔
345
              prioritizedLocalityWeights, dropOverloads);
346
      return StatusOr.fromValue(new ClusterResolutionResult(addresses, priorityChildConfigs,
1✔
347
          sortedPriorityNames));
348
    }
349

350
    private SocketAddress rewriteAddress(SocketAddress addr,
351
        ImmutableMap<String, Object> endpointMetadata,
352
        ImmutableMap<String, Object> localityMetadata) {
353
      if (!(addr instanceof InetSocketAddress)) {
1✔
354
        return addr;
×
355
      }
356

357
      SocketAddress proxyAddress;
358
      try {
359
        proxyAddress = (SocketAddress) endpointMetadata.get(
1✔
360
            "envoy.http11_proxy_transport_socket.proxy_address");
361
        if (proxyAddress == null) {
1✔
362
          proxyAddress = (SocketAddress) localityMetadata.get(
1✔
363
              "envoy.http11_proxy_transport_socket.proxy_address");
364
        }
365
      } catch (ClassCastException e) {
×
366
        return addr;
×
367
      }
1✔
368

369
      if (proxyAddress == null) {
1✔
370
        return addr;
1✔
371
      }
372

373
      return HttpConnectProxiedSocketAddress.newBuilder()
1✔
374
          .setTargetAddress((InetSocketAddress) addr)
1✔
375
          .setProxyAddress(proxyAddress)
1✔
376
          .build();
1✔
377
    }
378

379
    private List<String> generatePriorityNames(String name,
380
        Map<Locality, LocalityLbEndpoints> localityLbEndpoints) {
381
      TreeMap<Integer, List<Locality>> todo = new TreeMap<>();
1✔
382
      for (Locality locality : localityLbEndpoints.keySet()) {
1✔
383
        int priority = localityLbEndpoints.get(locality).priority();
1✔
384
        if (!todo.containsKey(priority)) {
1✔
385
          todo.put(priority, new ArrayList<>());
1✔
386
        }
387
        todo.get(priority).add(locality);
1✔
388
      }
1✔
389
      Map<Locality, String> newNames = new HashMap<>();
1✔
390
      Set<String> usedNames = new HashSet<>();
1✔
391
      List<String> ret = new ArrayList<>();
1✔
392
      for (Integer priority: todo.keySet()) {
1✔
393
        String foundName = "";
1✔
394
        for (Locality locality : todo.get(priority)) {
1✔
395
          if (localityPriorityNames.containsKey(locality)
1✔
396
              && usedNames.add(localityPriorityNames.get(locality))) {
1✔
397
            foundName = localityPriorityNames.get(locality);
1✔
398
            break;
1✔
399
          }
400
        }
1✔
401
        if ("".equals(foundName)) {
1✔
402
          foundName = priorityName(name, priorityNameGenId++);
1✔
403
        }
404
        for (Locality locality : todo.get(priority)) {
1✔
405
          newNames.put(locality, foundName);
1✔
406
        }
1✔
407
        ret.add(foundName);
1✔
408
      }
1✔
409
      localityPriorityNames = newNames;
1✔
410
      return ret;
1✔
411
    }
412
  }
413

414
  private static class ClusterResolutionResult {
415
    // Endpoint addresses.
416
    private final List<EquivalentAddressGroup> addresses;
417
    // Config (include load balancing policy/config) for each priority in the cluster.
418
    private final Map<String, PriorityChildConfig> priorityChildConfigs;
419
    // List of priority names ordered in descending priorities.
420
    private final List<String> priorities;
421

422
    ClusterResolutionResult(List<EquivalentAddressGroup> addresses,
423
        Map<String, PriorityChildConfig> configs, List<String> priorities) {
1✔
424
      this.addresses = addresses;
1✔
425
      this.priorityChildConfigs = configs;
1✔
426
      this.priorities = priorities;
1✔
427
    }
1✔
428
  }
429

430
  /**
431
   * Generates configs to be used in the priority LB policy for priorities in a cluster.
432
   *
433
   * <p>priority LB -> cluster_impl LB (one per priority) -> (weighted_target LB
434
   * -> round_robin / least_request_experimental (one per locality)) / ring_hash_experimental
435
   */
436
  private static Map<String, PriorityChildConfig> generatePriorityChildConfigs(
437
      String clusterName,
438
      CdsUpdate discovery,
439
      Object endpointLbConfig,
440
      LoadBalancerRegistry lbRegistry,
441
      Map<String, Map<Locality, Integer>> prioritizedLocalityWeights,
442
      List<DropOverload> dropOverloads) {
443
    Map<String, PriorityChildConfig> configs = new HashMap<>();
1✔
444
    for (String priority : prioritizedLocalityWeights.keySet()) {
1✔
445
      ClusterImplConfig clusterImplConfig =
1✔
446
          new ClusterImplConfig(
447
              clusterName, discovery.edsServiceName(), discovery.lrsServerInfo(),
1✔
448
              discovery.maxConcurrentRequests(), dropOverloads, endpointLbConfig,
1✔
449
              discovery.upstreamTlsContext(), discovery.filterMetadata(),
1✔
450
              discovery.backendMetricPropagation());
1✔
451
      LoadBalancerProvider clusterImplLbProvider =
1✔
452
          lbRegistry.getProvider(XdsLbPolicies.CLUSTER_IMPL_POLICY_NAME);
1✔
453
      Object priorityChildPolicy = GracefulSwitchLoadBalancer.createLoadBalancingPolicyConfig(
1✔
454
          clusterImplLbProvider, clusterImplConfig);
455

456
      // If outlier detection has been configured we wrap the child policy in the outlier detection
457
      // load balancer.
458
      if (discovery.outlierDetection() != null) {
1✔
459
        LoadBalancerProvider outlierDetectionProvider = lbRegistry.getProvider(
1✔
460
            "outlier_detection_experimental");
461
        priorityChildPolicy = GracefulSwitchLoadBalancer.createLoadBalancingPolicyConfig(
1✔
462
            outlierDetectionProvider,
463
            buildOutlierDetectionLbConfig(discovery.outlierDetection(), priorityChildPolicy));
1✔
464
      }
465

466
      boolean isEds = discovery.clusterType() == ClusterType.EDS;
1✔
467
      PriorityChildConfig priorityChildConfig =
1✔
468
          new PriorityChildConfig(priorityChildPolicy, isEds /* ignoreReresolution */);
469
      configs.put(priority, priorityChildConfig);
1✔
470
    }
1✔
471
    return configs;
1✔
472
  }
473

474
  /**
475
   * Converts {@link OutlierDetection} that represents the xDS configuration to {@link
476
   * OutlierDetectionLoadBalancerConfig} that the {@link io.grpc.util.OutlierDetectionLoadBalancer}
477
   * understands.
478
   */
479
  private static OutlierDetectionLoadBalancerConfig buildOutlierDetectionLbConfig(
480
      OutlierDetection outlierDetection, Object childConfig) {
481
    OutlierDetectionLoadBalancerConfig.Builder configBuilder
1✔
482
        = new OutlierDetectionLoadBalancerConfig.Builder();
483

484
    configBuilder.setChildConfig(childConfig);
1✔
485

486
    if (outlierDetection.intervalNanos() != null) {
1✔
487
      configBuilder.setIntervalNanos(outlierDetection.intervalNanos());
1✔
488
    }
489
    if (outlierDetection.baseEjectionTimeNanos() != null) {
1✔
490
      configBuilder.setBaseEjectionTimeNanos(outlierDetection.baseEjectionTimeNanos());
1✔
491
    }
492
    if (outlierDetection.maxEjectionTimeNanos() != null) {
1✔
493
      configBuilder.setMaxEjectionTimeNanos(outlierDetection.maxEjectionTimeNanos());
1✔
494
    }
495
    if (outlierDetection.maxEjectionPercent() != null) {
1✔
496
      configBuilder.setMaxEjectionPercent(outlierDetection.maxEjectionPercent());
1✔
497
    }
498

499
    SuccessRateEjection successRate = outlierDetection.successRateEjection();
1✔
500
    if (successRate != null) {
1✔
501
      OutlierDetectionLoadBalancerConfig.SuccessRateEjection.Builder
502
          successRateConfigBuilder = new OutlierDetectionLoadBalancerConfig
1✔
503
          .SuccessRateEjection.Builder();
504

505
      if (successRate.stdevFactor() != null) {
1✔
506
        successRateConfigBuilder.setStdevFactor(successRate.stdevFactor());
1✔
507
      }
508
      if (successRate.enforcementPercentage() != null) {
1✔
509
        successRateConfigBuilder.setEnforcementPercentage(successRate.enforcementPercentage());
1✔
510
      }
511
      if (successRate.minimumHosts() != null) {
1✔
512
        successRateConfigBuilder.setMinimumHosts(successRate.minimumHosts());
1✔
513
      }
514
      if (successRate.requestVolume() != null) {
1✔
515
        successRateConfigBuilder.setRequestVolume(successRate.requestVolume());
1✔
516
      }
517

518
      configBuilder.setSuccessRateEjection(successRateConfigBuilder.build());
1✔
519
    }
520

521
    FailurePercentageEjection failurePercentage = outlierDetection.failurePercentageEjection();
1✔
522
    if (failurePercentage != null) {
1✔
523
      OutlierDetectionLoadBalancerConfig.FailurePercentageEjection.Builder
524
          failurePercentageConfigBuilder = new OutlierDetectionLoadBalancerConfig
1✔
525
          .FailurePercentageEjection.Builder();
526

527
      if (failurePercentage.threshold() != null) {
1✔
528
        failurePercentageConfigBuilder.setThreshold(failurePercentage.threshold());
1✔
529
      }
530
      if (failurePercentage.enforcementPercentage() != null) {
1✔
531
        failurePercentageConfigBuilder.setEnforcementPercentage(
1✔
532
            failurePercentage.enforcementPercentage());
1✔
533
      }
534
      if (failurePercentage.minimumHosts() != null) {
1✔
535
        failurePercentageConfigBuilder.setMinimumHosts(failurePercentage.minimumHosts());
1✔
536
      }
537
      if (failurePercentage.requestVolume() != null) {
1✔
538
        failurePercentageConfigBuilder.setRequestVolume(failurePercentage.requestVolume());
1✔
539
      }
540

541
      configBuilder.setFailurePercentageEjection(failurePercentageConfigBuilder.build());
1✔
542
    }
543

544
    return configBuilder.build();
1✔
545
  }
546
}
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