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

grpc / grpc-java / #19592

17 Dec 2024 12:03AM UTC coverage: 88.586% (+0.01%) from 88.573%
#19592

push

github

ejona86
xds: Move specialized APIs out of XdsResourceType

StructOrError is a more generic API, but we have StatusOr now so we
don't want new usages of StructOrError. Moving StructOrError out of
io.grpc.xds.client will make it easier to delete StructOrError once
we've migrated to StatusOr in the future.

TRANSPORT_SOCKET_NAME_TLS should also move, but it wasn't immediately
clear to me where it should go.

33474 of 37787 relevant lines covered (88.59%)

0.89 hits per line

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

93.55
/../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.endpoint.v3.ClusterLoadAssignment;
38
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext;
39
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext;
40
import io.grpc.LoadBalancerRegistry;
41
import io.grpc.NameResolver;
42
import io.grpc.internal.GrpcUtil;
43
import io.grpc.internal.ServiceConfigUtil;
44
import io.grpc.internal.ServiceConfigUtil.LbConfig;
45
import io.grpc.xds.EnvoyServerProtoData.OutlierDetection;
46
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
47
import io.grpc.xds.XdsClusterResource.CdsUpdate;
48
import io.grpc.xds.client.XdsClient.ResourceUpdate;
49
import io.grpc.xds.client.XdsResourceType;
50
import io.grpc.xds.internal.security.CommonTlsContextUtil;
51
import java.util.List;
52
import java.util.Locale;
53
import java.util.Set;
54
import javax.annotation.Nullable;
55

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

66
  @VisibleForTesting
67
  static final String AGGREGATE_CLUSTER_TYPE_NAME = "envoy.clusters.aggregate";
68
  static final String ADS_TYPE_URL_CDS =
69
      "type.googleapis.com/envoy.config.cluster.v3.Cluster";
70
  private static final String TYPE_URL_CLUSTER_CONFIG =
71
      "type.googleapis.com/envoy.extensions.clusters.aggregate.v3.ClusterConfig";
72
  private static final String TYPE_URL_UPSTREAM_TLS_CONTEXT =
73
      "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext";
74
  private static final String TYPE_URL_UPSTREAM_TLS_CONTEXT_V2 =
75
      "type.googleapis.com/envoy.api.v2.auth.UpstreamTlsContext";
76
  private final LoadBalancerRegistry loadBalancerRegistry
1✔
77
      = LoadBalancerRegistry.getDefaultRegistry();
1✔
78

79
  private static final XdsClusterResource instance = new XdsClusterResource();
1✔
80

81
  public static XdsClusterResource getInstance() {
82
    return instance;
1✔
83
  }
84

85
  @Override
86
  @Nullable
87
  protected String extractResourceName(Message unpackedResource) {
88
    if (!(unpackedResource instanceof Cluster)) {
1✔
89
      return null;
×
90
    }
91
    return ((Cluster) unpackedResource).getName();
1✔
92
  }
93

94
  @Override
95
  public String typeName() {
96
    return "CDS";
1✔
97
  }
98

99
  @Override
100
  public String typeUrl() {
101
    return ADS_TYPE_URL_CDS;
1✔
102
  }
103

104
  @Override
105
  public boolean shouldRetrieveResourceKeysForArgs() {
106
    return true;
1✔
107
  }
108

109
  @Override
110
  protected boolean isFullStateOfTheWorld() {
111
    return true;
1✔
112
  }
113

114
  @Override
115
  @SuppressWarnings("unchecked")
116
  protected Class<Cluster> unpackedClassName() {
117
    return Cluster.class;
1✔
118
  }
119

120
  @Override
121
  protected CdsUpdate doParse(Args args, Message unpackedMessage) throws ResourceInvalidException {
122
    if (!(unpackedMessage instanceof Cluster)) {
1✔
123
      throw new ResourceInvalidException("Invalid message type: " + unpackedMessage.getClass());
×
124
    }
125
    Set<String> certProviderInstances = null;
1✔
126
    if (args.getBootstrapInfo() != null && args.getBootstrapInfo().certProviders() != null) {
1✔
127
      certProviderInstances = args.getBootstrapInfo().certProviders().keySet();
1✔
128
    }
129
    return processCluster((Cluster) unpackedMessage, certProviderInstances,
1✔
130
        args.getServerInfo(), loadBalancerRegistry);
1✔
131
  }
