Skip to content

Commit

Permalink
Ensure changes to eureka metadata are relected on refresh
Browse files Browse the repository at this point in the history
And also when using eureka-first for discovering config server.
The EurekaClient is very rigid and initializes itself, registering
with the remote service on instantiation. Hance it needs to be
in @RefreshScope for it to be properly refreshable. Doing that
leads to a sequence of other changes (e.g. to account for the
fact that there is no @RefreshScope in bootstrap context).

Fixes gh-551
  • Loading branch information
Dave Syer committed Sep 24, 2015
1 parent 581e9bb commit ded968a
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,11 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor;
import org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;

import com.netflix.appinfo.EurekaInstanceConfig;
import com.netflix.discovery.EurekaClientConfig;
import com.netflix.discovery.EurekaClient;

/**
* Bootstrap configuration for a config client that wants to lookup the config server via
Expand All @@ -36,36 +35,23 @@
*/
@ConditionalOnBean({ EurekaDiscoveryClientConfiguration.class })
@ConditionalOnProperty(value = "spring.cloud.config.discovery.enabled", matchIfMissing = false)
@Configuration
@Configuration(value = "DiscoveryClientConfigServiceAutoConfiguration")
public class DiscoveryClientConfigServiceAutoConfiguration {

@Autowired
private EurekaClientConfig clientConfig;

@Autowired
private EurekaInstanceConfig instanceConfig;

@Autowired
private ConfigurationPropertiesBindingPostProcessor binder;

@Autowired
private EurekaDiscoveryClientConfiguration clientConfiguration;
private ConfigurableApplicationContext context;

@PostConstruct
public void init() {
this.clientConfiguration.stop();
rebind(this.clientConfig, "eurekaClientConfig");
rebind(this.instanceConfig, "eurekaInstanceConfig");
// Danger, here be dragons (once it shuts down it's hard to resurrect it)
// eurekaClient.shutdown();
// FIXME: reinit EurekaClient and ApplicationInfoManager
// applicationInfoManager.initComponent(this.instanceConfig);
// discoveryManager.initComponent(this.instanceConfig, this.clientConfig);
this.clientConfiguration.start();
}

private void rebind(Object bean, String name) {
this.binder.postProcessBeforeInitialization(bean, name);
if (this.context.getParent() != null) {
if (this.context.getBeanNamesForType(EurekaClient.class).length > 0
&& this.context.getParent()
.getBeanNamesForType(EurekaClient.class).length > 0) {
// If the parent has a EurekaClient as well it should be shutdown, so the
// local one can register accurate instance info
this.context.getParent().getBean(EurekaClient.class).shutdown();
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,35 @@

package org.springframework.cloud.netflix.eureka;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
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.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration;
import org.springframework.cloud.client.CommonsClientAutoConfiguration;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.noop.NoopDiscoveryClientAutoConfiguration;
import org.springframework.cloud.context.scope.refresh.RefreshScope;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

import com.netflix.appinfo.ApplicationInfoManager;
Expand All @@ -51,11 +66,9 @@
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@AutoConfigureBefore({ NoopDiscoveryClientAutoConfiguration.class,
CommonsClientAutoConfiguration.class })
@AutoConfigureAfter(RefreshAutoConfiguration.class)
public class EurekaClientAutoConfiguration {

@Autowired
private ApplicationContext context;

@Value("${server.port:${SERVER_PORT:${PORT:8080}}}")
int nonSecurePort;

Expand All @@ -69,27 +82,19 @@ public void init() {
}

@Bean
@ConditionalOnMissingBean(EurekaClientConfig.class)
@ConditionalOnMissingBean(value = EurekaClientConfig.class, search = SearchStrategy.CURRENT)
public EurekaClientConfigBean eurekaClientConfigBean() {
return new EurekaClientConfigBean();
}

@Bean
@ConditionalOnMissingBean(EurekaInstanceConfig.class)
@ConditionalOnMissingBean(value = EurekaInstanceConfig.class, search = SearchStrategy.CURRENT)
public EurekaInstanceConfigBean eurekaInstanceConfigBean() {
EurekaInstanceConfigBean instance = new EurekaInstanceConfigBean();
instance.setNonSecurePort(this.nonSecurePort);
return instance;
}

@Bean
@ConditionalOnMissingBean(EurekaClient.class)
@SneakyThrows
public EurekaClient eurekaClient(ApplicationInfoManager applicationInfoManager,
EurekaClientConfig config) {
return new CloudEurekaClient(applicationInfoManager, config, this.context);
}

@Bean
@ConditionalOnMissingBean(ApplicationInfoManager.class)
public ApplicationInfoManager applicationInfoManager(EurekaInstanceConfig config,
Expand All @@ -109,4 +114,84 @@ public DiscoveryClient discoveryClient(EurekaInstanceConfig config,
return new EurekaDiscoveryClient(config, client);
}

@Configuration
@ConditionalOnMissingRefreshScope
protected static class EurekaClientConfiguration {

@Autowired
private ApplicationContext context;

@Bean(destroyMethod = "shutdown")
@ConditionalOnMissingBean(value = EurekaClient.class, search = SearchStrategy.CURRENT)
@SneakyThrows
public EurekaClient eurekaClient(ApplicationInfoManager applicationInfoManager,
EurekaClientConfig config, EurekaInstanceConfig instance) {
applicationInfoManager.initComponent(instance);
return new CloudEurekaClient(applicationInfoManager, config, this.context);
}
}

@Configuration
@ConditionalOnRefreshScope
protected static class RefreshableEurekaClientConfiguration {

@Autowired
private ApplicationContext context;

@Bean(destroyMethod = "shutdown")
@ConditionalOnMissingBean(value = EurekaClient.class, search = SearchStrategy.CURRENT)
@SneakyThrows
@org.springframework.cloud.context.config.annotation.RefreshScope
public EurekaClient eurekaClient(ApplicationInfoManager applicationInfoManager,
EurekaClientConfig config, EurekaInstanceConfig instance) {
applicationInfoManager.initComponent(instance);
return new CloudEurekaClient(applicationInfoManager, config, this.context);
}

}

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnMissingRefreshScopeCondition.class)
@interface ConditionalOnMissingRefreshScope {

}

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnRefreshScopeCondition.class)
@interface ConditionalOnRefreshScope {

}

private static class OnMissingRefreshScopeCondition extends AnyNestedCondition {

public OnMissingRefreshScopeCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}

@ConditionalOnMissingClass("org.springframework.cloud.context.scope.refresh.RefreshScope")
static class MissingClass {
}

@ConditionalOnMissingBean(RefreshScope.class)
static class MissingScope {
}

}

private static class OnRefreshScopeCondition extends AllNestedConditions {

public OnRefreshScopeCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}

@ConditionalOnClass(RefreshScope.class)
@ConditionalOnBean(RefreshScope.class)
static class FoundScope {
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.SmartLifecycle;
import org.springframework.context.annotation.Bean;
Expand Down Expand Up @@ -98,6 +98,7 @@ public void start() {
// because of containerPortInitializer below
if (!this.running.get() && this.instanceConfig.getNonSecurePort() > 0) {

this.eurekaClient.getApplications(); // force initialization
log.info("Registering application " + this.instanceConfig.getAppname()
+ " with eureka with status "
+ this.instanceConfig.getInitialStatus());
Expand Down Expand Up @@ -159,13 +160,13 @@ public void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) {
}

@Configuration
@ConditionalOnClass(EnvironmentChangeEvent.class)
@ConditionalOnClass(RefreshScopeRefreshedEvent.class)
protected static class EurekaClientConfigurationRefresher {
@Autowired
private EurekaDiscoveryClientConfiguration clientConfig;

@EventListener(EnvironmentChangeEvent.class)
public void onApplicationEvent(EnvironmentChangeEvent event) {
@EventListener(RefreshScopeRefreshedEvent.class)
public void onApplicationEvent(RefreshScopeRefreshedEvent event) {
// register in case meta data changed
this.clientConfig.stop();
this.clientConfig.start();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright 2015 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
*
* http://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;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;

import org.junit.After;
import org.junit.Test;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration;
import org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration.ConditionalOnRefreshScope;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* @author Dave Syer
*
*/
public class ConditionalOnRefreshScopeTests {

private ConfigurableApplicationContext context;

@After
public void close() {
if (this.context != null) {
this.context.close();
}
}

@Test
public void refreshScopeIncluded() {
this.context = new SpringApplicationBuilder(RefreshAutoConfiguration.class,
Beans.class).web(false).showBanner(false).run();
assertNotNull(this.context.getBean(
org.springframework.cloud.context.scope.refresh.RefreshScope.class));
assertEquals("foo", this.context.getBean("foo"));
}

@Test
public void refreshScopeNotIncluded() {
this.context = new SpringApplicationBuilder(Beans.class).web(false)
.showBanner(false).run();
assertFalse(this.context.containsBean("foo"));
}

@Configuration
protected static class Beans {
@Bean
@ConditionalOnRefreshScope
public String foo() {
return "foo";
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
import org.springframework.test.context.web.WebAppConfiguration;

import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.DiscoveryClient;
import com.netflix.discovery.EurekaClient;


/**
Expand All @@ -46,17 +46,17 @@
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = EurekaHealthCheckTests.EurekaHealthCheckApplication.class)
@WebAppConfiguration
@IntegrationTest({"server.port=0", "eureka.client.healthcheck.enabled=true"})
@IntegrationTest({"server.port=0", "eureka.client.healthcheck.enabled=true", "debug=true"})
@DirtiesContext
public class EurekaHealthCheckTests {

@Autowired
private DiscoveryClient discoveryClient;
private EurekaClient discoveryClient;

@Test
public void shouldRegisterService() {

InstanceInfo.InstanceStatus status = discoveryClient.getHealthCheckHandler()
InstanceInfo.InstanceStatus status = this.discoveryClient.getHealthCheckHandler()
.getStatus(InstanceInfo.InstanceStatus.UNKNOWN);

assertNotNull(status);
Expand Down
Loading

0 comments on commit ded968a

Please sign in to comment.