Makes fetching of config server from Eureka retryable

Makes fetching of config server from Eureka retryable, adds support for failfast

Retry requires spring-retry to be on the classpath.
This commit is contained in:
Nastya Smirnova
2017-03-24 22:14:51 +02:00
committed by Spencer Gibb
parent 065bbefddc
commit 3abc1ab84c
7 changed files with 317 additions and 79 deletions

View File

@@ -49,6 +49,13 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
<type>test-jar</type>
<scope>test</scope>
<version>${spring-cloud-commons.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<profiles>

View File

@@ -76,6 +76,12 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,33 @@
package org.springframework.cloud.config.client;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.retry.annotation.Retryable;
import java.util.List;
public class ConfigServerInstanceProvider {
private static Log logger = LogFactory.getLog(ConfigServerInstanceProvider.class);
private final DiscoveryClient client;
public ConfigServerInstanceProvider(DiscoveryClient client) {
this.client = client;
}
@Retryable(interceptor = "configServerRetryInterceptor")
public ServiceInstance getConfigServerInstance(String serviceId) {
logger.debug("Locating configserver (" + serviceId + ") via discovery");
List<ServiceInstance> instances = this.client.getInstances(serviceId);
if (instances.isEmpty()) {
throw new IllegalStateException(
"No instances found of configserver (" + serviceId + ")");
}
ServiceInstance instance = instances.get(0);
logger.debug(
"Located configserver (" + serviceId + ") via discovery: " + instance);
return instance;
}
}

View File

@@ -16,8 +16,6 @@
package org.springframework.cloud.config.client;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -28,6 +26,7 @@ import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
import org.springframework.cloud.client.discovery.event.HeartbeatMonitor;
import org.springframework.cloud.commons.util.UtilAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.event.ContextRefreshedEvent;
@@ -52,10 +51,16 @@ public class DiscoveryClientConfigServiceBootstrapConfiguration {
private ConfigClientProperties config;
@Autowired
private DiscoveryClient client;
private ConfigServerInstanceProvider instanceProvider;
private HeartbeatMonitor monitor = new HeartbeatMonitor();
@Bean
public ConfigServerInstanceProvider configServerInstanceProvider(
DiscoveryClient discoveryClient) {
return new ConfigServerInstanceProvider(discoveryClient);
}
@EventListener(ContextRefreshedEvent.class)
public void startup(ContextRefreshedEvent event) {
refresh();
@@ -70,14 +75,9 @@ public class DiscoveryClientConfigServiceBootstrapConfiguration {
private void refresh() {
try {
logger.debug("Locating configserver via discovery");
String serviceId = this.config.getDiscovery().getServiceId();
List<ServiceInstance> instances = this.client.getInstances(serviceId);
if (instances.isEmpty()) {
logger.warn("No instances found of configserver (" + serviceId + ")");
return;
}
ServiceInstance server = instances.get(0);
ServiceInstance server = this.instanceProvider
.getConfigServerInstance(serviceId);
String url = getHomePage(server);
if (server.getMetadata().containsKey("password")) {
String user = server.getMetadata().get("user");
@@ -96,7 +96,12 @@ public class DiscoveryClientConfigServiceBootstrapConfiguration {
this.config.setUri(url);
}
catch (Exception ex) {
logger.warn("Could not locate configserver via discovery", ex);
if (config.isFailFast()) {
throw ex;
}
else {
logger.warn("Could not locate configserver via discovery", ex);
}
}
}

View File

@@ -0,0 +1,106 @@
package org.springframework.cloud.config.client;
import org.junit.After;
import org.junit.Rule;
import org.junit.rules.ExpectedException;
import org.mockito.Mockito;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.commons.util.UtilAutoConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.Arrays;
import java.util.Collections;
import static org.junit.Assert.assertEquals;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.springframework.cloud.config.client.ConfigClientProperties.Discovery.DEFAULT_CONFIG_SERVER;
public abstract class BaseDiscoveryClientConfigServiceBootstrapConfigurationTests {
@Rule
public ExpectedException expectedException = ExpectedException.none();
protected AnnotationConfigApplicationContext context;
protected DiscoveryClient client = Mockito.mock(DiscoveryClient.class);
protected ServiceInstance info = new DefaultServiceInstance("app", "foo", 8877,
false);
@After
public void close() {
if (this.context != null) {
this.context.close();
}
}
void givenDiscoveryClientReturnsNoInfo() {
given(this.client.getInstances(DEFAULT_CONFIG_SERVER))
.willReturn(Collections.<ServiceInstance> emptyList());
}
void givenDiscoveryClientReturnsInfo() {
given(this.client.getInstances(DEFAULT_CONFIG_SERVER))
.willReturn(Arrays.asList(this.info));
}
void givenDiscoveryClientReturnsInfoOnThirdTry() {
given(this.client.getInstances(DEFAULT_CONFIG_SERVER))
.willReturn(Collections.<ServiceInstance> emptyList())
.willReturn(Collections.<ServiceInstance> emptyList())
.willReturn(Arrays.asList(this.info));
}
void expectNoInstancesOfConfigServerException() {
expectedException.expect(IllegalStateException.class);
expectedException.expectMessage(
"No instances found of configserver (" + DEFAULT_CONFIG_SERVER + ")");
}
void expectDiscoveryClientConfigServiceBootstrapConfigurationIsSetup() {
assertEquals(1, this.context.getBeanNamesForType(
DiscoveryClientConfigServiceBootstrapConfiguration.class).length);
}
void expectConfigClientPropertiesHasDefaultConfiguration() {
expectConfigClientPropertiesHasConfiguration("http://localhost:8888");
}
void expectConfigClientPropertiesHasConfigurationFromEureka() {
expectConfigClientPropertiesHasConfiguration("http://foo:8877/");
}
void expectConfigClientPropertiesHasConfiguration(final String expectedUri) {
ConfigClientProperties properties = this.context
.getBean(ConfigClientProperties.class);
assertEquals(expectedUri, properties.getRawUri());
}
void verifyDiscoveryClientCalledThreeTimes() {
verify(this.client, times(3)).getInstances(DEFAULT_CONFIG_SERVER);
}
void verifyDiscoveryClientCalledOnce() {
verify(this.client).getInstances(DEFAULT_CONFIG_SERVER);
}
void setup(String... env) {
this.context = new AnnotationConfigApplicationContext();
EnvironmentTestUtils.addEnvironment(this.context, env);
EnvironmentTestUtils.addEnvironment(this.context, "eureka.client.enabled=false");
this.context.getDefaultListableBeanFactory().registerSingleton("discoveryClient",
this.client);
this.context.register(UtilAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class,
DiscoveryClientConfigServiceBootstrapConfiguration.class,
ConfigServiceBootstrapConfiguration.class, ConfigClientProperties.class);
this.context.refresh();
}
}

View File

@@ -0,0 +1,50 @@
package org.springframework.cloud.config.client;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.cloud.ClassPathExclusions;
import org.springframework.cloud.FilteredClassPathRunner;
@RunWith(FilteredClassPathRunner.class)
@ClassPathExclusions({ "spring-retry-*.jar", "spring-boot-starter-aop-*.jar" })
public class DiscoveryClientConfigServiceBootstrapConfigurationNoSpringRetryTests
extends BaseDiscoveryClientConfigServiceBootstrapConfigurationTests {
@Test
public void shouldFailWithExceptionGetConfigServerInstanceFromDiscoveryClient()
throws Exception {
givenDiscoveryClientReturnsNoInfo();
expectNoInstancesOfConfigServerException();
setup("spring.cloud.config.discovery.enabled=true",
"spring.cloud.config.failFast=true");
}
@Test
public void shouldFailWithMessageGetConfigServerInstanceFromDiscoveryClient()
throws Exception {
givenDiscoveryClientReturnsNoInfo();
setup("spring.cloud.config.discovery.enabled=true",
"spring.cloud.config.failFast=false");
expectDiscoveryClientConfigServiceBootstrapConfigurationIsSetup();
expectConfigClientPropertiesHasDefaultConfiguration();
verifyDiscoveryClientCalledOnce();
}
@Test
public void shouldSucceedGetConfigServerInstanceFromDiscoveryClient()
throws Exception {
givenDiscoveryClientReturnsInfo();
setup("spring.cloud.config.discovery.enabled=true",
"spring.cloud.config.failFast=true");
expectDiscoveryClientConfigServiceBootstrapConfigurationIsSetup();
expectConfigClientPropertiesHasConfigurationFromEureka();
verifyDiscoveryClientCalledOnce();
}
}

View File

@@ -16,46 +16,24 @@
package org.springframework.cloud.config.client;
import static org.junit.Assert.assertEquals;
import static org.mockito.BDDMockito.given;
import static org.springframework.cloud.config.client.ConfigClientProperties.Discovery.DEFAULT_CONFIG_SERVER;
import java.util.Arrays;
import org.junit.After;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
import org.springframework.cloud.commons.util.UtilAutoConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.junit.Assert.assertEquals;
/**
* @author Dave Syer
*/
public class DiscoveryClientConfigServiceBootstrapConfigurationTests {
private AnnotationConfigApplicationContext context;
private DiscoveryClient client = Mockito.mock(DiscoveryClient.class);
private ServiceInstance info = new DefaultServiceInstance("app", "foo", 8877, false);
@After
public void close() {
if (this.context != null) {
this.context.close();
}
}
public class DiscoveryClientConfigServiceBootstrapConfigurationTests extends BaseDiscoveryClientConfigServiceBootstrapConfigurationTests {
@Test
public void offByDefault() throws Exception {
this.context = new AnnotationConfigApplicationContext(
DiscoveryClientConfigServiceBootstrapConfiguration.class);
assertEquals(0, this.context.getBeanNamesForType(DiscoveryClient.class).length);
assertEquals(0, this.context.getBeanNamesForType(
DiscoveryClientConfigServiceBootstrapConfiguration.class).length);
@@ -63,51 +41,49 @@ public class DiscoveryClientConfigServiceBootstrapConfigurationTests {
@Test
public void onWhenRequested() throws Exception {
given(this.client.getInstances(DEFAULT_CONFIG_SERVER))
.willReturn(Arrays.asList(this.info));
givenDiscoveryClientReturnsInfo();
setup("spring.cloud.config.discovery.enabled=true");
assertEquals(1, this.context.getBeanNamesForType(
DiscoveryClientConfigServiceBootstrapConfiguration.class).length);
Mockito.verify(this.client).getInstances(DEFAULT_CONFIG_SERVER);
ConfigClientProperties locator = this.context
.getBean(ConfigClientProperties.class);
assertEquals("http://foo:8877/", locator.getRawUri());
expectDiscoveryClientConfigServiceBootstrapConfigurationIsSetup();
verifyDiscoveryClientCalledOnce();
expectConfigClientPropertiesHasConfigurationFromEureka();
}
@Test
public void onWhenHeartbeat() throws Exception {
setup("spring.cloud.config.discovery.enabled=true");
assertEquals(1, this.context.getBeanNamesForType(
DiscoveryClientConfigServiceBootstrapConfiguration.class).length);
given(this.client.getInstances(DEFAULT_CONFIG_SERVER))
.willReturn(Arrays.asList(this.info));
Mockito.verify(this.client).getInstances(DEFAULT_CONFIG_SERVER);
expectDiscoveryClientConfigServiceBootstrapConfigurationIsSetup();
givenDiscoveryClientReturnsInfo();
verifyDiscoveryClientCalledOnce();
context.publishEvent(new HeartbeatEvent(context, "new"));
ConfigClientProperties locator = this.context
.getBean(ConfigClientProperties.class);
assertEquals("http://foo:8877/", locator.getRawUri());
expectConfigClientPropertiesHasConfigurationFromEureka();
}
@Test
public void secureWhenRequested() throws Exception {
this.info = new DefaultServiceInstance("app", "foo", 443, true);
given(this.client.getInstances(DEFAULT_CONFIG_SERVER))
.willReturn(Arrays.asList(this.info));
givenDiscoveryClientReturnsInfo();
setup("spring.cloud.config.discovery.enabled=true");
assertEquals(1, this.context.getBeanNamesForType(
DiscoveryClientConfigServiceBootstrapConfiguration.class).length);
Mockito.verify(this.client).getInstances(DEFAULT_CONFIG_SERVER);
ConfigClientProperties locator = this.context
.getBean(ConfigClientProperties.class);
assertEquals("https://foo:443/", locator.getRawUri());
expectDiscoveryClientConfigServiceBootstrapConfigurationIsSetup();
verifyDiscoveryClientCalledOnce();
expectConfigClientPropertiesHasConfiguration("https://foo:443/");
}
@Test
public void setsPasssword() throws Exception {
this.info.getMetadata().put("password", "bar");
given(this.client.getInstances(DEFAULT_CONFIG_SERVER))
.willReturn(Arrays.asList(this.info));
givenDiscoveryClientReturnsInfo();
setup("spring.cloud.config.discovery.enabled=true");
ConfigClientProperties locator = this.context
.getBean(ConfigClientProperties.class);
assertEquals("http://foo:8877/", locator.getRawUri());
@@ -118,25 +94,80 @@ public class DiscoveryClientConfigServiceBootstrapConfigurationTests {
@Test
public void setsPath() throws Exception {
this.info.getMetadata().put("configPath", "/bar");
given(this.client.getInstances(DEFAULT_CONFIG_SERVER))
.willReturn(Arrays.asList(this.info));
givenDiscoveryClientReturnsInfo();
setup("spring.cloud.config.discovery.enabled=true");
ConfigClientProperties locator = this.context
.getBean(ConfigClientProperties.class);
assertEquals("http://foo:8877/bar", locator.getRawUri());
expectConfigClientPropertiesHasConfiguration("http://foo:8877/bar");
}
private void setup(String... env) {
this.context = new AnnotationConfigApplicationContext();
EnvironmentTestUtils.addEnvironment(this.context, env);
EnvironmentTestUtils.addEnvironment(this.context, "eureka.client.enabled=false");
this.context.getDefaultListableBeanFactory().registerSingleton("discoveryClient",
this.client);
this.context.register(UtilAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class,
DiscoveryClientConfigServiceBootstrapConfiguration.class,
ConfigClientProperties.class);
this.context.refresh();
@Test
public void shouldFailGetConfigServerInstanceFromDiscoveryClient() throws Exception {
givenDiscoveryClientReturnsNoInfo();
setup("spring.cloud.config.discovery.enabled=true");
expectDiscoveryClientConfigServiceBootstrapConfigurationIsSetup();
verifyDiscoveryClientCalledOnce();
expectConfigClientPropertiesHasDefaultConfiguration();
}
@Test
public void shouldRetryAndSucceedGetConfigServerInstanceFromDiscoveryClient()
throws Exception {
givenDiscoveryClientReturnsInfoOnThirdTry();
setup("spring.cloud.config.discovery.enabled=true",
"spring.cloud.config.retry.maxAttempts=3",
"spring.cloud.config.retry.initialInterval=10",
"spring.cloud.config.failFast=true");
expectDiscoveryClientConfigServiceBootstrapConfigurationIsSetup();
verifyDiscoveryClientCalledThreeTimes();
context.publishEvent(new HeartbeatEvent(context, "new"));
expectConfigClientPropertiesHasConfigurationFromEureka();
}
@Test
public void shouldNotRetryIfNotFailFastPropertySet() throws Exception {
givenDiscoveryClientReturnsInfoOnThirdTry();
setup("spring.cloud.config.discovery.enabled=true",
"spring.cloud.config.retry.maxAttempts=3",
"spring.cloud.config.retry.initialInterval=10");
expectDiscoveryClientConfigServiceBootstrapConfigurationIsSetup();
verifyDiscoveryClientCalledOnce();
expectConfigClientPropertiesHasDefaultConfiguration();
}
@Test
public void shouldRetryAndFailWithExceptionGetConfigServerInstanceFromDiscoveryClient()
throws Exception {
givenDiscoveryClientReturnsNoInfo();
expectNoInstancesOfConfigServerException();
setup("spring.cloud.config.discovery.enabled=true",
"spring.cloud.config.retry.maxAttempts=3",
"spring.cloud.config.retry.initialInterval=10",
"spring.cloud.config.failFast=true");
}
@Test
public void shouldRetryAndFailWithMessageGetConfigServerInstanceFromDiscoveryClient()
throws Exception {
givenDiscoveryClientReturnsNoInfo();
setup("spring.cloud.config.discovery.enabled=true",
"spring.cloud.config.retry.maxAttempts=3",
"spring.cloud.config.retry.initialInterval=10",
"spring.cloud.config.failFast=false");
expectDiscoveryClientConfigServiceBootstrapConfigurationIsSetup();
expectConfigClientPropertiesHasDefaultConfiguration();
}
}