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