132

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

158
    ImmutableMap<String, ?> lbPolicyConfig = LoadBalancerConfigFactory.newConfig(cluster,
1✔
159
        enableLeastRequest);
160

161
    // Validate the LB config by trying to parse it with the corresponding LB provider.
162
    LbConfig lbConfig = ServiceConfigUtil.unwrapLoadBalancingConfig(lbPolicyConfig);
1✔
163
    NameResolver.ConfigOrError configOrError = loadBalancerRegistry.getProvider(
1✔
164
        lbConfig.getPolicyName()).parseLoadBalancingPolicyConfig(
1✔
165
        lbConfig.getRawConfigValue());
1✔
166
    if (configOrError.getError() != null) {
1✔
167
      throw new ResourceInvalidException(structOrError.getErrorDetail());
×
168
    }
169

170
    updateBuilder.lbPolicyConfig(lbPolicyConfig);
1✔
171
    updateBuilder.filterMetadata(
1✔
172
        ImmutableMap.copyOf(cluster.getMetadata().getFilterMetadataMap()));
1✔
173

174
    return updateBuilder.build();
1✔
175
  }
176

177
  private static StructOrError<CdsUpdate.Builder> parseAggregateCluster(Cluster cluster) {
178
    String clusterName = cluster.getName();
1✔
179
    Cluster.CustomClusterType customType = cluster.getClusterType();
1✔
180
    String typeName = customType.getName();
1✔
181
    if (!typeName.equals(AGGREGATE_CLUSTER_TYPE_NAME)) {
1✔
182
      return StructOrError.fromError(
×
183
          "Cluster " + clusterName + ": unsupported custom cluster type: " + typeName);
184
    }
185
    io.envoyproxy.envoy.extensions.clusters.aggregate.v3.ClusterConfig clusterConfig;
186
    try {
187
      clusterConfig = unpackCompatibleType(customType.getTypedConfig(),
1✔
188
          io.envoyproxy.envoy.extensions.clusters.aggregate.v3.ClusterConfig.class,
189
          TYPE_URL_CLUSTER_CONFIG, null);
190
    } catch (InvalidProtocolBufferException e) {
×
191
      return StructOrError.fromError("Cluster " + clusterName + ": malformed ClusterConfig: " + e);
×
192
    }
1✔
193
    return StructOrError.fromStruct(CdsUpdate.forAggregate(
1✔
194
        clusterName, clusterConfig.getClustersList()));
1✔
195
  }
196

