Commit ded968ad by Dave Syer

Ensure changes to eureka metadata are relected on refresh

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
parent 581e9bbd
......@@ -21,12 +21,11 @@ import javax.annotation.PostConstruct;
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
......@@ -36,36 +35,23 @@ import com.netflix.discovery.EurekaClientConfig;
*/
@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();
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();
}
}
private void rebind(Object bean, String name) {
this.binder.postProcessBeforeInitialization(bean, name);
}
}
......@@ -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;
......@@ -51,11 +66,9 @@ import lombok.SneakyThrows;
@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;
......@@ -69,13 +82,13 @@ public class EurekaClientAutoConfiguration {
}
@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);
......@@ -83,14 +96,6 @@ public class EurekaClientAutoConfiguration {
}
@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,
InstanceInfo instanceInfo) {
......@@ -109,4 +114,84 @@ public class EurekaClientAutoConfiguration {
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 {
}
}
}
......@@ -35,7 +35,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
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;
......@@ -98,6 +98,7 @@ public class EurekaDiscoveryClientConfiguration implements SmartLifecycle, Order
// 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());
......@@ -159,13 +160,13 @@ public class EurekaDiscoveryClientConfiguration implements SmartLifecycle, Order
}
@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();
......
/*
* 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";
}
}
}
......@@ -35,7 +35,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.DiscoveryClient;
import com.netflix.discovery.EurekaClient;
/**
......@@ -46,17 +46,17 @@ import com.netflix.discovery.DiscoveryClient;
@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);
......
......@@ -58,6 +58,7 @@ import org.springframework.web.context.ServletContextAware;
import com.netflix.blitz4j.DefaultBlitz4jConfig;
import com.netflix.blitz4j.LoggingConfiguration;
import com.netflix.discovery.EurekaClient;
import com.netflix.discovery.converters.JsonXStream;
import com.netflix.discovery.converters.XmlXStream;
import com.netflix.eureka.AbstractInstanceRegistry;
......@@ -72,8 +73,8 @@ import com.netflix.eureka.PeerAwareInstanceRegistryImpl;
*/
@Configuration
@EnableConfigurationProperties(EurekaServerConfigBean.class)
public class EurekaServerInitializerConfiguration implements ServletContextAware,
SmartLifecycle, Ordered {
public class EurekaServerInitializerConfiguration
implements ServletContextAware, SmartLifecycle, Ordered {
private static Log logger = LogFactory
.getLog(EurekaServerInitializerConfiguration.class);
......@@ -98,7 +99,8 @@ public class EurekaServerInitializerConfiguration implements ServletContextAware
@PostConstruct
public void initLogging() {
if (!(LoggingSystem.get(ClassUtils.getDefaultClassLoader()) instanceof Log4JLoggingSystem)) {
if (!(LoggingSystem
.get(ClassUtils.getDefaultClassLoader()) instanceof Log4JLoggingSystem)) {
LoggingConfiguration off = new LoggingConfiguration() {
@Override
......@@ -206,11 +208,11 @@ public class EurekaServerInitializerConfiguration implements ServletContextAware
@Configuration
@ConditionalOnClass(PeerAwareInstanceRegistry.class)
protected static class RegistryInstanceProxyInitializer implements
ApplicationListener<EurekaRegistryAvailableEvent> {
protected static class RegistryInstanceProxyInitializer
implements ApplicationListener<EurekaRegistryAvailableEvent> {
@Autowired
private ApplicationContext applicationContext;
@Autowired(required = false)
private EurekaClient client;
private PeerAwareInstanceRegistryImpl instance;
......@@ -221,6 +223,9 @@ public class EurekaServerInitializerConfiguration implements ServletContextAware
@Override
public void onApplicationEvent(EurekaRegistryAvailableEvent event) {
if (this.client != null) {
this.client.getApplications(); // force initialization
}
if (this.instance == null) {
this.instance = PeerAwareInstanceRegistryImpl.getInstance();
safeInit();
......@@ -230,8 +235,8 @@ public class EurekaServerInitializerConfiguration implements ServletContextAware
}
private void safeInit() {
Method method = ReflectionUtils
.findMethod(AbstractInstanceRegistry.class, "postInit");
Method method = ReflectionUtils.findMethod(AbstractInstanceRegistry.class,
"postInit");
ReflectionUtils.makeAccessible(method);
ReflectionUtils.invokeMethod(method, this.instance);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment