Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gh 3718 add zoned loadbalancer instrumentation #3720

Merged
merged 4 commits into from
Dec 19, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/src/main/asciidoc/spring-cloud-netflix.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,18 @@ When a refresh occurs clients will be unregistered from the Eureka server and th
where all instance of a given service are not available. One way to eliminate this from happening is to disable
the ability to refresh Eureka clients. To do this set `eureka.client.refresh.enable=false`.

=== Using Eureka with Spring Cloud LoadBalancer

We offer support for the Spring Cloud LoadBalancer `ZonePreferenceServiceInstanceListSupplier`.
The `zone` value from the Eureka instance metadata (`eureka.instance.metadataMap.zone`) is used for setting the
value of `spring-clod-loadbalancer-zone` property that is used to filter service instances by zone.

If that is missing and if the `spring.cloud.loadbalancer.eureka.approximateZoneFromHostname` flag is set to `true`,
it can use the domain name from the server hostname as a proxy for the zone.

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 (that is, the `eureka.client.region`, which defaults to "us-east-1", for compatibility with native Netflix).

[[spring-cloud-eureka-server]]
== Service Discovery: Eureka Server

Expand Down
5 changes: 5 additions & 0 deletions spring-cloud-netflix-eureka-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@
<artifactId>spring-cloud-config-server</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.netflix.eureka</groupId>
<artifactId>eureka-client</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Copyright 2013-2019 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
*
* https://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.eureka.loadbalancer;

import java.util.HashMap;
import java.util.Map;

import javax.annotation.PostConstruct;

import com.netflix.appinfo.EurekaInstanceConfig;
import com.netflix.discovery.EurekaClientConfig;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.ribbon.eureka.ZoneUtils;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.util.StringUtils;