197
  private static StructOrError<CdsUpdate.Builder> parseNonAggregateCluster(
198
      Cluster cluster, Set<String> certProviderInstances, ServerInfo serverInfo) {
199
    String clusterName = cluster.getName();
1✔
200
    ServerInfo lrsServerInfo = null;
1✔
201
    Long maxConcurrentRequests = null;
1✔
202
    UpstreamTlsContext upstreamTlsContext = null;
1✔
203
    OutlierDetection outlierDetection = null;
1✔
204
    if (cluster.hasLrsServer()) {
1✔
205
      if (!cluster.getLrsServer().hasSelf()) {
1✔
206
        return StructOrError.fromError(
×
207
            "Cluster " + clusterName + ": only support LRS for the same management server");
208
      }
209
      lrsServerInfo = serverInfo;
1✔
210
    }
211
    if (cluster.hasCircuitBreakers()) {
1✔
212
      List<Thresholds> thresholds = cluster.getCircuitBreakers().getThresholdsList();
1✔
213
      for (Thresholds threshold : thresholds) {
1✔
214
        if (threshold.getPriority() != RoutingPriority.DEFAULT) {
1✔
215
          continue;
1✔
216
        }
217
        if (threshold.hasMaxRequests()) {
1✔
218
          maxConcurrentRequests = (long) threshold.getMaxRequests().getValue();
1✔
219
        }
220
      }
1✔
221
    }
222
    if (cluster.getTransportSocketMatchesCount() > 0) {
1✔
223
      return StructOrError.fromError("Cluster " + clusterName
1✔
224
          + ": transport-socket-matches not supported.");
225
    }
226
    if (cluster.hasTransportSocket()) {
1✔
227
      if (!TRANSPORT_SOCKET_NAME_TLS.equals(cluster.getTransportSocket().getName())) {
1✔
228
        return StructOrError.fromError("transport-socket with name "
1✔
229
            + cluster.getTransportSocket().getName() + " not supported.");
1✔
230
      }
231
      try {
232
        upstreamTlsContext = UpstreamTlsContext.fromEnvoyProtoUpstreamTlsContext(
1✔
233
            validateUpstreamTlsContext(
1✔
234
                unpackCompatibleType(cluster.getTransportSocket().getTypedConfig(),
1✔
235
                io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext.class,
236
                TYPE_URL_UPSTREAM_TLS_CONTEXT, TYPE_URL_UPSTREAM_TLS_CONTEXT_V2),
237
                certProviderInstances));
238
      } catch (InvalidProtocolBufferException | ResourceInvalidException e) {
1✔
239
        return StructOrError.fromError(
1✔
240
            "Cluster " + clusterName + ": malformed UpstreamTlsContext: " + e);
241
      }
1✔
242
    }
243

244
    if (cluster.hasOutlierDetection()) {
1✔
245
      try {
246
        outlierDetection = OutlierDetection.fromEnvoyOutlierDetection(
1✔
247
            validateOutlierDetection(cluster.getOutlierDetection()));
1✔
248
      } catch (ResourceInvalidException e) {
1✔
249
        return StructOrError.fromError(
1✔
250
            "Cluster " + clusterName + ": malformed outlier_detection: " + e);
251
      }
1✔
252
    }
253

254
    Cluster.DiscoveryType type = cluster.getType();
1✔
255
    if (type == Cluster.DiscoveryType.EDS) {
1✔
256
      String edsServiceName = null;
1✔
257
      io.envoyproxy.envoy.config.cluster.v3.Cluster.EdsClusterConfig edsClusterConfig =
1✔
258
          cluster.getEdsClusterConfig();
1✔
259
      if (!edsClusterConfig.getEdsConfig().hasAds()
1✔
260
          && ! edsClusterConfig.getEdsConfig().hasSelf()) {
1✔
261
        return StructOrError.fromError(
1✔
262
            "Cluster " + clusterName + ": field eds_cluster_config must be set to indicate to use"
263
                + " EDS over ADS or self ConfigSource");
264
      }
265
      // If the service_name field is set, that value will be used for the EDS request.
266
      if (!edsClusterConfig.getServiceName().isEmpty()) {
1✔
267
        edsServiceName = edsClusterConfig.getServiceName();
1✔
268
      }
269
      // edsServiceName is required if the CDS resource has an xdstp name.
270
      if ((edsServiceName == null) && clusterName.toLowerCase(Locale.ROOT).startsWith("xdstp:")) {
1✔
271
        return StructOrError.fromError(
1✔
272
            "EDS service_name must be set when Cluster resource has an xdstp name");
273
      }
274
      return StructOrError.fromStruct(CdsUpdate.forEds(
1✔
275
          clusterName, edsServiceName, lrsServerInfo, maxConcurrentRequests, upstreamTlsContext,
276
          outlierDetection));
277
    } else if (type.equals(Cluster.DiscoveryType.LOGICAL_DNS)) {
1✔
278
      if (!cluster.hasLoadAssignment()) {
1✔
279
        return StructOrError.fromError(
×
280
            "Cluster " + clusterName + ": LOGICAL_DNS clusters must have a single host");
281
      }
282
      ClusterLoadAssignment assignment = cluster.getLoadAssignment();
1✔
283
      if (assignment.getEndpointsCount() != 1
1✔
284
          || assignment.getEndpoints(0).getLbEndpointsCount() != 1) {
1✔
285
        return StructOrError.fromError(
×
286
            "Cluster " + clusterName + ": LOGICAL_DNS clusters must have a single "
287
                + "locality_lb_endpoint and a single lb_endpoint");
288
      }
289
      io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint lbEndpoint =
1✔
290
          assignment.getEndpoints(0).getLbEndpoints(0);
1✔
291
      if (!lbEndpoint.hasEndpoint() || !lbEndpoint.getEndpoint().hasAddress()
1✔
292
          || !lbEndpoint.getEndpoint().getAddress().hasSocketAddress()) {
1✔
293
        return StructOrError.fromError(
×
294
            "Cluster " + clusterName
295
                + ": LOGICAL_DNS clusters must have an endpoint with address and socket_address");
296
      }
297
      SocketAddress socketAddress = lbEndpoint.getEndpoint().getAddress().getSocketAddress();
1✔
298
      if (!socketAddress.getResolverName().isEmpty()) {
1✔
299
        return StructOrError.fromError(
×
300
            "Cluster " + clusterName
301
                + ": LOGICAL DNS clusters must NOT have a custom resolver name set");
302
      }
303
      if (socketAddress.getPortSpecifierCase() != SocketAddress.PortSpecifierCase.PORT_VALUE) {
1✔
304
        return StructOrError.fromError(
×
305
            "Cluster " + clusterName
306
                + ": LOGICAL DNS clusters socket_address must have port_value");
307
      }
308
      String dnsHostName = String.format(
1✔
309
          Locale.US, "%s:%d", socketAddress.getAddress(), socketAddress.getPortValue());
1✔
310
      return StructOrError.fromStruct(CdsUpdate.forLogicalDns(
1✔
311
          clusterName, dnsHostName, lrsServerInfo, maxConcurrentRequests, upstreamTlsContext));
312
    }
313
    return StructOrError.fromError(
×
314
        "Cluster " + clusterName + ": unsupported built-in discovery type: " + type);
315
  }
