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

grpc / grpc-java / #19908

16 Jul 2025 07:54PM UTC coverage: 88.593% (+0.07%) from 88.528%
#19908

push

github

ejona86
Revert "xds: Convert CdsLb to XdsDepManager"

This reverts commit 297ab05ef.

b/430347751 shows multiple concerning behaviors in the xDS stack with
the new A74 config update model. XdsDepManager and CdsLB2 still seem to
be working correctly, but the change is exacerbated issues in other
parts of the stack, like RingHashConfig not having equals fixed in
a8de9f07ab.

Revert only for the v1.74.x release, leaving it on master.

34647 of 39108 relevant lines covered (88.59%)

0.89 hits per line

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

92.0
/../xds/src/main/java/io/grpc/xds/XdsClusterResource.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
import static io.grpc.xds.client.Bootstrapper.ServerInfo;
21

22
import com.google.auto.value.AutoValue;
23
import com.google.common.annotations.VisibleForTesting;
24
import com.google.common.base.MoreObjects;
25
import com.google.common.base.Strings;
26
import com.google.common.collect.ImmutableList;
27
import com.google.common.collect.ImmutableMap;
28
import com.google.protobuf.Duration;
29
import com.google.protobuf.InvalidProtocolBufferException;
30
import com.google.protobuf.Message;
31
import com.google.protobuf.Struct;
32
import com.google.protobuf.util.Durations;
33
import io.envoyproxy.envoy.config.cluster.v3.CircuitBreakers.Thresholds;
34
import io.envoyproxy.envoy.config.cluster.v3.Cluster;
35
import io.envoyproxy.envoy.config.core.v3.RoutingPriority;
36
import io.envoyproxy.envoy.config.core.v3.SocketAddress;
37
import io.envoyproxy.envoy.config.core.v3.TransportSocket;
38
import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment;
39
import io.envoyproxy.envoy.extensions.transport_sockets.http_11_proxy.v3.Http11ProxyUpstreamTransport;
40
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext;
41
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext;
42
import io.grpc.LoadBalancerRegistry;
43
import io.grpc.NameResolver;
44
import io.grpc.internal.GrpcUtil;
45
import io.grpc.internal.ServiceConfigUtil;
46
import io.grpc.internal.ServiceConfigUtil.LbConfig;
47
import io.grpc.xds.EnvoyServerProtoData.OutlierDetection;
48
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
49
import io.grpc.xds.XdsClusterResource.CdsUpdate;
50
import io.grpc.xds.client.XdsClient.ResourceUpdate;
51
import io.grpc.xds.client.XdsResourceType;
52
import io.grpc.xds.internal.security.CommonTlsContextUtil;
53
import java.util.List;
54
import java.util.Locale;
55
import java.util.Set;
56
import javax.annotation.Nullable;
57

