Load ribbon classes if defined via properties.

Allows ribbon class name properties to be used to define beans. This seems like boilerplate, but works, though not easily extensible. fixes gh-927
parent dfbfcfec
package org.springframework.cloud.netflix.ribbon;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.IPing;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.ServerList;
import com.netflix.loadbalancer.ServerListFilter;
import static org.springframework.cloud.netflix.ribbon.SpringClientFactory.NAMESPACE;
import static org.springframework.cloud.netflix.ribbon.SpringClientFactory.instantiateWithConfig;
/**
* @author Spencer Gibb
*/
public class PropertiesFactory {
@Autowired
private Environment environment;
private Map<Class, String> classToProperty = new HashMap<>();
public PropertiesFactory() {
classToProperty.put(ILoadBalancer.class, "NFLoadBalancerClassName");
classToProperty.put(IPing.class, "NFLoadBalancerPingClassName");
classToProperty.put(IRule.class, "NFLoadBalancerRuleClassName");
classToProperty.put(ServerList.class, "NIWSServerListClassName");
classToProperty.put(ServerListFilter.class, "NIWSServerListFilterClassName");
}
public boolean isSet(Class clazz, String name) {
return StringUtils.hasText(getClassName(clazz, name));
}
public String getClassName(Class clazz, String name) {
if (this.classToProperty.containsKey(clazz)) {
String classNameProperty = this.classToProperty.get(clazz);
String className = environment.getProperty(name + "." + NAMESPACE + "." + classNameProperty);
return className;
}
return null;
}
@SuppressWarnings("unchecked")
public <C> C get(Class<C> clazz, IClientConfig config, String name) {
String className = getClassName(clazz, name);
if (StringUtils.hasText(className)) {
try {
Class<?> toInstantiate = Class.forName(className);
return (C) instantiateWithConfig(toInstantiate, config);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Unknown class to load "+className+" for class " + clazz + " named " + name);
}
}
return null;
}
}
......@@ -71,6 +71,12 @@ public class RibbonAutoConfiguration {
return new RibbonLoadBalancerClient(springClientFactory());
}
@Bean
@ConditionalOnMissingBean
public PropertiesFactory propertiesFactory() {
return new PropertiesFactory();
}
@Configuration
@ConditionalOnClass(HttpRequest.class)
@ConditionalOnProperty(value = "ribbon.http.client.enabled", matchIfMissing = false)
......
......@@ -22,6 +22,7 @@ import javax.annotation.PostConstruct;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.client.params.CookiePolicy;
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;
......@@ -67,6 +68,9 @@ public class RibbonClientConfiguration {
// TODO: maybe re-instate autowired load balancers: identified by name they could be
// associated with ribbon clients
@Autowired
private PropertiesFactory propertiesFactory;
@Bean
@ConditionalOnMissingBean
public IClientConfig ribbonClientConfig() {
......@@ -78,6 +82,9 @@ public class RibbonClientConfiguration {
@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {
if (this.propertiesFactory.isSet(IRule.class, name)) {
return this.propertiesFactory.get(IRule.class, config, name);
}
ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
rule.initWithNiwsConfig(config);
return rule;
......@@ -86,13 +93,19 @@ public class RibbonClientConfiguration {
@Bean
@ConditionalOnMissingBean
public IPing ribbonPing(IClientConfig config) {
// TODO: use PingUrl
if (this.propertiesFactory.isSet(IPing.class, name)) {
return this.propertiesFactory.get(IPing.class, config, name);
}
return new NoOpPing();
}
@Bean
@ConditionalOnMissingBean
@SuppressWarnings("unchecked")
public ServerList<Server> ribbonServerList(IClientConfig config) {
if (this.propertiesFactory.isSet(ServerList.class, name)) {
return this.propertiesFactory.get(ServerList.class, config, name);
}
ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
serverList.initWithNiwsConfig(config);
return serverList;
......@@ -126,6 +139,9 @@ public class RibbonClientConfiguration {
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
IRule rule, IPing ping) {
if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
return this.propertiesFactory.get(ILoadBalancer.class, config, name);
}
ZoneAwareLoadBalancer<Server> balancer = LoadBalancerBuilder.newBuilder()
.withClientConfig(config).withRule(rule).withPing(ping)
.withServerListFilter(serverListFilter).withDynamicServerList(serverList)
......@@ -135,7 +151,11 @@ public class RibbonClientConfiguration {
@Bean
@ConditionalOnMissingBean
@SuppressWarnings("unchecked")
public ServerListFilter<Server> ribbonServerListFilter(IClientConfig config) {
if (this.propertiesFactory.isSet(ServerListFilter.class, name)) {
return this.propertiesFactory.get(ServerListFilter.class, config, name);
}
ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter();
filter.initWithNiwsConfig(config);
return filter;
......
......@@ -35,8 +35,10 @@ import com.netflix.loadbalancer.ILoadBalancer;
*/
public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {
static final String NAMESPACE = "ribbon";
public SpringClientFactory() {
super(RibbonClientConfiguration.class, "ribbon", "ribbon.client.name");
super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
}
/**
......@@ -71,7 +73,11 @@ public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecifi
return getInstance(serviceId, RibbonLoadBalancerContext.class);
}
private <C> C instantiateWithConfig(AnnotationConfigApplicationContext context,
static <C> C instantiateWithConfig(Class<C> clazz, IClientConfig config) {
return instantiateWithConfig(null, clazz, config);
}
static <C> C instantiateWithConfig(AnnotationConfigApplicationContext context,
Class<C> clazz, IClientConfig config) {
C result = null;
if (IClientConfigAware.class.isAssignableFrom(clazz)) {
......@@ -95,7 +101,9 @@ public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecifi
// NOPMD
}
}
context.getAutowireCapableBeanFactory().autowireBean(result);
if (context != null) {
context.getAutowireCapableBeanFactory().autowireBean(result);
}
return result;
}
......
/*
* Copyright 2013-2016 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 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.commons.util.UtilAutoConfiguration;
import org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration;
import org.springframework.cloud.netflix.ribbon.test.TestLoadBalancer;
import org.springframework.cloud.netflix.ribbon.test.TestServerList;
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.ConfigurationBasedServerList;
import com.netflix.loadbalancer.DummyPing;
import com.netflix.loadbalancer.NoOpPing;
import com.netflix.loadbalancer.RandomRule;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerListSubsetFilter;
import com.netflix.loadbalancer.ZoneAvoidanceRule;
import com.netflix.loadbalancer.ZoneAwareLoadBalancer;
/**
* @author Spencer Gibb
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(RibbonClientPreprocessorPropertiesOverridesIntegrationTests.TestConfiguration.class)
@DirtiesContext
public class RibbonClientPreprocessorPropertiesOverridesIntegrationTests {
@Autowired
private SpringClientFactory factory;
@Test
public void ruleOverridesToRandom() throws Exception {
RandomRule.class.cast(getLoadBalancer("foo2").getRule());
ZoneAvoidanceRule.class.cast(getLoadBalancer("bar").getRule());
}
@Test
public void pingOverridesToDummy() throws Exception {
DummyPing.class.cast(getLoadBalancer("foo2").getPing());
NoOpPing.class.cast(getLoadBalancer("bar").getPing());
}
@Test
public void serverListOverridesToTest() throws Exception {
TestServerList.class.cast(getLoadBalancer("foo2").getServerListImpl());
ConfigurationBasedServerList.class.cast(getLoadBalancer("bar").getServerListImpl());
}
@Test
public void loadBalancerOverridesToTest() throws Exception {
TestLoadBalancer.class.cast(getLoadBalancer("foo2"));
ZoneAwareLoadBalancer.class.cast(getLoadBalancer("bar"));
}
@Test
public void serverListFilterOverride() throws Exception {
ServerListSubsetFilter.class.cast(getLoadBalancer("foo2").getFilter());
ZonePreferenceServerListFilter.class.cast(getLoadBalancer("bar").getFilter());
}
@SuppressWarnings("unchecked")
private ZoneAwareLoadBalancer<Server> getLoadBalancer(String name) {
return (ZoneAwareLoadBalancer<Server>) this.factory.getLoadBalancer(name);
}
@Configuration
@RibbonClients
@Import({ UtilAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class,
ArchaiusAutoConfiguration.class, RibbonAutoConfiguration.class})
protected static class TestConfiguration {
}
}
......@@ -35,28 +35,32 @@ import static org.junit.Assert.assertEquals;
*/
public class SpringClientFactoryTests {
private SpringClientFactory factory = new SpringClientFactory();
@Test
public void testConfigureRetry() {
SpringClientFactory factory = new SpringClientFactory();
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext(
ArchaiusAutoConfiguration.class);
RibbonAutoConfiguration.class, ArchaiusAutoConfiguration.class);
EnvironmentTestUtils.addEnvironment(parent, "foo.ribbon.MaxAutoRetries:2");
this.factory.setApplicationContext(parent);
DefaultLoadBalancerRetryHandler retryHandler = (DefaultLoadBalancerRetryHandler) this.factory
factory.setApplicationContext(parent);
DefaultLoadBalancerRetryHandler retryHandler = (DefaultLoadBalancerRetryHandler) factory
.getLoadBalancerContext("foo").getRetryHandler();
assertEquals(2, retryHandler.getMaxRetriesOnSameServer());
parent.close();
this.factory.destroy();
factory.destroy();
}
@SuppressWarnings("deprecation")
@Test
public void testCookiePolicy() {
RestClient client = this.factory.getClient("foo", RestClient.class);
SpringClientFactory factory = new SpringClientFactory();
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext(
RibbonAutoConfiguration.class, ArchaiusAutoConfiguration.class);
factory.setApplicationContext(parent);
RestClient client = factory.getClient("foo", RestClient.class);
ApacheHttpClient4 jerseyClient = (ApacheHttpClient4) client.getJerseyClient();
assertEquals(CookiePolicy.IGNORE_COOKIES, jerseyClient.getClientHandler()
.getHttpClient().getParams().getParameter(ClientPNames.COOKIE_POLICY));
this.factory.destroy();
factory.destroy();
}
}
......@@ -22,6 +22,7 @@ import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpUriRequest;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
......@@ -121,7 +122,7 @@ public class RibbonLoadBalancingHttpClientTests {
SpringClientFactory factory = new SpringClientFactory();
factory.setApplicationContext(new AnnotationConfigApplicationContext(
defaultConfigurationClass));
RibbonAutoConfiguration.class, defaultConfigurationClass));
HttpClient delegate = mock(HttpClient.class);
RibbonLoadBalancingHttpClient client = factory.getClient("service",
RibbonLoadBalancingHttpClient.class);
......
......@@ -20,6 +20,7 @@ import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
......@@ -87,7 +88,7 @@ public class OkHttpLoadBalancingClientTests {
IClientConfig configOverride) throws Exception {
SpringClientFactory factory = new SpringClientFactory();
factory.setApplicationContext(new AnnotationConfigApplicationContext(
defaultConfigurationClass));
RibbonAutoConfiguration.class, defaultConfigurationClass));
OkHttpLoadBalancingClient client = factory.getClient("service",
OkHttpLoadBalancingClient.class);
......
/*
* Copyright 2013-2016 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.test;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ZoneAwareLoadBalancer;
/**
* @author Spencer Gibb
*/
public class TestLoadBalancer<T extends Server> extends ZoneAwareLoadBalancer<T> {
}
/*
* Copyright 2013-2016 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.test;
import java.util.ArrayList;
import java.util.List;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
/**
* @author Spencer Gibb
*/
public class TestServerList<T extends Server> implements ServerList<T> {
private final List<T> servers;
public TestServerList() {
this.servers = new ArrayList<>();
}
public void add(T server) {
this.servers.add(server);
}
@Override
public List<T> getInitialListOfServers() {
return servers;
}
@Override
public List<T> getUpdatedListOfServers() {
return servers;
}
}
......@@ -26,6 +26,14 @@ foo:
ribbon:
ConnectTimeout: 7
ReadTimeout: 17
# for RibbonClientPreprocessorPropertiesOverridesIntegrationTests
foo2:
ribbon:
NFLoadBalancerPingClassName: com.netflix.loadbalancer.DummyPing
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
NIWSServerListClassName: org.springframework.cloud.netflix.ribbon.test.TestServerList
NIWSServerListFilterClassName: com.netflix.loadbalancer.ServerListSubsetFilter
NFLoadBalancerClassName: org.springframework.cloud.netflix.ribbon.test.TestLoadBalancer
badClients:
ribbon:
MaxAutoRetriesNextServer: 10
......
......@@ -23,6 +23,7 @@ import com.netflix.discovery.EurekaClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.netflix.ribbon.PropertiesFactory;
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
......@@ -69,6 +70,9 @@ public class EurekaRibbonClientConfiguration {
@Autowired(required = false)
private EurekaInstanceConfig eurekaConfig;
@Autowired
private PropertiesFactory propertiesFactory;
public EurekaRibbonClientConfiguration() {
}
......@@ -84,6 +88,9 @@ public class EurekaRibbonClientConfiguration {
@Bean
@ConditionalOnMissingBean
public IPing ribbonPing(IClientConfig config) {
if (this.propertiesFactory.isSet(IPing.class, serviceId)) {
return this.propertiesFactory.get(IPing.class, config, serviceId);
}
NIWSDiscoveryPing ping = new NIWSDiscoveryPing();
ping.initWithNiwsConfig(config);
return ping;
......@@ -92,6 +99,9 @@ public class EurekaRibbonClientConfiguration {
@Bean
@ConditionalOnMissingBean
public ServerList<?> ribbonServerList(IClientConfig config, Provider<EurekaClient> eurekaClientProvider) {
if (this.propertiesFactory.isSet(ServerList.class, serviceId)) {
return this.propertiesFactory.get(ServerList.class, config, serviceId);
}
DiscoveryEnabledNIWSServerList discoveryServerList = new DiscoveryEnabledNIWSServerList(
config, eurekaClientProvider);
DomainExtractingServerList serverList = new DomainExtractingServerList(
......
/*
* Copyright 2013-2016 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.mockito.Mockito.mock;
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.commons.util.UtilAutoConfiguration;
import org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration;
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
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.discovery.EurekaClient;
import com.netflix.loadbalancer.ConfigurationBasedServerList;
import com.netflix.loadbalancer.DummyPing;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ZoneAwareLoadBalancer;
import com.netflix.niws.loadbalancer.NIWSDiscoveryPing;
/**
* @author Spencer Gibb
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(EurekaRibbonClientPropertyOverrideIntegrationTests.TestConfiguration.class)
@DirtiesContext
public class EurekaRibbonClientPropertyOverrideIntegrationTests {
@Autowired
private SpringClientFactory factory;
@Test
public void pingOverridesToDummy() throws Exception {
DummyPing.class.cast(getLoadBalancer("foo3").getPing());
NIWSDiscoveryPing.class.cast(getLoadBalancer("bar").getPing());
}
@Test
public void serverListOverridesToTest() throws Exception {
ConfigurationBasedServerList.class.cast(getLoadBalancer("foo3").getServerListImpl());
DomainExtractingServerList.class.cast(getLoadBalancer("bar").getServerListImpl());
}
@SuppressWarnings("unchecked")
private ZoneAwareLoadBalancer<Server> getLoadBalancer(String name) {
return (ZoneAwareLoadBalancer<Server>) this.factory.getLoadBalancer(name);
}
@Configuration
@RibbonClients
@Import({ UtilAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class,
ArchaiusAutoConfiguration.class, RibbonAutoConfiguration.class,
RibbonEurekaAutoConfiguration.class })
protected static class TestConfiguration {
@Bean
public EurekaClient eurekaClient() {
return mock(EurekaClient.class);
}
}
}
# for EurekaRibbonClientPropertyOverrideIntegrationTests
foo3:
ribbon:
NFLoadBalancerPingClassName: com.netflix.loadbalancer.DummyPing
NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment