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

grpc / grpc-java / #19249

24 May 2024 10:08PM UTC coverage: 88.444% (-0.07%) from 88.512%
#19249

push

github

web-flow
xds: Plumb the Cluster's filterMetadata to RPCs

This will be used by CSM observability, and may get exposed to further
uses in the future.

32060 of 36249 relevant lines covered (88.44%)

0.88 hits per line

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

88.77
/../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.ServiceConfigUtil;
43
import io.grpc.internal.ServiceConfigUtil.LbConfig;
44
import io.grpc.xds.EnvoyServerProtoData.OutlierDetection;
45
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
46
import io.grpc.xds.XdsClusterResource.CdsUpdate;
47
import io.grpc.xds.client.XdsClient.ResourceUpdate;
48
import io.grpc.xds.client.XdsResourceType;
49
import java.util.List;
50
import java.util.Locale;
51
import java.util.Set;
52
import javax.annotation.Nullable;
53

54
class XdsClusterResource extends XdsResourceType<CdsUpdate> {
1✔
55
  @VisibleForTesting
56
  static boolean enableLeastRequest =
57
      !Strings.isNullOrEmpty(System.getenv("GRPC_EXPERIMENTAL_ENABLE_LEAST_REQUEST"))
1✔
58
          ? Boolean.parseBoolean(System.getenv("GRPC_EXPERIMENTAL_ENABLE_LEAST_REQUEST"))
×
59
          : Boolean.parseBoolean(System.getProperty("io.grpc.xds.experimentalEnableLeastRequest"));
1✔
60

61
  @VisibleForTesting
62
  static final String AGGREGATE_CLUSTER_TYPE_NAME = "envoy.clusters.aggregate";
63
  static final String ADS_TYPE_URL_CDS =
64
      "type.googleapis.com/envoy.config.cluster.v3.Cluster";
65
  private static final String TYPE_URL_UPSTREAM_TLS_CONTEXT =
66
      "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext";
67
  private static final String TYPE_URL_UPSTREAM_TLS_CONTEXT_V2 =
68
      "type.googleapis.com/envoy.api.v2.auth.UpstreamTlsContext";
69
  private final LoadBalancerRegistry loadBalancerRegistry
1✔
70
      = LoadBalancerRegistry.getDefaultRegistry();
1✔
71

72
  private static final XdsClusterResource instance = new XdsClusterResource();
1✔
73

74
  public static XdsClusterResource getInstance() {
75
    return instance;
1✔
76
  }
77

78
  @Override
79
  @Nullable
80
  protected String extractResourceName(Message unpackedResource) {
81
    if (!(unpackedResource instanceof Cluster)) {
1✔
82
      return null;
×
83
    }
84
    return ((Cluster) unpackedResource).getName();
1✔
85
  }
86

87
  @Override
88
  public String typeName() {
89
    return "CDS";
1✔
90
  }
91

92
  @Override
93
  public String typeUrl() {
94
    return ADS_TYPE_URL_CDS;
1✔
95
  }
96

97
  @Override
98
  public boolean shouldRetrieveResourceKeysForArgs() {
99
    return true;
1✔
100
  }
101

102
  @Override
103
  protected boolean isFullStateOfTheWorld() {
104
    return true;
1✔
105
  }
106

107
  @Override
108
  @SuppressWarnings("unchecked")
109
  protected Class<Cluster> unpackedClassName() {
110
    return Cluster.class;
1✔
111
  }
112

113
  @Override
114
  protected CdsUpdate doParse(Args args, Message unpackedMessage) throws ResourceInvalidException {
115
    if (!(unpackedMessage instanceof Cluster)) {
1✔
116
      throw new ResourceInvalidException("Invalid message type: " + unpackedMessage.getClass());
×
117
    }
118
    Set<String> certProviderInstances = null;
1✔
119
    if (args.getBootstrapInfo() != null && args.getBootstrapInfo().certProviders() != null) {
1✔
120
      certProviderInstances = args.getBootstrapInfo().certProviders().keySet();
1✔
121
    }
122
    return processCluster((Cluster) unpackedMessage, certProviderInstances,
1✔
123
        args.getServerInfo(), loadBalancerRegistry);
1✔
124
  }
125

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

151
    ImmutableMap<String, ?> lbPolicyConfig = LoadBalancerConfigFactory.newConfig(cluster,
1✔
152
        enableLeastRequest);
153

154
    // Validate the LB config by trying to parse it with the corresponding LB provider.
155
    LbConfig lbConfig = ServiceConfigUtil.unwrapLoadBalancingConfig(lbPolicyConfig);
1✔
156
    NameResolver.ConfigOrError configOrError = loadBalancerRegistry.getProvider(
1✔
157
        lbConfig.getPolicyName()).parseLoadBalancingPolicyConfig(
1✔
158
        lbConfig.getRawConfigValue());
1✔
159
    if (configOrError.getError() != null) {
1✔
160
      throw new ResourceInvalidException(structOrError.getErrorDetail());
×
161
    }
162

163
    updateBuilder.lbPolicyConfig(lbPolicyConfig);
1✔
164
    updateBuilder.filterMetadata(
1✔
165
        ImmutableMap.copyOf(cluster.getMetadata().getFilterMetadataMap()));
1✔
166

167
    return updateBuilder.build();
1✔
168
  }