316

317
  static io.envoyproxy.envoy.config.cluster.v3.OutlierDetection validateOutlierDetection(
318
      io.envoyproxy.envoy.config.cluster.v3.OutlierDetection outlierDetection)
319
      throws ResourceInvalidException {
320
    if (outlierDetection.hasInterval()) {
1✔
321
      if (!Durations.isValid(outlierDetection.getInterval())) {
1✔
322
        throw new ResourceInvalidException("outlier_detection interval is not a valid Duration");
1✔
323
      }
324
      if (hasNegativeValues(outlierDetection.getInterval())) {
1✔
325
        throw new ResourceInvalidException("outlier_detection interval has a negative value");
1✔
326
      }
327
    }
328
    if (outlierDetection.hasBaseEjectionTime()) {
1✔
329
      if (!Durations.isValid(outlierDetection.getBaseEjectionTime())) {
1✔
330
        throw new ResourceInvalidException(
1✔
331
            "outlier_detection base_ejection_time is not a valid Duration");
332
      }
333
      if (hasNegativeValues(outlierDetection.getBaseEjectionTime())) {
1✔
334
        throw new ResourceInvalidException(
1✔
335
            "outlier_detection base_ejection_time has a negative value");
336
      }
337
    }
338
    if (outlierDetection.hasMaxEjectionTime()) {
1✔
339
      if (!Durations.isValid(outlierDetection.getMaxEjectionTime())) {
1✔
340
        throw new ResourceInvalidException(
1✔
341
            "outlier_detection max_ejection_time is not a valid Duration");
342
      }
343
      if (hasNegativeValues(outlierDetection.getMaxEjectionTime())) {
1✔
344
        throw new ResourceInvalidException(
1✔
345
            "outlier_detection max_ejection_time has a negative value");
346
      }
347
    }
348
    if (outlierDetection.hasMaxEjectionPercent()
1✔
349
        && outlierDetection.getMaxEjectionPercent().getValue() > 100) {
1✔
350
      throw new ResourceInvalidException(
1✔
351
          "outlier_detection max_ejection_percent is > 100");
352
    }
353
    if (outlierDetection.hasEnforcingSuccessRate()
1✔
354
        && outlierDetection.getEnforcingSuccessRate().getValue() > 100) {
1✔
355
      throw new ResourceInvalidException(
1✔
356
          "outlier_detection enforcing_success_rate is > 100");
357
    }
358
    if (outlierDetection.hasFailurePercentageThreshold()
1✔
359
        && outlierDetection.getFailurePercentageThreshold().getValue() > 100) {
1✔
360
      throw new ResourceInvalidException(
1✔
361
          "outlier_detection failure_percentage_threshold is > 100");
362
    }
363
    if (outlierDetection.hasEnforcingFailurePercentage()
1✔
364
        && outlierDetection.getEnforcingFailurePercentage().getValue() > 100) {
1✔
365
      throw new ResourceInvalidException(
1✔
366
          "outlier_detection enforcing_failure_percentage is > 100");
367
    }
368

369
    return outlierDetection;
1✔
370
  }
