Extract ribbon client factory into ApplicationContext

Using Spring as an object factory to control the lifecycle of ribbon clients.
So far we have a single, parameterized configuration class for all clients, but
can be extended to allow user to supply additional configuration. The current
model for user extensions is

@EnableRibbonClient(@RibbonClient(name = "foo", configuration = FooConfiguration.class))
public class MainConfiguration {
    ...
}

So in this example, MainConfiguration is part of the "main" application context
and FooConfiguration is used to create the Ribbon client and load balancer for
the "foo" service.
This commit is contained in:
Dave Syer
2014-12-05 15:39:14 +00:00
parent bfb6ca6d8d
commit 6417923523
25 changed files with 812 additions and 423 deletions

View File

@@ -8,8 +8,11 @@ import static com.netflix.config.ConfigurationManager.ENV_CONFIG_NAME;
import static com.netflix.config.ConfigurationManager.SYS_CONFIG_NAME;
import static com.netflix.config.ConfigurationManager.URL_CONFIG_NAME;
import java.lang.reflect.Field;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.PreDestroy;
import org.apache.commons.configuration.ConfigurationBuilder;
import org.apache.commons.configuration.EnvironmentConfiguration;
import org.apache.commons.configuration.SystemConfiguration;
@@ -20,85 +23,111 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.ReflectionUtils;
import com.netflix.config.ConcurrentCompositeConfiguration;
import com.netflix.config.ConfigurationManager;
import com.netflix.config.DynamicPropertyFactory;
import com.netflix.config.DynamicURLConfiguration;
/**
* @author Spencer Gibb
*/
@Configuration
@ConditionalOnClass({ConcurrentCompositeConfiguration.class, ConfigurationBuilder.class})
@ConditionalOnClass({ ConcurrentCompositeConfiguration.class, ConfigurationBuilder.class })
public class ArchaiusAutoConfiguration {
private static final Logger logger = LoggerFactory.getLogger(ArchaiusAutoConfiguration.class);
private static final AtomicBoolean initialized = new AtomicBoolean(false);
private static final Logger logger = LoggerFactory
.getLogger(ArchaiusAutoConfiguration.class);
private static final AtomicBoolean initialized = new AtomicBoolean(false);
@Autowired
private ConfigurableEnvironment env;
@Autowired
private ConfigurableEnvironment env;
@Bean
public ConfigurableEnvironmentConfiguration configurableEnvironmentConfiguration() {
ConfigurableEnvironmentConfiguration envConfig = new ConfigurableEnvironmentConfiguration(env);
configureArchaius(envConfig);
return envConfig;
}
@Bean
protected ArchaiusEndpoint archaiusEndpoint() {
return new ArchaiusEndpoint();
}
@PreDestroy
public void close() {
setStatic(ConfigurationManager.class, "instance", null);
setStatic(ConfigurationManager.class, "customConfigurationInstalled", false);
setStatic(DynamicPropertyFactory.class, "config", null);
setStatic(DynamicPropertyFactory.class, "initializedWithDefaultConfig", false);
initialized.compareAndSet(true, false);
}
@SuppressWarnings("deprecation")
@Bean
public ConfigurableEnvironmentConfiguration configurableEnvironmentConfiguration() {
ConfigurableEnvironmentConfiguration envConfig = new ConfigurableEnvironmentConfiguration(
env);
configureArchaius(envConfig);
return envConfig;
}
@Bean
protected ArchaiusEndpoint archaiusEndpoint() {
return new ArchaiusEndpoint();
}
@SuppressWarnings("deprecation")
protected void configureArchaius(ConfigurableEnvironmentConfiguration envConfig) {
if (initialized.compareAndSet(false, true)) {
String appName = env.getProperty("spring.application.name");
if (appName == null) {
appName = "application";
logger.warn("No spring.application.name found, defaulting to 'application'");
}
//this is deprecated, but currently it seams the only way to set it initially
System.setProperty(DEPLOYMENT_APPLICATION_ID_PROPERTY, appName);
if (initialized.compareAndSet(false, true)) {
String appName = env.getProperty("spring.application.name");
if (appName == null) {
appName = "application";
logger.warn("No spring.application.name found, defaulting to 'application'");
}
// this is deprecated, but currently it seams the only way to set it initially
System.setProperty(DEPLOYMENT_APPLICATION_ID_PROPERTY, appName);
//TODO: support for other DeploymentContexts
// TODO: support for other DeploymentContexts
ConcurrentCompositeConfiguration config = new ConcurrentCompositeConfiguration();
ConcurrentCompositeConfiguration config = new ConcurrentCompositeConfiguration();
//support to add other Configurations (Jdbc, DynamoDb, Zookeeper, jclouds, etc...)
/*if (factories != null && !factories.isEmpty()) {
for (PropertiesSourceFactory factory: factories) {
config.addConfiguration(factory.getConfiguration(), factory.getName());
}
}*/
config.addConfiguration(envConfig, ConfigurableEnvironmentConfiguration.class.getSimpleName());
// support to add other Configurations (Jdbc, DynamoDb, Zookeeper, jclouds,
// etc...)
/*
* if (factories != null && !factories.isEmpty()) { for
* (PropertiesSourceFactory factory: factories) {
* config.addConfiguration(factory.getConfiguration(), factory.getName()); } }
*/
config.addConfiguration(envConfig,
ConfigurableEnvironmentConfiguration.class.getSimpleName());
//below come from ConfigurationManager.createDefaultConfigInstance()
DynamicURLConfiguration defaultURLConfig = new DynamicURLConfiguration();
try {
config.addConfiguration(defaultURLConfig, URL_CONFIG_NAME);
} catch (Throwable e) {
logger.error("Cannot create config from " + defaultURLConfig, e);
}
// below come from ConfigurationManager.createDefaultConfigInstance()
DynamicURLConfiguration defaultURLConfig = new DynamicURLConfiguration();
try {
config.addConfiguration(defaultURLConfig, URL_CONFIG_NAME);
}
catch (Throwable e) {
logger.error("Cannot create config from " + defaultURLConfig, e);
}
//TODO: sys/env above urls?
if (!Boolean.getBoolean(DISABLE_DEFAULT_SYS_CONFIG)) {
SystemConfiguration sysConfig = new SystemConfiguration();
config.addConfiguration(sysConfig, SYS_CONFIG_NAME);
}
if (!Boolean.getBoolean(DISABLE_DEFAULT_ENV_CONFIG)) {
EnvironmentConfiguration environmentConfiguration = new EnvironmentConfiguration();
config.addConfiguration(environmentConfiguration, ENV_CONFIG_NAME);
}
// TODO: sys/env above urls?
if (!Boolean.getBoolean(DISABLE_DEFAULT_SYS_CONFIG)) {
SystemConfiguration sysConfig = new SystemConfiguration();
config.addConfiguration(sysConfig, SYS_CONFIG_NAME);
}
if (!Boolean.getBoolean(DISABLE_DEFAULT_ENV_CONFIG)) {
EnvironmentConfiguration environmentConfiguration = new EnvironmentConfiguration();
config.addConfiguration(environmentConfiguration, ENV_CONFIG_NAME);
}
ConcurrentCompositeConfiguration appOverrideConfig = new ConcurrentCompositeConfiguration();
config.addConfiguration(appOverrideConfig, APPLICATION_PROPERTIES);
config.setContainerConfigurationIndex(config.getIndexOfConfiguration(appOverrideConfig));
ConcurrentCompositeConfiguration appOverrideConfig = new ConcurrentCompositeConfiguration();
config.addConfiguration(appOverrideConfig, APPLICATION_PROPERTIES);
config.setContainerConfigurationIndex(config
.getIndexOfConfiguration(appOverrideConfig));
ConfigurationManager.install(config);
}
else {
// TODO: reinstall ConfigurationManager
logger.warn("Netflix ConfigurationManager has already been installed, unable to re-install");
}
}
private static void setStatic(Class<?> type, String name, Object value) {
// Hack a private static field
Field field = ReflectionUtils.findField(type, name);
ReflectionUtils.makeAccessible(field);
ReflectionUtils.setField(field, null, value);
}
ConfigurationManager.install(config);
} else {
//TODO: reinstall ConfigurationManager
logger.warn("Netflix ConfigurationManager has already been installed, unable to re-install");
}
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* 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 org.springframework.cloud.netflix.archaius;
import org.apache.commons.configuration.AbstractConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import com.netflix.config.ConfigurationManager;
/**
* @author Dave Syer
*
*/
public class ArchaiusDelegatingProxyUtils {
public static String APPLICATION_CONTEXT = ApplicationContext.class.getName();
public static <T> T getNamedInstance(Class<T> type, String name) {
ApplicationContext context = (ApplicationContext) ConfigurationManager.getConfigInstance().getProperty(APPLICATION_CONTEXT);
return context!=null && context.containsBean(name) ? context.getBean(name, type) : null;
}
public static <T> T getInstanceWithPrefix(Class<T> type, String prefix) {
String name = prefix + type.getSimpleName();
return getNamedInstance(type, name);
}
public static void addApplicationContext(ConfigurableApplicationContext context) {
AbstractConfiguration config = ConfigurationManager.getConfigInstance();
config .clearProperty(APPLICATION_CONTEXT);
config.setProperty(APPLICATION_CONTEXT, context);
}
}

View File

@@ -54,6 +54,7 @@ public class FeignConfiguration {
protected <T> T loadBalance(Feign.Builder builder, Class<T> type, String schemeName) {
String name = URI.create(schemeName).getHost();
// TODO: This should be transparent
ribbonClientPreprocessor.preprocess(name);
if(ribbonClient != null) {

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* 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 org.springframework.cloud.netflix.ribbon;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* @author Dave Syer
*
*/
@Configuration
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@Documented
@Import(RibbonClientConfigurationRegistrar.class)
public @interface EnableRibbonClient {
RibbonClient[] value() default {};
}

View File

@@ -1,7 +1,6 @@
package org.springframework.cloud.netflix.ribbon;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
@@ -16,7 +15,6 @@ import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate;
import com.netflix.client.IClient;
import com.netflix.loadbalancer.BaseLoadBalancer;
/**
* Auto configuration for Ribbon (client side load balancing)
@@ -25,15 +23,18 @@ import com.netflix.loadbalancer.BaseLoadBalancer;
*/
@Configuration
@ConditionalOnClass(IClient.class)
@EnableRibbonClient
@AutoConfigureAfter(EurekaClientAutoConfiguration.class)
public class RibbonAutoConfiguration {
@Autowired(required = false)
private List<BaseLoadBalancer> balancers = Collections.emptyList();
@Autowired(required=false)
private List<RibbonClientSpecification> configurations = new ArrayList<>();
@Bean
@Bean
public SpringClientFactory springClientFactory() {
return new SpringClientFactory();
SpringClientFactory factory = new SpringClientFactory();
factory.setConfigurations(configurations);
return factory;
}
@Bean
@@ -48,8 +49,8 @@ public class RibbonAutoConfiguration {
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient(RibbonClientPreprocessor clientPreprocessor) {
return new RibbonLoadBalancerClient(clientPreprocessor, springClientFactory(), balancers);
public LoadBalancerClient loadBalancerClient() {
return new RibbonLoadBalancerClient(springClientFactory());
}
@Bean

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* 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 org.springframework.cloud.netflix.ribbon;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Dave Syer
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RibbonClient {
String name();
Class<?>[] configuration() default {};
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* 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 org.springframework.cloud.netflix.ribbon;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.netflix.client.config.DefaultClientConfigImpl;
import com.netflix.client.config.IClientConfig;
import com.netflix.discovery.EurekaClientConfig;
import com.netflix.loadbalancer.DynamicServerListLoadBalancer;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
import com.netflix.loadbalancer.ServerListFilter;
import com.netflix.loadbalancer.ZoneAffinityServerListFilter;
import com.netflix.loadbalancer.ZoneAwareLoadBalancer;
import com.netflix.niws.client.http.RestClient;
import com.netflix.servo.monitor.Monitors;
/**
* @author Dave Syer
*
*/
@SuppressWarnings("deprecation")
@Configuration
@EnableConfigurationProperties
public class RibbonClientConfiguration {
@Value("${ribbon.client.name}")
private String name = "client";
@Autowired(required = false)
private EurekaClientConfig eurekaClientConfig;
@Autowired(required = false)
private RibbonClientPreprocessor preprocessor;
// TODO: maybe re-instate autowired load balancers: identified by name they could be
// associated with ribbon clients
@PostConstruct
public void init() {
if (preprocessor!=null) {
preprocessor.preprocess(name);
}
}
@Bean
@ConditionalOnMissingBean
public IClientConfig ribbonClientConfig() {
DefaultClientConfigImpl config = new DefaultClientConfigImpl();
config.loadProperties(name);
return config;
}
@Bean
@ConditionalOnMissingBean
public RestClient ribbonRestClient(IClientConfig config, ILoadBalancer loadBalancer) {
RestClient client = new RestClient(config);
client.setLoadBalancer(loadBalancer);
Monitors.registerObject("Client_" + name, client);
return client;
}
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerListFilter<Server> filter) {
ZoneAwareLoadBalancer<Server> balancer = new ZoneAwareLoadBalancer<Server>(config);
wrapServerList(balancer);
balancer.setFilter(filter);
return balancer;
}
@Bean
@ConditionalOnMissingBean
public ServerListFilter<Server> ribbonServerListFilter(IClientConfig config) {
return new ZoneAffinityServerListFilter<Server>(config);
}
@Bean
@ConditionalOnMissingBean
public RibbonLoadBalancerContext ribbonLoadBalancerContext(ILoadBalancer loadBalancer, IClientConfig config) {
return new RibbonLoadBalancerContext(loadBalancer, config);
}
private void wrapServerList(ILoadBalancer balancer) {
if (balancer instanceof DynamicServerListLoadBalancer) {
@SuppressWarnings("unchecked")
DynamicServerListLoadBalancer<Server> dynamic = (DynamicServerListLoadBalancer<Server>) balancer;
ServerList<Server> list = dynamic.getServerListImpl();
if (!(list instanceof DomainExtractingServerList)) {
// This is optional: you can use the native Eureka AWS features as long as
// the server zone is populated. TODO: verify that we back off if AWS
// metadata *is* available.
// @see com.netflix.appinfo.AmazonInfo.Builder
dynamic.setServerListImpl(new DomainExtractingServerList(list, dynamic
.getClientConfig()));
}
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* 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 org.springframework.cloud.netflix.ribbon;
import java.util.Map;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
/**
* @author Dave Syer
*
*/
public class RibbonClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
Map<String, Object> attrs = metadata.getAnnotationAttributes(
EnableRibbonClient.class.getName(), true);
if (attrs.containsKey("value")) {
AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");
for (AnnotationAttributes attr : clients) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(RibbonClientSpecification.class);
builder.addConstructorArgValue(attr.get("name"));
builder.addConstructorArgValue(attr.get("configuration"));
registry.registerBeanDefinition(attr.get("name")
+ "RibbonClientSpecification", builder.getBeanDefinition());
}
}
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* 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 org.springframework.cloud.netflix.ribbon;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* @author Dave Syer
*
*/
@Data
@AllArgsConstructor
public class RibbonClientSpecification {
private String name;
private Class<?>[] configuration;
}

View File

@@ -1,20 +1,17 @@
package org.springframework.cloud.netflix.ribbon;
import com.google.common.base.Throwables;
import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerStats;
import com.netflix.servo.monitor.Stopwatch;
import java.net.URI;
import java.util.concurrent.TimeUnit;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest;
import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import com.google.common.base.Throwables;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerStats;
import com.netflix.servo.monitor.Stopwatch;
/**
* @author Spencer Gibb
@@ -22,25 +19,16 @@ import java.util.concurrent.TimeUnit;
*/
public class RibbonLoadBalancerClient implements LoadBalancerClient {
private RibbonClientPreprocessor ribbonClientPreprocessor;
private SpringClientFactory clientFactory;
private Map<String, ILoadBalancer> balancers = new ConcurrentHashMap<>();
private Map<String, RibbonLoadBalancerContext> contexts = new ConcurrentHashMap<>();
public RibbonLoadBalancerClient(RibbonClientPreprocessor ribbonClientPreprocessor, SpringClientFactory clientFactory, List<BaseLoadBalancer> balancers) {
this.ribbonClientPreprocessor = ribbonClientPreprocessor;
public RibbonLoadBalancerClient(SpringClientFactory clientFactory) {
this.clientFactory = clientFactory;
for (BaseLoadBalancer balancer : balancers) {
this.balancers.put(balancer.getName(), balancer);
}
}
@Override
public URI reconstructURI(ServiceInstance instance, URI original) {
String serviceId = instance.getServiceId();
RibbonLoadBalancerContext context = getOrCreateLoadBalancerContext(serviceId, getLoadBalancer(serviceId));
RibbonLoadBalancerContext context = clientFactory.getLoadBalancerContext(serviceId);
Server server = new Server(instance.getHost(), instance.getPort());
return context.reconstructURIWithServer(server, original);
}
@@ -53,7 +41,7 @@ public class RibbonLoadBalancerClient implements LoadBalancerClient {
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) {
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
RibbonLoadBalancerContext context = getOrCreateLoadBalancerContext(serviceId, loadBalancer);
RibbonLoadBalancerContext context = clientFactory.getLoadBalancerContext(serviceId);
Server server = getServer(serviceId, loadBalancer);
RibbonServer ribbonServer = new RibbonServer(serviceId, server);
@@ -79,18 +67,10 @@ public class RibbonLoadBalancerClient implements LoadBalancerClient {
context.noteRequestCompletion(serverStats, entity, exception, duration, null/*errorHandler*/);
}
protected RibbonLoadBalancerContext getOrCreateLoadBalancerContext(String serviceId, ILoadBalancer loadBalancer) {
RibbonLoadBalancerContext context = contexts.get(serviceId);
if (context == null) {
context = new RibbonLoadBalancerContext(loadBalancer);
contexts.put(serviceId, context);
}
return context;
}
protected Server getServer(String serviceId) {
return getServer(serviceId, getLoadBalancer(serviceId));
}
protected Server getServer(String serviceId, ILoadBalancer loadBalancer) {
Server server = loadBalancer.chooseServer("default");
if (server == null) {
@@ -101,12 +81,7 @@ public class RibbonLoadBalancerClient implements LoadBalancerClient {
}
protected ILoadBalancer getLoadBalancer(String serviceId) {
ribbonClientPreprocessor.preprocess(serviceId);
ILoadBalancer loadBalancer = this.balancers.get(serviceId);
if (loadBalancer == null) {
loadBalancer = clientFactory.getNamedLoadBalancer(serviceId);
}
return loadBalancer;
return clientFactory.getLoadBalancer(serviceId);
}
protected static class RibbonServer implements ServiceInstance {

View File

@@ -1,238 +1,153 @@
package org.springframework.cloud.netflix.ribbon;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.env.MapPropertySource;
import com.netflix.client.AbstractLoadBalancerAwareClient;
import com.netflix.client.ClientException;
import com.netflix.client.IClient;
import com.netflix.client.IClientConfigAware;
import com.netflix.client.config.CommonClientConfigKey;
import com.netflix.client.config.DefaultClientConfigImpl;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.servo.monitor.Monitors;
/**
* A factory that creates client, load balancer and client configuration instances from properties. It also keeps mappings of client names to
* the created instances.
* A factory that creates client, load balancer and client configuration instances. It
* creates a Spring ApplicationContext per client name, and extracts the beans that it
* needs from there.
*
*/
@Slf4j
public class SpringClientFactory {
public class SpringClientFactory implements DisposableBean, ApplicationContextAware {
private Map<String, IClient<?,?>> simpleClientMap = new ConcurrentHashMap<>();
private Map<String, ILoadBalancer> namedLBMap = new ConcurrentHashMap<>();
private ConcurrentHashMap<String, IClientConfig> namedConfig = new ConcurrentHashMap<>();
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
private Map<String, RibbonClientSpecification> configurations = new ConcurrentHashMap<>();
private ApplicationContext parent;
/**
* Utility method to create client and load balancer (if enabled in client config) given the name and client config.
* Instances are created using reflection (see {@link #instantiateInstanceWithClientConfig(String, IClientConfig)}
*
* @param restClientName
* @param clientConfig
* @throws ClientException if any errors occurs in the process, or if the client with the same name already exists
*/
public synchronized IClient<?, ?> registerClientFromProperties(String restClientName, IClientConfig clientConfig) throws ClientException {
IClient<?, ?> client;
ILoadBalancer loadBalancer = null;
if (simpleClientMap.get(restClientName) != null) {
throw new ClientException(
ClientException.ErrorType.GENERAL,
"A Rest Client with this name is already registered. Please use a different name");
}
try {
String clientClassName = (String) clientConfig.get(CommonClientConfigKey.ClientClassName);
client = (IClient<?, ?>) instantiateInstanceWithClientConfig(clientClassName, clientConfig);
boolean initializeNFLoadBalancer = Boolean.parseBoolean(clientConfig.get(
CommonClientConfigKey.InitializeNFLoadBalancer, DefaultClientConfigImpl.DEFAULT_ENABLE_LOADBALANCER).toString());
if (initializeNFLoadBalancer) {
loadBalancer = getNamedLoadBalancer(restClientName, clientConfig.getClass());
}
if (client instanceof AbstractLoadBalancerAwareClient) {
((AbstractLoadBalancerAwareClient<?,?>) client).setLoadBalancer(loadBalancer);
}
} catch (Throwable e) {
String message = "Unable to InitializeAndAssociateNFLoadBalancer set for RestClient:"
+ restClientName;
log.warn(message);
throw new ClientException(ClientException.ErrorType.CONFIGURATION,
message, e);
}
simpleClientMap.put(restClientName, client);
@Override
public void setApplicationContext(ApplicationContext parent) throws BeansException {
this.parent = parent;
}
public void setConfigurations(List<RibbonClientSpecification> configurations) {
for (RibbonClientSpecification client : configurations) {
this.configurations.put(client.getName(), client);
}
}
Monitors.registerObject("Client_" + restClientName, client);
@Override
public void destroy() {
Collection<AnnotationConfigApplicationContext> values = contexts.values();
contexts.clear();
for (AnnotationConfigApplicationContext context : values) {
context.close();
}
}
log.info("Client Registered:" + client.toString());
return client;
}
/**
* Get the rest client associated with the name.
*
* @throws RuntimeException if any error occurs
*/
public <C extends IClient<?, ?>> C getClient(String name, Class<C> clientClass) {
return getInstance(name, clientClass);
}
/**
* Return the named client from map if already created. Otherwise creates the client using the configuration returned by {@link #getNamedConfig(String)}.
*
* @throws RuntimeException if an error occurs in creating the client.
*/
public synchronized IClient<?,?> getNamedClient(String name) {
return getNamedClient(name, DefaultClientConfigImpl.class);
}
/**
* Get the load balancer associated with the name.
*
* @throws RuntimeException if any error occurs
*/
public ILoadBalancer getLoadBalancer(String name) {
return getInstance(name, ILoadBalancer.class);
}
public synchronized <C extends IClient<?,?>> C namedClient(String name, Class<C> clientClass) {
return clientClass.cast(getNamedClient(name, DefaultClientConfigImpl.class));
}
/**
* Get the load balancer context associated with the name.
*
* @throws RuntimeException if any error occurs
*/
public RibbonLoadBalancerContext getLoadBalancerContext(String serviceId) {
return getInstance(serviceId, RibbonLoadBalancerContext.class);
}
/**
* Return the named client from map if already created. Otherwise creates the client using the configuration returned by {@link #createNamedClient(String, Class)}.
*
* @throws RuntimeException if an error occurs in creating the client.
*/
public synchronized IClient<?,?> getNamedClient(String name, Class<? extends IClientConfig> configClass) {
if (simpleClientMap.get(name) != null) {
return simpleClientMap.get(name);
}
try {
return createNamedClient(name, configClass);
} catch (ClientException e) {
throw new RuntimeException("Unable to create client", e);
}
}
private AnnotationConfigApplicationContext getContext(String name) {
if (!contexts.containsKey(name)) {
synchronized (contexts) {
if (!contexts.containsKey(name)) {
contexts.put(name, createContext(name));
}
}
}
return contexts.get(name);
}
/**
* Creates a named client using a IClientConfig instance created off the configClass class object passed in as the parameter.
*
* @throws ClientException if any error occurs, or if the client with the same name already exists
*/
public synchronized IClient<?,?> createNamedClient(String name, Class<? extends IClientConfig> configClass) throws ClientException {
IClientConfig config = getNamedConfig(name, configClass);
return registerClientFromProperties(name, config);
}
private AnnotationConfigApplicationContext createContext(String name) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
if (configurations.containsKey(name)) {
for (Class<?> configuration : configurations.get(name).getConfiguration()) {
context.register(configuration);
}
}
context.register(PropertyPlaceholderAutoConfiguration.class,
RibbonClientConfiguration.class);
context.getEnvironment()
.getPropertySources()
.addFirst(
new MapPropertySource("ribbon",
Collections.<String, Object> singletonMap(
"ribbon.client.name", name)));
if (parent != null) {
// Uses Environment from parent as well as beans
context.setParent(parent);
}
context.refresh();
return context;
}
/**
* Get the load balancer associated with the name, or create one with an instance {@link DefaultClientConfigImpl} if does not exist
*
* @throws RuntimeException if any error occurs
*/
public synchronized ILoadBalancer getNamedLoadBalancer(String name) {
return getNamedLoadBalancer(name, DefaultClientConfigImpl.class);
}
private <C> C instantiateWithConfig(AnnotationConfigApplicationContext context,
Class<C> clazz, IClientConfig config) {
C result = null;
if (IClientConfigAware.class.isAssignableFrom(clazz)) {
IClientConfigAware obj = (IClientConfigAware) BeanUtils.instantiate(clazz);
obj.initWithNiwsConfig(config);
@SuppressWarnings("unchecked")
C value = (C) obj;
result = value;
}
else {
try {
if (clazz.getConstructor(IClientConfig.class) != null) {
result = clazz.getConstructor(IClientConfig.class)
.newInstance(config);
}
else {
result = BeanUtils.instantiate(clazz);
}
}
catch (Throwable e) { // NOPMD
}
}
context.getAutowireCapableBeanFactory().autowireBean(result);
return result;
}
/**
* Get the load balancer associated with the name, or create one with an instance of configClass if does not exist
*
* @throws RuntimeException if any error occurs
* @see #registerNamedLoadBalancerFromProperties(String, Class)
*/
public synchronized ILoadBalancer getNamedLoadBalancer(String name, Class<? extends IClientConfig> configClass) {
ILoadBalancer lb = namedLBMap.get(name);
if (lb != null) {
return lb;
} else {
try {
lb = registerNamedLoadBalancerFromProperties(name, configClass);
} catch (ClientException e) {
throw new RuntimeException("Unable to create load balancer", e);
}
return lb;
}
}
private <C> C getInstance(String name, Class<C> type) {
AnnotationConfigApplicationContext context = getContext(name);
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, type).length > 0) {
return context.getBean(type);
}
IClientConfig config = getInstance(name, IClientConfig.class);
return instantiateWithConfig(context, type, config);
}
/**
* Create and register a load balancer with the name and given the class of configClass.
*
* @throws ClientException if load balancer with the same name already exists or any error occurs
* @see #instantiateInstanceWithClientConfig(String, IClientConfig)
*/
public ILoadBalancer registerNamedLoadBalancerFromclientConfig(String name, IClientConfig clientConfig) throws ClientException {
if (namedLBMap.get(name) != null) {
throw new ClientException("LoadBalancer for name " + name + " already exists");
}
ILoadBalancer lb = null;
try {
String loadBalancerClassName = (String) clientConfig.get(CommonClientConfigKey.NFLoadBalancerClassName);
lb = (ILoadBalancer) instantiateInstanceWithClientConfig(loadBalancerClassName, clientConfig);
namedLBMap.put(name, lb);
log.info("Client:" + name
+ " instantiated a LoadBalancer:" + lb.toString());
return lb;
} catch (Exception e) {
throw new ClientException("Unable to instantiate/associate LoadBalancer with Client:" + name, e);
}
}
/**
* Create and register a load balancer with the name and given the class of configClass.
*
* @throws ClientException if load balancer with the same name already exists or any error occurs
* @see #instantiateInstanceWithClientConfig(String, IClientConfig)
*/
public synchronized ILoadBalancer registerNamedLoadBalancerFromProperties(String name, Class<? extends IClientConfig> configClass) throws ClientException {
if (namedLBMap.get(name) != null) {
throw new ClientException("LoadBalancer for name " + name + " already exists");
}
IClientConfig clientConfig = getNamedConfig(name, configClass);
return registerNamedLoadBalancerFromclientConfig(name, clientConfig);
}
/**
* Creates instance related to client framework using reflection. It first checks if the object is an instance of
* {@link IClientConfigAware} and if so invoke {@link IClientConfigAware#initWithNiwsConfig(IClientConfig)}. If that does not
* apply, it tries to find if there is a constructor with {@link IClientConfig} as a parameter and if so invoke that constructor. If neither applies,
* it simply invokes the no-arg constructor and ignores the clientConfig parameter.
*
* @param className Class name of the object
* @param clientConfig IClientConfig object used for initialization.
*/
public Object instantiateInstanceWithClientConfig(String className, IClientConfig clientConfig)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
Class<?> clazz = Class.forName(className);
if (IClientConfigAware.class.isAssignableFrom(clazz)) {
IClientConfigAware obj = (IClientConfigAware) clazz.newInstance();
obj.initWithNiwsConfig(clientConfig);
return obj;
} else {
try {
if (clazz.getConstructor(IClientConfig.class) != null) {
return clazz.getConstructor(IClientConfig.class).newInstance(clientConfig);
}
} catch (Throwable e) { // NOPMD
}
}
log.warn("Class " + className + " neither implements IClientConfigAware nor provides a constructor with IClientConfig as the parameter. Only default constructor will be used.");
return clazz.newInstance();
}
/**
* Get the client configuration given the name or create one with {@link DefaultClientConfigImpl} if it does not exist.
*
* @see #getNamedConfig(String, Class)
*/
public IClientConfig getNamedConfig(String name) {
return getNamedConfig(name, DefaultClientConfigImpl.class);
}
/**
* Get the client configuration given the name or create one with clientConfigClass if it does not exist. An instance of IClientConfig
* is created and {@link IClientConfig#loadProperties(String)} will be called.
*/
public IClientConfig getNamedConfig(String name, Class<? extends IClientConfig> clientConfigClass) {
IClientConfig config = namedConfig.get(name);
if (config != null) {
return config;
} else {
try {
config = clientConfigClass.newInstance();
config.loadProperties(name);
} catch (Throwable e) {
log.error("Unable to create client config instance", e);
return null;
}
config.loadProperties(name);
IClientConfig old = namedConfig.putIfAbsent(name, config);
if (old != null) {
config = old;
}
return config;
}
}
}

View File

@@ -6,41 +6,34 @@ import static com.netflix.client.config.CommonClientConfigKey.NFLoadBalancerRule
import static com.netflix.client.config.CommonClientConfigKey.NIWSServerListClassName;
import static com.netflix.client.config.CommonClientConfigKey.NIWSServerListFilterClassName;
import com.netflix.config.DynamicPropertyFactory;
import com.netflix.config.DynamicStringProperty;
import org.springframework.cloud.netflix.ribbon.RibbonClientPreprocessor;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import com.netflix.config.ConfigurationManager;
import com.netflix.config.DeploymentContext.ContextKey;
import com.netflix.config.DynamicPropertyFactory;
import com.netflix.config.DynamicStringProperty;
import com.netflix.discovery.EurekaClientConfig;
import com.netflix.loadbalancer.DynamicServerListLoadBalancer;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
import com.netflix.loadbalancer.ZoneAvoidanceRule;
import com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList;
/**
* Preprocessor that configures defaults for eureka-discovered ribbon clients.
* Such as: <code>@zone</code>, NIWSServerListClassName, DeploymentContextBasedVipAddresses,
* NFLoadBalancerRuleClassName, NIWSServerListFilterClassName and more
* Preprocessor that configures defaults for eureka-discovered ribbon clients. Such as:
* <code>@zone</code>, NIWSServerListClassName, DeploymentContextBasedVipAddresses,
* NFLoadBalancerRuleClassName, NIWSServerListFilterClassName and more
*
* @author Spencer Gibb
* @author Dave Syer
*/
public class EurekaRibbonClientPreprocessor implements RibbonClientPreprocessor {
protected static final String VALUE_NOT_SET = "__not__set__";
protected static final String DEFAULT_NAMESPACE = "ribbon";
protected static final String VALUE_NOT_SET = "__not__set__";
protected static final String DEFAULT_NAMESPACE = "ribbon";
private EurekaClientConfig clientConfig;
private SpringClientFactory clientFactory;
private EurekaClientConfig clientConfig;
public EurekaRibbonClientPreprocessor(EurekaClientConfig clientConfig, SpringClientFactory clientFactory) {
public EurekaRibbonClientPreprocessor(EurekaClientConfig clientConfig) {
this.clientConfig = clientConfig;
this.clientFactory = clientFactory;
}
}
@Override
public void preprocess(String serviceId) {
@@ -67,40 +60,23 @@ public class EurekaRibbonClientPreprocessor implements RibbonClientPreprocessor
setProp(serviceId, NIWSServerListFilterClassName.key(),
ZonePreferenceServerListFilter.class.getName());
setProp(serviceId, EnableZoneAffinity.key(), "true");
ILoadBalancer loadBalancer = clientFactory.getNamedLoadBalancer(serviceId);
wrapServerList(loadBalancer);
}
private void wrapServerList(ILoadBalancer balancer) {
if (balancer instanceof DynamicServerListLoadBalancer) {
@SuppressWarnings("unchecked")
DynamicServerListLoadBalancer<Server> dynamic = (DynamicServerListLoadBalancer<Server>) balancer;
ServerList<Server> list = dynamic.getServerListImpl();
if (!(list instanceof DomainExtractingServerList)) {
// This is optional: you can use the native Eureka AWS features as long as
// the server zone is populated. TODO: find a way to back off if AWS
// metadata *is* available.
// @see com.netflix.appinfo.AmazonInfo.Builder
dynamic.setServerListImpl(new DomainExtractingServerList(list, dynamic.getClientConfig()));
}
}
}
protected void setProp(String serviceId, String suffix, String value) {
// how to set the namespace properly?
String key = getKey(serviceId, suffix);
DynamicStringProperty property = getProperty(key);
if (property.get().equals(VALUE_NOT_SET)) {
ConfigurationManager.getConfigInstance().setProperty(key, value);
}
String key = getKey(serviceId, suffix);
DynamicStringProperty property = getProperty(key);
if (property.get().equals(VALUE_NOT_SET)) {
ConfigurationManager.getConfigInstance().setProperty(key, value);
}
}
protected DynamicStringProperty getProperty(String key) {
return DynamicPropertyFactory.getInstance().getStringProperty(key, VALUE_NOT_SET);
}
protected DynamicStringProperty getProperty(String key) {
return DynamicPropertyFactory.getInstance().getStringProperty(key, VALUE_NOT_SET);
}
protected String getKey(String serviceId, String suffix) {
return serviceId + "." + DEFAULT_NAMESPACE + "." + suffix;
}
protected String getKey(String serviceId, String suffix) {
return serviceId + "." + DEFAULT_NAMESPACE + "." + suffix;
}
}

View File

@@ -21,9 +21,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
import org.springframework.cloud.netflix.ribbon.RibbonClientPreprocessor;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -46,7 +46,7 @@ public class RibbonEurekaAutoConfiguration {
private EurekaClientConfig clientConfig;
@Bean
public RibbonClientPreprocessor ribbonClientPreprocessor(SpringClientFactory clientFactory) {
return new EurekaRibbonClientPreprocessor(clientConfig, clientFactory);
public RibbonClientPreprocessor ribbonClientPreprocessor() {
return new EurekaRibbonClientPreprocessor(clientConfig);
}
}

View File

@@ -18,6 +18,9 @@ package org.springframework.cloud.netflix.ribbon.eureka;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;
import com.netflix.client.config.IClientConfig;
import com.netflix.config.ConfigurationManager;
import com.netflix.config.DeploymentContext.ContextKey;
@@ -31,6 +34,8 @@ import com.netflix.loadbalancer.ZoneAffinityServerListFilter;
* @author Dave Syer
*
*/
@Data
@EqualsAndHashCode(callSuper=false)
public class ZonePreferenceServerListFilter extends ZoneAffinityServerListFilter<Server> {
private String zone;

View File

@@ -81,9 +81,7 @@ public class RibbonRoutingFilter extends ZuulFilter {
String serviceId = (String) context.get("serviceId");
preprocessor.preprocess(serviceId);
RestClient restClient = clientFactory.namedClient(serviceId, RestClient.class);
RestClient restClient = clientFactory.getClient(serviceId, RestClient.class);
String uri = request.getRequestURI();
if (context.get("requestURI") != null) {

View File

@@ -15,6 +15,9 @@
*/
package org.springframework.cloud.netflix.eureka;
import static org.junit.Assert.assertEquals;
import static org.springframework.boot.test.EnvironmentTestUtils.addEnvironment;
import org.junit.After;
import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException;
@@ -22,13 +25,9 @@ import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfigurati
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.AbstractApplicationContext;
import com.netflix.appinfo.UniqueIdentifier;
import com.netflix.appinfo.InstanceInfo.InstanceStatus;
import static org.junit.Assert.*;
import static org.springframework.boot.test.EnvironmentTestUtils.*;
import com.netflix.appinfo.UniqueIdentifier;
/**
* @author Dave Syer

View File

@@ -4,6 +4,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
@@ -11,6 +12,7 @@ import org.springframework.test.context.web.WebAppConfiguration;
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
@IntegrationTest("server.port=0")
@DirtiesContext
public class ApplicationTests {
@Test

View File

@@ -1,10 +1,10 @@
package org.springframework.cloud.netflix.feign;
import static org.junit.Assert.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
@@ -13,6 +13,7 @@ import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -29,6 +30,7 @@ import java.util.List;
@SpringApplicationConfiguration(classes = SpringDecoderTests.Application.class)
@WebAppConfiguration
@IntegrationTest({ "server.port=0", "spring.application.name=springdecodertest", "spring.jmx.enabled=true" })
@DirtiesContext
public class SpringDecoderTests extends FeignConfiguration {
@Value("${local.server.port}")

View File

@@ -26,7 +26,7 @@ import static org.mockito.Mockito.*;
/**
* @author Spencer Gibb
*/
public class RibbonInterceptorTest {
public class RibbonInterceptorTests {
@Mock
HttpRequest request;

View File

@@ -1,28 +1,34 @@
package org.springframework.cloud.netflix.ribbon;
import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.LoadBalancerStats;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerStats;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyDouble;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.net.URI;
import java.net.URL;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient.RibbonServer;
import java.net.URI;
import java.net.URL;
import java.util.Arrays;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.LoadBalancerStats;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerStats;
/**
* @author Spencer Gibb
*/
public class RibbonLoadBalancerClientTest {
public class RibbonLoadBalancerClientTests {
@Mock
RibbonClientPreprocessor preprocessor;
@@ -42,6 +48,7 @@ public class RibbonLoadBalancerClientTest {
@Before
public void init() {
MockitoAnnotations.initMocks(this);
Mockito.when(clientFactory.getLoadBalancerContext(anyString())).thenReturn(new RibbonLoadBalancerContext(loadBalancer));
}
@Test
@@ -122,11 +129,12 @@ public class RibbonLoadBalancerClientTest {
}
protected RibbonLoadBalancerClient getRibbonLoadBalancerClient(RibbonServer ribbonServer) {
when(loadBalancer.getName()).thenReturn(ribbonServer.getServiceId());
when(loadBalancer.getName()).thenReturn(ribbonServer.getServiceId());
when(loadBalancer.chooseServer(anyString())).thenReturn(ribbonServer.server);
when(loadBalancer.getLoadBalancerStats()).thenReturn(loadBalancerStats);
when(loadBalancerStats.getSingleServerStat(ribbonServer.server)).thenReturn(serverStats);
when(clientFactory.getLoadBalancer(loadBalancer.getName())).thenReturn(loadBalancer);
return new RibbonLoadBalancerClient(preprocessor, clientFactory, Arrays.asList(loadBalancer));
return new RibbonLoadBalancerClient(clientFactory);
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* 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 org.springframework.cloud.netflix.ribbon;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.netflix.client.DefaultLoadBalancerRetryHandler;
/**
* @author Dave Syer
*
*/
public class SpringClientFactoryTests {
private SpringClientFactory factory = new SpringClientFactory();
@Test
public void testConfigureRetry() {
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext(ArchaiusAutoConfiguration.class);
EnvironmentTestUtils.addEnvironment(parent, "foo.ribbon.MaxAutoRetries:2");
factory.setApplicationContext(parent);
DefaultLoadBalancerRetryHandler retryHandler = (DefaultLoadBalancerRetryHandler) factory
.getLoadBalancerContext("foo").getRetryHandler();
assertEquals(2, retryHandler.getMaxRetriesOnSameServer());
parent.close();
factory.destroy();
}
}

View File

@@ -1,5 +1,16 @@
package org.springframework.cloud.netflix.ribbon.eureka;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import com.google.common.collect.ImmutableMap;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.client.config.CommonClientConfigKey;
@@ -7,14 +18,6 @@ import com.netflix.client.config.DefaultClientConfigImpl;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;
/**
* @author Spencer Gibb
@@ -64,7 +67,8 @@ public class DomainExtractingServerListTests {
protected DomainExtractingServerList getDomainExtractingServerList(DefaultClientConfigImpl config) {
DiscoveryEnabledServer server = mock(DiscoveryEnabledServer.class);
ServerList originalServerList = mock(ServerList.class);
@SuppressWarnings("unchecked")
ServerList<Server> originalServerList = mock(ServerList.class);
InstanceInfo instanceInfo = mock(InstanceInfo.class);
when(server.getInstanceInfo()).thenReturn(instanceInfo);
@@ -75,7 +79,7 @@ public class DomainExtractingServerListTests {
when(instanceInfo.getIPAddr()).thenReturn(IP_ADDR);
when(instanceInfo.getPort()).thenReturn(PORT);
when(originalServerList.getInitialListOfServers()).thenReturn(Arrays.asList(server));
when(originalServerList.getInitialListOfServers()).thenReturn(Arrays.<Server>asList(server));
return new DomainExtractingServerList(originalServerList, config);
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* 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 org.springframework.cloud.netflix.ribbon.eureka;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration;
import org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration;
import org.springframework.cloud.netflix.ribbon.EnableRibbonClient;
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.cloud.netflix.ribbon.eureka.EurekaRibbonClientPreprocessorIntegrationTests.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ZoneAvoidanceRule;
import com.netflix.loadbalancer.ZoneAwareLoadBalancer;
/**
* @author Dave Syer
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TestConfiguration.class)
@DirtiesContext
public class EurekaRibbonClientPreprocessorIntegrationTests {
@Autowired
private SpringClientFactory factory;
@Test
public void serverListIsWrapped() throws Exception {
@SuppressWarnings("unchecked")
ZoneAwareLoadBalancer<Server> loadBalancer = (ZoneAwareLoadBalancer<Server>) factory
.getLoadBalancer("foo");
DomainExtractingServerList.class.cast(loadBalancer.getServerListImpl());
}
@Test
public void ruleDefaultsToZoneAvoidance() throws Exception {
@SuppressWarnings("unchecked")
ZoneAwareLoadBalancer<Server> loadBalancer = (ZoneAwareLoadBalancer<Server>) factory
.getLoadBalancer("foo");
ZoneAvoidanceRule.class.cast(loadBalancer.getRule());
}
@Test
public void serverListFilterOverride() throws Exception {
@SuppressWarnings("unchecked")
ZoneAwareLoadBalancer<Server> loadBalancer = (ZoneAwareLoadBalancer<Server>) factory
.getLoadBalancer("foo");
assertEquals("myTestZone",
ZonePreferenceServerListFilter.class.cast(loadBalancer.getFilter()).getZone());
}
@Configuration
@EnableRibbonClient(@RibbonClient(name = "foo", configuration = FooConfiguration.class))
@Import({ PropertyPlaceholderAutoConfiguration.class,
ArchaiusAutoConfiguration.class, EurekaClientAutoConfiguration.class,
RibbonAutoConfiguration.class, RibbonEurekaAutoConfiguration.class })
protected static class TestConfiguration {
}
@Configuration
protected static class FooConfiguration {
@Bean
public ZonePreferenceServerListFilter serverListFilter() {
ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter();
filter.setZone("myTestZone");
return filter;
}
}
}

View File

@@ -18,16 +18,16 @@ package org.springframework.cloud.netflix.ribbon.eureka;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.cloud.netflix.ribbon.eureka.EurekaRibbonClientPreprocessor.*;
import static org.springframework.cloud.netflix.ribbon.eureka.EurekaRibbonClientPreprocessor.VALUE_NOT_SET;
import com.netflix.config.DynamicStringProperty;
import org.junit.After;
import org.junit.Test;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.cloud.netflix.eureka.EurekaClientConfigBean;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import com.netflix.config.ConfigurationManager;
import com.netflix.config.DeploymentContext.ContextKey;
import com.netflix.config.DynamicStringProperty;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ZoneAwareLoadBalancer;
@@ -37,7 +37,7 @@ import com.netflix.loadbalancer.ZoneAwareLoadBalancer;
*
*/
public class EurekaRibbonClientPreprocessorTests {
@After
public void close() {
ConfigurationManager.getDeploymentContext().setValue(ContextKey.zone, "");
@@ -47,40 +47,41 @@ public class EurekaRibbonClientPreprocessorTests {
public void basicConfigurationCreatedForLoadBalancer() {
EurekaClientConfigBean client = new EurekaClientConfigBean();
client.getAvailabilityZones().put(client.getRegion(), "foo");
SpringClientFactory clientFactory = new SpringClientFactory();
EurekaRibbonClientPreprocessor clientPreprocessor = new EurekaRibbonClientPreprocessor(
client, clientFactory);
SpringClientFactory clientFactory = new SpringClientFactory();
EurekaRibbonClientPreprocessor clientPreprocessor = new EurekaRibbonClientPreprocessor(
client);
clientPreprocessor.preprocess("service");
ILoadBalancer balancer = clientFactory.getNamedLoadBalancer("service");
ILoadBalancer balancer = clientFactory.getLoadBalancer("service");
assertNotNull(balancer);
@SuppressWarnings("unchecked")
ZoneAwareLoadBalancer<Server> aware = (ZoneAwareLoadBalancer<Server>) balancer;
assertTrue(aware.getServerListImpl() instanceof DomainExtractingServerList);
assertEquals("foo", ConfigurationManager.getDeploymentContext().getValue(ContextKey.zone));
assertEquals("foo",
ConfigurationManager.getDeploymentContext().getValue(ContextKey.zone));
}
@Test
public void testSetProp() {
EurekaClientConfigBean client = new EurekaClientConfigBean();
SpringClientFactory clientFactory = new SpringClientFactory();
EurekaRibbonClientPreprocessor preprocessor = new EurekaRibbonClientPreprocessor(
client, clientFactory);
@Test
public void testSetProp() {
EurekaClientConfigBean client = new EurekaClientConfigBean();
EurekaRibbonClientPreprocessor preprocessor = new EurekaRibbonClientPreprocessor(
client);
String serviceId = "myService";
String suffix = "mySuffix";
String value = "myValue";
String serviceId = "myService";
String suffix = "mySuffix";
String value = "myValue";
DynamicStringProperty property = preprocessor.getProperty(preprocessor.getKey(serviceId, suffix));
DynamicStringProperty property = preprocessor.getProperty(preprocessor.getKey(
serviceId, suffix));
assertEquals("property doesn't have default value", VALUE_NOT_SET, property.get());
assertEquals("property doesn't have default value", VALUE_NOT_SET, property.get());
preprocessor.setProp(serviceId, suffix, value);
preprocessor.setProp(serviceId, suffix, value);
assertEquals("property has wrong value", value, property.get());
assertEquals("property has wrong value", value, property.get());
preprocessor.setProp(serviceId, suffix, value);
preprocessor.setProp(serviceId, suffix, value);
assertEquals("property has wrong value", value, property.get());
}
assertEquals("property has wrong value", value, property.get());
}
}

View File

@@ -13,6 +13,7 @@ import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
@@ -22,6 +23,7 @@ import org.springframework.test.context.web.WebAppConfiguration;
@IntegrationTest({ "server.port: 0",
"zuul.routes.other: /test/**=http://localhost:7777/local",
"zuul.routes.simple: /simple/**" })
@DirtiesContext
public class SampleZuulProxyApplicationTests {
@Value("${local.server.port}")