From f4a69e43eb8d8355e90fa51c4cd42b81defcbece Mon Sep 17 00:00:00 2001 From: Ryan J Baxter Date: Wed, 13 May 2015 10:43:19 -0400 Subject: [PATCH] Sets the zone based on the hostname of the service if approximateZoneFromHostname is true. The flag has always been there, it just isn't carried through to the ribbon client configuration without this change if I understand this change correctly. Fixes gh-315, fixes gh-423. --- .../eureka/DomainExtractingServerList.java | 18 +------ .../EurekaRibbonClientConfiguration.java | 49 ++++++++++++++----- .../netflix/ribbon/eureka/ZoneUtils.java | 44 +++++++++++++++++ .../EurekaRibbonClientConfigurationTests.java | 39 +++++++++++++-- ...bonClientPreprocessorIntegrationTests.java | 7 +++ .../netflix/ribbon/eureka/ZoneUtilsTest.java | 35 +++++++++++++ 6 files changed, 158 insertions(+), 34 deletions(-) create mode 100644 spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/eureka/ZoneUtils.java create mode 100644 spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/eureka/ZoneUtilsTest.java 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 index 202c2855..3c400a2c 100644 --- 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 @@ -22,8 +22,6 @@ import java.util.List; import lombok.Getter; import lombok.Setter; -import org.springframework.util.StringUtils; - import com.netflix.appinfo.InstanceInfo; import com.netflix.client.config.CommonClientConfigKey; import com.netflix.client.config.IClientConfig; @@ -92,7 +90,7 @@ class DomainExtractingServer extends DiscoveryEnabledServer { setZone(server.getInstanceInfo().getMetadata().get("zone")); } else if (approximateZoneFromHostname) { - setZone(extractApproximateZone(server)); + setZone(ZoneUtils.extractApproximateZone(server.getHost())); } else { setZone(server.getZone()); @@ -112,18 +110,4 @@ class DomainExtractingServer extends DiscoveryEnabledServer { } return super.getId(); } - - private String extractApproximateZone(Server server) { - String host = server.getHost(); - if (!host.contains(".")) { - return host; - } - String[] split = StringUtils.split(host, "."); - StringBuilder builder = new StringBuilder(split[1]); - for (int i = 2; i < split.length; i++) { - builder.append(".").append(split[i]); - } - return builder.toString(); - } - } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/eureka/EurekaRibbonClientConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/eureka/EurekaRibbonClientConfiguration.java index 5c57e4df..daa28d0d 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/eureka/EurekaRibbonClientConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/eureka/EurekaRibbonClientConfiguration.java @@ -21,11 +21,15 @@ import static com.netflix.client.config.CommonClientConfigKey.EnableZoneAffinity import javax.annotation.PostConstruct; +import lombok.extern.apachecommons.CommonsLog; + 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.eureka.EurekaInstanceConfigBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.util.StringUtils; import com.netflix.client.config.IClientConfig; import com.netflix.config.ConfigurationManager; @@ -45,8 +49,10 @@ import com.netflix.niws.loadbalancer.NIWSDiscoveryPing; * * @author Spencer Gibb * @author Dave Syer + * @author Ryan Baxter */ @Configuration +@CommonsLog public class EurekaRibbonClientConfiguration { @Value("${ribbon.eureka.approximateZoneFromHostname:false}") @@ -62,13 +68,19 @@ public class EurekaRibbonClientConfiguration { @Autowired(required = false) private EurekaClientConfig clientConfig; + @Autowired + private EurekaInstanceConfigBean eurekaConfig; + public EurekaRibbonClientConfiguration() { } public EurekaRibbonClientConfiguration(EurekaClientConfig clientConfig, - String serviceId) { + String serviceId, EurekaInstanceConfigBean eurekaConfig, + boolean approximateZoneFromHostname) { this.clientConfig = clientConfig; this.serviceId = serviceId; + this.eurekaConfig = eurekaConfig; + this.approximateZoneFromHostname = approximateZoneFromHostname; } @Bean @@ -82,23 +94,36 @@ public class EurekaRibbonClientConfiguration { @Bean @ConditionalOnMissingBean public ServerList ribbonServerList(IClientConfig config) { - DiscoveryEnabledNIWSServerList discoveryServerList = new DiscoveryEnabledNIWSServerList(config); - DomainExtractingServerList serverList = new DomainExtractingServerList(discoveryServerList, config, this.approximateZoneFromHostname); + DiscoveryEnabledNIWSServerList discoveryServerList = new DiscoveryEnabledNIWSServerList( + config); + DomainExtractingServerList serverList = new DomainExtractingServerList( + discoveryServerList, config, this.approximateZoneFromHostname); return serverList; } @PostConstruct public void preprocess() { - if (this.clientConfig != null - && ConfigurationManager.getDeploymentContext().getValue(ContextKey.zone) == null) { - String[] zones = this.clientConfig.getAvailabilityZones(this.clientConfig - .getRegion()); - String zone = zones != null && zones.length > 0 ? zones[0] : null; - if (zone != null) { - // You can set this with archaius.deployment.* (maybe requires - // custom deployment context)? + String zone = ConfigurationManager.getDeploymentContext().getValue( + ContextKey.zone); + if (this.clientConfig != null && StringUtils.isEmpty(zone)) { + if (approximateZoneFromHostname) { + String approxZone = ZoneUtils.extractApproximateZone(eurekaConfig + .getHostname()); + log.debug("Setting Zone To " + approxZone); ConfigurationManager.getDeploymentContext().setValue(ContextKey.zone, - zone); + approxZone); + } + else { + String[] zones = this.clientConfig.getAvailabilityZones(this.clientConfig + .getRegion()); + String availabilityZone = zones != null && zones.length > 0 ? zones[0] + : null; + if (availabilityZone != null) { + // You can set this with archaius.deployment.* (maybe requires + // custom deployment context)? + ConfigurationManager.getDeploymentContext().setValue(ContextKey.zone, + availabilityZone); + } } } setProp(this.serviceId, DeploymentContextBasedVipAddresses.key(), this.serviceId); diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/eureka/ZoneUtils.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/eureka/ZoneUtils.java new file mode 100644 index 00000000..71ea689b --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/eureka/ZoneUtils.java @@ -0,0 +1,44 @@ +/* Copyright 2013-2015 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 org.springframework.util.StringUtils; + +/** + * Utility class for dealing with zones. + * @author Ryan Baxter + * + */ +public class ZoneUtils { + + /** + * Approximates Eureka zones from a host name. This method approximates the zone to be + * everything after the first "." in the host name. + * @param host The host name to extract the host name from + * @return The approximate zone + */ + public static String extractApproximateZone(String host) { + if (!host.contains(".")) { + return host; + } + String[] split = StringUtils.split(host, "."); + StringBuilder builder = new StringBuilder(split[1]); + for (int i = 2; i < split.length; i++) { + builder.append(".").append(split[i]); + } + return builder.toString(); + } + +} diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/eureka/EurekaRibbonClientConfigurationTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/eureka/EurekaRibbonClientConfigurationTests.java index 6c655a15..bd0cd217 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/eureka/EurekaRibbonClientConfigurationTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/eureka/EurekaRibbonClientConfigurationTests.java @@ -16,10 +16,17 @@ 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.EurekaRibbonClientConfiguration.VALUE_NOT_SET; + import org.junit.After; +import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.springframework.cloud.netflix.eureka.EurekaClientConfigBean; +import org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean; import org.springframework.cloud.netflix.ribbon.SpringClientFactory; import com.netflix.config.ConfigurationManager; @@ -29,15 +36,14 @@ import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.ZoneAwareLoadBalancer; import com.netflix.niws.loadbalancer.DiscoveryEnabledServer; -import static org.junit.Assert.*; -import static org.springframework.cloud.netflix.ribbon.eureka.EurekaRibbonClientConfiguration.VALUE_NOT_SET; - /** * @author Dave Syer + * @author Ryan Baxter */ public class EurekaRibbonClientConfigurationTests { @After + @Before public void close() { ConfigurationManager.getDeploymentContext().setValue(ContextKey.zone, ""); } @@ -46,10 +52,11 @@ public class EurekaRibbonClientConfigurationTests { @Ignore public void basicConfigurationCreatedForLoadBalancer() { EurekaClientConfigBean client = new EurekaClientConfigBean(); + EurekaInstanceConfigBean configBean = new EurekaInstanceConfigBean(); client.getAvailabilityZones().put(client.getRegion(), "foo"); SpringClientFactory clientFactory = new SpringClientFactory(); EurekaRibbonClientConfiguration clientPreprocessor = new EurekaRibbonClientConfiguration( - client, "service"); + client, "service", configBean, false); clientPreprocessor.preprocess(); ILoadBalancer balancer = clientFactory.getLoadBalancer("service"); assertNotNull(balancer); @@ -63,8 +70,9 @@ public class EurekaRibbonClientConfigurationTests { @Test public void testSetProp() { EurekaClientConfigBean client = new EurekaClientConfigBean(); + EurekaInstanceConfigBean configBean = new EurekaInstanceConfigBean(); EurekaRibbonClientConfiguration preprocessor = new EurekaRibbonClientConfiguration( - client, "myService"); + client, "myService", configBean, false); String serviceId = "myService"; String suffix = "mySuffix"; String value = "myValue"; @@ -76,5 +84,26 @@ public class EurekaRibbonClientConfigurationTests { preprocessor.setProp(serviceId, suffix, value); assertEquals("property has wrong value", value, property.get()); } + + @Test + public void testDefaultZone() { + EurekaClientConfigBean client = new EurekaClientConfigBean(); + EurekaInstanceConfigBean configBean = new EurekaInstanceConfigBean(); + EurekaRibbonClientConfiguration preprocessor = new EurekaRibbonClientConfiguration( + client, "myService", configBean, false); + preprocessor.preprocess(); + assertEquals("defaultZone", ConfigurationManager.getDeploymentContext().getValue(ContextKey.zone)); + } + + @Test + public void testApproximateZone() { + EurekaClientConfigBean client = new EurekaClientConfigBean(); + EurekaInstanceConfigBean configBean = new EurekaInstanceConfigBean(); + configBean.setHostname("this.is.a.test.com"); + EurekaRibbonClientConfiguration preprocessor = new EurekaRibbonClientConfiguration( + client, "myService", configBean, true); + preprocessor.preprocess(); + assertEquals("is.a.test.com", ConfigurationManager.getDeploymentContext().getValue(ContextKey.zone)); + } } diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/eureka/RibbonClientPreprocessorIntegrationTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/eureka/RibbonClientPreprocessorIntegrationTests.java index afdb4c6f..164aa3f9 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/eureka/RibbonClientPreprocessorIntegrationTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/eureka/RibbonClientPreprocessorIntegrationTests.java @@ -24,6 +24,7 @@ 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.EurekaInstanceConfigBean; import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration; import org.springframework.cloud.netflix.ribbon.RibbonClient; import org.springframework.cloud.netflix.ribbon.SpringClientFactory; @@ -98,6 +99,12 @@ public class RibbonClientPreprocessorIntegrationTests { filter.setZone("myTestZone"); return filter; } + + @Bean + public EurekaInstanceConfigBean getEurekaInstanceConfigBean() { + EurekaInstanceConfigBean bean = new EurekaInstanceConfigBean(); + return bean; + } } } diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/eureka/ZoneUtilsTest.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/eureka/ZoneUtilsTest.java new file mode 100644 index 00000000..2238ec43 --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/eureka/ZoneUtilsTest.java @@ -0,0 +1,35 @@ +/* Copyright 2013-2015 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.assertTrue; + +import org.junit.Test; + +/** + * + * @author Ryan Baxter + * + */ +public class ZoneUtilsTest { + + @Test + public void extractApproximateZoneTest() { + assertTrue("foo".equals(ZoneUtils.extractApproximateZone("foo"))); + assertTrue("bar".equals(ZoneUtils.extractApproximateZone("foo.bar"))); + assertTrue("world.foo.bar".equals(ZoneUtils + .extractApproximateZone("hello.world.foo.bar"))); + } +}