371

372
  static boolean hasNegativeValues(Duration duration) {
373
    return duration.getSeconds() < 0 || duration.getNanos() < 0;
1✔
374
  }
375

376
  @VisibleForTesting
377
  static io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
378
      validateUpstreamTlsContext(
379
      io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext upstreamTlsContext,
380
      Set<String> certProviderInstances)
381
      throws ResourceInvalidException {
382
    if (upstreamTlsContext.hasCommonTlsContext()) {
1✔
383
      validateCommonTlsContext(upstreamTlsContext.getCommonTlsContext(), certProviderInstances,
1✔
384
          false);
385
    } else {
386
      throw new ResourceInvalidException("common-tls-context is required in upstream-tls-context");
1✔
387
    }
388
    return upstreamTlsContext;
1✔
389
  }
390

391
  @VisibleForTesting
392
  static void validateCommonTlsContext(
393
      CommonTlsContext commonTlsContext, Set<String> certProviderInstances, boolean server)
394
      throws ResourceInvalidException {
395
    if (commonTlsContext.hasCustomHandshaker()) {
1✔
396
      throw new ResourceInvalidException(
1✔
397
          "common-tls-context with custom_handshaker is not supported");
398
    }
399
    if (commonTlsContext.hasTlsParams()) {
1✔
400
      throw new ResourceInvalidException("common-tls-context with tls_params is not supported");
1✔
401
    }
402
    if (commonTlsContext.hasValidationContextSdsSecretConfig()) {
1✔
403
      throw new ResourceInvalidException(
1✔
404
          "common-tls-context with validation_context_sds_secret_config is not supported");
405
    }
406
    if (commonTlsContext.hasValidationContextCertificateProvider()) {
1✔
407
      throw new ResourceInvalidException(
1✔
408
          "common-tls-context with validation_context_certificate_provider is not supported");
409
    }
410
    if (commonTlsContext.hasValidationContextCertificateProviderInstance()) {
1✔
411
      throw new ResourceInvalidException(
1✔
412
          "common-tls-context with validation_context_certificate_provider_instance is not"
413
              + " supported");
414
    }
415
    String certInstanceName = getIdentityCertInstanceName(commonTlsContext);
1✔
416
    if (certInstanceName == null) {
1✔
417
      if (server) {
1✔
418
        throw new ResourceInvalidException(
1✔
419
            "tls_certificate_provider_instance is required in downstream-tls-context");
420
      }
421
      if (commonTlsContext.getTlsCertificatesCount() > 0) {
1✔
422
        throw new ResourceInvalidException(
1✔
423
            "tls_certificate_provider_instance is unset");
424
      }
425
      if (commonTlsContext.getTlsCertificateSdsSecretConfigsCount() > 0) {
1✔
426
        throw new ResourceInvalidException(
1✔
427
            "tls_certificate_provider_instance is unset");
428
      }
429
      if (commonTlsContext.hasTlsCertificateCertificateProvider()) {
1✔
430
        throw new ResourceInvalidException(
1✔
431
            "tls_certificate_provider_instance is unset");
432
      }
433
    } else if (certProviderInstances == null || !certProviderInstances.contains(certInstanceName)) {
1✔
434
      throw new ResourceInvalidException(
1✔
435
          "CertificateProvider instance name '" + certInstanceName
436
              + "' not defined in the bootstrap file.");
437
    }
438
    String rootCaInstanceName = getRootCertInstanceName(commonTlsContext);
1✔
439
    if (rootCaInstanceName == null) {
1✔
440
      if (!server && (!enableSystemRootCerts
1✔
441
          || !CommonTlsContextUtil.isUsingSystemRootCerts(commonTlsContext))) {
1✔
442
        throw new ResourceInvalidException(
1✔
443
            "ca_certificate_provider_instance or system_root_certs is required in "
444
                + "upstream-tls-context");
445
      }
446
    } else {
447
      if (certProviderInstances == null || !certProviderInstances.contains(rootCaInstanceName)) {
1✔
448
        throw new ResourceInvalidException(
1✔
449
            "ca_certificate_provider_instance name '" + rootCaInstanceName
450
                + "' not defined in the bootstrap file.");
451
      }
452
      CertificateValidationContext certificateValidationContext = null;
1✔
453
      if (commonTlsContext.hasValidationContext()) {
1✔
454
        certificateValidationContext = commonTlsContext.getValidationContext();
1✔
455
      } else if (commonTlsContext.hasCombinedValidationContext() && commonTlsContext
1✔
456
          .getCombinedValidationContext().hasDefaultValidationContext()) {
1✔
457
        certificateValidationContext = commonTlsContext.getCombinedValidationContext()
1✔
458
            .getDefaultValidationContext();
1✔
459
      }
460
      if (certificateValidationContext != null) {
1✔
461
        if (certificateValidationContext.getMatchSubjectAltNamesCount() > 0 && server) {
1✔
462
          throw new ResourceInvalidException(
1✔
463
              "match_subject_alt_names only allowed in upstream_tls_context");
464
        }
465
        if (certificateValidationContext.getVerifyCertificateSpkiCount() > 0) {
1✔
466
          throw new ResourceInvalidException(
1✔
467
              "verify_certificate_spki in default_validation_context is not supported");
468
        }
469
        if (certificateValidationContext.getVerifyCertificateHashCount() > 0) {
1✔
470
          throw new ResourceInvalidException(
1✔
471
              "verify_certificate_hash in default_validation_context is not supported");
472
        }
473
        if (certificateValidationContext.hasRequireSignedCertificateTimestamp()) {
1✔
474
          throw new ResourceInvalidException(
1✔
475
              "require_signed_certificate_timestamp in default_validation_context is not "
476
                  + "supported");
477
        }
478
        if (certificateValidationContext.hasCrl()) {
1✔
479
          throw new ResourceInvalidException("crl in default_validation_context is not supported");
1✔
480
        }
481
        if (certificateValidationContext.hasCustomValidatorConfig()) {
1✔
482
          throw new ResourceInvalidException(
1✔
483
              "custom_validator_config in default_validation_context is not supported");
484
        }
485
      }
486
    }
487
  }
