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

grpc / grpc-java / #19811

15 May 2025 05:29AM UTC coverage: 88.597% (-0.005%) from 88.602%
#19811

push

github

web-flow
kokoro: add cloud run tests config (#12065)

kokoro: add cloud run tests config

34778 of 39254 relevant lines covered (88.6%)

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

© 2026 Coveralls, Inc