169

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

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

237
    if (cluster.hasOutlierDetection()) {
1✔
238
      try {
239
        outlierDetection = OutlierDetection.fromEnvoyOutlierDetection(
1✔
240
            validateOutlierDetection(cluster.getOutlierDetection()));
1✔
241
      } catch (ResourceInvalidException e) {
1✔
242
        return StructOrError.fromError(
1✔
243
            "Cluster " + clusterName + ": malformed outlier_detection: " + e);
244
      }
1✔
245
    }
246

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

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

362
    return outlierDetection;
1✔
363
  }
364

365
  static boolean hasNegativeValues(Duration duration) {
366
    return duration.getSeconds() < 0 || duration.getNanos() < 0;
1✔
367
  }
368

369
  @VisibleForTesting
370
  static io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
371
      validateUpstreamTlsContext(
372
      io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext upstreamTlsContext,
373
      Set<String> certProviderInstances)
374
      throws ResourceInvalidException {
375
    if (upstreamTlsContext.hasCommonTlsContext()) {
1✔
376
      validateCommonTlsContext(upstreamTlsContext.getCommonTlsContext(), certProviderInstances,
1✔
377
          false);
378
    } else {
379
      throw new ResourceInvalidException("common-tls-context is required in upstream-tls-context");
1✔
380
    }
381
    return upstreamTlsContext;
1✔
382
  }
383

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

480
  private static String getIdentityCertInstanceName(CommonTlsContext commonTlsContext) {
481
    if (commonTlsContext.hasTlsCertificateProviderInstance()) {
1✔
482
      return commonTlsContext.getTlsCertificateProviderInstance().getInstanceName();
1✔
483
    } else if (commonTlsContext.hasTlsCertificateCertificateProviderInstance()) {
1✔
484
      return commonTlsContext.getTlsCertificateCertificateProviderInstance().getInstanceName();
1✔
485
    }
486
    return null;
1✔
487
  }
488

489
  private static String getRootCertInstanceName(CommonTlsContext commonTlsContext) {
490
    if (commonTlsContext.hasValidationContext()) {
1✔
491
      if (commonTlsContext.getValidationContext().hasCaCertificateProviderInstance()) {
1✔
492
        return commonTlsContext.getValidationContext().getCaCertificateProviderInstance()
1✔
493
            .getInstanceName();
1✔
494
      }
495
    } else if (commonTlsContext.hasCombinedValidationContext()) {
1✔
496
      CommonTlsContext.CombinedCertificateValidationContext combinedCertificateValidationContext
1✔
497
          = commonTlsContext.getCombinedValidationContext();
1✔
498
      if (combinedCertificateValidationContext.hasDefaultValidationContext()
1✔
499
          && combinedCertificateValidationContext.getDefaultValidationContext()
1✔
500
          .hasCaCertificateProviderInstance()) {
1✔
501
        return combinedCertificateValidationContext.getDefaultValidationContext()
×
502
            .getCaCertificateProviderInstance().getInstanceName();
×
503
      } else if (combinedCertificateValidationContext
1✔
504
          .hasValidationContextCertificateProviderInstance()) {
1✔
505
        return combinedCertificateValidationContext
1✔
506
            .getValidationContextCertificateProviderInstance().getInstanceName();
1✔
507
      }
508
    }
509
    return null;
1✔
510
  }
