diff --git a/spring-cloud-netflix-core/pom.xml b/spring-cloud-netflix-core/pom.xml index 91e7bb5b..198cb4d6 100644 --- a/spring-cloud-netflix-core/pom.xml +++ b/spring-cloud-netflix-core/pom.xml @@ -113,6 +113,10 @@ neo4j-cypher-compiler-2.1 2.1.2 + + org.apache.activemq + activemq-client + diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonAutoConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonAutoConfiguration.java index 9a2c5549..a15f6937 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonAutoConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonAutoConfiguration.java @@ -1,15 +1,20 @@ package org.springframework.cloud.netflix.ribbon; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration; +import org.springframework.cloud.netflix.ribbon.eureka.EurekaRibbonInitializer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.web.client.RestTemplate; +import com.netflix.loadbalancer.BaseLoadBalancer; + import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -18,6 +23,12 @@ import java.util.List; @Configuration @AutoConfigureAfter(EurekaClientAutoConfiguration.class) public class RibbonAutoConfiguration { + + @Autowired(required=false) + private List balancers = Collections.emptyList(); + + @Autowired(required=false) + private EurekaRibbonInitializer initializer; @Bean @ConditionalOnMissingBean(RestTemplate.class) @@ -32,7 +43,7 @@ public class RibbonAutoConfiguration { @Bean @ConditionalOnMissingBean(LoadBalancerClient.class) public LoadBalancerClient loadBalancerClient() { - return new RibbonLoadBalancerClient(); + return new RibbonLoadBalancerClient(balancers); } @Bean diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonLoadBalancerClient.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonLoadBalancerClient.java index 7b6542e8..268925c7 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonLoadBalancerClient.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonLoadBalancerClient.java @@ -1,57 +1,77 @@ package org.springframework.cloud.netflix.ribbon; -import com.netflix.client.ClientFactory; -import com.netflix.loadbalancer.ILoadBalancer; -import com.netflix.loadbalancer.Server; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; +import com.netflix.client.ClientFactory; +import com.netflix.loadbalancer.BaseLoadBalancer; +import com.netflix.loadbalancer.ILoadBalancer; +import com.netflix.loadbalancer.Server; + /** * @author Spencer Gibb + * @author Dave Syer */ public class RibbonLoadBalancerClient implements LoadBalancerClient { - @Autowired - private ServerListInitializer serverListInitializer; - @Override - public ServiceInstance choose(String serviceId) { - serverListInitializer.initialize(serviceId); - ILoadBalancer loadBalancer = ClientFactory.getNamedLoadBalancer(serviceId); - Server server = loadBalancer.chooseServer(null); - if (server == null) { - throw new IllegalStateException("Unable to locate ILoadBalancer for service: "+ serviceId); - } - return new RibbonServer(serviceId, server); - } + @Autowired + private ServerListInitializer serverListInitializer; - private class RibbonServer implements ServiceInstance { - private String serviceId; - private Server server; + private Map balancers = new HashMap(); - private RibbonServer(String serviceId, Server server) { - this.serviceId = serviceId; - this.server = server; - } + public RibbonLoadBalancerClient(List balancers) { + for (BaseLoadBalancer balancer : balancers) { + this.balancers.put(balancer.getName(), balancer); + } + } - @Override - public String getServiceId() { - return serviceId; - } + @Override + public ServiceInstance choose(String serviceId) { + serverListInitializer.initialize(serviceId); + ILoadBalancer loadBalancer = this.balancers.get(serviceId); + if (loadBalancer == null) { + loadBalancer = ClientFactory.getNamedLoadBalancer(serviceId); + } + Server server = loadBalancer.chooseServer("default"); + if (server == null) { + throw new IllegalStateException( + "Unable to locate ILoadBalancer for service: " + serviceId); + } + return new RibbonServer(serviceId, server); + } - @Override - public String getHost() { - return server.getHost(); - } + private class RibbonServer implements ServiceInstance { + private String serviceId; + private Server server; - @Override - public String getIpAddress() { - return null; //TODO: ribbon doesn't supply ip - } + private RibbonServer(String serviceId, Server server) { + this.serviceId = serviceId; + this.server = server; + } - @Override - public int getPort() { - return server.getPort(); - } - } + @Override + public String getServiceId() { + return serviceId; + } + + @Override + public String getHost() { + return server.getHost(); + } + + @Override + public String getIpAddress() { + return null; // TODO: ribbon doesn't supply ip + } + + @Override + public int getPort() { + return server.getPort(); + } + } } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/eureka/DomainExtractingServerList.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/eureka/DomainExtractingServerList.java new file mode 100644 index 00000000..3685fed8 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/eureka/DomainExtractingServerList.java @@ -0,0 +1,81 @@ +/* + * 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 java.net.MalformedURLException; +import java.net.URL; +import java.util.List; + +import org.springframework.util.StringUtils; + +import com.netflix.loadbalancer.Server; +import com.netflix.loadbalancer.ServerList; + +/** + * @author Dave Syer + * + */ +public class DomainExtractingServerList implements ServerList { + + private ServerList list; + + public DomainExtractingServerList(ServerList list) { + this.list = list; + } + + @Override + public List getInitialListOfServers() { + List servers = setZones(list.getInitialListOfServers()); + return servers; + } + + @Override + public List getUpdatedListOfServers() { + List servers = setZones(list.getUpdatedListOfServers()); + return servers; + } + + private List setZones(List servers) { + for (Server server : servers) { + if (!server.getZone().equals("default")) { + String zone = extractApproximateZone(server.getId()); + server.setZone(zone); + } + } + return servers; + } + + private String extractApproximateZone(String id) { + try { + URL url = new URL("http://" + id); + String host = url.getHost(); + if (!host.contains(".")) { + return host; + } + String[] split = StringUtils.split(host, "."); + StringBuilder builder = new StringBuilder(split[1]); + for (int i=2; i dynamic = (DynamicServerListLoadBalancer) balancer; + ServerList list = dynamic.getServerListImpl(); + if (!(list instanceof DomainExtractingServerList) + && !(instance.getDataCenterInfo() instanceof AmazonInfo)) { + // This is optional: you can use the native Eureka AWS features by making + // the EurekaInstanceConfig.dataCenterInfo an AmazonInfo + dynamic.setServerListImpl(new DomainExtractingServerList(list)); + } + } + } + + protected void setProp(String serviceId, String suffix, String value) { + // how to set the namespace properly? + String namespace = "ribbon"; + ConfigurationManager.getConfigInstance().setProperty( + serviceId + "." + namespace + "." + suffix, value); + } - protected void setProp(String serviceId, String suffix, String value) { - //how to set the namespace properly? - String namespace = "ribbon"; - ConfigurationManager.getConfigInstance().setProperty(serviceId + "."+ namespace +"." + suffix, value); - } } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/eureka/RibbonEurekaAutoConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/eureka/RibbonEurekaAutoConfiguration.java index a924a547..f40f0525 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/eureka/RibbonEurekaAutoConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/eureka/RibbonEurekaAutoConfiguration.java @@ -15,7 +15,10 @@ */ package org.springframework.cloud.netflix.ribbon.eureka; +import com.netflix.appinfo.EurekaInstanceConfig; import com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList; + +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; @@ -35,9 +38,12 @@ import org.springframework.context.annotation.Configuration; @ConditionalOnExpression("${ribbon.eureka.enabled:true}") @AutoConfigureAfter(RibbonAutoConfiguration.class) public class RibbonEurekaAutoConfiguration { + + @Autowired(required=false) + private EurekaInstanceConfig instance; @Bean public ServerListInitializer serverListInitializer() { - return new EurekaRibbonInitializer(); + return new EurekaRibbonInitializer(instance); } } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/eureka/ZonePreferenceServerListFilter.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/eureka/ZonePreferenceServerListFilter.java new file mode 100644 index 00000000..9d34d55c --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/eureka/ZonePreferenceServerListFilter.java @@ -0,0 +1,63 @@ +/* + * 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 java.util.ArrayList; +import java.util.List; + +import com.netflix.client.config.IClientConfig; +import com.netflix.config.ConfigurationManager; +import com.netflix.config.DeploymentContext.ContextKey; +import com.netflix.loadbalancer.Server; +import com.netflix.loadbalancer.ZoneAffinityServerListFilter; + +/** + * A filter that actively prefers the local zone (as defined by the deployment context, or + * the Eureka instance metadata). + * + * @author Dave Syer + * + */ +public class ZonePreferenceServerListFilter extends ZoneAffinityServerListFilter { + + private String zone; + + @Override + public void initWithNiwsConfig(IClientConfig niwsClientConfig) { + super.initWithNiwsConfig(niwsClientConfig); + if (ConfigurationManager.getDeploymentContext() != null) { + zone = ConfigurationManager.getDeploymentContext().getValue(ContextKey.zone); + } + } + + @Override + public List getFilteredListOfServers(List servers) { + List output = super.getFilteredListOfServers(servers); + if (zone != null && output.size() == servers.size()) { + List local = new ArrayList(); + for (Server server : output) { + if (zone.equalsIgnoreCase(server.getZone())) { + local.add(server); + } + } + if (!local.isEmpty()) { + return local; + } + } + return output; + } + +} diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/eureka/EurekaRibbonInitializerTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/eureka/EurekaRibbonInitializerTests.java new file mode 100644 index 00000000..50f88402 --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/eureka/EurekaRibbonInitializerTests.java @@ -0,0 +1,59 @@ +/* + * 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 static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.junit.After; +import org.junit.Test; +import org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean; + +import com.netflix.client.ClientFactory; +import com.netflix.config.ConfigurationManager; +import com.netflix.config.DeploymentContext.ContextKey; +import com.netflix.loadbalancer.ILoadBalancer; +import com.netflix.loadbalancer.Server; +import com.netflix.loadbalancer.ZoneAwareLoadBalancer; + +/** + * @author Dave Syer + * + */ +public class EurekaRibbonInitializerTests { + + @After + public void close() { + ConfigurationManager.getDeploymentContext().setValue(ContextKey.zone, ""); + } + + @Test + public void basicConfigurationCreatedForLoadBalancer() { + EurekaInstanceConfigBean instance = new EurekaInstanceConfigBean(); + instance.getMetadataMap().put("zone", "foo"); + EurekaRibbonInitializer initializer = new EurekaRibbonInitializer( + instance); + initializer.initialize("service"); + ILoadBalancer balancer = ClientFactory.getNamedLoadBalancer("service"); + assertNotNull(balancer); + @SuppressWarnings("unchecked") + ZoneAwareLoadBalancer aware = (ZoneAwareLoadBalancer) balancer; + assertTrue(aware.getServerListImpl() instanceof DomainExtractingServerList); + assertEquals("foo", ConfigurationManager.getDeploymentContext().getValue(ContextKey.zone)); + } + +} diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/eureka/ZonePreferenceServerListFilterTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/eureka/ZonePreferenceServerListFilterTests.java new file mode 100644 index 00000000..2ed52384 --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/eureka/ZonePreferenceServerListFilterTests.java @@ -0,0 +1,67 @@ +/* + * 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 java.util.Arrays; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import com.netflix.loadbalancer.Server; + +/** + * @author Dave Syer + * + */ +public class ZonePreferenceServerListFilterTests { + + private Server dsyer = new Server("dsyer", 8080); + private Server localhost = new Server("localhost", 8080); + + @Before + public void init() { + dsyer.setZone("dsyer"); + localhost.setZone("localhost"); + } + + @Test + public void noZoneSet() { + ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter(); + List result = filter.getFilteredListOfServers(Arrays.asList(localhost)); + assertEquals(1, result.size()); + } + + @Test + public void withZoneSetAndNoMatches() { + ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter(); + ReflectionTestUtils.setField(filter, "zone", "dsyer"); + List result = filter.getFilteredListOfServers(Arrays.asList(localhost)); + assertEquals(1, result.size()); + } + + @Test + public void withZoneSetAndMatches() { + ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter(); + ReflectionTestUtils.setField(filter, "zone", "dsyer"); + List result = filter.getFilteredListOfServers(Arrays.asList(dsyer, localhost)); + assertEquals(1, result.size()); + } + +}