/* * Copyright 2015 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.netflix.discovery.shared.transport; import java.util.List; import com.netflix.appinfo.InstanceInfo; import com.netflix.discovery.EurekaClientConfig; import com.netflix.discovery.EurekaClientNames; import com.netflix.discovery.shared.resolver.AsyncResolver; import com.netflix.discovery.shared.resolver.ClosableResolver; import com.netflix.discovery.shared.resolver.ClusterResolver; import com.netflix.discovery.shared.resolver.EurekaEndpoint; import com.netflix.discovery.shared.resolver.aws.ApplicationsResolver; import com.netflix.discovery.shared.resolver.aws.AwsEndpoint; import com.netflix.discovery.shared.resolver.aws.ConfigClusterResolver; import com.netflix.discovery.shared.resolver.aws.EurekaHttpResolver; import com.netflix.discovery.shared.resolver.aws.ZoneAffinityClusterResolver; import com.netflix.discovery.shared.transport.decorator.SessionedEurekaHttpClient; import com.netflix.discovery.shared.transport.decorator.RedirectingEurekaHttpClient; import com.netflix.discovery.shared.transport.decorator.RetryableEurekaHttpClient; import com.netflix.discovery.shared.transport.decorator.ServerStatusEvaluators; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Tomasz Bak */ public final class EurekaHttpClients { private static final Logger logger = LoggerFactory.getLogger(EurekaHttpClients.class); private EurekaHttpClients() { } public static EurekaHttpClientFactory queryClientFactory(ClusterResolver bootstrapResolver, TransportClientFactory transportClientFactory, EurekaClientConfig clientConfig, EurekaTransportConfig transportConfig, InstanceInfo myInstanceInfo, ApplicationsResolver.ApplicationsSource applicationsSource) { ClosableResolver queryResolver = transportConfig.useBootstrapResolverForQuery() ? wrapClosable(bootstrapResolver) : queryClientResolver(bootstrapResolver, transportClientFactory, clientConfig, transportConfig, myInstanceInfo, applicationsSource); return canonicalClientFactory(EurekaClientNames.QUERY, transportConfig, queryResolver, transportClientFactory); } public static EurekaHttpClientFactory registrationClientFactory(ClusterResolver bootstrapResolver, TransportClientFactory transportClientFactory, EurekaTransportConfig transportConfig) { return canonicalClientFactory(EurekaClientNames.REGISTRATION, transportConfig, bootstrapResolver, transportClientFactory); } static EurekaHttpClientFactory canonicalClientFactory(final String name, final EurekaTransportConfig transportConfig, final ClusterResolver<EurekaEndpoint> clusterResolver, final TransportClientFactory transportClientFactory) { return new EurekaHttpClientFactory() { @Override public EurekaHttpClient newClient() { return new SessionedEurekaHttpClient( name, RetryableEurekaHttpClient.createFactory( name, transportConfig, clusterResolver, RedirectingEurekaHttpClient.createFactory(transportClientFactory), ServerStatusEvaluators.legacyEvaluator()), transportConfig.getSessionedClientReconnectIntervalSeconds() * 1000 ); } @Override public void shutdown() { wrapClosable(clusterResolver).shutdown(); } }; } // ================================== // Resolvers for the client factories // ================================== public static final String COMPOSITE_BOOTSTRAP_STRATEGY = "composite"; public static ClosableResolver<AwsEndpoint> newBootstrapResolver( final EurekaClientConfig clientConfig, final EurekaTransportConfig transportConfig, final TransportClientFactory transportClientFactory, final InstanceInfo myInstanceInfo, final ApplicationsResolver.ApplicationsSource applicationsSource) { if (COMPOSITE_BOOTSTRAP_STRATEGY.equals(transportConfig.getBootstrapResolverStrategy())) { if (clientConfig.shouldFetchRegistry()) { return compositeBootstrapResolver( clientConfig, transportConfig, transportClientFactory, myInstanceInfo, applicationsSource ); } else { logger.warn("Cannot create a composite bootstrap resolver if registry fetch is disabled." + " Falling back to using a default bootstrap resolver."); } } // if all else fails, return the default return defaultBootstrapResolver(clientConfig, myInstanceInfo); } /** * @return a bootstrap resolver that resolves eureka server endpoints based on either DNS or static config, * depending on configuration for one or the other. This resolver will warm up at the start. */ static ClosableResolver<AwsEndpoint> defaultBootstrapResolver(final EurekaClientConfig clientConfig, final InstanceInfo myInstanceInfo) { String[] availZones = clientConfig.getAvailabilityZones(clientConfig.getRegion()); String myZone = InstanceInfo.getZone(availZones, myInstanceInfo); ClusterResolver<AwsEndpoint> delegateResolver = new ZoneAffinityClusterResolver( new ConfigClusterResolver(clientConfig, myInstanceInfo), myZone, true ); List<AwsEndpoint> initialValue = delegateResolver.getClusterEndpoints(); if (initialValue.isEmpty()) { String msg = "Initial resolution of Eureka server endpoints failed. Check ConfigClusterResolver logs for more info"; logger.error(msg); failFastOnInitCheck(clientConfig, msg); } return new AsyncResolver<>( EurekaClientNames.BOOTSTRAP, delegateResolver, initialValue, 1, clientConfig.getEurekaServiceUrlPollIntervalSeconds() * 1000 ); } /** * @return a bootstrap resolver that resolves eureka server endpoints via a remote call to a "vip source" * the local registry, where the source is found from a rootResolver (dns or config) */ static ClosableResolver<AwsEndpoint> compositeBootstrapResolver( final EurekaClientConfig clientConfig, final EurekaTransportConfig transportConfig, final TransportClientFactory transportClientFactory, final InstanceInfo myInstanceInfo, final ApplicationsResolver.ApplicationsSource applicationsSource) { final ClusterResolver rootResolver = new ConfigClusterResolver(clientConfig, myInstanceInfo); final EurekaHttpResolver remoteResolver = new EurekaHttpResolver( clientConfig, transportConfig, rootResolver, transportClientFactory, transportConfig.getWriteClusterVip() ); final ApplicationsResolver localResolver = new ApplicationsResolver( clientConfig, transportConfig, applicationsSource, transportConfig.getWriteClusterVip() ); ClusterResolver<AwsEndpoint> compositeResolver = new ClusterResolver<AwsEndpoint>() { @Override public String getRegion() { return clientConfig.getRegion(); } @Override public List<AwsEndpoint> getClusterEndpoints() { List<AwsEndpoint> result = localResolver.getClusterEndpoints(); if (result.isEmpty()) { result = remoteResolver.getClusterEndpoints(); } return result; } }; List<AwsEndpoint> initialValue = compositeResolver.getClusterEndpoints(); if (initialValue.isEmpty()) { String msg = "Initial resolution of Eureka endpoints failed. Check ConfigClusterResolver logs for more info"; logger.error(msg); failFastOnInitCheck(clientConfig, msg); } String[] availZones = clientConfig.getAvailabilityZones(clientConfig.getRegion()); String myZone = InstanceInfo.getZone(availZones, myInstanceInfo); return new AsyncResolver<>( EurekaClientNames.BOOTSTRAP, new ZoneAffinityClusterResolver(compositeResolver, myZone, true), initialValue, transportConfig.getAsyncExecutorThreadPoolSize(), transportConfig.getAsyncResolverRefreshIntervalMs() ); } /** * @return a resolver that resolves eureka server endpoints for query operations */ static ClosableResolver<AwsEndpoint> queryClientResolver(final ClusterResolver bootstrapResolver, final TransportClientFactory transportClientFactory, final EurekaClientConfig clientConfig, final EurekaTransportConfig transportConfig, final InstanceInfo myInstanceInfo, final ApplicationsResolver.ApplicationsSource applicationsSource) { final EurekaHttpResolver remoteResolver = new EurekaHttpResolver( clientConfig, transportConfig, bootstrapResolver, transportClientFactory, transportConfig.getReadClusterVip() ); final ApplicationsResolver localResolver = new ApplicationsResolver( clientConfig, transportConfig, applicationsSource, transportConfig.getReadClusterVip() ); return compositeQueryResolver( remoteResolver, localResolver, clientConfig, transportConfig, myInstanceInfo ); } /** * @return a composite resolver that resolves eureka server endpoints for query operations, given two resolvers: * a resolver that can resolve targets via a remote call to a remote source, and a resolver that * can resolve targets via data in the local registry. */ /* testing */ static ClosableResolver<AwsEndpoint> compositeQueryResolver( final ClusterResolver<AwsEndpoint> remoteResolver, final ClusterResolver<AwsEndpoint> localResolver, final EurekaClientConfig clientConfig, final EurekaTransportConfig transportConfig, final InstanceInfo myInstanceInfo) { String[] availZones = clientConfig.getAvailabilityZones(clientConfig.getRegion()); String myZone = InstanceInfo.getZone(availZones, myInstanceInfo); ClusterResolver<AwsEndpoint> compositeResolver = new ClusterResolver<AwsEndpoint>() { @Override public String getRegion() { return clientConfig.getRegion(); } @Override public List<AwsEndpoint> getClusterEndpoints() { List<AwsEndpoint> result = localResolver.getClusterEndpoints(); if (result.isEmpty()) { result = remoteResolver.getClusterEndpoints(); } return result; } }; return new AsyncResolver<>( EurekaClientNames.QUERY, new ZoneAffinityClusterResolver(compositeResolver, myZone, true), transportConfig.getAsyncExecutorThreadPoolSize(), transportConfig.getAsyncResolverRefreshIntervalMs(), transportConfig.getAsyncResolverWarmUpTimeoutMs() ); } static <T extends EurekaEndpoint> ClosableResolver<T> wrapClosable(final ClusterResolver<T> clusterResolver) { if (clusterResolver instanceof ClosableResolver) { return (ClosableResolver) clusterResolver; } return new ClosableResolver<T>() { @Override public void shutdown() { // no-op } @Override public String getRegion() { return clusterResolver.getRegion(); } @Override public List<T> getClusterEndpoints() { return clusterResolver.getClusterEndpoints(); } }; } // potential future feature, guarding with experimental flag for now private static void failFastOnInitCheck(EurekaClientConfig clientConfig, String msg) { if ("true".equals(clientConfig.getExperimental("clientTransportFailFastOnInit"))) { throw new RuntimeException(msg); } } }