Skip to content

Commit

Permalink
Adds ConfigServerInstanceProvider.Function interface.
Browse files Browse the repository at this point in the history
This allows for another implementation besides DiscoveryClient.

Moves SmartApplicationListener from bootstrap configuration to HeartbeatListener class.
  • Loading branch information
spencergibb committed May 14, 2020
1 parent 0492371 commit 235c50b
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 74 deletions.
Expand Up @@ -34,16 +34,20 @@ public class ConfigServerInstanceProvider {

private static Log logger = LogFactory.getLog(ConfigServerInstanceProvider.class);

private final DiscoveryClient client;
private final Function function;

public ConfigServerInstanceProvider(DiscoveryClient client) {
this.client = client;
this.function = client::getInstances;
}

public ConfigServerInstanceProvider(Function function) {
this.function = function;
}

@Retryable(interceptor = "configServerRetryInterceptor")
public List<ServiceInstance> getConfigServerInstances(String serviceId) {
logger.debug("Locating configserver (" + serviceId + ") via discovery");
List<ServiceInstance> instances = this.client.getInstances(serviceId);
List<ServiceInstance> instances = this.function.apply(serviceId);
if (instances.isEmpty()) {
throw new IllegalStateException(
"No instances found of configserver (" + serviceId + ")");
Expand All @@ -53,4 +57,11 @@ public List<ServiceInstance> getConfigServerInstances(String serviceId) {
return instances;
}

@FunctionalInterface
public interface Function {

List<ServiceInstance> apply(String serviceId);

}

}
Expand Up @@ -22,6 +22,7 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.ServiceInstance;
Expand All @@ -48,100 +49,119 @@
@Configuration(proxyBeanMethods = false)
@Import({ UtilAutoConfiguration.class })
@EnableDiscoveryClient
public class DiscoveryClientConfigServiceBootstrapConfiguration
implements SmartApplicationListener {
public class DiscoveryClientConfigServiceBootstrapConfiguration {

private static Log logger = LogFactory
.getLog(DiscoveryClientConfigServiceBootstrapConfiguration.class);

@Autowired
private ConfigClientProperties config;

@Autowired
private ConfigServerInstanceProvider instanceProvider;

private HeartbeatMonitor monitor = new HeartbeatMonitor();

@Bean
public ConfigServerInstanceProvider configServerInstanceProvider(
DiscoveryClient discoveryClient) {
return new ConfigServerInstanceProvider(discoveryClient);
ObjectProvider<ConfigServerInstanceProvider.Function> function,
ObjectProvider<DiscoveryClient> discoveryClient) {
ConfigServerInstanceProvider.Function fn = function.getIfAvailable();
if (fn != null) {
return new ConfigServerInstanceProvider(fn);
}
DiscoveryClient client = discoveryClient.getIfAvailable();
if (client == null) {
throw new IllegalStateException(
"ConfigServerInstanceProvider reqiures a DiscoveryClient or Function");
}
return new ConfigServerInstanceProvider(client);
}

@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return ContextRefreshedEvent.class.isAssignableFrom(eventType)
|| HeartbeatEvent.class.isAssignableFrom(eventType);
@Bean
public SmartApplicationListener heartbeatListener(
ConfigServerInstanceProvider provider) {
return new HeartbeatListener();
}

@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextRefreshedEvent) {
startup((ContextRefreshedEvent) event);
}
else if (event instanceof HeartbeatEvent) {
heartbeat((HeartbeatEvent) event);
private static class HeartbeatListener implements SmartApplicationListener {

@Autowired
private ConfigClientProperties config;

@Autowired
private ConfigServerInstanceProvider instanceProvider;

private HeartbeatMonitor monitor = new HeartbeatMonitor();

@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return ContextRefreshedEvent.class.isAssignableFrom(eventType)
|| HeartbeatEvent.class.isAssignableFrom(eventType);
}
}

public void startup(ContextRefreshedEvent event) {
refresh();
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextRefreshedEvent) {
startup((ContextRefreshedEvent) event);
}
else if (event instanceof HeartbeatEvent) {
heartbeat((HeartbeatEvent) event);
}
}

public void heartbeat(HeartbeatEvent event) {
if (this.monitor.update(event.getValue())) {
public void startup(ContextRefreshedEvent event) {
refresh();
}
}

private void refresh() {
try {
String serviceId = this.config.getDiscovery().getServiceId();
List<String> listOfUrls = new ArrayList<>();
List<ServiceInstance> serviceInstances = this.instanceProvider
.getConfigServerInstances(serviceId);
public void heartbeat(HeartbeatEvent event) {
if (this.monitor.update(event.getValue())) {
refresh();
}
}

for (int i = 0; i < serviceInstances.size(); i++) {
private void refresh() {
try {
String serviceId = this.config.getDiscovery().getServiceId();
List<String> listOfUrls = new ArrayList<>();
List<ServiceInstance> serviceInstances = this.instanceProvider
.getConfigServerInstances(serviceId);

ServiceInstance server = serviceInstances.get(i);
String url = getHomePage(server);
for (int i = 0; i < serviceInstances.size(); i++) {

if (server.getMetadata().containsKey("password")) {
String user = server.getMetadata().get("user");
user = user == null ? "user" : user;
this.config.setUsername(user);
String password = server.getMetadata().get("password");
this.config.setPassword(password);
}
ServiceInstance server = serviceInstances.get(i);
String url = getHomePage(server);

if (server.getMetadata().containsKey("configPath")) {
String path = server.getMetadata().get("configPath");
if (url.endsWith("/") && path.startsWith("/")) {
url = url.substring(0, url.length() - 1);
if (server.getMetadata().containsKey("password")) {
String user = server.getMetadata().get("user");
user = user == null ? "user" : user;
this.config.setUsername(user);
String password = server.getMetadata().get("password");
this.config.setPassword(password);
}
url = url + path;
}

listOfUrls.add(url);
}
if (server.getMetadata().containsKey("configPath")) {
String path = server.getMetadata().get("configPath");
if (url.endsWith("/") && path.startsWith("/")) {
url = url.substring(0, url.length() - 1);
}
url = url + path;
}

String[] uri = new String[listOfUrls.size()];
uri = listOfUrls.toArray(uri);
this.config.setUri(uri);
listOfUrls.add(url);
}

String[] uri = new String[listOfUrls.size()];
uri = listOfUrls.toArray(uri);
this.config.setUri(uri);

}
catch (Exception ex) {
if (this.config.isFailFast()) {
throw ex;
}
else {
logger.warn("Could not locate configserver via discovery", ex);
catch (Exception ex) {
if (this.config.isFailFast()) {
throw ex;
}
else {
logger.warn("Could not locate configserver via discovery", ex);
}
}
}
}

private String getHomePage(ServiceInstance server) {
return server.getUri().toString() + "/";
private String getHomePage(ServiceInstance server) {
return server.getUri().toString() + "/";
}

}

}
Expand Up @@ -65,7 +65,7 @@ void givenDiscoveryClientReturnsNoInfo() {

void givenDiscoveryClientReturnsInfo() {
given(this.client.getInstances(DEFAULT_CONFIG_SERVER))
.willReturn(Arrays.asList(this.info));
.willReturn(Collections.singletonList(this.info));
}

void givenDiscoveryClientReturnsInfoForMultipleInstances(ServiceInstance info1,
Expand All @@ -78,7 +78,7 @@ void givenDiscoveryClientReturnsInfoOnThirdTry() {
given(this.client.getInstances(DEFAULT_CONFIG_SERVER))
.willReturn(Collections.<ServiceInstance>emptyList())
.willReturn(Collections.<ServiceInstance>emptyList())
.willReturn(Arrays.asList(this.info));
.willReturn(Collections.singletonList(this.info));
}

void expectNoInstancesOfConfigServerException() {
Expand Down Expand Up @@ -128,16 +128,24 @@ void verifyDiscoveryClientCalledOnce() {
}

void setup(String... env) {
setup(true, true, env);
}

void setup(boolean refresh, boolean registerDiscoveryClient, String... env) {
this.context = new AnnotationConfigApplicationContext();
TestPropertyValues.of(env).applyTo(this.context);
TestPropertyValues.of("eureka.client.enabled=false").applyTo(this.context);
this.context.getDefaultListableBeanFactory().registerSingleton("discoveryClient",
this.client);
if (registerDiscoveryClient) {
this.context.getDefaultListableBeanFactory()
.registerSingleton("discoveryClient", this.client);
}
this.context.register(UtilAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class,
DiscoveryClientConfigServiceBootstrapConfiguration.class,
ConfigServiceBootstrapConfiguration.class, ConfigClientProperties.class);
this.context.refresh();
if (refresh) {
this.context.refresh();
}
}

}
Expand Up @@ -16,6 +16,8 @@

package org.springframework.cloud.config.client;

import java.util.Collections;

import org.junit.Test;

import org.springframework.cloud.client.DefaultServiceInstance;
Expand All @@ -26,6 +28,10 @@
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.springframework.cloud.config.client.ConfigClientProperties.Discovery.DEFAULT_CONFIG_SERVER;

/**
* @author Dave Syer
Expand Down Expand Up @@ -56,6 +62,23 @@ public void onWhenRequested() throws Exception {
expectConfigClientPropertiesHasConfigurationFromEureka();
}

@Test
public void configServerInstanceProviderFunction() {
ConfigServerInstanceProvider.Function function = mock(
ConfigServerInstanceProvider.Function.class);
given(function.apply(DEFAULT_CONFIG_SERVER))
.willReturn(Collections.singletonList(this.info));

setup(false, false, "spring.cloud.config.discovery.enabled=true");
this.context.getDefaultListableBeanFactory().registerSingleton("myFunction",
function);
this.context.refresh();

expectDiscoveryClientConfigServiceBootstrapConfigurationIsSetup();
verify(function).apply(DEFAULT_CONFIG_SERVER);
expectConfigClientPropertiesHasConfigurationFromEureka();
}

@Test
public void onWhenHeartbeat() throws Exception {
setup("spring.cloud.config.discovery.enabled=true");
Expand Down

0 comments on commit 235c50b

Please sign in to comment.