From 6087bc755ae11dcb9f571994f098dd98fa8a94d0 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Wed, 27 Apr 2016 09:06:57 +0100 Subject: [PATCH] Use eureka.instance zone data consistently determining client zone The zone for a remote server comes from its eureka.instance.metadataMap.zone, but we didn't (until this change) apply the same logic to the client. If the client is registering itslef with eureka then it will have a metadataMap itself and we should be able to just reverse the logic. Fixes gh-991 --- .../main/asciidoc/spring-cloud-netflix.adoc | 24 ++++++++++--- .../EurekaRibbonClientConfiguration.java | 30 +++++++++------- .../EurekaRibbonClientConfigurationTests.java | 34 ++++++++++++++----- 3 files changed, 62 insertions(+), 26 deletions(-) diff --git a/docs/src/main/asciidoc/spring-cloud-netflix.adoc b/docs/src/main/asciidoc/spring-cloud-netflix.adoc index 2f943e14..53e24e5c 100644 --- a/docs/src/main/asciidoc/spring-cloud-netflix.adoc +++ b/docs/src/main/asciidoc/spring-cloud-netflix.adoc @@ -693,13 +693,27 @@ default is a `DomainExtractingServerList` and the purpose of this is to make physical metadata available to the load balancer without using AWS AMI metadata (which is what Netflix relies on). By default the server list will be constructed with "zone" information as provided in -the instance metadata (so on the client set +the instance metadata (so on the remote clients set `eureka.instance.metadataMap.zone`), and if that is missing it can use the domain name from the server hostname as a proxy for zone (if the -flag `approximateZoneFromHostname` is set). Once the zone information is -available it can be used in a `ServerListFilter` (by default it will -be used to locate a server in the same zone as the client because the -default is a `ZonePreferenceServerListFilter`). +flag `approximateZoneFromHostname` is set). Once the zone information +is available it can be used in a `ServerListFilter`. By default it +will be used to locate a server in the same zone as the client because +the default is a `ZonePreferenceServerListFilter`. The zone of the +client is determined the same way as the remote instances by default, +i.e. via `eureka.instance.metadataMap.zone`. + +NOTE: The orthodox "archaius" way to set the client zone is via a +configuration property called "@zone", and Spring Cloud will use that +in preference to all other settings if it is available (note that the +key will have to be quoted in YAML configuration). + +NOTE: If there is no other source of zone data then a guess is made +based on the client configuration (as opposed to the instance +configuration). We take `eureka.client.availabilityZones`, which is a +map from region name to a list of zones, and pull out the first zone +for the instance's own region (i.e. the `eureka.client.region`, which +defaults to "us-east-1" for comatibility with native Netflix). [[spring-cloud-ribbon-without-eureka]] === Example: How to Use Ribbon Without Eureka diff --git a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/ribbon/eureka/EurekaRibbonClientConfiguration.java b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/ribbon/eureka/EurekaRibbonClientConfiguration.java index d551316b..89827cfb 100644 --- a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/ribbon/eureka/EurekaRibbonClientConfiguration.java +++ b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/ribbon/eureka/EurekaRibbonClientConfiguration.java @@ -36,12 +36,12 @@ import com.netflix.loadbalancer.ServerList; import com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList; import com.netflix.niws.loadbalancer.NIWSDiscoveryPing; -import lombok.extern.apachecommons.CommonsLog; - import static com.netflix.client.config.CommonClientConfigKey.DeploymentContextBasedVipAddresses; import static com.netflix.client.config.CommonClientConfigKey.EnableZoneAffinity; import static org.springframework.cloud.netflix.ribbon.RibbonProperyUtils.setRibbonProperty; +import lombok.extern.apachecommons.CommonsLog; + /** * Preprocessor that configures defaults for eureka-discovered ribbon clients. Such as: * @zone, NIWSServerListClassName, DeploymentContextBasedVipAddresses, @@ -104,21 +104,26 @@ public class EurekaRibbonClientConfiguration { @PostConstruct public void preprocess() { - String zone = ConfigurationManager.getDeploymentContext().getValue( - ContextKey.zone); + String zone = ConfigurationManager.getDeploymentContext() + .getValue(ContextKey.zone); if (this.clientConfig != null && StringUtils.isEmpty(zone)) { - if (approximateZoneFromHostname && eurekaConfig != null) { - String approxZone = ZoneUtils.extractApproximateZone(eurekaConfig - .getHostName(false)); + if (this.approximateZoneFromHostname && this.eurekaConfig != null) { + String approxZone = ZoneUtils + .extractApproximateZone(this.eurekaConfig.getHostName(false)); log.debug("Setting Zone To " + approxZone); ConfigurationManager.getDeploymentContext().setValue(ContextKey.zone, approxZone); } else { - String[] zones = this.clientConfig.getAvailabilityZones(this.clientConfig - .getRegion()); - String availabilityZone = zones != null && zones.length > 0 ? zones[0] - : null; + String availabilityZone = this.eurekaConfig == null ? null + : this.eurekaConfig.getMetadataMap().get("zone"); + if (availabilityZone == null) { + String[] zones = this.clientConfig + .getAvailabilityZones(this.clientConfig.getRegion()); + // Pick the first one from the regions we want to connect to + availabilityZone = zones != null && zones.length > 0 ? zones[0] + : null; + } if (availabilityZone != null) { // You can set this with archaius.deployment.* (maybe requires // custom deployment context)? @@ -127,7 +132,8 @@ public class EurekaRibbonClientConfiguration { } } } - setRibbonProperty(this.serviceId, DeploymentContextBasedVipAddresses.key(), this.serviceId); + setRibbonProperty(this.serviceId, DeploymentContextBasedVipAddresses.key(), + this.serviceId); setRibbonProperty(this.serviceId, EnableZoneAffinity.key(), "true"); } diff --git a/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/ribbon/eureka/EurekaRibbonClientConfigurationTests.java b/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/ribbon/eureka/EurekaRibbonClientConfigurationTests.java index 46ac4b75..3e6866a5 100644 --- a/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/ribbon/eureka/EurekaRibbonClientConfigurationTests.java +++ b/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/ribbon/eureka/EurekaRibbonClientConfigurationTests.java @@ -36,8 +36,10 @@ import com.netflix.niws.loadbalancer.DiscoveryEnabledServer; 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.RibbonProperyUtils.*; +import static org.springframework.cloud.netflix.ribbon.RibbonProperyUtils.VALUE_NOT_SET; +import static org.springframework.cloud.netflix.ribbon.RibbonProperyUtils.getProperty; +import static org.springframework.cloud.netflix.ribbon.RibbonProperyUtils.getRibbonKey; +import static org.springframework.cloud.netflix.ribbon.RibbonProperyUtils.setRibbonProperty; /** * @author Dave Syer @@ -83,15 +85,27 @@ public class EurekaRibbonClientConfigurationTests { String serviceId = "myService"; String suffix = "mySuffix"; String value = "myValue"; - DynamicStringProperty property = getProperty(getRibbonKey( - serviceId, suffix)); - assertEquals("property doesn't have default value", VALUE_NOT_SET, property.get()); + DynamicStringProperty property = getProperty(getRibbonKey(serviceId, suffix)); + assertEquals("property doesn't have default value", VALUE_NOT_SET, + property.get()); setRibbonProperty(serviceId, suffix, value); assertEquals("property has wrong value", value, property.get()); setRibbonProperty(serviceId, suffix, value); assertEquals("property has wrong value", value, property.get()); } - + + @Test + public void testExplicitZone() { + EurekaClientConfigBean client = new EurekaClientConfigBean(); + EurekaInstanceConfigBean configBean = getEurekaInstanceConfigBean(); + configBean.getMetadataMap().put("zone", "myZone"); + EurekaRibbonClientConfiguration preprocessor = new EurekaRibbonClientConfiguration( + client, "myService", configBean, false); + preprocessor.preprocess(); + assertEquals("myZone", + ConfigurationManager.getDeploymentContext().getValue(ContextKey.zone)); + } + @Test public void testDefaultZone() { EurekaClientConfigBean client = new EurekaClientConfigBean(); @@ -99,9 +113,10 @@ public class EurekaRibbonClientConfigurationTests { EurekaRibbonClientConfiguration preprocessor = new EurekaRibbonClientConfiguration( client, "myService", configBean, false); preprocessor.preprocess(); - assertEquals("defaultZone", ConfigurationManager.getDeploymentContext().getValue(ContextKey.zone)); + assertEquals("defaultZone", + ConfigurationManager.getDeploymentContext().getValue(ContextKey.zone)); } - + @Test public void testApproximateZone() { EurekaClientConfigBean client = new EurekaClientConfigBean(); @@ -110,7 +125,8 @@ public class EurekaRibbonClientConfigurationTests { EurekaRibbonClientConfiguration preprocessor = new EurekaRibbonClientConfiguration( client, "myService", configBean, true); preprocessor.preprocess(); - assertEquals("is.a.test.com", ConfigurationManager.getDeploymentContext().getValue(ContextKey.zone)); + assertEquals("is.a.test.com", + ConfigurationManager.getDeploymentContext().getValue(ContextKey.zone)); } }