Adds ConfigData support for ConfigServerInstanceProvider.Function

DiscoveryClient implementation can now register a ConfigServerInstanceProvider.Function using a Bootstrapper registered in spring.factories.

If spring.cloud.config.discovery.enabled is true, config client will use the above function to locate the initial instance of config server. A ConfigServerInstanceMonitor will be promoted to the application context so subsequent updates to the instanecs of config server will be reported.
This commit is contained in:
spencergibb
2020-09-21 16:00:58 -04:00
parent dae4cafb96
commit 5188344f1b
8 changed files with 498 additions and 118 deletions

View File

@@ -21,6 +21,7 @@ import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnable
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.context.refresh.ContextRefresher;
import org.springframework.context.ApplicationContext;
@@ -43,6 +44,7 @@ import org.springframework.core.env.Environment;
public class ConfigClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public ConfigClientProperties configClientProperties(Environment environment, ApplicationContext context) {
if (context.getParent() != null && BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context.getParent(),
ConfigClientProperties.class).length > 0) {

View File

@@ -44,6 +44,11 @@ public class ConfigClientProperties {
*/
public static final String PREFIX = "spring.cloud.config";
/**
* Name of config discovery enabled property.
*/
public static final String CONFIG_DISCOVERY_ENABLED = PREFIX + ".discovery.enabled";
/**
* Token header name.
*/

View File

@@ -73,6 +73,10 @@ public class ConfigServerConfigDataLoader implements ConfigDataLoader<ConfigServ
@Override
// TODO: implement retry LoaderInterceptor
public ConfigData load(ConfigDataLoaderContext context, ConfigServerConfigDataLocation location) {
if (context.getBootstrapContext().isRegistered(ConfigServerInstanceMonitor.class)) {
// force initialization if needed
context.getBootstrapContext().get(ConfigServerInstanceMonitor.class);
}
if (context.getBootstrapContext().isRegistered(LoaderInterceptor.class)) {
LoaderInterceptor interceptor = context.getBootstrapContext().get(LoaderInterceptor.class);
Binder binder = context.getBootstrapContext().get(Binder.class);

View File

@@ -37,6 +37,7 @@ import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import static org.springframework.cloud.config.client.ConfigClientProperties.AUTHORIZATION;
import static org.springframework.cloud.config.client.ConfigClientProperties.CONFIG_DISCOVERY_ENABLED;
public class ConfigServerConfigDataLocationResolver
implements ConfigDataLocationResolver<ConfigServerConfigDataLocation>, Ordered {
@@ -109,10 +110,10 @@ public class ConfigServerConfigDataLocationResolver
return Collections.emptyList();
}
public List<ConfigServerConfigDataLocation> resolveProfileSpecific(ConfigDataLocationResolverContext context,
String location, boolean optional, Profiles profiles) {
public List<ConfigServerConfigDataLocation> resolveProfileSpecific(
ConfigDataLocationResolverContext resolverContext, String location, boolean optional, Profiles profiles) {
ConfigClientProperties properties = loadProperties(context.getBinder());
ConfigClientProperties properties = loadProperties(resolverContext.getBinder());
String uris = (location.startsWith(getPrefix())) ? location.substring(getPrefix().length()) : location;
@@ -121,15 +122,41 @@ public class ConfigServerConfigDataLocationResolver
properties.setUri(uri);
}
ConfigurableBootstrapContext bootstrapContext = context.getBootstrapContext();
ConfigurableBootstrapContext bootstrapContext = resolverContext.getBootstrapContext();
bootstrapContext.registerIfAbsent(ConfigClientProperties.class, InstanceSupplier.of(properties));
bootstrapContext.addCloseListener(event -> event.getApplicationContext().getBeanFactory().registerSingleton(
"configDataConfigClientProperties", event.getBootstrapContext().get(ConfigClientProperties.class)));
bootstrapContext.registerIfAbsent(RestTemplate.class, InstanceSupplier.from(() -> {
ConfigClientProperties props = bootstrapContext.get(ConfigClientProperties.class);
bootstrapContext.registerIfAbsent(RestTemplate.class, context -> {
ConfigClientProperties props = context.get(ConfigClientProperties.class);
return createRestTemplate(props);
}));
});
boolean discoveryEnabled = resolverContext.getBinder().bind(CONFIG_DISCOVERY_ENABLED, Boolean.class)
.orElse(false);
if (discoveryEnabled) {
// register ConfigServerInstanceMonitor
bootstrapContext.registerIfAbsent(ConfigServerInstanceMonitor.class, context -> {
ConfigServerInstanceProvider.Function function = context
.get(ConfigServerInstanceProvider.Function.class);
ConfigServerInstanceProvider instanceProvider = new ConfigServerInstanceProvider(function);
instanceProvider.setLog(log);
ConfigClientProperties clientProperties = context.get(ConfigClientProperties.class);
ConfigServerInstanceMonitor instanceMonitor = new ConfigServerInstanceMonitor(log, clientProperties,
instanceProvider);
instanceMonitor.setRefreshOnStartup(false);
instanceMonitor.refresh();
return instanceMonitor;
});
// promote ConfigServerInstanceMonitor to bean so updates can be made to config client uri
bootstrapContext.addCloseListener(event -> {
ConfigServerInstanceMonitor configServerInstanceMonitor = event.getBootstrapContext()
.get(ConfigServerInstanceMonitor.class);
event.getApplicationContext().getBeanFactory().registerSingleton("configServerInstanceMonitor",
configServerInstanceMonitor);
});
}
List<ConfigServerConfigDataLocation> locations = new ArrayList<>();
locations.add(new ConfigServerConfigDataLocation(properties, optional, profiles));

View File

@@ -0,0 +1,139 @@
/*
* Copyright 2013-2020 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.config.client;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
import org.springframework.cloud.client.discovery.event.HeartbeatMonitor;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.SmartApplicationListener;
import org.springframework.util.Assert;
final class ConfigServerInstanceMonitor implements SmartApplicationListener {
private final Log log;
private final ConfigClientProperties config;
private final ConfigServerInstanceProvider instanceProvider;
private final HeartbeatMonitor monitor = new HeartbeatMonitor();
/**
* If bootstrap, this should be true, for config data false.
*/
private boolean refreshOnStartup = true;
ConfigServerInstanceMonitor(Log log, ConfigClientProperties config, ConfigServerInstanceProvider instanceProvider) {
this.log = log;
this.config = config;
this.instanceProvider = instanceProvider;
}
void setRefreshOnStartup(boolean refreshOnStartup) {
this.refreshOnStartup = refreshOnStartup;
}
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return ContextRefreshedEvent.class.isAssignableFrom(eventType)
|| HeartbeatEvent.class.isAssignableFrom(eventType);
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextRefreshedEvent) {
startup((ContextRefreshedEvent) event);
}
else if (event instanceof HeartbeatEvent) {
heartbeat((HeartbeatEvent) event);
}
}
public void startup(ContextRefreshedEvent event) {
if (refreshOnStartup) {
refresh();
}
}
public void heartbeat(HeartbeatEvent event) {
if (this.monitor.update(event.getValue())) {
refresh();
}
}
void refresh() {
try {
String serviceId = this.config.getDiscovery().getServiceId();
Assert.hasText(serviceId, () -> ConfigClientProperties.PREFIX + ".service-id may not be null or empty");
List<String> listOfUrls = new ArrayList<>();
List<ServiceInstance> serviceInstances = this.instanceProvider.getConfigServerInstances(serviceId);
for (int i = 0; i < serviceInstances.size(); i++) {
ServiceInstance server = serviceInstances.get(i);
String url = getHomePage(server);
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);
}
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;
}
listOfUrls.add(url);
}
if (log.isDebugEnabled()) {
log.debug("Updating config uris to " + listOfUrls);
}
String[] uri = new String[listOfUrls.size()];
uri = listOfUrls.toArray(uri);
this.config.setUri(uri);
}
catch (Exception ex) {
if (this.config.isFailFast()) {
throw ex;
}
else if (log.isWarnEnabled()) {
log.warn("Could not locate configserver via discovery", ex);
}
}
}
private String getHomePage(ServiceInstance server) {
return server.getUri().toString() + "/";
}
}