/**
* A configuration for Spring Cloud LoadBalancer that retrieves client instance zone from
* Eureka and sets it as a property. Based on
* {@link EurekaLoadBalancerClientConfiguration}.
*
* @author Olga Maciaszek-Sharma
* @since 2.2.1
* @see EurekaLoadBalancerClientConfiguration
*/
public class EurekaLoadBalancerClientConfiguration {

// Visible for tests
static final String LOADBALANCER_ZONE = "spring.cloud.loadbalancer.zone";

// Visible for tests
static final String APPROXIMATE_ZONE_FROM_HOSTNAME = "spring.cloud.loadbalancer.eureka.approximateZoneFromHostname";

private static final Log LOG = LogFactory
.getLog(EurekaLoadBalancerClientConfiguration.class);

private final EurekaClientConfig clientConfig;

private final EurekaInstanceConfig eurekaConfig;

private final ConfigurableEnvironment environment;

public EurekaLoadBalancerClientConfiguration(
@Autowired(required = false) EurekaClientConfig clientConfig,
@Autowired(required = false) EurekaInstanceConfig eurekaInstanceConfig,
ConfigurableEnvironment environment) {
this.clientConfig = clientConfig;
this.eurekaConfig = eurekaInstanceConfig;
this.environment = environment;
}

@PostConstruct
public void preprocess() {
if (!StringUtils.isEmpty(environment.getProperty(LOADBALANCER_ZONE))) {
return;
}
String zone = getZoneFromEureka();
if (zone != null) {
if (LOG.isDebugEnabled()) {
LOG.debug("Setting the value of '" + LOADBALANCER_ZONE + "' to " + zone);
}
addZoneProperty(zone);
}
}

private void addZoneProperty(String zone) {
MutablePropertySources propertySources = environment.getPropertySources();
Map<String, Object> myMap = new HashMap<>();
myMap.put(LOADBALANCER_ZONE, zone);
propertySources.addLast(new MapPropertySource("LOADBALANCER_MAP", myMap));
OlgaMaciaszek marked this conversation as resolved.
Show resolved Hide resolved
}

private String getZoneFromEureka() {
String zone;
boolean approximateZoneFromHostname = Boolean.parseBoolean(
environment.getProperty(APPROXIMATE_ZONE_FROM_HOSTNAME, "false"));
if (approximateZoneFromHostname && eurekaConfig != null) {
return ZoneUtils.extractApproximateZone(this.eurekaConfig.getHostName(false));
}
else {
zone = eurekaConfig == null ? null
: eurekaConfig.getMetadataMap().get("zone");
if (zone == null) {
OlgaMaciaszek marked this conversation as resolved.
Show resolved Hide resolved
String[] zones = clientConfig
.getAvailabilityZones(clientConfig.getRegion());
// Pick the first one from the regions we want to connect to
zone = zones != null && zones.length > 0 ? zones[0] : null;
}
return zone;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2013-2019 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
*
* https://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.eureka.loadbalancer;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientConfigurationRegistrar;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.context.annotation.Configuration;

/**
* An Autoconfiguration that loads default config for Spring Cloud LoadBalancer clients.
*
* @author Olga Maciaszek-Sharma
* @since 2.2.1
* @see EurekaLoadBalancerClientConfiguration
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(LoadBalancerClientConfigurationRegistrar.class)
@LoadBalancerClients(defaultConfiguration = EurekaLoadBalancerClientConfiguration.class)
public class LoadBalancerEurekaAutoConfiguration {

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServi
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\
org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration,\
org.springframework.cloud.netflix.eureka.reactive.EurekaReactiveDiscoveryClientConfiguration
org.springframework.cloud.netflix.eureka.reactive.EurekaReactiveDiscoveryClientConfiguration,\
org.springframework.cloud.netflix.eureka.loadbalancer.LoadBalancerEurekaAutoConfiguration

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceBootstrapConfiguration
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright 2015-2019 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
*
* https://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.eureka.loadbalancer;

import org.junit.jupiter.api.Test;

import org.springframework.cloud.commons.util.InetUtils;
import org.springframework.cloud.commons.util.InetUtilsProperties;
import org.springframework.cloud.netflix.eureka.EurekaClientConfigBean;
import org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean;
import org.springframework.mock.env.MockEnvironment;

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.cloud.netflix.eureka.loadbalancer.EurekaLoadBalancerClientConfiguration.APPROXIMATE_ZONE_FROM_HOSTNAME;
import static org.springframework.cloud.netflix.eureka.loadbalancer.EurekaLoadBalancerClientConfiguration.LOADBALANCER_ZONE;

/**
* Tests for {@link EurekaLoadBalancerClientConfiguration}.
*
* @author Olga Maciaszek-Sharma
*/
class EurekaLoadBalancerClientConfigurationTests {

private EurekaClientConfigBean eurekaClientConfig = new EurekaClientConfigBean();

private EurekaInstanceConfigBean eurekaInstanceConfig = new EurekaInstanceConfigBean(
new InetUtils(new InetUtilsProperties()));

private MockEnvironment environment = new MockEnvironment();

private EurekaLoadBalancerClientConfiguration preprocessor = new EurekaLoadBalancerClientConfiguration(
eurekaClientConfig, eurekaInstanceConfig, environment);

@Test
void shouldSetZoneFromInstanceMetadata() {
eurekaInstanceConfig.getMetadataMap().put("zone", "myZone");

preprocessor.preprocess();

assertThat(environment.getProperty(LOADBALANCER_ZONE)).isEqualTo("myZone");
}

@Test
public void shouldSetZoneToDefaultWhenNotSetInMetadata() {
preprocessor.preprocess();

assertThat(environment.getProperty(LOADBALANCER_ZONE)).isEqualTo("defaultZone");
}

@Test
public void shouldResolveApproximateZoneFromHost() {
eurekaInstanceConfig.setHostname("this.is.a.test.com");
environment.setProperty(APPROXIMATE_ZONE_FROM_HOSTNAME, "true");

preprocessor.preprocess();

assertThat(environment.getProperty(LOADBALANCER_ZONE)).isEqualTo("is.a.test.com");
}

}