511

512
  /** xDS resource update for cluster-level configuration. */
513
  @AutoValue
514
  abstract static class CdsUpdate implements ResourceUpdate {
1✔
515
    abstract String clusterName();
516

517
    abstract ClusterType clusterType();
518

519
    abstract ImmutableMap<String, ?> lbPolicyConfig();
520

521
    // Only valid if lbPolicy is "ring_hash_experimental".
522
    abstract long minRingSize();
523

524
    // Only valid if lbPolicy is "ring_hash_experimental".
525
    abstract long maxRingSize();
526

527
    // Only valid if lbPolicy is "least_request_experimental".
528
    abstract int choiceCount();
529

530
    // Alternative resource name to be used in EDS requests.
531
    /// Only valid for EDS cluster.
532
    @Nullable
533
    abstract String edsServiceName();
534

535
    // Corresponding DNS name to be used if upstream endpoints of the cluster is resolvable
536
    // via DNS.
537
    // Only valid for LOGICAL_DNS cluster.
538
    @Nullable
539
    abstract String dnsHostName();
540

541
    // Load report server info for reporting loads via LRS.
542
    // Only valid for EDS or LOGICAL_DNS cluster.
543
    @Nullable
544
    abstract ServerInfo lrsServerInfo();
545

546
    // Max number of concurrent requests can be sent to this cluster.
547
    // Only valid for EDS or LOGICAL_DNS cluster.
548
    @Nullable
549
    abstract Long maxConcurrentRequests();
550

551
    // TLS context used to connect to connect to this cluster.
552
    // Only valid for EDS or LOGICAL_DNS cluster.
553
    @Nullable
554
    abstract UpstreamTlsContext upstreamTlsContext();
555

556
    // List of underlying clusters making of this aggregate cluster.
557
    // Only valid for AGGREGATE cluster.
558
    @Nullable
559
    abstract ImmutableList<String> prioritizedClusterNames();
560

561
    // Outlier detection configuration.
562
    @Nullable
563
    abstract OutlierDetection outlierDetection();
564

565
    abstract ImmutableMap<String, Struct> filterMetadata();
566

567
    private static Builder newBuilder(String clusterName) {
568
      return new AutoValue_XdsClusterResource_CdsUpdate.Builder()
1✔
569
          .clusterName(clusterName)
1✔
570
          .minRingSize(0)
1✔
571
          .maxRingSize(0)
1✔
572
          .choiceCount(0)
1✔
573
          .filterMetadata(ImmutableMap.of());
1✔
574
    }
575

576
    static Builder forAggregate(String clusterName, List<String> prioritizedClusterNames) {
577
      checkNotNull(prioritizedClusterNames, "prioritizedClusterNames");
1✔
578
      return newBuilder(clusterName)
1✔
579
          .clusterType(ClusterType.AGGREGATE)
1✔
580
          .prioritizedClusterNames(ImmutableList.copyOf(prioritizedClusterNames));
1✔
581
    }
582

583
    static Builder forEds(String clusterName, @Nullable String edsServiceName,
584
                          @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests,
585
                          @Nullable UpstreamTlsContext upstreamTlsContext,
586
                          @Nullable OutlierDetection outlierDetection) {
587
      return newBuilder(clusterName)
1✔
588
          .clusterType(ClusterType.EDS)
1✔
589
          .edsServiceName(edsServiceName)
1✔
590
          .lrsServerInfo(lrsServerInfo)
1✔
591
          .maxConcurrentRequests(maxConcurrentRequests)
1✔
592
          .upstreamTlsContext(upstreamTlsContext)
1✔
593
          .outlierDetection(outlierDetection);
1✔
594
    }
595

596
    static Builder forLogicalDns(String clusterName, String dnsHostName,
597
                                 @Nullable ServerInfo lrsServerInfo,
598
                                 @Nullable Long maxConcurrentRequests,
599
                                 @Nullable UpstreamTlsContext upstreamTlsContext) {
600
      return newBuilder(clusterName)
1✔
601
          .clusterType(ClusterType.LOGICAL_DNS)
1✔
602
          .dnsHostName(dnsHostName)
1✔
603
          .lrsServerInfo(lrsServerInfo)
1✔
604
          .maxConcurrentRequests(maxConcurrentRequests)
1✔
605
          .upstreamTlsContext(upstreamTlsContext);
1✔
606
    }
607

608
    enum ClusterType {
1✔
609
      EDS, LOGICAL_DNS, AGGREGATE
1✔
610
    }
611

612
    enum LbPolicy {
×
613
      ROUND_ROBIN, RING_HASH, LEAST_REQUEST
×
614
    }
615

616
    // FIXME(chengyuanzhang): delete this after UpstreamTlsContext's toString() is fixed.
617
    @Override
618
    public final String toString() {
619
      return MoreObjects.toStringHelper(this)
×
620
          .add("clusterName", clusterName())
×
621
          .add("clusterType", clusterType())
×
622
          .add("lbPolicyConfig", lbPolicyConfig())
×
623
          .add("minRingSize", minRingSize())
×
624
          .add("maxRingSize", maxRingSize())
×
625
          .add("choiceCount", choiceCount())
×
626
          .add("edsServiceName", edsServiceName())
×
627
          .add("dnsHostName", dnsHostName())
×
628
          .add("lrsServerInfo", lrsServerInfo())
×
629
          .add("maxConcurrentRequests", maxConcurrentRequests())
×
630
          // Exclude upstreamTlsContext and outlierDetection as their string representations are
631
          // cumbersome.
632
          .add("prioritizedClusterNames", prioritizedClusterNames())
×
633
          .toString();
×
634
    }
635

636
    @AutoValue.Builder
637
    abstract static class Builder {
1✔
638
      // Private, use one of the static factory methods instead.
639
      protected abstract Builder clusterName(String clusterName);
640

641
      // Private, use one of the static factory methods instead.
642
      protected abstract Builder clusterType(ClusterType clusterType);
643

644
      protected abstract Builder lbPolicyConfig(ImmutableMap<String, ?> lbPolicyConfig);
645

646
      Builder roundRobinLbPolicy() {
647
        return this.lbPolicyConfig(ImmutableMap.of("round_robin", ImmutableMap.of()));
1✔
648
      }
649

650
      Builder ringHashLbPolicy(Long minRingSize, Long maxRingSize) {
651
        return this.lbPolicyConfig(ImmutableMap.of("ring_hash_experimental",
1✔
652
            ImmutableMap.of("minRingSize", minRingSize.doubleValue(), "maxRingSize",
1✔
653
                maxRingSize.doubleValue())));
1✔
654
      }
655

656
      Builder leastRequestLbPolicy(Integer choiceCount) {
657
        return this.lbPolicyConfig(ImmutableMap.of("least_request_experimental",
1✔
658
            ImmutableMap.of("choiceCount", choiceCount.doubleValue())));
1✔
659
      }
660

661
      // Private, use leastRequestLbPolicy(int).
662
      protected abstract Builder choiceCount(int choiceCount);
663

664
      // Private, use ringHashLbPolicy(long, long).
665
      protected abstract Builder minRingSize(long minRingSize);
666

667
      // Private, use ringHashLbPolicy(long, long).
668
      protected abstract Builder maxRingSize(long maxRingSize);
669

670
      // Private, use CdsUpdate.forEds() instead.
671
      protected abstract Builder edsServiceName(String edsServiceName);
672

673
      // Private, use CdsUpdate.forLogicalDns() instead.
674
      protected abstract Builder dnsHostName(String dnsHostName);
675

676
      // Private, use one of the static factory methods instead.
677
      protected abstract Builder lrsServerInfo(ServerInfo lrsServerInfo);
678

679
      // Private, use one of the static factory methods instead.
680
      protected abstract Builder maxConcurrentRequests(Long maxConcurrentRequests);
681

682
      // Private, use one of the static factory methods instead.
683
      protected abstract Builder upstreamTlsContext(UpstreamTlsContext upstreamTlsContext);
684

685
      // Private, use CdsUpdate.forAggregate() instead.
686
      protected abstract Builder prioritizedClusterNames(List<String> prioritizedClusterNames);
687

688
      protected abstract Builder outlierDetection(OutlierDetection outlierDetection);
689

690
      protected abstract Builder filterMetadata(ImmutableMap<String, Struct> filterMetadata);
691

692
      abstract CdsUpdate build();
693
    }
694
  }
695
}
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