1✔
488

489
  private static String getIdentityCertInstanceName(CommonTlsContext commonTlsContext) {
490
    if (commonTlsContext.hasTlsCertificateProviderInstance()) {
1✔
491
      return commonTlsContext.getTlsCertificateProviderInstance().getInstanceName();
1✔
492
    } else if (commonTlsContext.hasTlsCertificateCertificateProviderInstance()) {
1✔
493
      return commonTlsContext.getTlsCertificateCertificateProviderInstance().getInstanceName();
1✔
494
    }
495
    return null;
1✔
496
  }
497

498
  private static String getRootCertInstanceName(CommonTlsContext commonTlsContext) {
499
    if (commonTlsContext.hasValidationContext()) {
1✔
500
      if (commonTlsContext.getValidationContext().hasCaCertificateProviderInstance()) {
1✔
501
        return commonTlsContext.getValidationContext().getCaCertificateProviderInstance()
1✔
502
            .getInstanceName();
1✔
503
      }
504
    } else if (commonTlsContext.hasCombinedValidationContext()) {
1✔
505
      CommonTlsContext.CombinedCertificateValidationContext combinedCertificateValidationContext
1✔
506
          = commonTlsContext.getCombinedValidationContext();
1✔
507
      if (combinedCertificateValidationContext.hasDefaultValidationContext()
1✔
508
          && combinedCertificateValidationContext.getDefaultValidationContext()
1✔
509
          .hasCaCertificateProviderInstance()) {
1✔
510
        return combinedCertificateValidationContext.getDefaultValidationContext()
×
511
            .getCaCertificateProviderInstance().getInstanceName();
×
512
      } else if (combinedCertificateValidationContext
1✔
513
          .hasValidationContextCertificateProviderInstance()) {
1✔
514
        return combinedCertificateValidationContext
1✔
515
            .getValidationContextCertificateProviderInstance().getInstanceName();
1✔
516
      }
517
    }
518
    return null;
1✔
519
  }
