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; ...@@ -21,12 +21,11 @@ import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor;
import org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration; import org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import com.netflix.appinfo.EurekaInstanceConfig; import com.netflix.discovery.EurekaClient;
import com.netflix.discovery.EurekaClientConfig;
/** /**
* Bootstrap configuration for a config client that wants to lookup the config server via * Bootstrap configuration for a config client that wants to lookup the config server via
...@@ -36,36 +35,23 @@ import com.netflix.discovery.EurekaClientConfig; ...@@ -36,36 +35,23 @@ import com.netflix.discovery.EurekaClientConfig;
*/ */
@ConditionalOnBean({ EurekaDiscoveryClientConfiguration.class }) @ConditionalOnBean({ EurekaDiscoveryClientConfiguration.class })
@ConditionalOnProperty(value = "spring.cloud.config.discovery.enabled", matchIfMissing = false) @ConditionalOnProperty(value = "spring.cloud.config.discovery.enabled", matchIfMissing = false)
@Configuration @Configuration(value = "DiscoveryClientConfigServiceAutoConfiguration")
public class DiscoveryClientConfigServiceAutoConfiguration { public class DiscoveryClientConfigServiceAutoConfiguration {
@Autowired @Autowired
private EurekaClientConfig clientConfig; private ConfigurableApplicationContext context;
@Autowired
private EurekaInstanceConfig instanceConfig;
@Autowired
private ConfigurationPropertiesBindingPostProcessor binder;
@Autowired
private EurekaDiscoveryClientConfiguration clientConfiguration;
@PostConstruct @PostConstruct
public void init() { public void init() {
this.clientConfiguration.stop(); if (this.context.getParent() != null) {
rebind(this.clientConfig, "eurekaClientConfig"); if (this.context.getBeanNamesForType(EurekaClient.class).length > 0
rebind(this.instanceConfig, "eurekaInstanceConfig"); && this.context.getParent()
// Danger, here be dragons (once it shuts down it's hard to resurrect it) .getBeanNamesForType(EurekaClient.class).length > 0) {
// eurekaClient.shutdown(); // If the parent has a EurekaClient as well it should be shutdown, so the
// FIXME: reinit EurekaClient and ApplicationInfoManager // local one can register accurate instance info
// applicationInfoManager.initComponent(this.instanceConfig); this.context.getParent().getBean(EurekaClient.class).shutdown();
// discoveryManager.initComponent(this.instanceConfig, this.clientConfig); }
this.clientConfiguration.start(); }
}
private void rebind(Object bean, String name) {
this.binder.postProcessBeforeInitialization(bean, name);
} }
} }
...@@ -16,20 +16,35 @@ ...@@ -16,20 +16,35 @@
package org.springframework.cloud.netflix.eureka; 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 javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore; 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.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 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.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration;
import org.springframework.cloud.client.CommonsClientAutoConfiguration; import org.springframework.cloud.client.CommonsClientAutoConfiguration;
import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.noop.NoopDiscoveryClientAutoConfiguration; 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.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import com.netflix.appinfo.ApplicationInfoManager; import com.netflix.appinfo.ApplicationInfoManager;
...@@ -51,11 +66,9 @@ import lombok.SneakyThrows; ...@@ -51,11 +66,9 @@ import lombok.SneakyThrows;
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true) @ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@AutoConfigureBefore({ NoopDiscoveryClientAutoConfiguration.class, @AutoConfigureBefore({ NoopDiscoveryClientAutoConfiguration.class,
CommonsClientAutoConfiguration.class }) CommonsClientAutoConfiguration.class })
@AutoConfigureAfter(RefreshAutoConfiguration.class)
public class EurekaClientAutoConfiguration { public class EurekaClientAutoConfiguration {
@Autowired
private ApplicationContext context;
@Value("${server.port:${SERVER_PORT:${PORT:8080}}}") @Value("${server.port:${SERVER_PORT:${PORT:8080}}}")
int nonSecurePort; int nonSecurePort;
...@@ -69,13 +82,13 @@ public class EurekaClientAutoConfiguration { ...@@ -69,13 +82,13 @@ public class EurekaClientAutoConfiguration {
} }
@Bean @Bean
@ConditionalOnMissingBean(EurekaClientConfig.class) @ConditionalOnMissingBean(value = EurekaClientConfig.class, search = SearchStrategy.CURRENT)
public EurekaClientConfigBean eurekaClientConfigBean() { public EurekaClientConfigBean eurekaClientConfigBean() {
return new EurekaClientConfigBean(); return new EurekaClientConfigBean();
} }
@Bean @Bean
@ConditionalOnMissingBean(EurekaInstanceConfig.class) @ConditionalOnMissingBean(value = EurekaInstanceConfig.class, search = SearchStrategy.CURRENT)
public EurekaInstanceConfigBean eurekaInstanceConfigBean() { public EurekaInstanceConfigBean eurekaInstanceConfigBean() {
EurekaInstanceConfigBean instance = new EurekaInstanceConfigBean(); EurekaInstanceConfigBean instance = new EurekaInstanceConfigBean();
instance.setNonSecurePort(this.nonSecurePort); instance.setNonSecurePort(this.nonSecurePort);
...@@ -83,14 +96,6 @@ public class EurekaClientAutoConfiguration { ...@@ -83,14 +96,6 @@ public class EurekaClientAutoConfiguration {
} }
@Bean @Bean
@ConditionalOnMissingBean(EurekaClient.class)
@SneakyThrows
public EurekaClient eurekaClient(ApplicationInfoManager applicationInfoManager,
EurekaClientConfig config) {
return new CloudEurekaClient(applicationInfoManager, config, this.context);
}
@Bean
@ConditionalOnMissingBean(ApplicationInfoManager.class) @ConditionalOnMissingBean(ApplicationInfoManager.class)
public ApplicationInfoManager applicationInfoManager(EurekaInstanceConfig config, public ApplicationInfoManager applicationInfoManager(EurekaInstanceConfig config,
InstanceInfo instanceInfo) { InstanceInfo instanceInfo) {
...@@ -109,4 +114,84 @@ public class EurekaClientAutoConfiguration { ...@@ -109,4 +114,84 @@ public class EurekaClientAutoConfiguration {
return new EurekaDiscoveryClient(config, client); 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; ...@@ -35,7 +35,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent; import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent; 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.ApplicationContext;
import org.springframework.context.SmartLifecycle; import org.springframework.context.SmartLifecycle;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
...@@ -98,6 +98,7 @@ public class EurekaDiscoveryClientConfiguration implements SmartLifecycle, Order ...@@ -98,6 +98,7 @@ public class EurekaDiscoveryClientConfiguration implements SmartLifecycle, Order
// because of containerPortInitializer below // because of containerPortInitializer below
if (!this.running.get() && this.instanceConfig.getNonSecurePort() > 0) { if (!this.running.get() && this.instanceConfig.getNonSecurePort() > 0) {
this.eurekaClient.getApplications(); // force initialization
log.info("Registering application " + this.instanceConfig.getAppname() log.info("Registering application " + this.instanceConfig.getAppname()
+ " with eureka with status " + " with eureka with status "
+ this.instanceConfig.getInitialStatus()); + this.instanceConfig.getInitialStatus());
...@@ -159,13 +160,13 @@ public class EurekaDiscoveryClientConfiguration implements SmartLifecycle, Order ...@@ -159,13 +160,13 @@ public class EurekaDiscoveryClientConfiguration implements SmartLifecycle, Order
} }
@Configuration @Configuration
@ConditionalOnClass(EnvironmentChangeEvent.class) @ConditionalOnClass(RefreshScopeRefreshedEvent.class)
protected static class EurekaClientConfigurationRefresher { protected static class EurekaClientConfigurationRefresher {
@Autowired @Autowired
private EurekaDiscoveryClientConfiguration clientConfig; private EurekaDiscoveryClientConfiguration clientConfig;
@EventListener(EnvironmentChangeEvent.class) @EventListener(RefreshScopeRefreshedEvent.class)
public void onApplicationEvent(EnvironmentChangeEvent event) { public void onApplicationEvent(RefreshScopeRefreshedEvent event) {
// register in case meta data changed // register in case meta data changed
this.clientConfig.stop(); this.clientConfig.stop();
this.clientConfig.start(); 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; ...@@ -35,7 +35,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.context.web.WebAppConfiguration;
import com.netflix.appinfo.InstanceInfo; import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.DiscoveryClient; import com.netflix.discovery.EurekaClient;
/** /**
...@@ -46,17 +46,17 @@ import com.netflix.discovery.DiscoveryClient; ...@@ -46,17 +46,17 @@ import com.netflix.discovery.DiscoveryClient;
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = EurekaHealthCheckTests.EurekaHealthCheckApplication.class) @SpringApplicationConfiguration(classes = EurekaHealthCheckTests.EurekaHealthCheckApplication.class)
@WebAppConfiguration @WebAppConfiguration
@IntegrationTest({"server.port=0", "eureka.client.healthcheck.enabled=true"}) @IntegrationTest({"server.port=0", "eureka.client.healthcheck.enabled=true", "debug=true"})
@DirtiesContext @DirtiesContext
public class EurekaHealthCheckTests { public class EurekaHealthCheckTests {
@Autowired @Autowired
private DiscoveryClient discoveryClient; private EurekaClient discoveryClient;
@Test @Test
public void shouldRegisterService() { public void shouldRegisterService() {
InstanceInfo.InstanceStatus status = discoveryClient.getHealthCheckHandler() InstanceInfo.InstanceStatus status = this.discoveryClient.getHealthCheckHandler()
.getStatus(InstanceInfo.InstanceStatus.UNKNOWN); .getStatus(InstanceInfo.InstanceStatus.UNKNOWN);
assertNotNull(status); assertNotNull(status);
......
...@@ -58,6 +58,7 @@ import org.springframework.web.context.ServletContextAware; ...@@ -58,6 +58,7 @@ import org.springframework.web.context.ServletContextAware;
import com.netflix.blitz4j.DefaultBlitz4jConfig; import com.netflix.blitz4j.DefaultBlitz4jConfig;
import com.netflix.blitz4j.LoggingConfiguration; import com.netflix.blitz4j.LoggingConfiguration;
import com.netflix.discovery.EurekaClient;
import com.netflix.discovery.converters.JsonXStream; import com.netflix.discovery.converters.JsonXStream;
import com.netflix.discovery.converters.XmlXStream; import com.netflix.discovery.converters.XmlXStream;
import com.netflix.eureka.AbstractInstanceRegistry; import com.netflix.eureka.AbstractInstanceRegistry;
...@@ -72,8 +73,8 @@ import com.netflix.eureka.PeerAwareInstanceRegistryImpl; ...@@ -72,8 +73,8 @@ import com.netflix.eureka.PeerAwareInstanceRegistryImpl;
*/ */
@Configuration @Configuration
@EnableConfigurationProperties(EurekaServerConfigBean.class) @EnableConfigurationProperties(EurekaServerConfigBean.class)
public class EurekaServerInitializerConfiguration implements ServletContextAware, public class EurekaServerInitializerConfiguration
SmartLifecycle, Ordered { implements ServletContextAware, SmartLifecycle, Ordered {
private static Log logger = LogFactory private static Log logger = LogFactory
.getLog(EurekaServerInitializerConfiguration.class); .getLog(EurekaServerInitializerConfiguration.class);
...@@ -98,7 +99,8 @@ public class EurekaServerInitializerConfiguration implements ServletContextAware ...@@ -98,7 +99,8 @@ public class EurekaServerInitializerConfiguration implements ServletContextAware
@PostConstruct @PostConstruct
public void initLogging() { public void initLogging() {
if (!(LoggingSystem.get(ClassUtils.getDefaultClassLoader()) instanceof Log4JLoggingSystem)) { if (!(LoggingSystem
.get(ClassUtils.getDefaultClassLoader()) instanceof Log4JLoggingSystem)) {
LoggingConfiguration off = new LoggingConfiguration() { LoggingConfiguration off = new LoggingConfiguration() {
@Override @Override
...@@ -139,7 +141,7 @@ public class EurekaServerInitializerConfiguration implements ServletContextAware ...@@ -139,7 +141,7 @@ public class EurekaServerInitializerConfiguration implements ServletContextAware
System.setProperty("log4j.configuration", System.setProperty("log4j.configuration",
new ClassPathResource("log4j.properties", new ClassPathResource("log4j.properties",
Log4JLoggingSystem.class).getURL() Log4JLoggingSystem.class).getURL()
.toString()); .toString());
} }
} }
catch (IOException ex) { catch (IOException ex) {
...@@ -206,11 +208,11 @@ public class EurekaServerInitializerConfiguration implements ServletContextAware ...@@ -206,11 +208,11 @@ public class EurekaServerInitializerConfiguration implements ServletContextAware
@Configuration @Configuration
@ConditionalOnClass(PeerAwareInstanceRegistry.class) @ConditionalOnClass(PeerAwareInstanceRegistry.class)
protected static class RegistryInstanceProxyInitializer implements protected static class RegistryInstanceProxyInitializer
ApplicationListener<EurekaRegistryAvailableEvent> { implements ApplicationListener<EurekaRegistryAvailableEvent> {
@Autowired @Autowired(required = false)
private ApplicationContext applicationContext; private EurekaClient client;
private PeerAwareInstanceRegistryImpl instance; private PeerAwareInstanceRegistryImpl instance;
...@@ -221,6 +223,9 @@ public class EurekaServerInitializerConfiguration implements ServletContextAware ...@@ -221,6 +223,9 @@ public class EurekaServerInitializerConfiguration implements ServletContextAware
@Override @Override
public void onApplicationEvent(EurekaRegistryAvailableEvent event) { public void onApplicationEvent(EurekaRegistryAvailableEvent event) {
if (this.client != null) {
this.client.getApplications(); // force initialization
}
if (this.instance == null) { if (this.instance == null) {
this.instance = PeerAwareInstanceRegistryImpl.getInstance(); this.instance = PeerAwareInstanceRegistryImpl.getInstance();
safeInit(); safeInit();
...@@ -230,8 +235,8 @@ public class EurekaServerInitializerConfiguration implements ServletContextAware ...@@ -230,8 +235,8 @@ public class EurekaServerInitializerConfiguration implements ServletContextAware
} }
private void safeInit() { private void safeInit() {
Method method = ReflectionUtils Method method = ReflectionUtils.findMethod(AbstractInstanceRegistry.class,
.findMethod(AbstractInstanceRegistry.class, "postInit"); "postInit");
ReflectionUtils.makeAccessible(method); ReflectionUtils.makeAccessible(method);
ReflectionUtils.invokeMethod(method, this.instance); 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