View File

@@ -32,10 +32,11 @@ import org.springframework.retry.annotation.Retryable;
*/
public class ConfigServerInstanceProvider {
private static Log logger = LogFactory.getLog(ConfigServerInstanceProvider.class);
private Log log = LogFactory.getLog(getClass());
private final Function function;
@Deprecated
public ConfigServerInstanceProvider(DiscoveryClient client) {
this.function = client::getInstances;
}
@@ -44,15 +45,23 @@ public class ConfigServerInstanceProvider {
this.function = function;
}
void setLog(Log log) {
this.log = log;
}
@Retryable(interceptor = "configServerRetryInterceptor")
public List<ServiceInstance> getConfigServerInstances(String serviceId) {
logger.debug("Locating configserver (" + serviceId + ") via discovery");
if (log.isDebugEnabled()) {
log.debug("Locating configserver (" + serviceId + ") via discovery");
}
List<ServiceInstance> instances = this.function.apply(serviceId);
if (instances.isEmpty()) {
throw new IllegalStateException("No instances found of configserver (" + serviceId + ")");
}
logger.debug(
"Located configserver (" + serviceId + ") via discovery. No of instances found: " + instances.size());
if (log.isDebugEnabled()) {
log.debug("Located configserver (" + serviceId + ") via discovery. No of instances found: " + instances
.size());
}
return instances;
}

View File

@@ -16,26 +16,16 @@
package org.springframework.cloud.config.client;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
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.ApplicationEvent;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.SmartApplicationListener;
/**
* Bootstrap configuration for a config client that wants to lookup the config server via
@@ -43,14 +33,12 @@ import org.springframework.context.event.SmartApplicationListener;
*
* @author Dave Syer
*/
@ConditionalOnProperty(value = "spring.cloud.config.discovery.enabled", matchIfMissing = false)
@ConditionalOnProperty(ConfigClientProperties.CONFIG_DISCOVERY_ENABLED)
@Configuration(proxyBeanMethods = false)
@Import({ UtilAutoConfiguration.class })
@EnableDiscoveryClient
public class DiscoveryClientConfigServiceBootstrapConfiguration {
private static Log logger = LogFactory.getLog(DiscoveryClientConfigServiceBootstrapConfiguration.class);
@Bean
public ConfigServerInstanceProvider configServerInstanceProvider(
ObjectProvider<ConfigServerInstanceProvider.Function> function,
@@ -61,105 +49,16 @@ public class DiscoveryClientConfigServiceBootstrapConfiguration {
}
DiscoveryClient client = discoveryClient.getIfAvailable();
if (client == null) {
throw new IllegalStateException("ConfigServerInstanceProvider reqiures a DiscoveryClient or Function");
throw new IllegalStateException("ConfigServerInstanceProvider requires a DiscoveryClient or Function");
}
return new ConfigServerInstanceProvider(client);
return new ConfigServerInstanceProvider(client::getInstances);
}
@Bean
public SmartApplicationListener heartbeatListener(ConfigClientProperties properties,
public ConfigServerInstanceMonitor configServerInstanceMonitor(ConfigClientProperties properties,
ConfigServerInstanceProvider provider) {
return new HeartbeatListener(properties, provider);
}
private final static class HeartbeatListener implements SmartApplicationListener {
private final ConfigClientProperties config;
private final ConfigServerInstanceProvider instanceProvider;
private final HeartbeatMonitor monitor = new HeartbeatMonitor();
private HeartbeatListener(ConfigClientProperties config, ConfigServerInstanceProvider instanceProvider) {
this.config = config;
this.instanceProvider = instanceProvider;
}
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return ContextRefreshedEvent.class.isAssignableFrom(eventType)
|| HeartbeatEvent.class.isAssignableFrom(eventType);
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextRefreshedEvent) {
startup((ContextRefreshedEvent) event);
}
else if (event instanceof HeartbeatEvent) {
heartbeat((HeartbeatEvent) event);
}
}
public void startup(ContextRefreshedEvent event) {
refresh();
}
public void heartbeat(HeartbeatEvent event) {
if (this.monitor.update(event.getValue())) {
refresh();
}
}
private void refresh() {
try {
String serviceId = this.config.getDiscovery().getServiceId();
List<String> listOfUrls = new ArrayList<>();
List<ServiceInstance> serviceInstances = this.instanceProvider.getConfigServerInstances(serviceId);
for (int i = 0; i < serviceInstances.size(); i++) {
ServiceInstance server = serviceInstances.get(i);
String url = getHomePage(server);
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);
}
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;
}
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);
}
}
}
private String getHomePage(ServiceInstance server) {
return server.getUri().toString() + "/";
}
return new ConfigServerInstanceMonitor(LogFactory.getLog(ConfigServerInstanceMonitor.class), properties,
provider);
}
}

