xref: /aosp_15_r20/external/grpc-grpc-java/xds/src/main/java/io/grpc/xds/XdsClusterResource.java (revision e07d83d3ffcef9ecfc9f7f475418ec639ff0e5fe)
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> {
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();
61 
getInstance()62   public static XdsClusterResource getInstance() {
63     return instance;
64   }
65 
66   @Override
67   @Nullable
extractResourceName(Message unpackedResource)68   String extractResourceName(Message unpackedResource) {
69     if (!(unpackedResource instanceof Cluster)) {
70       return null;
71     }
72     return ((Cluster) unpackedResource).getName();
73   }
74 
75   @Override
typeName()76   String typeName() {
77     return "CDS";
78   }
79 
80   @Override
typeUrl()81   String typeUrl() {
82     return ADS_TYPE_URL_CDS;
83   }
84 
85   @Override
isFullStateOfTheWorld()86   boolean isFullStateOfTheWorld() {
87     return true;
88   }
89 
90   @Override
91   @SuppressWarnings("unchecked")
unpackedClassName()92   Class<Cluster> unpackedClassName() {
93     return Cluster.class;
94   }
95 
96   @Override
doParse(Args args, Message unpackedMessage)97   CdsUpdate doParse(Args args, Message unpackedMessage)
98       throws ResourceInvalidException {
99     if (!(unpackedMessage instanceof Cluster)) {
100       throw new ResourceInvalidException("Invalid message type: " + unpackedMessage.getClass());
101     }
102     Set<String> certProviderInstances = null;
103     if (args.bootstrapInfo != null && args.bootstrapInfo.certProviders() != null) {
104       certProviderInstances = args.bootstrapInfo.certProviders().keySet();
105     }
106     return processCluster((Cluster) unpackedMessage, certProviderInstances,
107         args.serverInfo, args.loadBalancerRegistry);
108   }
109 
110   @VisibleForTesting
processCluster(Cluster cluster, Set<String> certProviderInstances, Bootstrapper.ServerInfo serverInfo, LoadBalancerRegistry loadBalancerRegistry)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()) {
118       case TYPE:
119         structOrError = parseNonAggregateCluster(cluster,
120             certProviderInstances, serverInfo);
121         break;
122       case CLUSTER_TYPE:
123         structOrError = parseAggregateCluster(cluster);
124         break;
125       case CLUSTERDISCOVERYTYPE_NOT_SET:
126       default:
127         throw new ResourceInvalidException(
128             "Cluster " + cluster.getName() + ": unspecified cluster discovery type");
129     }
130     if (structOrError.getErrorDetail() != null) {
131       throw new ResourceInvalidException(structOrError.getErrorDetail());
132     }
133     CdsUpdate.Builder updateBuilder = structOrError.getStruct();
134 
135     ImmutableMap<String, ?> lbPolicyConfig = LoadBalancerConfigFactory.newConfig(cluster,
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);
140     NameResolver.ConfigOrError configOrError = loadBalancerRegistry.getProvider(
141         lbConfig.getPolicyName()).parseLoadBalancingPolicyConfig(
142         lbConfig.getRawConfigValue());
143     if (configOrError.getError() != null) {
144       throw new ResourceInvalidException(structOrError.getErrorDetail());
145     }
146 
147     updateBuilder.lbPolicyConfig(lbPolicyConfig);
148 
149     return updateBuilder.build();
150   }
151 
parseAggregateCluster(Cluster cluster)152   private static StructOrError<CdsUpdate.Builder> parseAggregateCluster(Cluster cluster) {
153     String clusterName = cluster.getName();
154     Cluster.CustomClusterType customType = cluster.getClusterType();
155     String typeName = customType.getName();
156     if (!typeName.equals(AGGREGATE_CLUSTER_TYPE_NAME)) {
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(),
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     }
168     return StructOrError.fromStruct(CdsUpdate.forAggregate(
169         clusterName, clusterConfig.getClustersList()));
170   }
171 
parseNonAggregateCluster( Cluster cluster, Set<String> certProviderInstances, Bootstrapper.ServerInfo serverInfo)172   private static StructOrError<CdsUpdate.Builder> parseNonAggregateCluster(
173       Cluster cluster, Set<String> certProviderInstances, Bootstrapper.ServerInfo serverInfo) {
174     String clusterName = cluster.getName();
175     Bootstrapper.ServerInfo lrsServerInfo = null;
176     Long maxConcurrentRequests = null;
177     EnvoyServerProtoData.UpstreamTlsContext upstreamTlsContext = null;
178     OutlierDetection outlierDetection = null;
179     if (cluster.hasLrsServer()) {
180       if (!cluster.getLrsServer().hasSelf()) {
181         return StructOrError.fromError(
182             "Cluster " + clusterName + ": only support LRS for the same management server");
183       }
184       lrsServerInfo = serverInfo;
185     }
186     if (cluster.hasCircuitBreakers()) {
187       List<Thresholds> thresholds = cluster.getCircuitBreakers().getThresholdsList();
188       for (Thresholds threshold : thresholds) {
189         if (threshold.getPriority() != RoutingPriority.DEFAULT) {
190           continue;
191         }
192         if (threshold.hasMaxRequests()) {
193           maxConcurrentRequests = (long) threshold.getMaxRequests().getValue();
194         }
195       }
196     }
197     if (cluster.getTransportSocketMatchesCount() > 0) {
198       return StructOrError.fromError("Cluster " + clusterName
199           + ": transport-socket-matches not supported.");
200     }
201     if (cluster.hasTransportSocket()) {
202       if (!TRANSPORT_SOCKET_NAME_TLS.equals(cluster.getTransportSocket().getName())) {
203         return StructOrError.fromError("transport-socket with name "
204             + cluster.getTransportSocket().getName() + " not supported.");
205       }
206       try {
207         upstreamTlsContext = UpstreamTlsContext.fromEnvoyProtoUpstreamTlsContext(
208             validateUpstreamTlsContext(
209                 unpackCompatibleType(cluster.getTransportSocket().getTypedConfig(),
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) {
214         return StructOrError.fromError(
215             "Cluster " + clusterName + ": malformed UpstreamTlsContext: " + e);
216       }
217     }
218 
219     if (cluster.hasOutlierDetection()) {
220       try {
221         outlierDetection = OutlierDetection.fromEnvoyOutlierDetection(
222             validateOutlierDetection(cluster.getOutlierDetection()));
223       } catch (ResourceInvalidException e) {
224         return StructOrError.fromError(
225             "Cluster " + clusterName + ": malformed outlier_detection: " + e);
226       }
227     }
228 
229     Cluster.DiscoveryType type = cluster.getType();
230     if (type == Cluster.DiscoveryType.EDS) {
231       String edsServiceName = null;
232       io.envoyproxy.envoy.config.cluster.v3.Cluster.EdsClusterConfig edsClusterConfig =
233           cluster.getEdsClusterConfig();
234       if (!edsClusterConfig.getEdsConfig().hasAds()
235           && ! edsClusterConfig.getEdsConfig().hasSelf()) {
236         return StructOrError.fromError(
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()) {
242         edsServiceName = edsClusterConfig.getServiceName();
243       }
244       return StructOrError.fromStruct(CdsUpdate.forEds(
245           clusterName, edsServiceName, lrsServerInfo, maxConcurrentRequests, upstreamTlsContext,
246           outlierDetection));
247     } else if (type.equals(Cluster.DiscoveryType.LOGICAL_DNS)) {
248       if (!cluster.hasLoadAssignment()) {
249         return StructOrError.fromError(
250             "Cluster " + clusterName + ": LOGICAL_DNS clusters must have a single host");
251       }
252       ClusterLoadAssignment assignment = cluster.getLoadAssignment();
253       if (assignment.getEndpointsCount() != 1
254           || assignment.getEndpoints(0).getLbEndpointsCount() != 1) {
255         return StructOrError.fromError(
256             "Cluster " + clusterName + ": LOGICAL_DNS clusters must have a single "
257                 + "locality_lb_endpoint and a single lb_endpoint");
258       }
259       io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint lbEndpoint =
260           assignment.getEndpoints(0).getLbEndpoints(0);
261       if (!lbEndpoint.hasEndpoint() || !lbEndpoint.getEndpoint().hasAddress()
262           || !lbEndpoint.getEndpoint().getAddress().hasSocketAddress()) {
263         return StructOrError.fromError(
264             "Cluster " + clusterName
265                 + ": LOGICAL_DNS clusters must have an endpoint with address and socket_address");
266       }
267       SocketAddress socketAddress = lbEndpoint.getEndpoint().getAddress().getSocketAddress();
268       if (!socketAddress.getResolverName().isEmpty()) {
269         return StructOrError.fromError(
270             "Cluster " + clusterName
271                 + ": LOGICAL DNS clusters must NOT have a custom resolver name set");
272       }
273       if (socketAddress.getPortSpecifierCase() != SocketAddress.PortSpecifierCase.PORT_VALUE) {
274         return StructOrError.fromError(
275             "Cluster " + clusterName
276                 + ": LOGICAL DNS clusters socket_address must have port_value");
277       }
278       String dnsHostName = String.format(
279           Locale.US, "%s:%d", socketAddress.getAddress(), socketAddress.getPortValue());
280       return StructOrError.fromStruct(CdsUpdate.forLogicalDns(
281           clusterName, dnsHostName, lrsServerInfo, maxConcurrentRequests, upstreamTlsContext));
282     }
283     return StructOrError.fromError(
284         "Cluster " + clusterName + ": unsupported built-in discovery type: " + type);
285   }
286 
validateOutlierDetection( io.envoyproxy.envoy.config.cluster.v3.OutlierDetection outlierDetection)287   static io.envoyproxy.envoy.config.cluster.v3.OutlierDetection validateOutlierDetection(
288       io.envoyproxy.envoy.config.cluster.v3.OutlierDetection outlierDetection)
289       throws ResourceInvalidException {
290     if (outlierDetection.hasInterval()) {
291       if (!Durations.isValid(outlierDetection.getInterval())) {
292         throw new ResourceInvalidException("outlier_detection interval is not a valid Duration");
293       }
294       if (hasNegativeValues(outlierDetection.getInterval())) {
295         throw new ResourceInvalidException("outlier_detection interval has a negative value");
296       }
297     }
298     if (outlierDetection.hasBaseEjectionTime()) {
299       if (!Durations.isValid(outlierDetection.getBaseEjectionTime())) {
300         throw new ResourceInvalidException(
301             "outlier_detection base_ejection_time is not a valid Duration");
302       }
303       if (hasNegativeValues(outlierDetection.getBaseEjectionTime())) {
304         throw new ResourceInvalidException(
305             "outlier_detection base_ejection_time has a negative value");
306       }
307     }
308     if (outlierDetection.hasMaxEjectionTime()) {
309       if (!Durations.isValid(outlierDetection.getMaxEjectionTime())) {
310         throw new ResourceInvalidException(
311             "outlier_detection max_ejection_time is not a valid Duration");
312       }
313       if (hasNegativeValues(outlierDetection.getMaxEjectionTime())) {
314         throw new ResourceInvalidException(
315             "outlier_detection max_ejection_time has a negative value");
316       }
317     }
318     if (outlierDetection.hasMaxEjectionPercent()
319         && outlierDetection.getMaxEjectionPercent().getValue() > 100) {
320       throw new ResourceInvalidException(
321           "outlier_detection max_ejection_percent is > 100");
322     }
323     if (outlierDetection.hasEnforcingSuccessRate()
324         && outlierDetection.getEnforcingSuccessRate().getValue() > 100) {
325       throw new ResourceInvalidException(
326           "outlier_detection enforcing_success_rate is > 100");
327     }
328     if (outlierDetection.hasFailurePercentageThreshold()
329         && outlierDetection.getFailurePercentageThreshold().getValue() > 100) {
330       throw new ResourceInvalidException(
331           "outlier_detection failure_percentage_threshold is > 100");
332     }
333     if (outlierDetection.hasEnforcingFailurePercentage()
334         && outlierDetection.getEnforcingFailurePercentage().getValue() > 100) {
335       throw new ResourceInvalidException(
336           "outlier_detection enforcing_failure_percentage is > 100");
337     }
338 
339     return outlierDetection;
340   }
341 
hasNegativeValues(Duration duration)342   static boolean hasNegativeValues(Duration duration) {
343     return duration.getSeconds() < 0 || duration.getNanos() < 0;
344   }
345 
346   @VisibleForTesting
347   static io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
validateUpstreamTlsContext( io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext upstreamTlsContext, Set<String> certProviderInstances)348       validateUpstreamTlsContext(
349       io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext upstreamTlsContext,
350       Set<String> certProviderInstances)
351       throws ResourceInvalidException {
352     if (upstreamTlsContext.hasCommonTlsContext()) {
353       validateCommonTlsContext(upstreamTlsContext.getCommonTlsContext(), certProviderInstances,
354           false);
355     } else {
356       throw new ResourceInvalidException("common-tls-context is required in upstream-tls-context");
357     }
358     return upstreamTlsContext;
359   }
360 
361   @VisibleForTesting
validateCommonTlsContext( CommonTlsContext commonTlsContext, Set<String> certProviderInstances, boolean server)362   static void validateCommonTlsContext(
363       CommonTlsContext commonTlsContext, Set<String> certProviderInstances, boolean server)
364       throws ResourceInvalidException {
365     if (commonTlsContext.hasCustomHandshaker()) {
366       throw new ResourceInvalidException(
367           "common-tls-context with custom_handshaker is not supported");
368     }
369     if (commonTlsContext.hasTlsParams()) {
370       throw new ResourceInvalidException("common-tls-context with tls_params is not supported");
371     }
372     if (commonTlsContext.hasValidationContextSdsSecretConfig()) {
373       throw new ResourceInvalidException(
374           "common-tls-context with validation_context_sds_secret_config is not supported");
375     }
376     if (commonTlsContext.hasValidationContextCertificateProvider()) {
377       throw new ResourceInvalidException(
378           "common-tls-context with validation_context_certificate_provider is not supported");
379     }
380     if (commonTlsContext.hasValidationContextCertificateProviderInstance()) {
381       throw new ResourceInvalidException(
382           "common-tls-context with validation_context_certificate_provider_instance is not"
383               + " supported");
384     }
385     String certInstanceName = getIdentityCertInstanceName(commonTlsContext);
386     if (certInstanceName == null) {
387       if (server) {
388         throw new ResourceInvalidException(
389             "tls_certificate_provider_instance is required in downstream-tls-context");
390       }
391       if (commonTlsContext.getTlsCertificatesCount() > 0) {
392         throw new ResourceInvalidException(
393             "tls_certificate_provider_instance is unset");
394       }
395       if (commonTlsContext.getTlsCertificateSdsSecretConfigsCount() > 0) {
396         throw new ResourceInvalidException(
397             "tls_certificate_provider_instance is unset");
398       }
399       if (commonTlsContext.hasTlsCertificateCertificateProvider()) {
400         throw new ResourceInvalidException(
401             "tls_certificate_provider_instance is unset");
402       }
403     } else if (certProviderInstances == null || !certProviderInstances.contains(certInstanceName)) {
404       throw new ResourceInvalidException(
405           "CertificateProvider instance name '" + certInstanceName
406               + "' not defined in the bootstrap file.");
407     }
408     String rootCaInstanceName = getRootCertInstanceName(commonTlsContext);
409     if (rootCaInstanceName == null) {
410       if (!server) {
411         throw new ResourceInvalidException(
412             "ca_certificate_provider_instance is required in upstream-tls-context");
413       }
414     } else {
415       if (certProviderInstances == null || !certProviderInstances.contains(rootCaInstanceName)) {
416         throw new ResourceInvalidException(
417             "ca_certificate_provider_instance name '" + rootCaInstanceName
418                 + "' not defined in the bootstrap file.");
419       }
420       CertificateValidationContext certificateValidationContext = null;
421       if (commonTlsContext.hasValidationContext()) {
422         certificateValidationContext = commonTlsContext.getValidationContext();
423       } else if (commonTlsContext.hasCombinedValidationContext() && commonTlsContext
424           .getCombinedValidationContext().hasDefaultValidationContext()) {
425         certificateValidationContext = commonTlsContext.getCombinedValidationContext()
426             .getDefaultValidationContext();
427       }
428       if (certificateValidationContext != null) {
429         if (certificateValidationContext.getMatchSubjectAltNamesCount() > 0 && server) {
430           throw new ResourceInvalidException(
431               "match_subject_alt_names only allowed in upstream_tls_context");
432         }
433         if (certificateValidationContext.getVerifyCertificateSpkiCount() > 0) {
434           throw new ResourceInvalidException(
435               "verify_certificate_spki in default_validation_context is not supported");
436         }
437         if (certificateValidationContext.getVerifyCertificateHashCount() > 0) {
438           throw new ResourceInvalidException(
439               "verify_certificate_hash in default_validation_context is not supported");
440         }
441         if (certificateValidationContext.hasRequireSignedCertificateTimestamp()) {
442           throw new ResourceInvalidException(
443               "require_signed_certificate_timestamp in default_validation_context is not "
444                   + "supported");
445         }
446         if (certificateValidationContext.hasCrl()) {
447           throw new ResourceInvalidException("crl in default_validation_context is not supported");
448         }
449         if (certificateValidationContext.hasCustomValidatorConfig()) {
450           throw new ResourceInvalidException(
451               "custom_validator_config in default_validation_context is not supported");
452         }
453       }
454     }
455   }
456 
getIdentityCertInstanceName(CommonTlsContext commonTlsContext)457   private static String getIdentityCertInstanceName(CommonTlsContext commonTlsContext) {
458     if (commonTlsContext.hasTlsCertificateProviderInstance()) {
459       return commonTlsContext.getTlsCertificateProviderInstance().getInstanceName();
460     } else if (commonTlsContext.hasTlsCertificateCertificateProviderInstance()) {
461       return commonTlsContext.getTlsCertificateCertificateProviderInstance().getInstanceName();
462     }
463     return null;
464   }
465 
getRootCertInstanceName(CommonTlsContext commonTlsContext)466   private static String getRootCertInstanceName(CommonTlsContext commonTlsContext) {
467     if (commonTlsContext.hasValidationContext()) {
468       if (commonTlsContext.getValidationContext().hasCaCertificateProviderInstance()) {
469         return commonTlsContext.getValidationContext().getCaCertificateProviderInstance()
470             .getInstanceName();
471       }
472     } else if (commonTlsContext.hasCombinedValidationContext()) {
473       CommonTlsContext.CombinedCertificateValidationContext combinedCertificateValidationContext
474           = commonTlsContext.getCombinedValidationContext();
475       if (combinedCertificateValidationContext.hasDefaultValidationContext()
476           && combinedCertificateValidationContext.getDefaultValidationContext()
477           .hasCaCertificateProviderInstance()) {
478         return combinedCertificateValidationContext.getDefaultValidationContext()
479             .getCaCertificateProviderInstance().getInstanceName();
480       } else if (combinedCertificateValidationContext
481           .hasValidationContextCertificateProviderInstance()) {
482         return combinedCertificateValidationContext
483             .getValidationContextCertificateProviderInstance().getInstanceName();
484       }
485     }
486     return null;
487   }
488 
489   /** xDS resource update for cluster-level configuration. */
490   @AutoValue
491   abstract static class CdsUpdate implements ResourceUpdate {
clusterName()492     abstract String clusterName();
493 
clusterType()494     abstract ClusterType clusterType();
495 
lbPolicyConfig()496     abstract ImmutableMap<String, ?> lbPolicyConfig();
497 
498     // Only valid if lbPolicy is "ring_hash_experimental".
minRingSize()499     abstract long minRingSize();
500 
501     // Only valid if lbPolicy is "ring_hash_experimental".
maxRingSize()502     abstract long maxRingSize();
503 
504     // Only valid if lbPolicy is "least_request_experimental".
choiceCount()505     abstract int choiceCount();
506 
507     // Alternative resource name to be used in EDS requests.
508     /// Only valid for EDS cluster.
509     @Nullable
edsServiceName()510     abstract String edsServiceName();
511 
512     // Corresponding DNS name to be used if upstream endpoints of the cluster is resolvable
513     // via DNS.
514     // Only valid for LOGICAL_DNS cluster.
515     @Nullable
dnsHostName()516     abstract String dnsHostName();
517 
518     // Load report server info for reporting loads via LRS.
519     // Only valid for EDS or LOGICAL_DNS cluster.
520     @Nullable
lrsServerInfo()521     abstract ServerInfo lrsServerInfo();
522 
523     // Max number of concurrent requests can be sent to this cluster.
524     // Only valid for EDS or LOGICAL_DNS cluster.
525     @Nullable
maxConcurrentRequests()526     abstract Long maxConcurrentRequests();
527 
528     // TLS context used to connect to connect to this cluster.
529     // Only valid for EDS or LOGICAL_DNS cluster.
530     @Nullable
upstreamTlsContext()531     abstract UpstreamTlsContext upstreamTlsContext();
532 
533     // List of underlying clusters making of this aggregate cluster.
534     // Only valid for AGGREGATE cluster.
535     @Nullable
prioritizedClusterNames()536     abstract ImmutableList<String> prioritizedClusterNames();
537 
538     // Outlier detection configuration.
539     @Nullable
outlierDetection()540     abstract OutlierDetection outlierDetection();
541 
forAggregate(String clusterName, List<String> prioritizedClusterNames)542     static Builder forAggregate(String clusterName, List<String> prioritizedClusterNames) {
543       checkNotNull(prioritizedClusterNames, "prioritizedClusterNames");
544       return new AutoValue_XdsClusterResource_CdsUpdate.Builder()
545           .clusterName(clusterName)
546           .clusterType(ClusterType.AGGREGATE)
547           .minRingSize(0)
548           .maxRingSize(0)
549           .choiceCount(0)
550           .prioritizedClusterNames(ImmutableList.copyOf(prioritizedClusterNames));
551     }
552 
forEds(String clusterName, @Nullable String edsServiceName, @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests, @Nullable UpstreamTlsContext upstreamTlsContext, @Nullable OutlierDetection outlierDetection)553     static Builder forEds(String clusterName, @Nullable String edsServiceName,
554                           @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests,
555                           @Nullable UpstreamTlsContext upstreamTlsContext,
556                           @Nullable OutlierDetection outlierDetection) {
557       return new AutoValue_XdsClusterResource_CdsUpdate.Builder()
558           .clusterName(clusterName)
559           .clusterType(ClusterType.EDS)
560           .minRingSize(0)
561           .maxRingSize(0)
562           .choiceCount(0)
563           .edsServiceName(edsServiceName)
564           .lrsServerInfo(lrsServerInfo)
565           .maxConcurrentRequests(maxConcurrentRequests)
566           .upstreamTlsContext(upstreamTlsContext)
567           .outlierDetection(outlierDetection);
568     }
569 
forLogicalDns(String clusterName, String dnsHostName, @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests, @Nullable UpstreamTlsContext upstreamTlsContext)570     static Builder forLogicalDns(String clusterName, String dnsHostName,
571                                  @Nullable ServerInfo lrsServerInfo,
572                                  @Nullable Long maxConcurrentRequests,
573                                  @Nullable UpstreamTlsContext upstreamTlsContext) {
574       return new AutoValue_XdsClusterResource_CdsUpdate.Builder()
575           .clusterName(clusterName)
576           .clusterType(ClusterType.LOGICAL_DNS)
577           .minRingSize(0)
578           .maxRingSize(0)
579           .choiceCount(0)
580           .dnsHostName(dnsHostName)
581           .lrsServerInfo(lrsServerInfo)
582           .maxConcurrentRequests(maxConcurrentRequests)
583           .upstreamTlsContext(upstreamTlsContext);
584     }
585 
586     enum ClusterType {
587       EDS, LOGICAL_DNS, AGGREGATE
588     }
589 
590     enum LbPolicy {
591       ROUND_ROBIN, RING_HASH, LEAST_REQUEST
592     }
593 
594     // FIXME(chengyuanzhang): delete this after UpstreamTlsContext's toString() is fixed.
595     @Override
toString()596     public final String toString() {
597       return MoreObjects.toStringHelper(this)
598           .add("clusterName", clusterName())
599           .add("clusterType", clusterType())
600           .add("lbPolicyConfig", lbPolicyConfig())
601           .add("minRingSize", minRingSize())
602           .add("maxRingSize", maxRingSize())
603           .add("choiceCount", choiceCount())
604           .add("edsServiceName", edsServiceName())
605           .add("dnsHostName", dnsHostName())
606           .add("lrsServerInfo", lrsServerInfo())
607           .add("maxConcurrentRequests", maxConcurrentRequests())
608           // Exclude upstreamTlsContext and outlierDetection as their string representations are
609           // cumbersome.
610           .add("prioritizedClusterNames", prioritizedClusterNames())
611           .toString();
612     }
613 
614     @AutoValue.Builder
615     abstract static class Builder {
616       // Private, use one of the static factory methods instead.
clusterName(String clusterName)617       protected abstract Builder clusterName(String clusterName);
618 
619       // Private, use one of the static factory methods instead.
clusterType(ClusterType clusterType)620       protected abstract Builder clusterType(ClusterType clusterType);
621 
lbPolicyConfig(ImmutableMap<String, ?> lbPolicyConfig)622       protected abstract Builder lbPolicyConfig(ImmutableMap<String, ?> lbPolicyConfig);
623 
roundRobinLbPolicy()624       Builder roundRobinLbPolicy() {
625         return this.lbPolicyConfig(ImmutableMap.of("round_robin", ImmutableMap.of()));
626       }
627 
ringHashLbPolicy(Long minRingSize, Long maxRingSize)628       Builder ringHashLbPolicy(Long minRingSize, Long maxRingSize) {
629         return this.lbPolicyConfig(ImmutableMap.of("ring_hash_experimental",
630             ImmutableMap.of("minRingSize", minRingSize.doubleValue(), "maxRingSize",
631                 maxRingSize.doubleValue())));
632       }
633 
leastRequestLbPolicy(Integer choiceCount)634       Builder leastRequestLbPolicy(Integer choiceCount) {
635         return this.lbPolicyConfig(ImmutableMap.of("least_request_experimental",
636             ImmutableMap.of("choiceCount", choiceCount.doubleValue())));
637       }
638 
639       // Private, use leastRequestLbPolicy(int).
choiceCount(int choiceCount)640       protected abstract Builder choiceCount(int choiceCount);
641 
642       // Private, use ringHashLbPolicy(long, long).
minRingSize(long minRingSize)643       protected abstract Builder minRingSize(long minRingSize);
644 
645       // Private, use ringHashLbPolicy(long, long).
maxRingSize(long maxRingSize)646       protected abstract Builder maxRingSize(long maxRingSize);
647 
648       // Private, use CdsUpdate.forEds() instead.
edsServiceName(String edsServiceName)649       protected abstract Builder edsServiceName(String edsServiceName);
650 
651       // Private, use CdsUpdate.forLogicalDns() instead.
dnsHostName(String dnsHostName)652       protected abstract Builder dnsHostName(String dnsHostName);
653 
654       // Private, use one of the static factory methods instead.
lrsServerInfo(ServerInfo lrsServerInfo)655       protected abstract Builder lrsServerInfo(ServerInfo lrsServerInfo);
656 
657       // Private, use one of the static factory methods instead.
maxConcurrentRequests(Long maxConcurrentRequests)658       protected abstract Builder maxConcurrentRequests(Long maxConcurrentRequests);
659 
660       // Private, use one of the static factory methods instead.
upstreamTlsContext(UpstreamTlsContext upstreamTlsContext)661       protected abstract Builder upstreamTlsContext(UpstreamTlsContext upstreamTlsContext);
662 
663       // Private, use CdsUpdate.forAggregate() instead.
prioritizedClusterNames(List<String> prioritizedClusterNames)664       protected abstract Builder prioritizedClusterNames(List<String> prioritizedClusterNames);
665 
outlierDetection(OutlierDetection outlierDetection)666       protected abstract Builder outlierDetection(OutlierDetection outlierDetection);
667 
build()668       abstract CdsUpdate build();
669     }
670   }
671 }
672