58
class XdsClusterResource extends XdsResourceType<CdsUpdate> {
1✔
59
  @VisibleForTesting
60
  static boolean enableLeastRequest =
61
      !Strings.isNullOrEmpty(System.getenv("GRPC_EXPERIMENTAL_ENABLE_LEAST_REQUEST"))
1✔
62
          ? Boolean.parseBoolean(System.getenv("GRPC_EXPERIMENTAL_ENABLE_LEAST_REQUEST"))
×
63
          : Boolean.parseBoolean(
1✔
64
              System.getProperty("io.grpc.xds.experimentalEnableLeastRequest", "true"));
1✔
65
  @VisibleForTesting
66
  public static boolean enableSystemRootCerts =
1✔
67
      GrpcUtil.getFlag("GRPC_EXPERIMENTAL_XDS_SYSTEM_ROOT_CERTS", false);
1✔
68
  static boolean isEnabledXdsHttpConnect =
1✔
69
      GrpcUtil.getFlag("GRPC_EXPERIMENTAL_XDS_HTTP_CONNECT", false);
1✔
70

71
  @VisibleForTesting
72
  static final String AGGREGATE_CLUSTER_TYPE_NAME = "envoy.clusters.aggregate";
73
  static final String ADS_TYPE_URL_CDS =
74
      "type.googleapis.com/envoy.config.cluster.v3.Cluster";
75
  private static final String TYPE_URL_CLUSTER_CONFIG =
76
      "type.googleapis.com/envoy.extensions.clusters.aggregate.v3.ClusterConfig";
77
  private static final String TYPE_URL_UPSTREAM_TLS_CONTEXT =
78
      "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext";
79
  private static final String TYPE_URL_UPSTREAM_TLS_CONTEXT_V2 =
80
      "type.googleapis.com/envoy.api.v2.auth.UpstreamTlsContext";
81
  static final String TRANSPORT_SOCKET_NAME_HTTP11_PROXY =
82
      "type.googleapis.com/envoy.extensions.transport_sockets.http_11_proxy.v3"
83
          + ".Http11ProxyUpstreamTransport";
84
  private final LoadBalancerRegistry loadBalancerRegistry
1✔
85
      = LoadBalancerRegistry.getDefaultRegistry();
1✔
86

87
  private static final XdsClusterResource instance = new XdsClusterResource();
1✔
88

89
  public static XdsClusterResource getInstance() {
90
    return instance;
1✔
91
  }
92

93
  @Override
94
  @Nullable
95
  protected String extractResourceName(Message unpackedResource) {
96
    if (!(unpackedResource instanceof Cluster)) {
1✔
97
      return null;
×
98
    }
99
    return ((Cluster) unpackedResource).getName();
1✔
100
  }
101

102
  @Override
103
  public String typeName() {
104
    return "CDS";
1✔
105
  }
106

107
  @Override
108
  public String typeUrl() {
109
    return ADS_TYPE_URL_CDS;
1✔
110
  }
111

112
  @Override
113
  public boolean shouldRetrieveResourceKeysForArgs() {
114
    return true;
1✔
115
  }
116

117
  @Override
118
  protected boolean isFullStateOfTheWorld() {
119
    return true;
1✔
120
  }
121

122
  @Override
123
  @SuppressWarnings("unchecked")
124
  protected Class<Cluster> unpackedClassName() {
125
    return Cluster.class;
1✔
126
  }
127

128
  @Override
129
  protected CdsUpdate doParse(Args args, Message unpackedMessage) throws ResourceInvalidException {
130
    if (!(unpackedMessage instanceof Cluster)) {
1✔
131
      throw new ResourceInvalidException("Invalid message type: " + unpackedMessage.getClass());
×
132
    }
133
    Set<String> certProviderInstances = null;
1✔
134
    if (args.getBootstrapInfo() != null && args.getBootstrapInfo().certProviders() != null) {
1✔
135
      certProviderInstances = args.getBootstrapInfo().certProviders().keySet();
1✔
136
    }
137
    return processCluster((Cluster) unpackedMessage, certProviderInstances,
1✔
138
        args.getServerInfo(), loadBalancerRegistry);
1✔
139
  }
140

141
  @VisibleForTesting
142
  static CdsUpdate processCluster(Cluster cluster,
143
                                  Set<String> certProviderInstances,
144
                                  ServerInfo serverInfo,
145
                                  LoadBalancerRegistry loadBalancerRegistry)
146
      throws ResourceInvalidException {
147
    StructOrError<CdsUpdate.Builder> structOrError;
148
    switch (cluster.getClusterDiscoveryTypeCase()) {
1✔
149
      case TYPE:
150
        structOrError = parseNonAggregateCluster(cluster,
1✔
151
            certProviderInstances, serverInfo);
152
        break;
1✔
153
      case CLUSTER_TYPE:
154
        structOrError = parseAggregateCluster(cluster);
1✔
155
        break;
1✔
156
      case CLUSTERDISCOVERYTYPE_NOT_SET:
157
      default:
158
        throw new ResourceInvalidException(
1✔
159
            "Cluster " + cluster.getName() + ": unspecified cluster discovery type");
1✔
160
    }
161
    if (structOrError.getErrorDetail() != null) {
1✔
162
      throw new ResourceInvalidException(structOrError.getErrorDetail());
1✔
163
    }
164
    CdsUpdate.Builder updateBuilder = structOrError.getStruct();
1✔
165

166
    ImmutableMap<String, ?> lbPolicyConfig = LoadBalancerConfigFactory.newConfig(cluster,
1✔
167
        enableLeastRequest);
168

169
    // Validate the LB config by trying to parse it with the corresponding LB provider.
170
    LbConfig lbConfig = ServiceConfigUtil.unwrapLoadBalancingConfig(lbPolicyConfig);
1✔
171
    NameResolver.ConfigOrError configOrError = loadBalancerRegistry.getProvider(
1✔
172
        lbConfig.getPolicyName()).parseLoadBalancingPolicyConfig(
1✔
173
        lbConfig.getRawConfigValue());
1✔
174
    if (configOrError.getError() != null) {
1✔
175
      throw new ResourceInvalidException(structOrError.getErrorDetail());
×
176
    }
177

178
    updateBuilder.lbPolicyConfig(lbPolicyConfig);
1✔
179
    updateBuilder.filterMetadata(
1✔
180
        ImmutableMap.copyOf(cluster.getMetadata().getFilterMetadataMap()));
1✔
181

182
    try {
183
      MetadataRegistry registry = MetadataRegistry.getInstance();
1✔
184
      ImmutableMap<String, Object> parsedFilterMetadata =
1✔
185
          registry.parseMetadata(cluster.getMetadata());
1✔
186
      updateBuilder.parsedMetadata(parsedFilterMetadata);
1✔
187
    } catch (ResourceInvalidException e) {
×
188
      throw new ResourceInvalidException(
×
189
          "Failed to parse xDS filter metadata for cluster '" + cluster.getName() + "': "
×
190
              + e.getMessage(), e);
×
191
    }
1✔
192

193
    return updateBuilder.build();
1✔
194
  }
195

196
  private static StructOrError<CdsUpdate.Builder> parseAggregateCluster(Cluster cluster) {
197
    String clusterName = cluster.getName();
1✔
198
    Cluster.CustomClusterType customType = cluster.getClusterType();
1✔
199
    String typeName = customType.getName();
1✔
200
    if (!typeName.equals(AGGREGATE_CLUSTER_TYPE_NAME)) {
1✔
201
      return StructOrError.fromError(
×
202
          "Cluster " + clusterName + ": unsupported custom cluster type: " + typeName);
203
    }
204
    io.envoyproxy.envoy.extensions.clusters.aggregate.v3.ClusterConfig clusterConfig;
205
    try {
206
      clusterConfig = unpackCompatibleType(customType.getTypedConfig(),
1✔
207
          io.envoyproxy.envoy.extensions.clusters.aggregate.v3.ClusterConfig.class,
208
          TYPE_URL_CLUSTER_CONFIG, null);
209
    } catch (InvalidProtocolBufferException e) {
×
210
      return StructOrError.fromError("Cluster " + clusterName + ": malformed ClusterConfig: " + e);
×
211
    }
1✔
212
    return StructOrError.fromStruct(CdsUpdate.forAggregate(
1✔
213
        clusterName, clusterConfig.getClustersList()));
1✔
214
  }
215

216
  private static StructOrError<CdsUpdate.Builder> parseNonAggregateCluster(
217
      Cluster cluster, Set<String> certProviderInstances, ServerInfo serverInfo) {
218
    String clusterName = cluster.getName();
1✔
219
    ServerInfo lrsServerInfo = null;
1✔
220
    Long maxConcurrentRequests = null;
1✔
221
    UpstreamTlsContext upstreamTlsContext = null;
1✔
222
    OutlierDetection outlierDetection = null;
1✔
223
    boolean isHttp11ProxyAvailable = false;
1✔
224
    if (cluster.hasLrsServer()) {
1✔
225
      if (!cluster.getLrsServer().hasSelf()) {
1✔
226
        return StructOrError.fromError(
×
227
            "Cluster " + clusterName + ": only support LRS for the same management server");
228
      }
229
      lrsServerInfo = serverInfo;
1✔
230
    }
231
    if (cluster.hasCircuitBreakers()) {
1✔
232
      List<Thresholds> thresholds = cluster.getCircuitBreakers().getThresholdsList();
1✔
233
      for (Thresholds threshold : thresholds) {
1✔
234
        if (threshold.getPriority() != RoutingPriority.DEFAULT) {
1✔
235
          continue;
1✔
236
        }
237
        if (threshold.hasMaxRequests()) {
1✔
238
          maxConcurrentRequests = Integer.toUnsignedLong(threshold.getMaxRequests().getValue());
1✔
239
        }
240
      }
1✔
241
    }
242
    if (cluster.getTransportSocketMatchesCount() > 0) {
1✔
243
      return StructOrError.fromError("Cluster " + clusterName
1✔
244
          + ": transport-socket-matches not supported.");
245
    }
246
    boolean hasTransportSocket = cluster.hasTransportSocket();
1✔
247
    TransportSocket transportSocket = cluster.getTransportSocket();
1✔
248

249
    if (hasTransportSocket && !TRANSPORT_SOCKET_NAME_TLS.equals(transportSocket.getName())
1✔
250
        && !(isEnabledXdsHttpConnect
251
        && TRANSPORT_SOCKET_NAME_HTTP11_PROXY.equals(transportSocket.getName()))) {
1✔
252
      return StructOrError.fromError(
1✔
253
          "transport-socket with name " + transportSocket.getName() + " not supported.");
1✔
254
    }
255

256
    if (hasTransportSocket && isEnabledXdsHttpConnect
1✔
257
        && TRANSPORT_SOCKET_NAME_HTTP11_PROXY.equals(transportSocket.getName())) {
1✔
258
      isHttp11ProxyAvailable = true;
1✔
259
      try {
260
        Http11ProxyUpstreamTransport wrappedTransportSocket = transportSocket
1✔
261
            .getTypedConfig().unpack(io.envoyproxy.envoy.extensions.transport_sockets
1✔
262
                .http_11_proxy.v3.Http11ProxyUpstreamTransport.class);
263
        hasTransportSocket = wrappedTransportSocket.hasTransportSocket();
1✔
264
        transportSocket = wrappedTransportSocket.getTransportSocket();
1✔
265
      } catch (InvalidProtocolBufferException e) {
×
266
        return StructOrError.fromError(
×
267
            "Cluster " + clusterName + ": malformed Http11ProxyUpstreamTransport: " + e);
268
      } catch (ClassCastException e) {
×
269
        return StructOrError.fromError(
×
270
            "Cluster " + clusterName
271
                + ": invalid transport_socket type in Http11ProxyUpstreamTransport");
272
      }
1✔
273
    }
274

275
    if (hasTransportSocket && TRANSPORT_SOCKET_NAME_TLS.equals(transportSocket.getName())) {
1✔
276
      try {
277
        upstreamTlsContext = UpstreamTlsContext.fromEnvoyProtoUpstreamTlsContext(
1✔
278
            validateUpstreamTlsContext(
1✔
279
                unpackCompatibleType(transportSocket.getTypedConfig(),
1✔
280
                    io.envoyproxy.envoy.extensions
281
                        .transport_sockets.tls.v3.UpstreamTlsContext.class,
282
                    TYPE_URL_UPSTREAM_TLS_CONTEXT, TYPE_URL_UPSTREAM_TLS_CONTEXT_V2),
283
                certProviderInstances));
284
      } catch (InvalidProtocolBufferException | ResourceInvalidException e) {
1✔
285
        return StructOrError.fromError(
1✔
286
            "Cluster " + clusterName + ": malformed UpstreamTlsContext: " + e);
287
      }
1✔
288
    }
289

290
    if (cluster.hasOutlierDetection()) {
1✔
291
      try {
292
        outlierDetection = OutlierDetection.fromEnvoyOutlierDetection(
1✔
293
            validateOutlierDetection(cluster.getOutlierDetection()));
1✔
294
      } catch (ResourceInvalidException e) {
1✔
295
        return StructOrError.fromError(
1✔
296
            "Cluster " + clusterName + ": malformed outlier_detection: " + e);
297
      }
1✔
298
    }
299

300
    Cluster.DiscoveryType type = cluster.getType();
1✔
301
    if (type == Cluster.DiscoveryType.EDS) {
1✔
302
      String edsServiceName = null;
1✔
303
      io.envoyproxy.envoy.config.cluster.v3.Cluster.EdsClusterConfig edsClusterConfig =
1✔
304
          cluster.getEdsClusterConfig();
1✔
305
      if (!edsClusterConfig.getEdsConfig().hasAds()
1✔
306
          && ! edsClusterConfig.getEdsConfig().hasSelf()) {
1✔
307
        return StructOrError.fromError(
1✔
308
            "Cluster " + clusterName + ": field eds_cluster_config must be set to indicate to use"
309
                + " EDS over ADS or self ConfigSource");
310
      }
311
      // If the service_name field is set, that value will be used for the EDS request.
312
      if (!edsClusterConfig.getServiceName().isEmpty()) {
1✔
313
        edsServiceName = edsClusterConfig.getServiceName();
1✔
314
      }
315
      // edsServiceName is required if the CDS resource has an xdstp name.
316
      if ((edsServiceName == null) && clusterName.toLowerCase(Locale.ROOT).startsWith("xdstp:")) {
1✔
317
        return StructOrError.fromError(
1✔
318
            "EDS service_name must be set when Cluster resource has an xdstp name");
319
      }
320

321
      return StructOrError.fromStruct(CdsUpdate.forEds(
1✔
322
          clusterName, edsServiceName, lrsServerInfo, maxConcurrentRequests, upstreamTlsContext,
323
          outlierDetection, isHttp11ProxyAvailable));
324
    } else if (type.equals(Cluster.DiscoveryType.LOGICAL_DNS)) {
1✔
325
      if (!cluster.hasLoadAssignment()) {
1✔
326
        return StructOrError.fromError(
×
327
            "Cluster " + clusterName + ": LOGICAL_DNS clusters must have a single host");
328
      }
329
      ClusterLoadAssignment assignment = cluster.getLoadAssignment();
1✔
330
      if (assignment.getEndpointsCount() != 1
1✔
331
          || assignment.getEndpoints(0).getLbEndpointsCount() != 1) {
1✔
332
        return StructOrError.fromError(
×
333
            "Cluster " + clusterName + ": LOGICAL_DNS clusters must have a single "
334
                + "locality_lb_endpoint and a single lb_endpoint");
335
      }
336
      io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint lbEndpoint =
1✔
337
          assignment.getEndpoints(0).getLbEndpoints(0);
1✔
338
      if (!lbEndpoint.hasEndpoint() || !lbEndpoint.getEndpoint().hasAddress()
1✔
339
          || !lbEndpoint.getEndpoint().getAddress().hasSocketAddress()) {
1✔
340
        return StructOrError.fromError(
×
341
            "Cluster " + clusterName
342
                + ": LOGICAL_DNS clusters must have an endpoint with address and socket_address");
343
      }
344
      SocketAddress socketAddress = lbEndpoint.getEndpoint().getAddress().getSocketAddress();
1✔
345
      if (!socketAddress.getResolverName().isEmpty()) {
1✔
346
        return StructOrError.fromError(
×
347
            "Cluster " + clusterName
348
                + ": LOGICAL DNS clusters must NOT have a custom resolver name set");
349
      }
350
      if (socketAddress.getPortSpecifierCase() != SocketAddress.PortSpecifierCase.PORT_VALUE) {
1✔
351
        return StructOrError.fromError(
×
352
            "Cluster " + clusterName
353
                + ": LOGICAL DNS clusters socket_address must have port_value");
354
      }
355
      String dnsHostName = String.format(
1✔
356
          Locale.US, "%s:%d", socketAddress.getAddress(), socketAddress.getPortValue());
1✔
357
      return StructOrError.fromStruct(CdsUpdate.forLogicalDns(
1✔
358
          clusterName, dnsHostName, lrsServerInfo, maxConcurrentRequests,
359
          upstreamTlsContext, isHttp11ProxyAvailable));
360
    }
361
    return StructOrError.fromError(
×
362
        "Cluster " + clusterName + ": unsupported built-in discovery type: " + type);
363
  }
364

365
  static io.envoyproxy.envoy.config.cluster.v3.OutlierDetection validateOutlierDetection(
366
      io.envoyproxy.envoy.config.cluster.v3.OutlierDetection outlierDetection)
367
      throws ResourceInvalidException {
368
    if (outlierDetection.hasInterval()) {
1✔
369
      if (!Durations.isValid(outlierDetection.getInterval())) {
1✔
370
        throw new ResourceInvalidException("outlier_detection interval is not a valid Duration");
1✔
371
      }
372
      if (hasNegativeValues(outlierDetection.getInterval())) {
1✔
373
        throw new ResourceInvalidException("outlier_detection interval has a negative value");
1✔
374
      }
375
    }
376
    if (outlierDetection.hasBaseEjectionTime()) {
1✔
377
      if (!Durations.isValid(outlierDetection.getBaseEjectionTime())) {
1✔
378
        throw new ResourceInvalidException(
1✔
379
            "outlier_detection base_ejection_time is not a valid Duration");
380
      }
381
      if (hasNegativeValues(outlierDetection.getBaseEjectionTime())) {
1✔
382
        throw new ResourceInvalidException(
1✔
383
            "outlier_detection base_ejection_time has a negative value");
384
      }
385
    }
386
    if (outlierDetection.hasMaxEjectionTime()) {
1✔
387
      if (!Durations.isValid(outlierDetection.getMaxEjectionTime())) {
1✔
388
        throw new ResourceInvalidException(
1✔
389
            "outlier_detection max_ejection_time is not a valid Duration");
390
      }
391
      if (hasNegativeValues(outlierDetection.getMaxEjectionTime())) {
1✔
392
        throw new ResourceInvalidException(
1✔
393
            "outlier_detection max_ejection_time has a negative value");
394
      }
395
    }
396
    if (outlierDetection.hasMaxEjectionPercent()
1✔
397
        && outlierDetection.getMaxEjectionPercent().getValue() > 100) {
1✔
398
      throw new ResourceInvalidException(
1✔
399
          "outlier_detection max_ejection_percent is > 100");
400
    }
401
    if (outlierDetection.hasEnforcingSuccessRate()
1✔
402
        && outlierDetection.getEnforcingSuccessRate().getValue() > 100) {
1✔
403
      throw new ResourceInvalidException(
1✔
404
          "outlier_detection enforcing_success_rate is > 100");
405
    }
406
    if (outlierDetection.hasFailurePercentageThreshold()
1✔
407
        && outlierDetection.getFailurePercentageThreshold().getValue() > 100) {
1✔
408
      throw new ResourceInvalidException(
1✔
409
          "outlier_detection failure_percentage_threshold is > 100");
410
    }
411
    if (outlierDetection.hasEnforcingFailurePercentage()
1✔
412
        && outlierDetection.getEnforcingFailurePercentage().getValue() > 100) {
1✔
413
      throw new ResourceInvalidException(
1✔
414
          "outlier_detection enforcing_failure_percentage is > 100");
415
    }
416

417
    return outlierDetection;
1✔
418
  }
419

420
  static boolean hasNegativeValues(Duration duration) {
421
    return duration.getSeconds() < 0 || duration.getNanos() < 0;
1✔
422
  }
423

424
  @VisibleForTesting
425
  static io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
426
      validateUpstreamTlsContext(
427
      io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext upstreamTlsContext,
428
      Set<String> certProviderInstances)
429
      throws ResourceInvalidException {
430
    if (upstreamTlsContext.hasCommonTlsContext()) {
1✔
431
      validateCommonTlsContext(upstreamTlsContext.getCommonTlsContext(), certProviderInstances,
1✔
432
          false);
433
    } else {
434
      throw new ResourceInvalidException("common-tls-context is required in upstream-tls-context");
1✔
435
    }
436
    return upstreamTlsContext;
1✔
437
  }
438

439
  @VisibleForTesting
440
  static void validateCommonTlsContext(
441
      CommonTlsContext commonTlsContext, Set<String> certProviderInstances, boolean server)
442
      throws ResourceInvalidException {
443
    if (commonTlsContext.hasCustomHandshaker()) {
1✔
444
      throw new ResourceInvalidException(
1✔
445
          "common-tls-context with custom_handshaker is not supported");
446
    }
447
    if (commonTlsContext.hasTlsParams()) {
1✔
448
      throw new ResourceInvalidException("common-tls-context with tls_params is not supported");
1✔
449
    }
450
    if (commonTlsContext.hasValidationContextSdsSecretConfig()) {
1✔
451
      throw new ResourceInvalidException(
1✔
452
          "common-tls-context with validation_context_sds_secret_config is not supported");
453
    }
454
    String certInstanceName = getIdentityCertInstanceName(commonTlsContext);
1✔
455
    if (certInstanceName == null) {
1✔
456
      if (server) {
1✔
457
        throw new ResourceInvalidException(
1✔
458
            "tls_certificate_provider_instance is required in downstream-tls-context");
459
      }
460
      if (commonTlsContext.getTlsCertificatesCount() > 0) {
1✔
461
        throw new ResourceInvalidException(
1✔
462
            "tls_certificate_provider_instance is unset");
463
      }
464
      if (commonTlsContext.getTlsCertificateSdsSecretConfigsCount() > 0) {
1✔
465
        throw new ResourceInvalidException(
1✔
466
            "tls_certificate_provider_instance is unset");
467
      }
468
    } else if (certProviderInstances == null || !certProviderInstances.contains(certInstanceName)) {
1✔
469
      throw new ResourceInvalidException(
1✔
470
          "CertificateProvider instance name '" + certInstanceName
471
              + "' not defined in the bootstrap file.");
472
    }
473
    String rootCaInstanceName = getRootCertInstanceName(commonTlsContext);
1✔
474
    if (rootCaInstanceName == null) {
1✔
475
      if (!server && (!enableSystemRootCerts
1✔
476
          || !CommonTlsContextUtil.isUsingSystemRootCerts(commonTlsContext))) {
1✔
477
        throw new ResourceInvalidException(
1✔
478
            "ca_certificate_provider_instance or system_root_certs is required in "
479
                + "upstream-tls-context");
480
      }
481
    } else {
482
      if (certProviderInstances == null || !certProviderInstances.contains(rootCaInstanceName)) {
1✔
483
        throw new ResourceInvalidException(
1✔
484
            "ca_certificate_provider_instance name '" + rootCaInstanceName
485
                + "' not defined in the bootstrap file.");
486
      }
487
      CertificateValidationContext certificateValidationContext = null;
1✔
488
      if (commonTlsContext.hasValidationContext()) {
1✔
489
        certificateValidationContext = commonTlsContext.getValidationContext();
1✔
490
      } else if (commonTlsContext.hasCombinedValidationContext() && commonTlsContext
1✔
491
          .getCombinedValidationContext().hasDefaultValidationContext()) {
1✔
492
        certificateValidationContext = commonTlsContext.getCombinedValidationContext()
1✔
493
            .getDefaultValidationContext();
1✔
494
      }
495
      if (certificateValidationContext != null) {
1✔
496
        @SuppressWarnings("deprecation") // gRFC A29 predates match_typed_subject_alt_names
497
        int matchSubjectAltNamesCount = certificateValidationContext.getMatchSubjectAltNamesCount();
1✔
498
        if (matchSubjectAltNamesCount > 0 && server) {
1✔
499
          throw new ResourceInvalidException(
1✔
500
              "match_subject_alt_names only allowed in upstream_tls_context");
501
        }
502
        if (certificateValidationContext.getVerifyCertificateSpkiCount() > 0) {
1✔
503
          throw new ResourceInvalidException(
1✔
504
              "verify_certificate_spki in default_validation_context is not supported");
505
        }
506
        if (certificateValidationContext.getVerifyCertificateHashCount() > 0) {
1✔
507
          throw new ResourceInvalidException(
1✔
508
              "verify_certificate_hash in default_validation_context is not supported");
509
        }
510
        if (certificateValidationContext.hasRequireSignedCertificateTimestamp()) {
1✔
511
          throw new ResourceInvalidException(
1✔
512
              "require_signed_certificate_timestamp in default_validation_context is not "
513
                  + "supported");
514
        }
515
        if (certificateValidationContext.hasCrl()) {
1✔
516
          throw new ResourceInvalidException("crl in default_validation_context is not supported");
1✔
517
        }
518
        if (certificateValidationContext.hasCustomValidatorConfig()) {
1✔
519
          throw new ResourceInvalidException(
1✔
520
              "custom_validator_config in default_validation_context is not supported");
521
        }
522
      }
523
    }
524
  }
1✔
525

526
  private static String getIdentityCertInstanceName(CommonTlsContext commonTlsContext) {
527
    if (commonTlsContext.hasTlsCertificateProviderInstance()) {
1✔
528
      return commonTlsContext.getTlsCertificateProviderInstance().getInstanceName();
1✔
529
    }
530
    return null;
1✔
531
  }
532

533
  private static String getRootCertInstanceName(CommonTlsContext commonTlsContext) {
534
    if (commonTlsContext.hasValidationContext()) {
1✔
535
      if (commonTlsContext.getValidationContext().hasCaCertificateProviderInstance()) {
1✔
536
        return commonTlsContext.getValidationContext().getCaCertificateProviderInstance()
1✔
537
            .getInstanceName();
1✔
538
      }
539
    } else if (commonTlsContext.hasCombinedValidationContext()) {
1✔
540
      CommonTlsContext.CombinedCertificateValidationContext combinedCertificateValidationContext
1✔
541
          = commonTlsContext.getCombinedValidationContext();
1✔
542
      if (combinedCertificateValidationContext.hasDefaultValidationContext()
1✔
543
          && combinedCertificateValidationContext.getDefaultValidationContext()
1✔
544
          .hasCaCertificateProviderInstance()) {
1✔
545
        return combinedCertificateValidationContext.getDefaultValidationContext()
1✔
546
            .getCaCertificateProviderInstance().getInstanceName();
1✔
547
      }
548
    }
549
    return null;
1✔
550
  }
551

552
  /** xDS resource update for cluster-level configuration. */
553
  @AutoValue
554
  abstract static class CdsUpdate implements ResourceUpdate {
1✔
555
    abstract String clusterName();
556

557
    abstract ClusterType clusterType();
558

559
    abstract ImmutableMap<String, ?> lbPolicyConfig();
560

561
    // Only valid if lbPolicy is "ring_hash_experimental".
562
    abstract long minRingSize();
563

564
    // Only valid if lbPolicy is "ring_hash_experimental".
565
    abstract long maxRingSize();
566

567
    // Only valid if lbPolicy is "least_request_experimental".
568
    abstract int choiceCount();
569

570
    // Alternative resource name to be used in EDS requests.
571
    /// Only valid for EDS cluster.
572
    @Nullable
573
    abstract String edsServiceName();
574

575
    // Corresponding DNS name to be used if upstream endpoints of the cluster is resolvable
576
    // via DNS.
577
    // Only valid for LOGICAL_DNS cluster.
578
    @Nullable
579
    abstract String dnsHostName();
580

581
    // Load report server info for reporting loads via LRS.
582
    // Only valid for EDS or LOGICAL_DNS cluster.
583
    @Nullable
584
    abstract ServerInfo lrsServerInfo();
585

586
    // Max number of concurrent requests can be sent to this cluster.
587
    // Only valid for EDS or LOGICAL_DNS cluster.
588
    @Nullable
589
    abstract Long maxConcurrentRequests();
590

591
    // TLS context used to connect to connect to this cluster.
592
    // Only valid for EDS or LOGICAL_DNS cluster.
593
    @Nullable
594
    abstract UpstreamTlsContext upstreamTlsContext();
595

596
    abstract boolean isHttp11ProxyAvailable();
597

598
    // List of underlying clusters making of this aggregate cluster.
599
    // Only valid for AGGREGATE cluster.
600
    @Nullable
601
    abstract ImmutableList<String> prioritizedClusterNames();
602

603
    // Outlier detection configuration.
604
    @Nullable
605
    abstract OutlierDetection outlierDetection();
606

607
    abstract ImmutableMap<String, Struct> filterMetadata();
608

609
    abstract ImmutableMap<String, Object> parsedMetadata();
610

611
    private static Builder newBuilder(String clusterName) {
612
      return new AutoValue_XdsClusterResource_CdsUpdate.Builder()
1✔
613
          .clusterName(clusterName)
1✔
614
          .minRingSize(0)
1✔
615
          .maxRingSize(0)
1✔
616
          .choiceCount(0)
1✔
617
          .filterMetadata(ImmutableMap.of())
1✔
618
          .parsedMetadata(ImmutableMap.of())
1✔
619
          .isHttp11ProxyAvailable(false);
1✔
620
    }
621

622
    static Builder forAggregate(String clusterName, List<String> prioritizedClusterNames) {
623
      checkNotNull(prioritizedClusterNames, "prioritizedClusterNames");
1✔
624
      return newBuilder(clusterName)
1✔
625
          .clusterType(ClusterType.AGGREGATE)
1✔
626
          .prioritizedClusterNames(ImmutableList.copyOf(prioritizedClusterNames));
1✔
627
    }
628

629
    static Builder forEds(String clusterName, @Nullable String edsServiceName,
630
                          @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests,
631
                          @Nullable UpstreamTlsContext upstreamTlsContext,
632
                          @Nullable OutlierDetection outlierDetection,
633
                          boolean isHttp11ProxyAvailable) {
634
      return newBuilder(clusterName)
1✔
635
          .clusterType(ClusterType.EDS)
1✔
636
          .edsServiceName(edsServiceName)
1✔
637
          .lrsServerInfo(lrsServerInfo)
1✔
638
          .maxConcurrentRequests(maxConcurrentRequests)
1✔
639
          .upstreamTlsContext(upstreamTlsContext)
1✔
640
          .outlierDetection(outlierDetection)
1✔
641
          .isHttp11ProxyAvailable(isHttp11ProxyAvailable);
1✔
642
    }
643

644
    static Builder forLogicalDns(String clusterName, String dnsHostName,
645
                                 @Nullable ServerInfo lrsServerInfo,
646
                                 @Nullable Long maxConcurrentRequests,
647
                                 @Nullable UpstreamTlsContext upstreamTlsContext,
648
                                 boolean isHttp11ProxyAvailable) {
649
      return newBuilder(clusterName)
1✔
650
          .clusterType(ClusterType.LOGICAL_DNS)
1✔
651
          .dnsHostName(dnsHostName)
1✔
652
          .lrsServerInfo(lrsServerInfo)
1✔
653
          .maxConcurrentRequests(maxConcurrentRequests)
1✔
654
          .upstreamTlsContext(upstreamTlsContext)
1✔
655
          .isHttp11ProxyAvailable(isHttp11ProxyAvailable);
1✔
656
    }
657

658
    enum ClusterType {
1✔
659
      EDS, LOGICAL_DNS, AGGREGATE
1✔
660
    }
661

662
    enum LbPolicy {
×
663
      ROUND_ROBIN, RING_HASH, LEAST_REQUEST
×
664
    }
665

666
    // FIXME(chengyuanzhang): delete this after UpstreamTlsContext's toString() is fixed.
667
    @Override
668
    public final String toString() {
669
      return MoreObjects.toStringHelper(this)
1✔
670
          .add("clusterName", clusterName())
1✔
671
          .add("clusterType", clusterType())
1✔
672
          .add("lbPolicyConfig", lbPolicyConfig())
1✔
673
          .add("minRingSize", minRingSize())
1✔
674
          .add("maxRingSize", maxRingSize())
1✔
675
          .add("choiceCount", choiceCount())
1✔
676
          .add("edsServiceName", edsServiceName())
1✔
677
          .add("dnsHostName", dnsHostName())
1✔
678
          .add("lrsServerInfo", lrsServerInfo())
1✔
679
          .add("maxConcurrentRequests", maxConcurrentRequests())
1✔
680
          // Exclude upstreamTlsContext and outlierDetection as their string representations are
681
          // cumbersome.
682
          .add("prioritizedClusterNames", prioritizedClusterNames())
1✔
683
          .toString();
1✔
684
    }
685

686
    @AutoValue.Builder
687
    abstract static class Builder {
1✔
688
      // Private, use one of the static factory methods instead.
689
      protected abstract Builder clusterName(String clusterName);
690

691
      // Private, use one of the static factory methods instead.
692
      protected abstract Builder clusterType(ClusterType clusterType);
693

694
      protected abstract Builder lbPolicyConfig(ImmutableMap<String, ?> lbPolicyConfig);
695

696
      Builder roundRobinLbPolicy() {
697
        return this.lbPolicyConfig(ImmutableMap.of("round_robin", ImmutableMap.of()));
1✔
698
      }
699

700
      Builder ringHashLbPolicy(Long minRingSize, Long maxRingSize) {
701
        return this.lbPolicyConfig(ImmutableMap.of("ring_hash_experimental",
1✔
702
            ImmutableMap.of("minRingSize", minRingSize.doubleValue(), "maxRingSize",
1✔
703
                maxRingSize.doubleValue())));
1✔
704
      }
705

706
      Builder leastRequestLbPolicy(Integer choiceCount) {
707
        return this.lbPolicyConfig(ImmutableMap.of("least_request_experimental",
1✔
708
            ImmutableMap.of("choiceCount", choiceCount.doubleValue())));
1✔
709
      }
710

711
      // Private, use leastRequestLbPolicy(int).
712
      protected abstract Builder choiceCount(int choiceCount);
713

714
      // Private, use ringHashLbPolicy(long, long).
715
      protected abstract Builder minRingSize(long minRingSize);
716

717
      // Private, use ringHashLbPolicy(long, long).
718
      protected abstract Builder maxRingSize(long maxRingSize);
719

720
      // Private, use CdsUpdate.forEds() instead.
721
      protected abstract Builder edsServiceName(String edsServiceName);
722

723
      // Private, use CdsUpdate.forLogicalDns() instead.
724
      protected abstract Builder dnsHostName(String dnsHostName);
725

726
      // Private, use one of the static factory methods instead.
727
      protected abstract Builder lrsServerInfo(ServerInfo lrsServerInfo);
728

729
      // Private, use one of the static factory methods instead.
730
      protected abstract Builder maxConcurrentRequests(Long maxConcurrentRequests);
731

732
      protected abstract Builder isHttp11ProxyAvailable(boolean isHttp11ProxyAvailable);
733

734
      // Private, use one of the static factory methods instead.
735
      protected abstract Builder upstreamTlsContext(UpstreamTlsContext upstreamTlsContext);
736

737
      // Private, use CdsUpdate.forAggregate() instead.
738
      protected abstract Builder prioritizedClusterNames(List<String> prioritizedClusterNames);
739

740
      protected abstract Builder outlierDetection(OutlierDetection outlierDetection);
741

742
      protected abstract Builder filterMetadata(ImmutableMap<String, Struct> filterMetadata);
743

744
      protected abstract Builder parsedMetadata(ImmutableMap<String, Object> parsedMetadata);
745

746
      abstract CdsUpdate build();
747
    }
748
  }
749
}
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