Add health indicators infrastructure

- Adds a generic 'bindersHealthIndicator' bean controlled by `management.health.binders.enabled` that tallies results from binders
- Binders are expected to expose one or more health indicators based on the status of the middleware connection. If no health indicators are exposed, the status is deemed to be 'UNKNOWN'
- Add support for Redis and Rabbit based on existing health indicators

Moved binder health indicator to autoconfiguration

Also updated copyright
This commit is contained in:
Marius Bogoevici
2016-01-27 16:35:01 -05:00
committed by Ilayaperumal Gopinathan
parent a2486db905
commit 7477fcbee9
11 changed files with 252 additions and 16 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-2016 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.
@@ -17,6 +17,9 @@
package org.springframework.cloud.stream.binder.rabbit.config;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.RabbitHealthIndicator;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
import org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration;
@@ -66,5 +69,11 @@ public class RabbitServiceAutoConfiguration {
@Profile("!cloud")
@Import(RabbitAutoConfiguration.class)
protected static class NoCloudConfig {
}
@Bean
public HealthIndicator binderHealthIndicator(RabbitTemplate rabbitTemplate) {
return new RabbitHealthIndicator(rabbitTemplate);
}
}

View File

@@ -16,13 +16,17 @@
package org.springframework.cloud.stream.binder.rabbit.integration;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.collection.IsMapContaining.hasKey;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.junit.After;
import org.junit.ClassRule;
@@ -34,6 +38,9 @@ import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.health.CompositeHealthIndicator;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.binder.Binder;
@@ -73,7 +80,7 @@ public class RabbitBinderModuleTests {
@Test
public void testParentConnectionFactoryInheritedByDefault() {
context = SpringApplication.run(SimpleProcessor.class);
context = SpringApplication.run(SimpleProcessor.class, "--server.port=0");
BinderFactory<?> binderFactory = context.getBean(BinderFactory.class);
Binder<?> binder = binderFactory.getBinder(null);
assertThat(binder, instanceOf(RabbitMessageChannelBinder.class));
@@ -83,11 +90,20 @@ public class RabbitBinderModuleTests {
assertThat(binderConnectionFactory, instanceOf(CachingConnectionFactory.class));
ConnectionFactory connectionFactory = context.getBean(ConnectionFactory.class);
assertThat(binderConnectionFactory, is(connectionFactory));
CompositeHealthIndicator bindersHealthIndicator =
context.getBean("bindersHealthIndicator", CompositeHealthIndicator.class);
DirectFieldAccessor directFieldAccessor = new DirectFieldAccessor(bindersHealthIndicator);
assertNotNull(bindersHealthIndicator);
@SuppressWarnings("unchecked")
Map<String,HealthIndicator> healthIndicators =
(Map<String, HealthIndicator>) directFieldAccessor.getPropertyValue("indicators");
assertThat(healthIndicators, hasKey("rabbit"));
assertThat(healthIndicators.get("rabbit").health().getStatus(), equalTo(Status.UP));
}
@Test
public void testParentConnectionFactoryInheritedIfOverridden() {
context = new SpringApplication(SimpleProcessor.class, ConnectionFactoryConfiguration.class).run();
context = new SpringApplication(SimpleProcessor.class, ConnectionFactoryConfiguration.class).run("--server.port=0");
BinderFactory<?> binderFactory = context.getBean(BinderFactory.class);
Binder<?> binder = binderFactory.getBinder(null);
assertThat(binder, instanceOf(RabbitMessageChannelBinder.class));
@@ -97,6 +113,16 @@ public class RabbitBinderModuleTests {
assertThat(binderConnectionFactory, is(MOCK_CONNECTION_FACTORY));
ConnectionFactory connectionFactory = context.getBean(ConnectionFactory.class);
assertThat(binderConnectionFactory, is(connectionFactory));
CompositeHealthIndicator bindersHealthIndicator =
context.getBean("bindersHealthIndicator", CompositeHealthIndicator.class);
assertNotNull(bindersHealthIndicator);
DirectFieldAccessor directFieldAccessor = new DirectFieldAccessor(bindersHealthIndicator);
@SuppressWarnings("unchecked")
Map<String,HealthIndicator> healthIndicators =
(Map<String, HealthIndicator>) directFieldAccessor.getPropertyValue("indicators");
assertThat(healthIndicators, hasKey("rabbit"));
// mock connection factory behaves as if down
assertThat(healthIndicators.get("rabbit").health().getStatus(), equalTo(Status.DOWN));
}
@Test
@@ -106,6 +132,7 @@ public class RabbitBinderModuleTests {
params.add("--spring.cloud.stream.output.binder=custom");
params.add("--spring.cloud.stream.binders.custom.type=rabbit");
params.add("--spring.cloud.stream.binders.custom.environment.foo=bar");
params.add("--server.port=0");
context = SpringApplication.run(SimpleProcessor.class, params.toArray(new String[params.size()]));
BinderFactory<?> binderFactory = context.getBean(BinderFactory.class);
Binder<?> binder = binderFactory.getBinder(null);
@@ -115,6 +142,15 @@ public class RabbitBinderModuleTests {
(ConnectionFactory) binderFieldAccessor.getPropertyValue("connectionFactory");
ConnectionFactory connectionFactory = context.getBean(ConnectionFactory.class);
assertThat(binderConnectionFactory, not(is(connectionFactory)));
CompositeHealthIndicator bindersHealthIndicator =
context.getBean("bindersHealthIndicator", CompositeHealthIndicator.class);
assertNotNull(bindersHealthIndicator);
DirectFieldAccessor directFieldAccessor = new DirectFieldAccessor(bindersHealthIndicator);
@SuppressWarnings("unchecked")
Map<String,HealthIndicator> healthIndicators =
(Map<String, HealthIndicator>) directFieldAccessor.getPropertyValue("indicators");
assertThat(healthIndicators, hasKey("custom"));
assertThat(healthIndicators.get("custom").health().getStatus(), equalTo(Status.UP));
}
@EnableBinding(Processor.class)

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-2016 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.
@@ -18,7 +18,6 @@ package org.springframework.cloud.stream.binder.redis.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.stream.binder.redis.RedisMessageChannelBinder;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-2016 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.
@@ -16,6 +16,8 @@
package org.springframework.cloud.stream.binder.redis.config;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.RedisHealthIndicator;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -66,4 +68,8 @@ public class RedisServiceAutoConfiguration {
protected static class NoCloudConfig {
}
@Bean
public HealthIndicator binderHealthIndicator(RedisConnectionFactory redisConnectionFactory) {
return new RedisHealthIndicator(redisConnectionFactory);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-2016 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.
@@ -16,13 +16,17 @@
package org.springframework.cloud.stream.binder.redis.integration;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.collection.IsMapContaining.hasKey;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.junit.After;
import org.junit.ClassRule;
@@ -31,6 +35,9 @@ import org.mockito.Mockito;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.health.CompositeHealthIndicator;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.binder.Binder;
@@ -65,7 +72,7 @@ public class RedisBinderModuleTests {
@Test
public void testParentConnectionFactoryInheritedByDefault() {
context = SpringApplication.run(SimpleProcessor.class);
context = SpringApplication.run(SimpleProcessor.class, "--server.port=0");
BinderFactory<?> binderFactory = context.getBean(BinderFactory.class);
Binder<?> binder = binderFactory.getBinder(null);
assertThat(binder, instanceOf(RedisMessageChannelBinder.class));
@@ -75,6 +82,15 @@ public class RedisBinderModuleTests {
assertThat(binderConnectionFactory, instanceOf(RedisConnectionFactory.class));
RedisConnectionFactory connectionFactory = context.getBean(RedisConnectionFactory.class);
assertThat(binderConnectionFactory, is(connectionFactory));
CompositeHealthIndicator bindersHealthIndicator =
context.getBean("bindersHealthIndicator", CompositeHealthIndicator.class);
assertNotNull(bindersHealthIndicator);
DirectFieldAccessor directFieldAccessor = new DirectFieldAccessor(bindersHealthIndicator);
@SuppressWarnings("unchecked")
Map<String,HealthIndicator> healthIndicators =
(Map<String, HealthIndicator>) directFieldAccessor.getPropertyValue("indicators");
assertThat(healthIndicators, hasKey("redis"));
assertThat(healthIndicators.get("redis").health().getStatus(), equalTo(Status.UP));
}
@Test
@@ -89,6 +105,15 @@ public class RedisBinderModuleTests {
assertThat(binderConnectionFactory, is(MOCK_CONNECTION_FACTORY));
RedisConnectionFactory connectionFactory = context.getBean(RedisConnectionFactory.class);
assertThat(binderConnectionFactory, is(connectionFactory));
CompositeHealthIndicator bindersHealthIndicator =
context.getBean("bindersHealthIndicator", CompositeHealthIndicator.class);
assertNotNull(bindersHealthIndicator);
DirectFieldAccessor directFieldAccessor = new DirectFieldAccessor(bindersHealthIndicator);
@SuppressWarnings("unchecked")
Map<String,HealthIndicator> healthIndicators =
(Map<String, HealthIndicator>) directFieldAccessor.getPropertyValue("indicators");
assertThat(healthIndicators, hasKey("redis"));
assertThat(healthIndicators.get("redis").health().getStatus(), equalTo(Status.UP));
}
@Test
@@ -107,6 +132,15 @@ public class RedisBinderModuleTests {
(RedisConnectionFactory) binderFieldAccessor.getPropertyValue("connectionFactory");
RedisConnectionFactory connectionFactory = context.getBean(RedisConnectionFactory.class);
assertThat(binderConnectionFactory, not(is(connectionFactory)));
CompositeHealthIndicator bindersHealthIndicator =
context.getBean("bindersHealthIndicator", CompositeHealthIndicator.class);
assertNotNull(bindersHealthIndicator);
DirectFieldAccessor directFieldAccessor = new DirectFieldAccessor(bindersHealthIndicator);
@SuppressWarnings("unchecked")
Map<String,HealthIndicator> healthIndicators =
(Map<String, HealthIndicator>) directFieldAccessor.getPropertyValue("indicators");
assertThat(healthIndicators, hasKey("custom"));
assertThat(healthIndicators.get("custom").health().getStatus(), equalTo(Status.UP));
}
@EnableBinding(Processor.class)