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,35 +173,40 @@ true + + org.springframework.boot + spring-boot + test-jar + test + ch.qos.logback logback-classic test + + org.apache.tomcat.embed + tomcat-embed-logging-juli + test + org.crashub crash.connectors.telnet test - - org.springframework - spring-test - test - org.hsqldb hsqldb 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; +public class ElasticsearchHealthIndicator extends AbstractHealthIndicator { - @Autowired - private ElasticsearchHealthIndicatorProperties properties; + private final Client client; - @Override - protected void doHealthCheck(Health.Builder builder) throws Exception { - try { - ClusterHealthResponse response = client.admin().cluster().health(Requests.clusterHealthRequest( - properties.getIndexNamesAsArray() - )).actionGet(100); + private final ElasticsearchHealthIndicatorProperties properties; - 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 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(); - /** - * comma separated index names. the default includes all indices. - */ - private String indices = ALL; + /** + * The time, in milliseconds, to wait for a response from the cluster + */ + private long responseTimeout = 100L; - public String getIndices() { - return indices; - } + public List getIndices() { + return this.indices; + } - public void setIndices(String indices) { - this.indices = indices; - } + public long getResponseTimeout() { + return this.responseTimeout; + } + + 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