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

grpc / grpc-java / #18702

pending completion
#18702

push

github-actions

web-flow
require EDS service name in CDS resources with xdstp name (#10329)

* require EDS service name in CDS resources with xdstp name

30555 of 34638 relevant lines covered (88.21%)

0.88 hits per line

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

89.01
/../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.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.collect.ImmutableList;
26
import com.google.common.collect.ImmutableMap;
27
import com.google.protobuf.Duration;
28
import com.google.protobuf.InvalidProtocolBufferException;
29
import com.google.protobuf.Message;
30
import com.google.protobuf.util.Durations;
31
import io.envoyproxy.envoy.config.cluster.v3.CircuitBreakers.Thresholds;
32
import io.envoyproxy.envoy.config.cluster.v3.Cluster;
33
import io.envoyproxy.envoy.config.core.v3.RoutingPriority;
34
import io.envoyproxy.envoy.config.core.v3.SocketAddress;
35
import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment;
36
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext;
37
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext;
38
import io.grpc.LoadBalancerRegistry;
39
import io.grpc.NameResolver;
40
import io.grpc.internal.ServiceConfigUtil;
41
import io.grpc.internal.ServiceConfigUtil.LbConfig;
42
import io.grpc.xds.EnvoyServerProtoData.OutlierDetection;
43
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
44
import io.grpc.xds.XdsClient.ResourceUpdate;
45
import io.grpc.xds.XdsClientImpl.ResourceInvalidException;
46
import io.grpc.xds.XdsClusterResource.CdsUpdate;
47
import java.util.List;
48
import java.util.Locale;
49
import java.util.Set;
50
import javax.annotation.Nullable;
51

52
class XdsClusterResource extends XdsResourceType<CdsUpdate> {
1✔
53
  static final String ADS_TYPE_URL_CDS =
54
      "type.googleapis.com/envoy.config.cluster.v3.Cluster";
55
  private static final String TYPE_URL_UPSTREAM_TLS_CONTEXT =
56
      "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext";
57
  private static final String TYPE_URL_UPSTREAM_TLS_CONTEXT_V2 =
58
      "type.googleapis.com/envoy.api.v2.auth.UpstreamTlsContext";
59

60
  private static final XdsClusterResource instance = new XdsClusterResource();
1✔
61

62
  public static XdsClusterResource getInstance() {
63
    return instance;
1✔
64
  }
65

66
  @Override
67
  @Nullable
68
  String extractResourceName(Message unpackedResource) {
69
    if (!(unpackedResource instanceof Cluster)) {
1✔
70
      return null;
×
71
    }
72
    return ((Cluster) unpackedResource).getName();
1✔
73
  }
74

75
  @Override
76
  String typeName() {
77
    return "CDS";
1✔
78
  }
79

80
  @Override
81
  String typeUrl() {
82
    return ADS_TYPE_URL_CDS;
1✔
83
  }
84

85
  @Override
86
  boolean isFullStateOfTheWorld() {
87
    return true;
1✔
88
  }
89

90
  @Override
91
  @SuppressWarnings("unchecked")
92
  Class<Cluster> unpackedClassName() {
93
    return Cluster.class;
1✔
94
  }
95

96
  @Override
97
  CdsUpdate doParse(Args args, Message unpackedMessage)
98
      throws ResourceInvalidException {
99
    if (!(unpackedMessage instanceof Cluster)) {
1✔
100
      throw new ResourceInvalidException("Invalid message type: " + unpackedMessage.getClass());
×
101
    }
102
    Set<String> certProviderInstances = null;
1✔
103
    if (args.bootstrapInfo != null && args.bootstrapInfo.certProviders() != null) {
1✔
104
      certProviderInstances = args.bootstrapInfo.certProviders().keySet();
1✔
105
    }
106
    return processCluster((Cluster) unpackedMessage, certProviderInstances,
1✔
107
        args.serverInfo, args.loadBalancerRegistry);
108
  }
109

110
  @VisibleForTesting
111
  static CdsUpdate processCluster(Cluster cluster,
112
                                  Set<String> certProviderInstances,
113
                                  Bootstrapper.ServerInfo serverInfo,
114
                                  LoadBalancerRegistry loadBalancerRegistry)
115
      throws ResourceInvalidException {
116
    StructOrError<CdsUpdate.Builder> structOrError;
117
    switch (cluster.getClusterDiscoveryTypeCase()) {
1✔
118
      case TYPE:
119
        structOrError = parseNonAggregateCluster(cluster,
1✔
120
            certProviderInstances, serverInfo);
121
        break;
1✔
122
      case CLUSTER_TYPE:
123
        structOrError = parseAggregateCluster(cluster);
1✔
124
        break;
1✔
125
      case CLUSTERDISCOVERYTYPE_NOT_SET:
126
      default:
127
        throw new ResourceInvalidException(
1✔
128
            "Cluster " + cluster.getName() + ": unspecified cluster discovery type");
1✔
129
    }
130
    if (structOrError.getErrorDetail() != null) {
1✔
131
      throw new ResourceInvalidException(structOrError.getErrorDetail());
1✔
132
    }
133
    CdsUpdate.Builder updateBuilder = structOrError.getStruct();
1✔
134

135
    ImmutableMap<String, ?> lbPolicyConfig = LoadBalancerConfigFactory.newConfig(cluster,
1✔
136
        enableLeastRequest, enableWrr, enablePickFirst);
137

138
    // Validate the LB config by trying to parse it with the corresponding LB provider.
139
    LbConfig lbConfig = ServiceConfigUtil.unwrapLoadBalancingConfig(lbPolicyConfig);
1✔
140
    NameResolver.ConfigOrError configOrError = loadBalancerRegistry.getProvider(
1✔
141
        lbConfig.getPolicyName()).parseLoadBalancingPolicyConfig(
1✔
142
        lbConfig.getRawConfigValue());
1✔
143
    if (configOrError.getError() != null) {
1✔
144
      throw new ResourceInvalidException(structOrError.getErrorDetail());
×
145
    }
146

147
    updateBuilder.lbPolicyConfig(lbPolicyConfig);
1✔
148

149
    return updateBuilder.build();
1✔
150
  }
151

152
  private static StructOrError<CdsUpdate.Builder> parseAggregateCluster(Cluster cluster) {
153
    String clusterName = cluster.getName();
1✔
154
    Cluster.CustomClusterType customType = cluster.getClusterType();
1✔
155
    String typeName = customType.getName();
1✔
156
    if (!typeName.equals(AGGREGATE_CLUSTER_TYPE_NAME)) {
1✔
157
      return StructOrError.fromError(
×
158
          "Cluster " + clusterName + ": unsupported custom cluster type: " + typeName);
159
    }
160
    io.envoyproxy.envoy.extensions.clusters.aggregate.v3.ClusterConfig clusterConfig;
161
    try {
162
      clusterConfig = unpackCompatibleType(customType.getTypedConfig(),
1✔
163
          io.envoyproxy.envoy.extensions.clusters.aggregate.v3.ClusterConfig.class,
164
          TYPE_URL_CLUSTER_CONFIG, null);
165
    } catch (InvalidProtocolBufferException e) {
×
166
      return StructOrError.fromError("Cluster " + clusterName + ": malformed ClusterConfig: " + e);
×
167
    }
1✔
168
    return StructOrError.fromStruct(CdsUpdate.forAggregate(
1✔
169
        clusterName, clusterConfig.getClustersList()));
1✔
170
  }
171

172
  private static StructOrError<CdsUpdate.Builder> parseNonAggregateCluster(
173
      Cluster cluster, Set<String> certProviderInstances, Bootstrapper.ServerInfo serverInfo) {
174
    String clusterName = cluster.getName();
1✔
175
    Bootstrapper.ServerInfo lrsServerInfo = null;
1✔
176
    Long maxConcurrentRequests = null;
1✔
177
    EnvoyServerProtoData.UpstreamTlsContext upstreamTlsContext = null;
1✔
178
    OutlierDetection outlierDetection = null;
1✔
179
    if (cluster.hasLrsServer()) {
1✔
180
      if (!cluster.getLrsServer().hasSelf()) {
1✔
181
        return StructOrError.fromError(
×
182
            "Cluster " + clusterName + ": only support LRS for the same management server");
183
      }
184
      lrsServerInfo = serverInfo;
1✔
185
    }
186
    if (cluster.hasCircuitBreakers()) {
1✔
187
      List<Thresholds> thresholds = cluster.getCircuitBreakers().getThresholdsList();
1✔
188
      for (Thresholds threshold : thresholds) {
1✔
189
        if (threshold.getPriority() != RoutingPriority.DEFAULT) {
1✔
190
          continue;
1✔
191
        }
192
        if (threshold.hasMaxRequests()) {
1✔
193
          maxConcurrentRequests = (long) threshold.getMaxRequests().getValue();
1✔
194
        }
195
      }
1✔
196
    }
197
    if (cluster.getTransportSocketMatchesCount() > 0) {
1✔
198
      return StructOrError.fromError("Cluster " + clusterName
1✔
199
          + ": transport-socket-matches not supported.");
200
    }
201
    if (cluster.hasTransportSocket()) {
1✔
202
      if (!TRANSPORT_SOCKET_NAME_TLS.equals(cluster.getTransportSocket().getName())) {
1✔
203
        return StructOrError.fromError("transport-socket with name "
1✔
204
            + cluster.getTransportSocket().getName() + " not supported.");
1✔
205
      }
206
      try {
207
        upstreamTlsContext = UpstreamTlsContext.fromEnvoyProtoUpstreamTlsContext(
1✔
208
            validateUpstreamTlsContext(
1✔
209
                unpackCompatibleType(cluster.getTransportSocket().getTypedConfig(),
1✔
210
                io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext.class,
211
                TYPE_URL_UPSTREAM_TLS_CONTEXT, TYPE_URL_UPSTREAM_TLS_CONTEXT_V2),
212
                certProviderInstances));
213
      } catch (InvalidProtocolBufferException | ResourceInvalidException e) {
1✔
214
        return StructOrError.fromError(
1✔
215
            "Cluster " + clusterName + ": malformed UpstreamTlsContext: " + e);
216
      }
1✔
217
    }
218

219
    if (cluster.hasOutlierDetection()) {
1✔
220
      try {
221
        outlierDetection = OutlierDetection.fromEnvoyOutlierDetection(
1✔
222
            validateOutlierDetection(cluster.getOutlierDetection()));
1✔
223
      } catch (ResourceInvalidException e) {
1✔
224
        return StructOrError.fromError(
1✔
225
            "Cluster " + clusterName + ": malformed outlier_detection: " + e);
226
      }
1✔
227
    }
228

229
    Cluster.DiscoveryType type = cluster.getType();
1✔
230
    if (type == Cluster.DiscoveryType.EDS) {
1✔
231
      String edsServiceName = null;
1✔
232
      io.envoyproxy.envoy.config.cluster.v3.Cluster.EdsClusterConfig edsClusterConfig =
1✔
233
          cluster.getEdsClusterConfig();
1✔
234
      if (!edsClusterConfig.getEdsConfig().hasAds()
1✔
235
          && ! edsClusterConfig.getEdsConfig().hasSelf()) {
1✔
236
        return StructOrError.fromError(
1✔
237
            "Cluster " + clusterName + ": field eds_cluster_config must be set to indicate to use"
238
                + " EDS over ADS or self ConfigSource");
239
      }
240
      // If the service_name field is set, that value will be used for the EDS request.
241
      if (!edsClusterConfig.getServiceName().isEmpty()) {
1✔
242
        edsServiceName = edsClusterConfig.getServiceName();
1✔
243
      }
244
      // edsServiceName is required if the CDS resource has an xdstp name.
245
      if ((edsServiceName == null) && clusterName.toLowerCase().startsWith("xdstp:")) {
1✔
246
        return StructOrError.fromError(
1✔
247
            "EDS service_name must be set when Cluster resource has an xdstp name");
248
      }
249
      return StructOrError.fromStruct(CdsUpdate.forEds(
1✔
250
          clusterName, edsServiceName, lrsServerInfo, maxConcurrentRequests, upstreamTlsContext,
251
          outlierDetection));
252
    } else if (type.equals(Cluster.DiscoveryType.LOGICAL_DNS)) {
1✔
253
      if (!cluster.hasLoadAssignment()) {
1✔
254
        return StructOrError.fromError(
×
255
            "Cluster " + clusterName + ": LOGICAL_DNS clusters must have a single host");
256
      }
257
      ClusterLoadAssignment assignment = cluster.getLoadAssignment();
1✔
258
      if (assignment.getEndpointsCount() != 1
1✔
259
          || assignment.getEndpoints(0).getLbEndpointsCount() != 1) {
1✔
260
        return StructOrError.fromError(
×
261
            "Cluster " + clusterName + ": LOGICAL_DNS clusters must have a single "
262
                + "locality_lb_endpoint and a single lb_endpoint");
263
      }
264
      io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint lbEndpoint =
1✔
265
          assignment.getEndpoints(0).getLbEndpoints(0);
1✔
266
      if (!lbEndpoint.hasEndpoint() || !lbEndpoint.getEndpoint().hasAddress()
1✔
267
          || !lbEndpoint.getEndpoint().getAddress().hasSocketAddress()) {
1✔
268
        return StructOrError.fromError(
×
269
            "Cluster " + clusterName
270
                + ": LOGICAL_DNS clusters must have an endpoint with address and socket_address");
271
      }
272
      SocketAddress socketAddress = lbEndpoint.getEndpoint().getAddress().getSocketAddress();
1✔
273
      if (!socketAddress.getResolverName().isEmpty()) {
1✔
274
        return StructOrError.fromError(
×
275
            "Cluster " + clusterName
276
                + ": LOGICAL DNS clusters must NOT have a custom resolver name set");
277
      }
278
      if (socketAddress.getPortSpecifierCase() != SocketAddress.PortSpecifierCase.PORT_VALUE) {
1✔
279
        return StructOrError.fromError(
×
280
            "Cluster " + clusterName
281
                + ": LOGICAL DNS clusters socket_address must have port_value");
282
      }
283
      String dnsHostName = String.format(
1✔
284
          Locale.US, "%s:%d", socketAddress.getAddress(), socketAddress.getPortValue());
1✔
285
      return StructOrError.fromStruct(CdsUpdate.forLogicalDns(
1✔
286
          clusterName, dnsHostName, lrsServerInfo, maxConcurrentRequests, upstreamTlsContext));
287
    }
288
    return StructOrError.fromError(
×
289
        "Cluster " + clusterName + ": unsupported built-in discovery type: " + type);
290
  }
291

292
  static io.envoyproxy.envoy.config.cluster.v3.OutlierDetection validateOutlierDetection(
293
      io.envoyproxy.envoy.config.cluster.v3.OutlierDetection outlierDetection)
294
      throws ResourceInvalidException {
295
    if (outlierDetection.hasInterval()) {
1✔
296
      if (!Durations.isValid(outlierDetection.getInterval())) {
1✔
297
        throw new ResourceInvalidException("outlier_detection interval is not a valid Duration");
1✔
298
      }
299
      if (hasNegativeValues(outlierDetection.getInterval())) {
1✔
300
        throw new ResourceInvalidException("outlier_detection interval has a negative value");
1✔
301
      }
302
    }
303
    if (outlierDetection.hasBaseEjectionTime()) {
1✔
304
      if (!Durations.isValid(outlierDetection.getBaseEjectionTime())) {
1✔
305
        throw new ResourceInvalidException(
1✔
306
            "outlier_detection base_ejection_time is not a valid Duration");
307
      }
308
      if (hasNegativeValues(outlierDetection.getBaseEjectionTime())) {
1✔
309
        throw new ResourceInvalidException(
1✔
310
            "outlier_detection base_ejection_time has a negative value");
311
      }
312
    }
313
    if (outlierDetection.hasMaxEjectionTime()) {
1✔
314
      if (!Durations.isValid(outlierDetection.getMaxEjectionTime())) {
1✔
315
        throw new ResourceInvalidException(
1✔
316
            "outlier_detection max_ejection_time is not a valid Duration");
317
      }
318
      if (hasNegativeValues(outlierDetection.getMaxEjectionTime())) {
1✔
319
        throw new ResourceInvalidException(
1✔
320
            "outlier_detection max_ejection_time has a negative value");
321
      }
322
    }
323
    if (outlierDetection.hasMaxEjectionPercent()
1✔
324
        && outlierDetection.getMaxEjectionPercent().getValue() > 100) {
1✔
325
      throw new ResourceInvalidException(
1✔
326
          "outlier_detection max_ejection_percent is > 100");
327
    }
328
    if (outlierDetection.hasEnforcingSuccessRate()
1✔
329
        && outlierDetection.getEnforcingSuccessRate().getValue() > 100) {
1✔
330
      throw new ResourceInvalidException(
1✔
331
          "outlier_detection enforcing_success_rate is > 100");
332
    }
333
    if (outlierDetection.hasFailurePercentageThreshold()
1✔
334
        && outlierDetection.getFailurePercentageThreshold().getValue() > 100) {
1✔
335
      throw new ResourceInvalidException(
1✔
336
          "outlier_detection failure_percentage_threshold is > 100");
337
    }
338
    if (outlierDetection.hasEnforcingFailurePercentage()
1✔
339
        && outlierDetection.getEnforcingFailurePercentage().getValue() > 100) {
1✔
340
      throw new ResourceInvalidException(
1✔
341
          "outlier_detection enforcing_failure_percentage is > 100");
342
    }
343

344
    return outlierDetection;
1✔
345
  }
346

347
  static boolean hasNegativeValues(Duration duration) {
348
    return duration.getSeconds() < 0 || duration.getNanos() < 0;
1✔
349
  }
350

351
  @VisibleForTesting
352
  static io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
353
      validateUpstreamTlsContext(
354
      io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext upstreamTlsContext,
355
      Set<String> certProviderInstances)
356
      throws ResourceInvalidException {
357
    if (upstreamTlsContext.hasCommonTlsContext()) {
1✔
358
      validateCommonTlsContext(upstreamTlsContext.getCommonTlsContext(), certProviderInstances,
1✔
359
          false);
360
    } else {
361
      throw new ResourceInvalidException("common-tls-context is required in upstream-tls-context");
1✔
362
    }
363
    return upstreamTlsContext;
1✔
364
  }
365

366
  @VisibleForTesting
367
  static void validateCommonTlsContext(
368
      CommonTlsContext commonTlsContext, Set<String> certProviderInstances, boolean server)
369
      throws ResourceInvalidException {
370
    if (commonTlsContext.hasCustomHandshaker()) {
1✔
371
      throw new ResourceInvalidException(
1✔
372
          "common-tls-context with custom_handshaker is not supported");
373
    }
374
    if (commonTlsContext.hasTlsParams()) {
1✔
375
      throw new ResourceInvalidException("common-tls-context with tls_params is not supported");
1✔
376
    }
377
    if (commonTlsContext.hasValidationContextSdsSecretConfig()) {
1✔
378
      throw new ResourceInvalidException(
1✔
379
          "common-tls-context with validation_context_sds_secret_config is not supported");
380
    }
381
    if (commonTlsContext.hasValidationContextCertificateProvider()) {
1✔
382
      throw new ResourceInvalidException(
1✔
383
          "common-tls-context with validation_context_certificate_provider is not supported");
384
    }
385
    if (commonTlsContext.hasValidationContextCertificateProviderInstance()) {
1✔
386
      throw new ResourceInvalidException(
1✔
387
          "common-tls-context with validation_context_certificate_provider_instance is not"
388
              + " supported");
389
    }
390
    String certInstanceName = getIdentityCertInstanceName(commonTlsContext);
1✔
391
    if (certInstanceName == null) {
1✔
392
      if (server) {
1✔
393
        throw new ResourceInvalidException(
1✔
394
            "tls_certificate_provider_instance is required in downstream-tls-context");
395
      }
396
      if (commonTlsContext.getTlsCertificatesCount() > 0) {
1✔
397
        throw new ResourceInvalidException(
1✔
398
            "tls_certificate_provider_instance is unset");
399
      }
400
      if (commonTlsContext.getTlsCertificateSdsSecretConfigsCount() > 0) {
1✔
401
        throw new ResourceInvalidException(
1✔
402
            "tls_certificate_provider_instance is unset");
403
      }
404
      if (commonTlsContext.hasTlsCertificateCertificateProvider()) {
1✔
405
        throw new ResourceInvalidException(
1✔
406
            "tls_certificate_provider_instance is unset");
407
      }
408
    } else if (certProviderInstances == null || !certProviderInstances.contains(certInstanceName)) {
1✔
409
      throw new ResourceInvalidException(
1✔
410
          "CertificateProvider instance name '" + certInstanceName
411
              + "' not defined in the bootstrap file.");
412
    }
413
    String rootCaInstanceName = getRootCertInstanceName(commonTlsContext);
1✔
414
    if (rootCaInstanceName == null) {
1✔
415
      if (!server) {
1✔
416
        throw new ResourceInvalidException(
1✔
417
            "ca_certificate_provider_instance is required in upstream-tls-context");
418
      }
419
    } else {
420
      if (certProviderInstances == null || !certProviderInstances.contains(rootCaInstanceName)) {
1✔
421
        throw new ResourceInvalidException(
1✔
422
            "ca_certificate_provider_instance name '" + rootCaInstanceName
423
                + "' not defined in the bootstrap file.");
424
      }
425
      CertificateValidationContext certificateValidationContext = null;
1✔
426
      if (commonTlsContext.hasValidationContext()) {
1✔
427
        certificateValidationContext = commonTlsContext.getValidationContext();
1✔
428
      } else if (commonTlsContext.hasCombinedValidationContext() && commonTlsContext
1✔
429
          .getCombinedValidationContext().hasDefaultValidationContext()) {
1✔
430
        certificateValidationContext = commonTlsContext.getCombinedValidationContext()
1✔
431
            .getDefaultValidationContext();
1✔
432
      }
433
      if (certificateValidationContext != null) {
1✔
434
        if (certificateValidationContext.getMatchSubjectAltNamesCount() > 0 && server) {
1✔
435
          throw new ResourceInvalidException(
1✔
436
              "match_subject_alt_names only allowed in upstream_tls_context");
437
        }
438
        if (certificateValidationContext.getVerifyCertificateSpkiCount() > 0) {
1✔
439
          throw new ResourceInvalidException(
1✔
440
              "verify_certificate_spki in default_validation_context is not supported");
441
        }
442
        if (certificateValidationContext.getVerifyCertificateHashCount() > 0) {
1✔
443
          throw new ResourceInvalidException(
1✔
444
              "verify_certificate_hash in default_validation_context is not supported");
445
        }
446
        if (certificateValidationContext.hasRequireSignedCertificateTimestamp()) {
1✔
447
          throw new ResourceInvalidException(
1✔
448
              "require_signed_certificate_timestamp in default_validation_context is not "
449
                  + "supported");
450
        }
451
        if (certificateValidationContext.hasCrl()) {
1✔
452
          throw new ResourceInvalidException("crl in default_validation_context is not supported");
1✔
453
        }
454
        if (certificateValidationContext.hasCustomValidatorConfig()) {
1✔
455
          throw new ResourceInvalidException(
1✔
456
              "custom_validator_config in default_validation_context is not supported");
457
        }
458
      }
459
    }
460
  }
1✔
461

462
  private static String getIdentityCertInstanceName(CommonTlsContext commonTlsContext) {
463
    if (commonTlsContext.hasTlsCertificateProviderInstance()) {
1✔
464
      return commonTlsContext.getTlsCertificateProviderInstance().getInstanceName();
1✔
465
    } else if (commonTlsContext.hasTlsCertificateCertificateProviderInstance()) {
1✔
466
      return commonTlsContext.getTlsCertificateCertificateProviderInstance().getInstanceName();
1✔
467
    }
468
    return null;
1✔
469
  }
470

471
  private static String getRootCertInstanceName(CommonTlsContext commonTlsContext) {
472
    if (commonTlsContext.hasValidationContext()) {
1✔
473
      if (commonTlsContext.getValidationContext().hasCaCertificateProviderInstance()) {
1✔
474
        return commonTlsContext.getValidationContext().getCaCertificateProviderInstance()
1✔
475
            .getInstanceName();
1✔
476
      }
477
    } else if (commonTlsContext.hasCombinedValidationContext()) {
1✔
478
      CommonTlsContext.CombinedCertificateValidationContext combinedCertificateValidationContext
1✔
479
          = commonTlsContext.getCombinedValidationContext();
1✔
480
      if (combinedCertificateValidationContext.hasDefaultValidationContext()
1✔
481
          && combinedCertificateValidationContext.getDefaultValidationContext()
1✔
482
          .hasCaCertificateProviderInstance()) {
1✔
483
        return combinedCertificateValidationContext.getDefaultValidationContext()
×
484
            .getCaCertificateProviderInstance().getInstanceName();
×
485
      } else if (combinedCertificateValidationContext
1✔
486
          .hasValidationContextCertificateProviderInstance()) {
1✔
487
        return combinedCertificateValidationContext
1✔
488
            .getValidationContextCertificateProviderInstance().getInstanceName();
1✔
489
      }
490
    }
491
    return null;
1✔
492
  }
493

494
  /** xDS resource update for cluster-level configuration. */
495
  @AutoValue
496
  abstract static class CdsUpdate implements ResourceUpdate {
1✔
497
    abstract String clusterName();
498

499
    abstract ClusterType clusterType();
500

501
    abstract ImmutableMap<String, ?> lbPolicyConfig();
502

503
    // Only valid if lbPolicy is "ring_hash_experimental".
504
    abstract long minRingSize();
505

506
    // Only valid if lbPolicy is "ring_hash_experimental".
507
    abstract long maxRingSize();
508

509
    // Only valid if lbPolicy is "least_request_experimental".
510
    abstract int choiceCount();
511

512
    // Alternative resource name to be used in EDS requests.
513
    /// Only valid for EDS cluster.
514
    @Nullable
515
    abstract String edsServiceName();
516

517
    // Corresponding DNS name to be used if upstream endpoints of the cluster is resolvable
518
    // via DNS.
519
    // Only valid for LOGICAL_DNS cluster.
520
    @Nullable
521
    abstract String dnsHostName();
522

523
    // Load report server info for reporting loads via LRS.
524
    // Only valid for EDS or LOGICAL_DNS cluster.
525
    @Nullable
526
    abstract ServerInfo lrsServerInfo();
527

528
    // Max number of concurrent requests can be sent to this cluster.
529
    // Only valid for EDS or LOGICAL_DNS cluster.
530
    @Nullable
531
    abstract Long maxConcurrentRequests();
532

533
    // TLS context used to connect to connect to this cluster.
534
    // Only valid for EDS or LOGICAL_DNS cluster.
535
    @Nullable
536
    abstract UpstreamTlsContext upstreamTlsContext();
537

538
    // List of underlying clusters making of this aggregate cluster.
539
    // Only valid for AGGREGATE cluster.
540
    @Nullable
541
    abstract ImmutableList<String> prioritizedClusterNames();
542

543
    // Outlier detection configuration.
544
    @Nullable
545
    abstract OutlierDetection outlierDetection();
546

547
    static Builder forAggregate(String clusterName, List<String> prioritizedClusterNames) {
548
      checkNotNull(prioritizedClusterNames, "prioritizedClusterNames");
1✔
549
      return new AutoValue_XdsClusterResource_CdsUpdate.Builder()
1✔
550
          .clusterName(clusterName)
1✔
551
          .clusterType(ClusterType.AGGREGATE)
1✔
552
          .minRingSize(0)
1✔
553
          .maxRingSize(0)
1✔
554
          .choiceCount(0)
1✔
555
          .prioritizedClusterNames(ImmutableList.copyOf(prioritizedClusterNames));
1✔
556
    }
557

558
    static Builder forEds(String clusterName, @Nullable String edsServiceName,
559
                          @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests,
560
                          @Nullable UpstreamTlsContext upstreamTlsContext,
561
                          @Nullable OutlierDetection outlierDetection) {
562
      return new AutoValue_XdsClusterResource_CdsUpdate.Builder()
1✔
563
          .clusterName(clusterName)
1✔
564
          .clusterType(ClusterType.EDS)
1✔
565
          .minRingSize(0)
1✔
566
          .maxRingSize(0)
1✔
567
          .choiceCount(0)
1✔
568
          .edsServiceName(edsServiceName)
1✔
569
          .lrsServerInfo(lrsServerInfo)
1✔
570
          .maxConcurrentRequests(maxConcurrentRequests)
1✔
571
          .upstreamTlsContext(upstreamTlsContext)
1✔
572
          .outlierDetection(outlierDetection);
1✔
573
    }
574

575
    static Builder forLogicalDns(String clusterName, String dnsHostName,
576
                                 @Nullable ServerInfo lrsServerInfo,
577
                                 @Nullable Long maxConcurrentRequests,
578
                                 @Nullable UpstreamTlsContext upstreamTlsContext) {
579
      return new AutoValue_XdsClusterResource_CdsUpdate.Builder()
1✔
580
          .clusterName(clusterName)
1✔
581
          .clusterType(ClusterType.LOGICAL_DNS)
1✔
582
          .minRingSize(0)
1✔
583
          .maxRingSize(0)
1✔
584
          .choiceCount(0)
1✔
585
          .dnsHostName(dnsHostName)
1✔
586
          .lrsServerInfo(lrsServerInfo)
1✔
587
          .maxConcurrentRequests(maxConcurrentRequests)
1✔
588
          .upstreamTlsContext(upstreamTlsContext);
1✔
589
    }
590

591
    enum ClusterType {
1✔
592
      EDS, LOGICAL_DNS, AGGREGATE
1✔
593
    }
594

595
    enum LbPolicy {
×
596
      ROUND_ROBIN, RING_HASH, LEAST_REQUEST
×
597
    }
598

599
    // FIXME(chengyuanzhang): delete this after UpstreamTlsContext's toString() is fixed.
600
    @Override
601
    public final String toString() {
602
      return MoreObjects.toStringHelper(this)
×
603
          .add("clusterName", clusterName())
×
604
          .add("clusterType", clusterType())
×
605
          .add("lbPolicyConfig", lbPolicyConfig())
×
606
          .add("minRingSize", minRingSize())
×
607
          .add("maxRingSize", maxRingSize())
×
608
          .add("choiceCount", choiceCount())
×
609
          .add("edsServiceName", edsServiceName())
×
610
          .add("dnsHostName", dnsHostName())
×
611
          .add("lrsServerInfo", lrsServerInfo())
×
612
          .add("maxConcurrentRequests", maxConcurrentRequests())
×
613
          // Exclude upstreamTlsContext and outlierDetection as their string representations are
614
          // cumbersome.
615
          .add("prioritizedClusterNames", prioritizedClusterNames())
×
616
          .toString();
×
617
    }
618

619
    @AutoValue.Builder
620
    abstract static class Builder {
1✔
621
      // Private, use one of the static factory methods instead.
622
      protected abstract Builder clusterName(String clusterName);
623

624
      // Private, use one of the static factory methods instead.
625
      protected abstract Builder clusterType(ClusterType clusterType);
626

627
      protected abstract Builder lbPolicyConfig(ImmutableMap<String, ?> lbPolicyConfig);
628

629
      Builder roundRobinLbPolicy() {
630
        return this.lbPolicyConfig(ImmutableMap.of("round_robin", ImmutableMap.of()));
1✔
631
      }
632

633
      Builder ringHashLbPolicy(Long minRingSize, Long maxRingSize) {
634
        return this.lbPolicyConfig(ImmutableMap.of("ring_hash_experimental",
1✔
635
            ImmutableMap.of("minRingSize", minRingSize.doubleValue(), "maxRingSize",
1✔
636
                maxRingSize.doubleValue())));
1✔
637
      }
638

639
      Builder leastRequestLbPolicy(Integer choiceCount) {
640
        return this.lbPolicyConfig(ImmutableMap.of("least_request_experimental",
1✔
641
            ImmutableMap.of("choiceCount", choiceCount.doubleValue())));
1✔
642
      }
643

644
      // Private, use leastRequestLbPolicy(int).
645
      protected abstract Builder choiceCount(int choiceCount);
646

647
      // Private, use ringHashLbPolicy(long, long).
648
      protected abstract Builder minRingSize(long minRingSize);
649

650
      // Private, use ringHashLbPolicy(long, long).
651
      protected abstract Builder maxRingSize(long maxRingSize);
652

653
      // Private, use CdsUpdate.forEds() instead.
654
      protected abstract Builder edsServiceName(String edsServiceName);
655

656
      // Private, use CdsUpdate.forLogicalDns() instead.
657
      protected abstract Builder dnsHostName(String dnsHostName);
658

659
      // Private, use one of the static factory methods instead.
660
      protected abstract Builder lrsServerInfo(ServerInfo lrsServerInfo);
661

662
      // Private, use one of the static factory methods instead.
663
      protected abstract Builder maxConcurrentRequests(Long maxConcurrentRequests);
664

665
      // Private, use one of the static factory methods instead.
666
      protected abstract Builder upstreamTlsContext(UpstreamTlsContext upstreamTlsContext);
667

668
      // Private, use CdsUpdate.forAggregate() instead.
669
      protected abstract Builder prioritizedClusterNames(List<String> prioritizedClusterNames);
670

671
      protected abstract Builder outlierDetection(OutlierDetection outlierDetection);
672

673
      abstract CdsUpdate build();
674
    }
675
  }
676
}
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