520

521
  /** xDS resource update for cluster-level configuration. */
522
  @AutoValue
523
  abstract static class CdsUpdate implements ResourceUpdate {
1✔
524
    abstract String clusterName();
525

526
    abstract ClusterType clusterType();
527

528
    abstract ImmutableMap<String, ?> lbPolicyConfig();
529

530
    // Only valid if lbPolicy is "ring_hash_experimental".
531
    abstract long minRingSize();
532

533
    // Only valid if lbPolicy is "ring_hash_experimental".
534
    abstract long maxRingSize();
535

536
    // Only valid if lbPolicy is "least_request_experimental".
537
    abstract int choiceCount();
538

539
    // Alternative resource name to be used in EDS requests.
540
    /// Only valid for EDS cluster.
541
    @Nullable
542
    abstract String edsServiceName();
543

544
    // Corresponding DNS name to be used if upstream endpoints of the cluster is resolvable
545
    // via DNS.
546
    // Only valid for LOGICAL_DNS cluster.
547
    @Nullable
548
    abstract String dnsHostName();
549

550
    // Load report server info for reporting loads via LRS.
551
    // Only valid for EDS or LOGICAL_DNS cluster.
552
    @Nullable
553
    abstract ServerInfo lrsServerInfo();
554

555
    // Max number of concurrent requests can be sent to this cluster.
556
    // Only valid for EDS or LOGICAL_DNS cluster.
557
    @Nullable
558
    abstract Long maxConcurrentRequests();
559

560
    // TLS context used to connect to connect to this cluster.
561
    // Only valid for EDS or LOGICAL_DNS cluster.
562
    @Nullable
563
    abstract UpstreamTlsContext upstreamTlsContext();
564

565
    // List of underlying clusters making of this aggregate cluster.
566
    // Only valid for AGGREGATE cluster.
567
    @Nullable
568
    abstract ImmutableList<String> prioritizedClusterNames();
569

570
    // Outlier detection configuration.
571
    @Nullable
572
    abstract OutlierDetection outlierDetection();
573

574
    abstract ImmutableMap<String, Struct> filterMetadata();
575

576
    private static Builder newBuilder(String clusterName) {
577
      return new AutoValue_XdsClusterResource_CdsUpdate.Builder()
1✔
578
          .clusterName(clusterName)
1✔
579
          .minRingSize(0)
1✔
580
          .maxRingSize(0)
1✔
581
          .choiceCount(0)
1✔
582
          .filterMetadata(ImmutableMap.of());
1✔
583
    }
584

585
    static Builder forAggregate(String clusterName, List<String> prioritizedClusterNames) {
586
      checkNotNull(prioritizedClusterNames, "prioritizedClusterNames");
1✔
587
      return newBuilder(clusterName)
1✔
588
          .clusterType(ClusterType.AGGREGATE)
1✔
589
          .prioritizedClusterNames(ImmutableList.copyOf(prioritizedClusterNames));
1✔
590
    }
591

592
    static Builder forEds(String clusterName, @Nullable String edsServiceName,
593
                          @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests,
594
                          @Nullable UpstreamTlsContext upstreamTlsContext,
595
                          @Nullable OutlierDetection outlierDetection) {
596
      return newBuilder(clusterName)
1✔
597
          .clusterType(ClusterType.EDS)
1✔
598
          .edsServiceName(edsServiceName)
1✔
599
          .lrsServerInfo(lrsServerInfo)
1✔
600
          .maxConcurrentRequests(maxConcurrentRequests)
1✔
601
          .upstreamTlsContext(upstreamTlsContext)
1✔
602
          .outlierDetection(outlierDetection);
1✔
603
    }
604

605
    static Builder forLogicalDns(String clusterName, String dnsHostName,
606
                                 @Nullable ServerInfo lrsServerInfo,
607
                                 @Nullable Long maxConcurrentRequests,
608
                                 @Nullable UpstreamTlsContext upstreamTlsContext) {
609
      return newBuilder(clusterName)
1✔
610
          .clusterType(ClusterType.LOGICAL_DNS)
1✔
611
          .dnsHostName(dnsHostName)
1✔
612
          .lrsServerInfo(lrsServerInfo)
1✔
613
          .maxConcurrentRequests(maxConcurrentRequests)
1✔
614
          .upstreamTlsContext(upstreamTlsContext);
1✔
615
    }
616

617
    enum ClusterType {
1✔
618
      EDS, LOGICAL_DNS, AGGREGATE
1✔
619
    }
620

621
    enum LbPolicy {
×
622
      ROUND_ROBIN, RING_HASH, LEAST_REQUEST
×
623
    }
624

625
    // FIXME(chengyuanzhang): delete this after UpstreamTlsContext's toString() is fixed.
626
    @Override
627
    public final String toString() {
628
      return MoreObjects.toStringHelper(this)
1✔
629
          .add("clusterName", clusterName())
1✔
630
          .add("clusterType", clusterType())
1✔
631
          .add("lbPolicyConfig", lbPolicyConfig())
1✔
632
          .add("minRingSize", minRingSize())
1✔
633
          .add("maxRingSize", maxRingSize())
1✔
634
          .add("choiceCount", choiceCount())
1✔
635
          .add("edsServiceName", edsServiceName())
1✔
636
          .add("dnsHostName", dnsHostName())
1✔
637
          .add("lrsServerInfo", lrsServerInfo())
1✔
638
          .add("maxConcurrentRequests", maxConcurrentRequests())
1✔
639
          // Exclude upstreamTlsContext and outlierDetection as their string representations are
640
          // cumbersome.
641
          .add("prioritizedClusterNames", prioritizedClusterNames())
1✔
642
          .toString();
1✔
643
    }
644

645
    @AutoValue.Builder
646
    abstract static class Builder {
1✔
647
      // Private, use one of the static factory methods instead.
648
      protected abstract Builder clusterName(String clusterName);
649

650
      // Private, use one of the static factory methods instead.
651
      protected abstract Builder clusterType(ClusterType clusterType);
652

653
      protected abstract Builder lbPolicyConfig(ImmutableMap<String, ?> lbPolicyConfig);
654

655
      Builder roundRobinLbPolicy() {
656
        return this.lbPolicyConfig(ImmutableMap.of("round_robin", ImmutableMap.of()));
1✔
657
      }
658

659
      Builder ringHashLbPolicy(Long minRingSize, Long maxRingSize) {
660
        return this.lbPolicyConfig(ImmutableMap.of("ring_hash_experimental",
1✔
661
            ImmutableMap.of("minRingSize", minRingSize.doubleValue(), "maxRingSize",
1✔
662
                maxRingSize.doubleValue())));
1✔
663
      }
664

665
      Builder leastRequestLbPolicy(Integer choiceCount) {
666
        return this.lbPolicyConfig(ImmutableMap.of("least_request_experimental",
1✔
667
            ImmutableMap.of("choiceCount", choiceCount.doubleValue())));
1✔
668
      }
669

670
      // Private, use leastRequestLbPolicy(int).
671
      protected abstract Builder choiceCount(int choiceCount);
672

673
      // Private, use ringHashLbPolicy(long, long).
674
      protected abstract Builder minRingSize(long minRingSize);
675

676
      // Private, use ringHashLbPolicy(long, long).
677
      protected abstract Builder maxRingSize(long maxRingSize);
678

679
      // Private, use CdsUpdate.forEds() instead.
680
      protected abstract Builder edsServiceName(String edsServiceName);
681

682
      // Private, use CdsUpdate.forLogicalDns() instead.
683
      protected abstract Builder dnsHostName(String dnsHostName);
684

685
      // Private, use one of the static factory methods instead.
686
      protected abstract Builder lrsServerInfo(ServerInfo lrsServerInfo);
687

688
      // Private, use one of the static factory methods instead.
689
      protected abstract Builder maxConcurrentRequests(Long maxConcurrentRequests);
690

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

694
      // Private, use CdsUpdate.forAggregate() instead.
695
      protected abstract Builder prioritizedClusterNames(List<String> prioritizedClusterNames);
696

697
      protected abstract Builder outlierDetection(OutlierDetection outlierDetection);
698

699
      protected abstract Builder filterMetadata(ImmutableMap<String, Struct> filterMetadata);
700

701
      abstract CdsUpdate build();
702
    }
703
  }
704
}
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