From 58c8c2ccdcbb77bce6f24dc84bcd5e0b9e30a0ee Mon Sep 17 00:00:00 2001 From: Binwei Yang Date: Thu, 22 Jan 2015 08:41:28 -0800 Subject: [PATCH 1/2] Add a health indicator for an Elasticsearch cluster Closes gh-2399 --- spring-boot-actuator/pom.xml | 5 ++ ...ticsearchHealthIndicatorConfiguration.java | 55 ++++++++++++ .../health/ElasticsearchHealthIndicator.java | 62 ++++++++++++++ ...lasticsearchHealthIndicatorProperties.java | 52 ++++++++++++ .../main/resources/META-INF/spring.factories | 3 +- .../ElasticsearchHealthIndicatorTest.java | 84 +++++++++++++++++++ 6 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ElasticsearchHealthIndicatorConfiguration.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ElasticsearchHealthIndicator.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ElasticsearchHealthIndicatorProperties.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ElasticsearchHealthIndicatorTest.java diff --git a/spring-boot-actuator/pom.xml b/spring-boot-actuator/pom.xml index 508e125c14..e58f54bded 100644 --- a/spring-boot-actuator/pom.xml +++ b/spring-boot-actuator/pom.xml @@ -96,6 +96,11 @@ spring-data-solr true + + org.springframework.data + spring-data-elasticsearch + true + org.springframework.security spring-security-web diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ElasticsearchHealthIndicatorConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ElasticsearchHealthIndicatorConfiguration.java new file mode 100644 index 0000000000..2f934bf3dd --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ElasticsearchHealthIndicatorConfiguration.java @@ -0,0 +1,55 @@ +/* + * Copyright 2014 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.boot.actuate.autoconfigure; + +import org.elasticsearch.client.Client; +import org.springframework.boot.actuate.health.ElasticsearchHealthIndicator; +import org.springframework.boot.actuate.health.ElasticsearchHealthIndicatorProperties; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration} for + * {@link org.springframework.boot.actuate.health.ElasticsearchHealthIndicator}. + * + * @author Binwei Yang + * @since 1.2.2 + */ +@Configuration +@AutoConfigureBefore({EndpointAutoConfiguration.class}) +@AutoConfigureAfter({HealthIndicatorAutoConfiguration.class}) +@ConditionalOnProperty(prefix = "management.health.elasticsearch", name = "enabled", matchIfMissing = true) +public class ElasticsearchHealthIndicatorConfiguration { + + @Bean + @ConditionalOnBean(Client.class) + @ConditionalOnMissingBean(name = "elasticsearchHealthIndicator") + public HealthIndicator elasticsearchHealthIndicator() { + return new ElasticsearchHealthIndicator(); + } + + @Bean + public ElasticsearchHealthIndicatorProperties elasticsearchHealthIndicatorProperties() { + return new ElasticsearchHealthIndicatorProperties(); + } +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ElasticsearchHealthIndicator.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ElasticsearchHealthIndicator.java new file mode 100644 index 0000000000..9c062bbab0 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ElasticsearchHealthIndicator.java @@ -0,0 +1,62 @@ +/* + * Copyright 2014 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.boot.actuate.health; + +import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; +import org.elasticsearch.client.Client; +import org.elasticsearch.client.Requests; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Simple implementation of a {@link HealthIndicator} returning health status information for + * ElasticSearch cluster. + * + * @author Binwei Yang + * @since 1.2.2 + */ +public class ElasticsearchHealthIndicator extends ApplicationHealthIndicator { + @Autowired + private Client client; + + @Autowired + private ElasticsearchHealthIndicatorProperties properties; + + @Override + protected void doHealthCheck(Health.Builder builder) throws Exception { + try { + ClusterHealthResponse response = client.admin().cluster().health(Requests.clusterHealthRequest( + properties.getIndexNamesAsArray() + )).actionGet(100); + + switch (response.getStatus()) { + case GREEN: + builder.up(); + break; + case RED: + builder.down(); + break; + case YELLOW: + default: + builder.unknown(); + break; + } + builder.withDetail("clusterHealth", response); + } catch (Exception handled) { + builder.unknown().withDetail("exception", handled); + } + } +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ElasticsearchHealthIndicatorProperties.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ElasticsearchHealthIndicatorProperties.java new file mode 100644 index 0000000000..c0b10aa46a --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ElasticsearchHealthIndicatorProperties.java @@ -0,0 +1,52 @@ +/* + * Copyright 2014 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.boot.actuate.health; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * External configuration properties for {@link ElasticsearchHealthIndicator} + * + * @author Binwei Yang + * @since 1.2.2 + */ +@ConfigurationProperties("management.health.elasticsearch") +public class ElasticsearchHealthIndicatorProperties { + + public static final String ALL = "_all"; + + /** + * comma separated index names. the default includes all indices. + */ + private String indices = ALL; + + public String getIndices() { + return indices; + } + + public void setIndices(String indices) { + this.indices = indices; + } + + String[] getIndexNamesAsArray() { + if (null == indices) { + return new String[]{ALL}; + } else { + return indices.split(","); + } + } +} diff --git a/spring-boot-actuator/src/main/resources/META-INF/spring.factories b/spring-boot-actuator/src/main/resources/META-INF/spring.factories index 799422da3c..52b28beda8 100644 --- a/spring-boot-actuator/src/main/resources/META-INF/spring.factories +++ b/spring-boot-actuator/src/main/resources/META-INF/spring.factories @@ -12,4 +12,5 @@ org.springframework.boot.actuate.autoconfigure.MetricFilterAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.MetricRepositoryAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.PublicMetricsAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.TraceRepositoryAutoConfiguration,\ -org.springframework.boot.actuate.autoconfigure.TraceWebFilterAutoConfiguration +org.springframework.boot.actuate.autoconfigure.TraceWebFilterAutoConfiguration,\ +org.springframework.boot.actuate.autoconfigure.ElasticsearchHealthIndicatorConfiguration diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ElasticsearchHealthIndicatorTest.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ElasticsearchHealthIndicatorTest.java new file mode 100644 index 0000000000..2f59537f89 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ElasticsearchHealthIndicatorTest.java @@ -0,0 +1,84 @@ +/* + * Copyright 2014 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.boot.actuate.health; + +import org.elasticsearch.client.Client; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.boot.actuate.autoconfigure.ElasticsearchHealthIndicatorConfiguration; +import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchAutoConfiguration; +import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchDataAutoConfiguration; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Test for {@link ElasticsearchHealthIndicator} for ElasticSearch cluster. + * + * @author Binwei Yang + * @since 1.2.2 + */ +public class ElasticsearchHealthIndicatorTest { + private AnnotationConfigApplicationContext context; + + @Before + public void setUp() throws Exception { + this.context = new AnnotationConfigApplicationContext( + PropertyPlaceholderAutoConfiguration.class, + ElasticsearchAutoConfiguration.class, + ElasticsearchDataAutoConfiguration.class, + EndpointAutoConfiguration.class, + ElasticsearchHealthIndicatorConfiguration.class + ); + } + + @After + public void close() { + if (null != context) { + context.close(); + } + } + + @Test + public void indicatorExists() { + assertEquals(1, this.context.getBeanNamesForType(Client.class).length); + + ElasticsearchHealthIndicator healthIndicator = this.context.getBean(ElasticsearchHealthIndicator.class); + assertNotNull(healthIndicator); + } + + @Test + public void configPropertiesUsed() { + ElasticsearchHealthIndicatorProperties properties = this.context.getBean(ElasticsearchHealthIndicatorProperties.class); + + // test default index + assertEquals(ElasticsearchHealthIndicatorProperties.ALL, properties.getIndices()); + assertEquals(1, properties.getIndexNamesAsArray().length); + assertEquals(ElasticsearchHealthIndicatorProperties.ALL, properties.getIndexNamesAsArray()[0]); + + // test specific indices + properties.setIndices("no-such-index"); + + ElasticsearchHealthIndicator healthIndicator = this.context.getBean(ElasticsearchHealthIndicator.class); + Health health = healthIndicator.health(); + assertEquals(Status.UNKNOWN, health.getStatus()); + } +} From 03b109a2c8fd01d3ed2c922fdaef93fe1bdba39c Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 9 Apr 2015 10:08:20 +0100 Subject: [PATCH 2/2] Polish Elasticsearch health indicator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Nest the configuration class in HealthIndicatorAutoConfiguration, bringing it into line with the other health indicator configuration classes - Include the statistics from the response in the health’s details - Map YELLOW to UP rather than UNKNOWN as it indicates that the cluster is running but that “the primary shard is allocated but replicas are not” [1]. The details can be used to determine the precise state of the cluster. - Add a property to configure the time that the health indicator will wait to receive a response from the cluster - Document the configuration properties - Update the tests to cover the updated functionality See gh-2399 [1] http://www.elastic.co/guide/en/elasticsearch/reference/1.x/cluster-health.html --- spring-boot-actuator/.gitignore | 1 + spring-boot-actuator/pom.xml | 27 +- ...ticsearchHealthIndicatorConfiguration.java | 55 ---- .../HealthIndicatorAutoConfiguration.java | 33 ++- .../health/ElasticsearchHealthIndicator.java | 83 +++--- ...lasticsearchHealthIndicatorProperties.java | 44 ++-- .../main/resources/META-INF/spring.factories | 3 +- ...HealthIndicatorAutoConfigurationTests.java | 36 +++ .../ElasticsearchHealthIndicatorTest.java | 84 ------- .../ElasticsearchHealthIndicatorTests.java | 238 ++++++++++++++++++ spring-boot-dependencies/pom.xml | 6 + .../appendix-application-properties.adoc | 6 +- 12 files changed, 405 insertions(+), 211 deletions(-) create mode 100644 spring-boot-actuator/.gitignore delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ElasticsearchHealthIndicatorConfiguration.java delete mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ElasticsearchHealthIndicatorTest.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ElasticsearchHealthIndicatorTests.java diff --git a/spring-boot-actuator/.gitignore b/spring-boot-actuator/.gitignore new file mode 100644 index 0000000000..3af0ccb687 --- /dev/null +++ b/spring-boot-actuator/.gitignore @@ -0,0 +1 @@ +/data diff --git a/spring-boot-actuator/pom.xml b/spring-boot-actuator/pom.xml index e58f54bded..924506687e 100644 --- a/spring-boot-actuator/pom.xml +++ b/spring-boot-actuator/pom.xml @@ -97,8 +97,8 @@ true - org.springframework.data - spring-data-elasticsearch + org.elasticsearch + elasticsearch true @@ -173,19 +173,25 @@ true + + org.springframework.boot + spring-boot + test-jar + test + ch.qos.logback logback-classic test - org.crashub - crash.connectors.telnet + org.apache.tomcat.embed + tomcat-embed-logging-juli test - org.springframework - spring-test + org.crashub + crash.connectors.telnet test @@ -194,14 +200,13 @@ test - org.springframework.boot - spring-boot - test-jar + org.springframework + spring-test test - org.apache.tomcat.embed - tomcat-embed-logging-juli + org.springframework.data + spring-data-elasticsearch test diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ElasticsearchHealthIndicatorConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ElasticsearchHealthIndicatorConfiguration.java deleted file mode 100644 index 2f934bf3dd..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ElasticsearchHealthIndicatorConfiguration.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2014 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.boot.actuate.autoconfigure; - -import org.elasticsearch.client.Client; -import org.springframework.boot.actuate.health.ElasticsearchHealthIndicator; -import org.springframework.boot.actuate.health.ElasticsearchHealthIndicatorProperties; -import org.springframework.boot.actuate.health.HealthIndicator; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.AutoConfigureBefore; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration} for - * {@link org.springframework.boot.actuate.health.ElasticsearchHealthIndicator}. - * - * @author Binwei Yang - * @since 1.2.2 - */ -@Configuration -@AutoConfigureBefore({EndpointAutoConfiguration.class}) -@AutoConfigureAfter({HealthIndicatorAutoConfiguration.class}) -@ConditionalOnProperty(prefix = "management.health.elasticsearch", name = "enabled", matchIfMissing = true) -public class ElasticsearchHealthIndicatorConfiguration { - - @Bean - @ConditionalOnBean(Client.class) - @ConditionalOnMissingBean(name = "elasticsearchHealthIndicator") - public HealthIndicator elasticsearchHealthIndicator() { - return new ElasticsearchHealthIndicator(); - } - - @Bean - public ElasticsearchHealthIndicatorProperties elasticsearchHealthIndicatorProperties() { - return new ElasticsearchHealthIndicatorProperties(); - } -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java index 7a1790e17f..518444f3bb 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java @@ -23,6 +23,7 @@ import javax.jms.ConnectionFactory; import javax.sql.DataSource; import org.apache.solr.client.solrj.SolrServer; +import org.elasticsearch.client.Client; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; @@ -31,6 +32,8 @@ import org.springframework.boot.actuate.health.CompositeHealthIndicator; import org.springframework.boot.actuate.health.DataSourceHealthIndicator; import org.springframework.boot.actuate.health.DiskSpaceHealthIndicator; import org.springframework.boot.actuate.health.DiskSpaceHealthIndicatorProperties; +import org.springframework.boot.actuate.health.ElasticsearchHealthIndicator; +import org.springframework.boot.actuate.health.ElasticsearchHealthIndicatorProperties; import org.springframework.boot.actuate.health.HealthAggregator; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.JmsHealthIndicator; @@ -47,6 +50,7 @@ import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadata; import org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvider; @@ -79,7 +83,8 @@ import org.springframework.mail.javamail.JavaMailSenderImpl; @AutoConfigureAfter({ DataSourceAutoConfiguration.class, MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, RedisAutoConfiguration.class, RabbitAutoConfiguration.class, SolrAutoConfiguration.class, - MailSenderAutoConfiguration.class, JmsAutoConfiguration.class }) + MailSenderAutoConfiguration.class, JmsAutoConfiguration.class, + ElasticsearchAutoConfiguration.class }) @EnableConfigurationProperties({ HealthIndicatorAutoConfigurationProperties.class }) public class HealthIndicatorAutoConfiguration { @@ -306,4 +311,30 @@ public class HealthIndicatorAutoConfiguration { } + @Configuration + @ConditionalOnBean(Client.class) + @ConditionalOnProperty(prefix = "management.health.elasticsearch", name = "enabled", matchIfMissing = true) + @EnableConfigurationProperties(ElasticsearchHealthIndicatorProperties.class) + public static class ElasticsearchHealthIndicatorConfiguration extends + CompositeHealthIndicatorConfiguration { + + @Autowired + private Map clients; + + @Autowired + private ElasticsearchHealthIndicatorProperties properties; + + @Bean + @ConditionalOnMissingBean(name = "elasticsearchHealthIndicator") + public HealthIndicator elasticsearchHealthIndicator() { + return createHealthIndicator(this.clients); + } + + @Override + protected ElasticsearchHealthIndicator createHealthIndicator(Client client) { + return new ElasticsearchHealthIndicator(client, this.properties); + } + + } + } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ElasticsearchHealthIndicator.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ElasticsearchHealthIndicator.java index 9c062bbab0..55b424d64d 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ElasticsearchHealthIndicator.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ElasticsearchHealthIndicator.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2012-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. @@ -16,47 +16,58 @@ package org.springframework.boot.actuate.health; +import java.util.List; + import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; import org.elasticsearch.client.Client; import org.elasticsearch.client.Requests; -import org.springframework.beans.factory.annotation.Autowired; /** - * Simple implementation of a {@link HealthIndicator} returning health status information for - * ElasticSearch cluster. + * {@link HealthIndicator} for an Elasticsearch cluster. * * @author Binwei Yang - * @since 1.2.2 + * @author Andy Wilkinson + * @since 1.3.0 */ -public class ElasticsearchHealthIndicator extends ApplicationHealthIndicator { - @Autowired - private Client client; - - @Autowired - private ElasticsearchHealthIndicatorProperties properties; - - @Override - protected void doHealthCheck(Health.Builder builder) throws Exception { - try { - ClusterHealthResponse response = client.admin().cluster().health(Requests.clusterHealthRequest( - properties.getIndexNamesAsArray() - )).actionGet(100); - - switch (response.getStatus()) { - case GREEN: - builder.up(); - break; - case RED: - builder.down(); - break; - case YELLOW: - default: - builder.unknown(); - break; - } - builder.withDetail("clusterHealth", response); - } catch (Exception handled) { - builder.unknown().withDetail("exception", handled); - } - } +public class ElasticsearchHealthIndicator extends AbstractHealthIndicator { + + private final Client client; + + private final ElasticsearchHealthIndicatorProperties properties; + + public ElasticsearchHealthIndicator(Client client, + ElasticsearchHealthIndicatorProperties properties) { + this.client = client; + this.properties = properties; + } + + @Override + protected void doHealthCheck(Health.Builder builder) throws Exception { + List indices = this.properties.getIndices(); + ClusterHealthResponse response = this.client + .admin() + .cluster() + .health(Requests.clusterHealthRequest(indices.isEmpty() ? null : indices + .toArray(new String[indices.size()]))) + .actionGet(this.properties.getResponseTimeout()); + + switch (response.getStatus()) { + case GREEN: + case YELLOW: + builder.up(); + break; + case RED: + default: + builder.down(); + break; + } + builder.withDetail("clusterName", response.getClusterName()); + builder.withDetail("numberOfNodes", response.getNumberOfNodes()); + builder.withDetail("numberOfDataNodes", response.getNumberOfDataNodes()); + builder.withDetail("activePrimaryShards", response.getActivePrimaryShards()); + builder.withDetail("activeShards", response.getActiveShards()); + builder.withDetail("relocatingShards", response.getRelocatingShards()); + builder.withDetail("initializingShards", response.getInitializingShards()); + builder.withDetail("unassignedShards", response.getUnassignedShards()); + } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ElasticsearchHealthIndicatorProperties.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ElasticsearchHealthIndicatorProperties.java index c0b10aa46a..2c52c8c865 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ElasticsearchHealthIndicatorProperties.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ElasticsearchHealthIndicatorProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2012-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. @@ -16,37 +16,41 @@ package org.springframework.boot.actuate.health; +import java.util.ArrayList; +import java.util.List; + import org.springframework.boot.context.properties.ConfigurationProperties; /** * External configuration properties for {@link ElasticsearchHealthIndicator} * * @author Binwei Yang - * @since 1.2.2 + * @author Andy Wilkinson + * @since 1.3.0 */ @ConfigurationProperties("management.health.elasticsearch") public class ElasticsearchHealthIndicatorProperties { - public static final String ALL = "_all"; + /** + * Comma-separated index names + */ + private List indices = new ArrayList(); + + /** + * The time, in milliseconds, to wait for a response from the cluster + */ + private long responseTimeout = 100L; - /** - * comma separated index names. the default includes all indices. - */ - private String indices = ALL; + public List getIndices() { + return this.indices; + } - public String getIndices() { - return indices; - } + public long getResponseTimeout() { + return this.responseTimeout; + } - public void setIndices(String indices) { - this.indices = indices; - } + public void setResponseTimeout(long responseTimeout) { + this.responseTimeout = responseTimeout; + } - String[] getIndexNamesAsArray() { - if (null == indices) { - return new String[]{ALL}; - } else { - return indices.split(","); - } - } } diff --git a/spring-boot-actuator/src/main/resources/META-INF/spring.factories b/spring-boot-actuator/src/main/resources/META-INF/spring.factories index 52b28beda8..6d4f6a7867 100644 --- a/spring-boot-actuator/src/main/resources/META-INF/spring.factories +++ b/spring-boot-actuator/src/main/resources/META-INF/spring.factories @@ -12,5 +12,4 @@ org.springframework.boot.actuate.autoconfigure.MetricFilterAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.MetricRepositoryAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.PublicMetricsAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.TraceRepositoryAutoConfiguration,\ -org.springframework.boot.actuate.autoconfigure.TraceWebFilterAutoConfiguration,\ -org.springframework.boot.actuate.autoconfigure.ElasticsearchHealthIndicatorConfiguration +org.springframework.boot.actuate.autoconfigure.TraceWebFilterAutoConfiguration \ No newline at end of file diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfigurationTests.java index 251214a9aa..91171bbf06 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfigurationTests.java @@ -26,6 +26,7 @@ import org.junit.Test; import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.health.DataSourceHealthIndicator; import org.springframework.boot.actuate.health.DiskSpaceHealthIndicator; +import org.springframework.boot.actuate.health.ElasticsearchHealthIndicator; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.JmsHealthIndicator; import org.springframework.boot.actuate.health.MailHealthIndicator; @@ -35,6 +36,7 @@ import org.springframework.boot.actuate.health.RedisHealthIndicator; import org.springframework.boot.actuate.health.SolrHealthIndicator; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration; +import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; @@ -59,6 +61,7 @@ import static org.junit.Assert.assertEquals; * * @author Christian Dupuis * @author Stephane Nicoll + * @author Andy Wilkinson */ public class HealthIndicatorAutoConfigurationTests { @@ -362,6 +365,39 @@ public class HealthIndicatorAutoConfigurationTests { .getClass()); } + @Test + public void elasticSearchHealthIndicator() { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, + "management.health.diskspace.enabled:false"); + this.context.register(ElasticsearchAutoConfiguration.class, + ManagementServerProperties.class, HealthIndicatorAutoConfiguration.class); + this.context.refresh(); + + Map beans = this.context + .getBeansOfType(HealthIndicator.class); + assertEquals(1, beans.size()); + assertEquals(ElasticsearchHealthIndicator.class, beans.values().iterator().next() + .getClass()); + } + + @Test + public void notElasticSearchHealthIndicator() { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, + "management.health.elasticsearch.enabled:false", + "management.health.diskspace.enabled:false"); + this.context.register(ElasticsearchAutoConfiguration.class, + ManagementServerProperties.class, HealthIndicatorAutoConfiguration.class); + this.context.refresh(); + + Map beans = this.context + .getBeansOfType(HealthIndicator.class); + assertEquals(1, beans.size()); + assertEquals(ApplicationHealthIndicator.class, beans.values().iterator().next() + .getClass()); + } + @Configuration @EnableConfigurationProperties protected static class DataSourceConfig { diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ElasticsearchHealthIndicatorTest.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ElasticsearchHealthIndicatorTest.java deleted file mode 100644 index 2f59537f89..0000000000 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ElasticsearchHealthIndicatorTest.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2014 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.boot.actuate.health; - -import org.elasticsearch.client.Client; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.springframework.boot.actuate.autoconfigure.ElasticsearchHealthIndicatorConfiguration; -import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration; -import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; -import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchAutoConfiguration; -import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchDataAutoConfiguration; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -/** - * Test for {@link ElasticsearchHealthIndicator} for ElasticSearch cluster. - * - * @author Binwei Yang - * @since 1.2.2 - */ -public class ElasticsearchHealthIndicatorTest { - private AnnotationConfigApplicationContext context; - - @Before - public void setUp() throws Exception { - this.context = new AnnotationConfigApplicationContext( - PropertyPlaceholderAutoConfiguration.class, - ElasticsearchAutoConfiguration.class, - ElasticsearchDataAutoConfiguration.class, - EndpointAutoConfiguration.class, - ElasticsearchHealthIndicatorConfiguration.class - ); - } - - @After - public void close() { - if (null != context) { - context.close(); - } - } - - @Test - public void indicatorExists() { - assertEquals(1, this.context.getBeanNamesForType(Client.class).length); - - ElasticsearchHealthIndicator healthIndicator = this.context.getBean(ElasticsearchHealthIndicator.class); - assertNotNull(healthIndicator); - } - - @Test - public void configPropertiesUsed() { - ElasticsearchHealthIndicatorProperties properties = this.context.getBean(ElasticsearchHealthIndicatorProperties.class); - - // test default index - assertEquals(ElasticsearchHealthIndicatorProperties.ALL, properties.getIndices()); - assertEquals(1, properties.getIndexNamesAsArray().length); - assertEquals(ElasticsearchHealthIndicatorProperties.ALL, properties.getIndexNamesAsArray()[0]); - - // test specific indices - properties.setIndices("no-such-index"); - - ElasticsearchHealthIndicator healthIndicator = this.context.getBean(ElasticsearchHealthIndicator.class); - Health health = healthIndicator.health(); - assertEquals(Status.UNKNOWN, health.getStatus()); - } -} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ElasticsearchHealthIndicatorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ElasticsearchHealthIndicatorTests.java new file mode 100644 index 0000000000..611ca1086f --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ElasticsearchHealthIndicatorTests.java @@ -0,0 +1,238 @@ +/* + * Copyright 2012-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.boot.actuate.health; + +import java.util.Arrays; +import java.util.Map; + +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.ElasticsearchTimeoutException; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthStatus; +import org.elasticsearch.action.support.PlainActionFuture; +import org.elasticsearch.client.AdminClient; +import org.elasticsearch.client.Client; +import org.elasticsearch.client.ClusterAdminClient; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.hamcrest.Matchers.arrayContaining; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.any; + +/** + * Test for {@link ElasticsearchHealthIndicator}. + * + * @author Andy Wilkinson + */ +@RunWith(MockitoJUnitRunner.class) +public class ElasticsearchHealthIndicatorTests { + + @Mock + private Client client; + + @Mock + private AdminClient admin; + + @Mock + private ClusterAdminClient cluster; + + private ElasticsearchHealthIndicator indicator; + + private ElasticsearchHealthIndicatorProperties properties = new ElasticsearchHealthIndicatorProperties(); + + @Before + public void setUp() throws Exception { + given(this.client.admin()).willReturn(this.admin); + given(this.admin.cluster()).willReturn(this.cluster); + + this.indicator = new ElasticsearchHealthIndicator(this.client, this.properties); + } + + @Test + public void defaultConfigurationQueriesAllIndicesWith100msTimeout() { + TestActionFuture responseFuture = new TestActionFuture(); + responseFuture.onResponse(new StubClusterHealthResponse()); + ArgumentCaptor requestCaptor = ArgumentCaptor + .forClass(ClusterHealthRequest.class); + given(this.cluster.health(requestCaptor.capture())).willReturn(responseFuture); + Health health = this.indicator.health(); + assertThat(responseFuture.getTimeout, is(100L)); + assertThat(requestCaptor.getValue().indices(), is(nullValue())); + assertThat(health.getStatus(), is(Status.UP)); + } + + @Test + public void certainIndices() { + PlainActionFuture responseFuture = new PlainActionFuture(); + responseFuture.onResponse(new StubClusterHealthResponse()); + ArgumentCaptor requestCaptor = ArgumentCaptor + .forClass(ClusterHealthRequest.class); + given(this.cluster.health(requestCaptor.capture())).willReturn(responseFuture); + this.properties.getIndices() + .addAll(Arrays.asList("test-index-1", "test-index-2")); + Health health = this.indicator.health(); + assertThat(requestCaptor.getValue().indices(), + is(arrayContaining("test-index-1", "test-index-2"))); + assertThat(health.getStatus(), is(Status.UP)); + } + + @Test + public void customTimeout() { + TestActionFuture responseFuture = new TestActionFuture(); + responseFuture.onResponse(new StubClusterHealthResponse()); + ArgumentCaptor requestCaptor = ArgumentCaptor + .forClass(ClusterHealthRequest.class); + given(this.cluster.health(requestCaptor.capture())).willReturn(responseFuture); + this.properties.setResponseTimeout(1000L); + this.indicator.health(); + assertThat(responseFuture.getTimeout, is(1000L)); + } + + @Test + public void healthDetails() { + PlainActionFuture responseFuture = new PlainActionFuture(); + responseFuture.onResponse(new StubClusterHealthResponse()); + given(this.cluster.health(any(ClusterHealthRequest.class))).willReturn( + responseFuture); + Health health = this.indicator.health(); + assertThat(health.getStatus(), is(Status.UP)); + Map details = health.getDetails(); + assertDetail(details, "clusterName", "test-cluster"); + assertDetail(details, "activeShards", 1); + assertDetail(details, "relocatingShards", 2); + assertDetail(details, "activePrimaryShards", 3); + assertDetail(details, "initializingShards", 4); + assertDetail(details, "unassignedShards", 5); + assertDetail(details, "numberOfNodes", 6); + assertDetail(details, "numberOfDataNodes", 7); + } + + @Test + public void redResponseMapsToDown() { + PlainActionFuture responseFuture = new PlainActionFuture(); + responseFuture.onResponse(new StubClusterHealthResponse(ClusterHealthStatus.RED)); + given(this.cluster.health(any(ClusterHealthRequest.class))).willReturn( + responseFuture); + assertThat(this.indicator.health().getStatus(), is(Status.DOWN)); + } + + @Test + public void yellowResponseMapsToUp() { + PlainActionFuture responseFuture = new PlainActionFuture(); + responseFuture.onResponse(new StubClusterHealthResponse( + ClusterHealthStatus.YELLOW)); + given(this.cluster.health(any(ClusterHealthRequest.class))).willReturn( + responseFuture); + assertThat(this.indicator.health().getStatus(), is(Status.UP)); + } + + @Test + public void responseTimeout() { + PlainActionFuture responseFuture = new PlainActionFuture(); + given(this.cluster.health(any(ClusterHealthRequest.class))).willReturn( + responseFuture); + Health health = this.indicator.health(); + assertThat(health.getStatus(), is(Status.DOWN)); + assertThat((String) health.getDetails().get("error"), + containsString(ElasticsearchTimeoutException.class.getName())); + } + + @SuppressWarnings("unchecked") + private void assertDetail(Map details, String detail, T value) { + assertThat((T) details.get(detail), is(equalTo(value))); + } + + private static class StubClusterHealthResponse extends ClusterHealthResponse { + + private final ClusterHealthStatus status; + + private StubClusterHealthResponse() { + this(ClusterHealthStatus.GREEN); + } + + private StubClusterHealthResponse(ClusterHealthStatus status) { + super("test-cluster", null); + this.status = status; + } + + @Override + public int getActiveShards() { + return 1; + } + + @Override + public int getRelocatingShards() { + return 2; + } + + @Override + public int getActivePrimaryShards() { + return 3; + } + + @Override + public int getInitializingShards() { + return 4; + } + + @Override + public int getUnassignedShards() { + return 5; + } + + @Override + public int getNumberOfNodes() { + return 6; + } + + @Override + public int getNumberOfDataNodes() { + return 7; + } + + @Override + public ClusterHealthStatus getStatus() { + return this.status; + } + + } + + private static class TestActionFuture extends + PlainActionFuture { + + private long getTimeout = -1L; + + @Override + public ClusterHealthResponse actionGet(long timeoutMillis) + throws ElasticsearchException { + this.getTimeout = timeoutMillis; + return super.actionGet(timeoutMillis); + } + + } +} diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index 7fb6ae32c1..a32ec83c1b 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -63,6 +63,7 @@ 2.9.1 3.2 2.3.22 + 1.4.4 7.0.2 3.0.0 1.12 @@ -1084,6 +1085,11 @@ websocket-server ${jetty.version} + + org.elasticsearch + elasticsearch + ${elasticsearch.version} + org.flywaydb flyway-core diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 5714f57996..31bb2ab0f3 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -542,13 +542,15 @@ content into your application; rather pick only the properties that you need. # HEALTH INDICATORS (previously health.*) management.health.db.enabled=true + management.health.elasticsearch.enabled=true + management.health.elasticsearch.response-timeout=100 # the time, in milliseconds, to wait for a response from the cluster management.health.diskspace.enabled=true + management.health.diskspace.path=. + management.health.diskspace.threshold=10485760 management.health.mongo.enabled=true management.health.rabbit.enabled=true management.health.redis.enabled=true management.health.solr.enabled=true - management.health.diskspace.path=. - management.health.diskspace.threshold=10485760 management.health.status.order=DOWN, OUT_OF_SERVICE, UNKNOWN, UP # MVC ONLY ENDPOINTS