View File

@@ -0,0 +1,295 @@
/*
* 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.config.client;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.boot.BootstrapRegistry;
import org.springframework.boot.Bootstrapper;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
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.config.client.ConfigClientProperties.Credentials;
import org.springframework.context.ConfigurableApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
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;
/**
* @author Dave Syer
*/
public class DiscoveryClientConfigDataConfigurationTests {
protected ConfigurableApplicationContext context;
protected DiscoveryClient client = Mockito.mock(DiscoveryClient.class);
protected ServiceInstance info = new DefaultServiceInstance("app:8877", "app", "foo", 8877, false);
@AfterEach
public void close() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void offByDefault() {
context = new SpringApplicationBuilder(TestConfig.class)
.addBootstrapper(registry -> registry.addCloseListener(event -> {
try {
event.getBootstrapContext().get(ConfigServerInstanceMonitor.class);
fail("ConfigServerInstanceMonitor was created when it shouldn't");
}
catch (IllegalStateException e) {
// expected
}
})).run();
}
@Test
public void onWhenRequested() {
givenDiscoveryClientReturnsInfo();
setupAndRun();
verifyDiscoveryClientCalledOnce();
expectConfigClientPropertiesHasConfigurationFromDiscovery();
}
@Test
public void onWhenHeartbeat() {
setupAndRun();
givenDiscoveryClientReturnsInfo();
verifyDiscoveryClientCalledOnce();
this.context.publishEvent(new HeartbeatEvent(this.context, "new"));
expectConfigClientPropertiesHasConfigurationFromDiscovery();
}
@Test
public void secureWhenRequested() {
this.info = new DefaultServiceInstance("app:443", "app", "foo", 443, true);
givenDiscoveryClientReturnsInfo();
setupAndRun();
verifyDiscoveryClientCalledOnce();
expectConfigClientPropertiesHasConfiguration("https://foo:443/");
}
@Test
public void multipleInstancesReturnedFromDiscovery() {
ServiceInstance info1 = new DefaultServiceInstance("app1:8888", "app", "localhost", 8888, true);
ServiceInstance info2 = new DefaultServiceInstance("app2:8888", "app", "localhost1", 8888, false);
givenDiscoveryClientReturnsInfo(info1, info2);
setupAndRun();
verifyDiscoveryClientCalledOnce();
ConfigClientProperties properties = this.context.getBean(ConfigClientProperties.class);
assertThat(properties.getUri().length).isEqualTo(2);
Credentials credentials1 = properties.getCredentials(0);
Credentials credentials2 = properties.getCredentials(1);
assertThat(credentials1.getUri()).isEqualTo("https://localhost:8888/");
assertThat(credentials2.getUri()).isEqualTo("http://localhost1:8888/");
}
@Test
public void setsPasssword() {
this.info.getMetadata().put("password", "bar");
givenDiscoveryClientReturnsInfo();
setupAndRun();
ConfigClientProperties locator = this.context.getBean(ConfigClientProperties.class);
Credentials credentials = locator.getCredentials(0);
assertThat(credentials.getUri()).isEqualTo("http://foo:8877/");
assertThat(credentials.getPassword()).isEqualTo("bar");
assertThat(credentials.getUsername()).isEqualTo("user");
}
@Test
public void setsPath() {
this.info.getMetadata().put("configPath", "/bar");
givenDiscoveryClientReturnsInfo();
setupAndRun();
expectConfigClientPropertiesHasConfiguration("http://foo:8877/bar");
}
@Test
public void shouldFailGetConfigServerInstanceFromDiscoveryClient() {
givenDiscoveryClientReturnsNoInfo();
setupAndRun();
verifyDiscoveryClientCalledOnce();
expectConfigClientPropertiesHasDefaultConfiguration();
}
@Test
@Disabled
public void shouldRetryAndSucceedGetConfigServerInstanceFromDiscoveryClient() {
givenDiscoveryClientReturnsInfoOnThirdTry();
context = setup("spring.cloud.config.retry.maxAttempts=3", "spring.cloud.config.retry.initialInterval=10",
"spring.cloud.config.fail-fast=true").run();
verifyDiscoveryClientCalledThreeTimes();
this.context.publishEvent(new HeartbeatEvent(this.context, "new"));
expectConfigClientPropertiesHasConfigurationFromDiscovery();
}
@Test
@Disabled
public void shouldNotRetryIfNotFailFastPropertySet() {
givenDiscoveryClientReturnsInfoOnThirdTry();
context = setup("spring.cloud.config.retry.maxAttempts=3", "spring.cloud.config.retry.initialInterval=10")
.run();
verifyDiscoveryClientCalledOnce();
expectConfigClientPropertiesHasDefaultConfiguration();
}
@Test
@Disabled
public void shouldRetryAndFailWithExceptionGetConfigServerInstanceFromDiscoveryClient() {
givenDiscoveryClientReturnsNoInfo();
// expectNoInstancesOfConfigServerException();
context = setup("spring.cloud.config.retry.maxAttempts=3", "spring.cloud.config.retry.initialInterval=10",
"spring.cloud.config.fail-fast=true").run();
}
@Test
@Disabled
public void shouldRetryAndFailWithMessageGetConfigServerInstanceFromDiscoveryClient() {
givenDiscoveryClientReturnsNoInfo();
context = setup("spring.cloud.config.retry.maxAttempts=3", "spring.cloud.config.retry.initialInterval=10",
"spring.cloud.config.fail-fast=false").run();
expectConfigClientPropertiesHasDefaultConfiguration();
}
SpringApplicationBuilder setup(String... env) {
return setup(true, env);
}
SpringApplicationBuilder setup(boolean addInstanceProvider, String... env) {
SpringApplicationBuilder builder = new SpringApplicationBuilder(TestConfig.class)
.properties(addDefaultEnv(env));
if (addInstanceProvider) {
builder.addBootstrapper(instanceProviderBootstrapper());
}
return builder.addBootstrapper(registry -> registry.addCloseListener(event -> {
ConfigServerInstanceMonitor monitor = event.getBootstrapContext().get(ConfigServerInstanceMonitor.class);
assertThat(monitor).as("ConfigServerInstanceMonitor was not created when it should").isNotNull();
}));
}
protected Bootstrapper instanceProviderBootstrapper() {
return registry -> registry.register(ConfigServerInstanceProvider.Function.class,
BootstrapRegistry.InstanceSupplier.from(() -> this.client::getInstances));
}
protected void setupAndRun(String... env) {
context = setup(addDefaultEnv(env)).run();
}
private String[] addDefaultEnv(String[] env) {
Set<String> set = new LinkedHashSet<>();
if (env != null && env.length > 0) {
set.addAll(Arrays.asList(env));
}
set.add("spring.cloud.config.discovery.enabled=true");
set.add("spring.config.import=optional:configserver:");
return set.toArray(new String[0]);
}
void givenDiscoveryClientReturnsInfo() {
givenDiscoveryClientReturnsInfo(this.info);
}
void givenDiscoveryClientReturnsInfo(ServiceInstance... instances) {
given(this.client.getInstances(DEFAULT_CONFIG_SERVER)).willReturn(Arrays.asList(instances));
}
void givenDiscoveryClientReturnsNoInfo() {
given(this.client.getInstances(DEFAULT_CONFIG_SERVER)).willReturn(Collections.emptyList());
}
void givenDiscoveryClientReturnsInfoOnThirdTry() {
given(this.client.getInstances(DEFAULT_CONFIG_SERVER)).willReturn(Collections.emptyList())
.willReturn(Collections.emptyList()).willReturn(Collections.singletonList(this.info));
}
void verifyDiscoveryClientCalledOnce() {
verify(this.client).getInstances(DEFAULT_CONFIG_SERVER);
}
void verifyDiscoveryClientCalledThreeTimes() {
verify(this.client, times(3)).getInstances(DEFAULT_CONFIG_SERVER);
}
void expectConfigClientPropertiesHasConfigurationFromDiscovery() {
expectConfigClientPropertiesHasConfiguration("http://foo:8877/");
}
void expectConfigClientPropertiesHasDefaultConfiguration() {
expectConfigClientPropertiesHasConfiguration("http://localhost:8888");
}
void expectConfigClientPropertiesHasConfiguration(final String expectedUri) {
ConfigClientProperties properties = this.context.getBean(ConfigClientProperties.class);
Credentials credentials = properties.getCredentials(0);
assertThat(credentials.getUri()).isEqualTo(expectedUri);
}
@SpringBootConfiguration
@EnableAutoConfiguration
static class TestConfig {
}
}