Commit c9008ea2 by Jakub Narloch Committed by Spencer Gibb

Eureka health check

parent 72c89c51
......@@ -95,6 +95,28 @@ These links show up in the metadata that is consumed by clients, and
used in some scenarios to decide whether to send requests to your
application, so it's helpful if they are accurate.
=== Eureka's Health Checks
By default, Eureka uses the client heartbeat to determine if a client is up.
Unless specified otherwise the Discovery Client will not propagate the
application Spring Boot Actuator current health check status. Which means
that after successful registration Eureka will always announce that the
application is in 'UP' state. This behaviour can be altered by enabling
Eureka health checks, which results in propagating application status
to Eureka, as a consequence every other application will not sending
traffic to application in state other then 'UP'.
.application.yml
----
eureka:
client:
healthcheck:
enabled: true
----
If you require more control over the health checks, you may consider
implementing your own `com.netflix.appinfo.HealthCheckHandler`.
=== Eureka Metadata for Instances and Clients
It's worth spending a bit of time understanding how the Eureka metadata works, so you can use it in a way that makes sense in your platform. There is standard metadata for things like hostname, IP address, port numbers, status page and health check. These are published in the service registry and used by clients to contact the services in a straightforward way. Additional metadata can be added to the instance registration in the `eureka.instance.metadataMap`, and this will be accessible in the remote clients, but in general will not change the behaviour of the client, unless it is made aware of the meaning of the metadata. There are a couple of special cases described below where Spring Cloud already assigns meaning to the metadata map.
......
......@@ -25,6 +25,9 @@ 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;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.boot.actuate.metrics.reader.CompositeMetricReader;
import org.springframework.boot.actuate.metrics.reader.MetricReader;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
......@@ -50,6 +53,9 @@ import com.netflix.discovery.EurekaClientConfig;
/**
* @author Dave Syer
* @author Spencer Gibb
* @author Jon Schneider
* @author Jakub Narloch
*/
@Configuration
@EnableConfigurationProperties
......@@ -174,4 +180,18 @@ public class EurekaDiscoveryClientConfiguration implements SmartLifecycle, Order
}
}
@Configuration
@ConditionalOnProperty(value = "eureka.client.healthcheck.enabled", matchIfMissing = false)
@ConditionalOnBean(HealthIndicator.class)
protected static class EurekaHealthCheckHandlerConfiguration {
@Autowired(required = false)
private HealthAggregator healthAggregator = new OrderedHealthAggregator();
@Bean
@ConditionalOnMissingBean(HealthCheckHandler.class)
public EurekaHealthCheckHandler eurekaHealthCheckHandler() {
return new EurekaHealthCheckHandler(healthAggregator);
}
}
}
/*
* Copyright 2013-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 com.netflix.appinfo.InstanceInfo.InstanceStatus;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.actuate.health.CompositeHealthIndicator;
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.Assert;
import com.netflix.appinfo.HealthCheckHandler;
import com.netflix.appinfo.InstanceInfo;
/**
* A Eureka health checker, maps the application status into {@link InstanceStatus}
* that will be propagated to Eureka registry.
*
* On each heartbeat Eureka performs the health check invoking registered {@link HealthCheckHandler}. By default this
* implementation will perform aggregation of all registered {@link HealthIndicator}
* through registered {@link HealthAggregator}.
*
* @author Jakub Narloch
* @see HealthCheckHandler
* @see HealthAggregator
*/
public class EurekaHealthCheckHandler implements HealthCheckHandler, ApplicationContextAware, InitializingBean {
private static final Map<Status, InstanceInfo.InstanceStatus> STATUS_MAPPING =
new HashMap<Status, InstanceInfo.InstanceStatus>() {{
put(Status.UNKNOWN, InstanceStatus.UNKNOWN);
put(Status.OUT_OF_SERVICE, InstanceStatus.OUT_OF_SERVICE);
put(Status.DOWN, InstanceStatus.DOWN);
put(Status.UP, InstanceStatus.UP);
}};
private final CompositeHealthIndicator healthIndicator;
private ApplicationContext applicationContext;
public EurekaHealthCheckHandler(HealthAggregator healthAggregator) {
Assert.notNull(healthAggregator, "HealthAggregator must not be null");
this.healthIndicator = new CompositeHealthIndicator(healthAggregator);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void afterPropertiesSet() throws Exception {
final Map<String, HealthIndicator> healthIndicators = applicationContext.getBeansOfType(HealthIndicator.class);
for (Map.Entry<String, HealthIndicator> entry : healthIndicators.entrySet()) {
healthIndicator.addHealthIndicator(entry.getKey(), entry.getValue());
}
}
@Override
public InstanceStatus getStatus(InstanceStatus instanceStatus) {
return getHealthStatus();
}
protected InstanceStatus getHealthStatus() {
final Status status = healthIndicator.health().getStatus();
return mapToInstanceStatus(status);
}
protected InstanceStatus mapToInstanceStatus(Status status) {
if (!STATUS_MAPPING.containsKey(status)) {
return InstanceStatus.UNKNOWN;
}
return STATUS_MAPPING.get(status);
}
}
/*
* Copyright 2013-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 org.junit.Before;
import org.junit.Test;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import com.netflix.appinfo.InstanceInfo;
/**
* Tests the {@link EurekaHealthCheckHandler} with different health indicator registered.
*
* @author Jakub Narloch
*/
public class EurekaHealthCheckHandlerTests {
private EurekaHealthCheckHandler healthCheckHandler;
@Before
public void setUp() throws Exception {
healthCheckHandler = new EurekaHealthCheckHandler(new OrderedHealthAggregator());
}
@Test
public void testNoHealthCheckRegistered() throws Exception {
InstanceInfo.InstanceStatus status = healthCheckHandler.getStatus(InstanceInfo.InstanceStatus.UNKNOWN);
assertEquals(InstanceInfo.InstanceStatus.UNKNOWN, status);
}
@Test
public void testAllUp() throws Exception {
initialize(UpHealthConfiguration.class);
InstanceInfo.InstanceStatus status = healthCheckHandler.getStatus(InstanceInfo.InstanceStatus.UNKNOWN);
assertEquals(InstanceInfo.InstanceStatus.UP, status);
}
@Test
public void testDown() throws Exception {
initialize(UpHealthConfiguration.class, DownHealthConfiguration.class);
InstanceInfo.InstanceStatus status = healthCheckHandler.getStatus(InstanceInfo.InstanceStatus.UNKNOWN);
assertEquals(InstanceInfo.InstanceStatus.DOWN, status);
}
@Test
public void testUnknown() throws Exception {
initialize(FatalHealthConfiguration.class);
InstanceInfo.InstanceStatus status = healthCheckHandler.getStatus(InstanceInfo.InstanceStatus.UNKNOWN);
assertEquals(InstanceInfo.InstanceStatus.UNKNOWN, status);
}
private void initialize(Class<?>... configurations) throws Exception {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(configurations);
healthCheckHandler.setApplicationContext(applicationContext);
healthCheckHandler.afterPropertiesSet();
}
public static class UpHealthConfiguration {
@Bean
public HealthIndicator upIndicator() {
return new AbstractHealthIndicator() {
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
builder.up();
}
};
}
}
public static class DownHealthConfiguration {
@Bean
public HealthIndicator upIndicator() {
return new AbstractHealthIndicator() {
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
builder.down();
}
};
}
}
public static class FatalHealthConfiguration {
@Bean
public HealthIndicator upIndicator() {
return new AbstractHealthIndicator() {
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
builder.status("fatal");
}
};
}
}
}
\ No newline at end of file
/*
* Copyright 2013-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.healthcheck;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.DiscoveryClient;
/**
* Tests the Eureka health check handler.
*
* @author Jakub Narloch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = EurekaHealthCheckTests.EurekaHealthCheckApplication.class)
@WebAppConfiguration
@IntegrationTest({"server.port=0", "eureka.client.healthcheck.enabled=true"})
@DirtiesContext
public class EurekaHealthCheckTests {
@Autowired
private DiscoveryClient discoveryClient;
@Test
public void shouldRegisterService() {
InstanceInfo.InstanceStatus status = discoveryClient.getHealthCheckHandler()
.getStatus(InstanceInfo.InstanceStatus.UNKNOWN);
assertNotNull(status);
assertEquals(InstanceInfo.InstanceStatus.OUT_OF_SERVICE, status);
}
@Configuration
@EnableAutoConfiguration
@EnableEurekaClient
protected static class EurekaHealthCheckApplication {
@Bean
public HealthIndicator healthIndicator() {
return new HealthIndicator() {
@Override
public Health health() {
return new Health.Builder().outOfService().build();
}
};
}
}
}
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