Commit 797544e6 by Dave Syer

Clean up eureka lifecycle

When the parent (bootstrap) context registers the app as well as consuming the registry it causes problems, e.g. when you refresh that context is re-created and destroyed, which tends to unregister the app and it's hard to get it back. It is simpler if the parent context use eureka read-only. To ensure this we have to remove the @EnableDiscoveryClient from the bootstrap context. Tested with vanilla config server and eureka with the 'eureka-first' sample and the 'zuul-proxy-eureka' sample. Fixes gh-421
parent eb8a9020
......@@ -49,11 +49,11 @@ public class DiscoveryClientConfigServiceAutoConfiguration {
private ConfigurationPropertiesBindingPostProcessor binder;
@Autowired
private EurekaDiscoveryClientConfiguration lifecycle;
private EurekaDiscoveryClientConfiguration clientConfiguration;
@PostConstruct
public void init() {
this.lifecycle.stop();
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)
......@@ -61,7 +61,7 @@ public class DiscoveryClientConfigServiceAutoConfiguration {
// FIXME: reinit EurekaClient and ApplicationInfoManager
// applicationInfoManager.initComponent(this.instanceConfig);
// discoveryManager.initComponent(this.instanceConfig, this.clientConfig);
this.lifecycle.start();
this.clientConfiguration.start();
}
private void rebind(Object bean, String name) {
......
......@@ -19,17 +19,13 @@ package org.springframework.cloud.netflix.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
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.config.client.ConfigClientProperties;
import org.springframework.cloud.config.client.ConfigServicePropertySourceLocator;
import org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.SmartApplicationListener;
import org.springframework.context.event.EventListener;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.EurekaClient;
......@@ -45,13 +41,9 @@ import lombok.extern.apachecommons.CommonsLog;
@ConditionalOnClass({ EurekaClient.class, ConfigServicePropertySourceLocator.class })
@ConditionalOnProperty(value = "spring.cloud.config.discovery.enabled", matchIfMissing = false)
@Configuration
@EnableDiscoveryClient
@Import(EurekaClientAutoConfiguration.class)
@CommonsLog
public class DiscoveryClientConfigServiceBootstrapConfiguration implements
SmartApplicationListener {
private HeartbeatMonitor monitor = new HeartbeatMonitor();
public class DiscoveryClientConfigServiceBootstrapConfiguration {
@Autowired
private ConfigClientProperties config;
......@@ -59,40 +51,18 @@ public class DiscoveryClientConfigServiceBootstrapConfiguration implements
@Autowired
private EurekaClient eurekaClient;
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextRefreshedEvent) {
refresh();
}
else if (event instanceof HeartbeatEvent) {
if (this.monitor.update(((HeartbeatEvent) event).getValue())) {
refresh();
}
}
@EventListener(ContextRefreshedEvent.class)
public void onApplicationEvent(ContextRefreshedEvent event) {
refresh();
}
@Override
public int getOrder() {
return 0;
}
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return ContextRefreshedEvent.class.isAssignableFrom(eventType)
|| HeartbeatEvent.class.isAssignableFrom(eventType);
}
@Override
public boolean supportsSourceType(Class<?> sourceType) {
return true;
}
// TODO: re-instate heart beat (maybe? isn't it handled in the child context?)
private void refresh() {
try {
log.debug("Locating configserver via discovery");
InstanceInfo server = this.eurekaClient
.getNextServerFromEureka(this.config.getDiscovery().getServiceId(),
false);
InstanceInfo server = this.eurekaClient.getNextServerFromEureka(
this.config.getDiscovery().getServiceId(), false);
String url = server.getHomePageUrl();
if (server.getMetadata().containsKey("password")) {
String user = server.getMetadata().get("user");
......
......@@ -16,9 +16,6 @@
package org.springframework.cloud.netflix.eureka;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -27,16 +24,11 @@ import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.builder.ParentContextApplicationContextInitializer;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.CommonsClientAutoConfiguration;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
import org.springframework.cloud.client.discovery.event.ParentHeartbeatEvent;
import org.springframework.cloud.client.discovery.noop.NoopDiscoveryClientAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
......@@ -57,10 +49,9 @@ import lombok.SneakyThrows;
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@AutoConfigureBefore({NoopDiscoveryClientAutoConfiguration.class, CommonsClientAutoConfiguration.class})
public class EurekaClientAutoConfiguration implements ApplicationListener<ParentContextApplicationContextInitializer.ParentContextAvailableEvent> {
private static final ConcurrentMap<String, String> listenerAdded = new ConcurrentHashMap<>();
@AutoConfigureBefore({ NoopDiscoveryClientAutoConfiguration.class,
CommonsClientAutoConfiguration.class })
public class EurekaClientAutoConfiguration {
@Autowired
private ApplicationContext context;
......@@ -71,10 +62,10 @@ public class EurekaClientAutoConfiguration implements ApplicationListener<Parent
@PostConstruct
public void init() {
DataCenterAwareJacksonCodec.init();
XmlXStream.getInstance().setMarshallingStrategy(
new DataCenterAwareMarshallingStrategy());
JsonXStream.getInstance().setMarshallingStrategy(
new DataCenterAwareMarshallingStrategy());
XmlXStream.getInstance()
.setMarshallingStrategy(new DataCenterAwareMarshallingStrategy());
JsonXStream.getInstance()
.setMarshallingStrategy(new DataCenterAwareMarshallingStrategy());
}
@Bean
......@@ -94,13 +85,15 @@ public class EurekaClientAutoConfiguration implements ApplicationListener<Parent
@Bean
@ConditionalOnMissingBean(EurekaClient.class)
@SneakyThrows
public EurekaClient eurekaClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config) {
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) {
public ApplicationInfoManager applicationInfoManager(EurekaInstanceConfig config,
InstanceInfo instanceInfo) {
return new ApplicationInfoManager(config, instanceInfo);
}
......@@ -111,34 +104,9 @@ public class EurekaClientAutoConfiguration implements ApplicationListener<Parent
}
@Bean
public DiscoveryClient discoveryClient(EurekaInstanceConfig config, EurekaClient client) {
public DiscoveryClient discoveryClient(EurekaInstanceConfig config,
EurekaClient client) {
return new EurekaDiscoveryClient(config, client);
}
/**
* propagate HeartbeatEvent from parent to child. Do it via a
* ParentHeartbeatEvent since events get published to the parent context,
* otherwise results in a stack overflow
* @param event
*/
@Override
public void onApplicationEvent(final ParentContextApplicationContextInitializer.ParentContextAvailableEvent event) {
final ConfigurableApplicationContext context = event.getApplicationContext();
String childId = context.getId();
ApplicationContext parent = context.getParent();
if (parent != null && "bootstrap".equals(parent.getId())
&& parent instanceof ConfigurableApplicationContext) {
if (listenerAdded.putIfAbsent(childId, childId) == null) {
@SuppressWarnings("resource")
ConfigurableApplicationContext ctx = (ConfigurableApplicationContext) parent;
ctx.addApplicationListener(new ApplicationListener<HeartbeatEvent>() {
@Override
public void onApplicationEvent(HeartbeatEvent dhe) {
context.publishEvent(new ParentHeartbeatEvent(dhe
.getSource(), dhe.getValue()));
}
});
}
}
}
}
......@@ -21,8 +21,6 @@ import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import lombok.extern.apachecommons.CommonsLog;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.health.HealthAggregator;
......@@ -37,11 +35,13 @@ 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.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.SmartLifecycle;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.Ordered;
import com.netflix.appinfo.ApplicationInfoManager;
......@@ -51,6 +51,8 @@ import com.netflix.appinfo.InstanceInfo.InstanceStatus;
import com.netflix.discovery.EurekaClient;
import com.netflix.discovery.EurekaClientConfig;
import lombok.extern.apachecommons.CommonsLog;
/**
* @author Dave Syer
* @author Spencer Gibb
......@@ -100,25 +102,24 @@ public class EurekaDiscoveryClientConfiguration implements SmartLifecycle, Order
+ " with eureka with status "
+ this.instanceConfig.getInitialStatus());
applicationInfoManager.setInstanceStatus(
this.instanceConfig.getInitialStatus());
this.applicationInfoManager
.setInstanceStatus(this.instanceConfig.getInitialStatus());
if (this.healthCheckHandler != null) {
eurekaClient.registerHealthCheck(this.healthCheckHandler);
this.eurekaClient.registerHealthCheck(this.healthCheckHandler);
}
this.context.publishEvent(new InstanceRegisteredEvent<>(this,
this.instanceConfig));
this.context.publishEvent(
new InstanceRegisteredEvent<>(this, this.instanceConfig));
this.running.set(true);
}
}
@Override
public void stop() {
log.info("Unregistering application " + this.instanceConfig.getAppname()
+ " with eureka with status DOWN");
if (applicationInfoManager.getInfo() != null) {
applicationInfoManager.setInstanceStatus(
InstanceStatus.DOWN);
if (this.applicationInfoManager.getInfo() != null) {
log.info("Unregistering application " + this.instanceConfig.getAppname()
+ " with eureka with status DOWN");
this.applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN);
}
this.running.set(false);
}
......@@ -149,18 +150,26 @@ public class EurekaDiscoveryClientConfiguration implements SmartLifecycle, Order
return this.order;
}
@Bean
protected ApplicationListener<EmbeddedServletContainerInitializedEvent> containerPortInitializer() {
return new ApplicationListener<EmbeddedServletContainerInitializedEvent>() {
@EventListener(EmbeddedServletContainerInitializedEvent.class)
public void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) {
// TODO: take SSL into account when Spring Boot 1.2 is available
EurekaDiscoveryClientConfiguration.this.port.compareAndSet(0,
event.getEmbeddedServletContainer().getPort());
EurekaDiscoveryClientConfiguration.this.start();
}
@EventListener(EnvironmentChangeEvent.class)
public void onApplicationEvent(EnvironmentChangeEvent event) {
// register in case meta data changed
stop();
start();
}
@Override
public void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) {
// TODO: take SSL into account when Spring Boot 1.2 is available
EurekaDiscoveryClientConfiguration.this.port.compareAndSet(0, event
.getEmbeddedServletContainer().getPort());
EurekaDiscoveryClientConfiguration.this.start();
}
};
@EventListener(ContextClosedEvent.class)
public void onApplicationEvent(ContextClosedEvent event) {
// register in case meta data changed
stop();
this.eurekaClient.shutdown();
}
@Configuration
......@@ -175,7 +184,8 @@ public class EurekaDiscoveryClientConfiguration implements SmartLifecycle, Order
@ConditionalOnMissingBean
public EurekaHealthIndicator eurekaHealthIndicator(EurekaClient eurekaClient,
EurekaInstanceConfig config) {
CompositeMetricReader metrics = new CompositeMetricReader(this.metricReaders.toArray(new MetricReader[0]));
CompositeMetricReader metrics = new CompositeMetricReader(
this.metricReaders.toArray(new MetricReader[0]));
return new EurekaHealthIndicator(eurekaClient, metrics, config);
}
}
......@@ -191,7 +201,7 @@ public class EurekaDiscoveryClientConfiguration implements SmartLifecycle, Order
@Bean
@ConditionalOnMissingBean(HealthCheckHandler.class)
public EurekaHealthCheckHandler eurekaHealthCheckHandler() {
return new EurekaHealthCheckHandler(healthAggregator);
return new EurekaHealthCheckHandler(this.healthAggregator);
}
}
}
......@@ -29,6 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.cloud.config.client.ConfigClientProperties;
import org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
......@@ -61,19 +62,19 @@ public class DiscoveryClientConfigServiceAutoConfigurationTests {
@Test
public void onWhenRequested() throws Exception {
given(this.client.getNextServerFromEureka("CONFIGSERVER", false)).willReturn(
this.info);
given(this.client.getNextServerFromEureka("CONFIGSERVER", false))
.willReturn(this.info);
setup("spring.cloud.config.discovery.enabled=true");
assertEquals(
1,
this.context
.getBeanNamesForType(DiscoveryClientConfigServiceAutoConfiguration.class).length);
Mockito.verify(this.client, times(2)).getNextServerFromEureka("CONFIGSERVER", false);
assertEquals(1, this.context.getBeanNamesForType(
DiscoveryClientConfigServiceAutoConfiguration.class).length);
Mockito.verify(this.client, times(2)).getNextServerFromEureka("CONFIGSERVER",
false);
Mockito.verify(this.client, times(0)).shutdown();
ConfigClientProperties locator = this.context
.getBean(ConfigClientProperties.class);
assertEquals("http://foo:7001/", locator.getRawUri());
ApplicationInfoManager infoManager = this.context.getBean(ApplicationInfoManager.class);
ApplicationInfoManager infoManager = this.context
.getBean(ApplicationInfoManager.class);
assertEquals("bar", infoManager.getInfo().getMetadata().get("foo"));
}
......@@ -88,7 +89,8 @@ public class DiscoveryClientConfigServiceAutoConfigurationTests {
parent.refresh();
this.context = new AnnotationConfigApplicationContext();
this.context.setParent(parent);
this.context.register(DiscoveryClientConfigServiceAutoConfiguration.class);
this.context.register(DiscoveryClientConfigServiceAutoConfiguration.class,
EurekaDiscoveryClientConfiguration.class);
this.context.refresh();
}
......
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