From b76a519f654a4c204cadfcaabebf62aee3b951e5 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 22 May 2014 16:45:14 +0100 Subject: [PATCH] Add a HealthIndicator for Solr --- spring-boot-actuator/pom.xml | 9 +- .../HealthIndicatorAutoConfiguration.java | 37 +++++++- .../actuate/health/SolrHealthIndicator.java | 47 ++++++++++ ...HealthIndicatorAutoConfigurationTests.java | 36 +++++++- .../health/SolrHealthIndicatorTests.java | 91 +++++++++++++++++++ 5 files changed, 213 insertions(+), 7 deletions(-) create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/SolrHealthIndicator.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/SolrHealthIndicatorTests.java diff --git a/spring-boot-actuator/pom.xml b/spring-boot-actuator/pom.xml index d7f0cfdff8..6a8e20fb32 100644 --- a/spring-boot-actuator/pom.xml +++ b/spring-boot-actuator/pom.xml @@ -50,7 +50,7 @@ javax.servlet javax.servlet-api true - + org.hibernate hibernate-validator @@ -71,6 +71,11 @@ spring-webmvc true + + org.springframework.data + spring-data-mongodb + true + org.springframework.data spring-data-redis @@ -78,7 +83,7 @@ org.springframework.data - spring-data-mongodb + spring-data-solr true 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 55d697d6b9..b201e0ab6d 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 @@ -21,6 +21,7 @@ import java.util.Map; import javax.sql.DataSource; +import org.apache.solr.client.solrj.SolrServer; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -32,6 +33,7 @@ import org.springframework.boot.actuate.health.OrderedHealthAggregator; import org.springframework.boot.actuate.health.RabbitHealthIndicator; import org.springframework.boot.actuate.health.RedisHealthIndicator; import org.springframework.boot.actuate.health.SimpleDataSourceHealthIndicator; +import org.springframework.boot.actuate.health.SolrHealthIndicator; import org.springframework.boot.actuate.health.VanillaHealthIndicator; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; @@ -44,6 +46,7 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; import org.springframework.boot.autoconfigure.mongo.MongoDataAutoConfiguration; import org.springframework.boot.autoconfigure.redis.RedisAutoConfiguration; +import org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.mongodb.core.MongoTemplate; @@ -51,15 +54,16 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; /** * {@link EnableAutoConfiguration Auto-configuration} for {@link HealthIndicator}s. - * + * * @author Christian Dupuis + * @author Andy Wilkinson * @since 1.1.0 */ @Configuration @AutoConfigureBefore({ EndpointAutoConfiguration.class }) @AutoConfigureAfter({ DataSourceAutoConfiguration.class, MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, RedisAutoConfiguration.class, - RabbitAutoConfiguration.class }) + RabbitAutoConfiguration.class, SolrAutoConfiguration.class }) public class HealthIndicatorAutoConfiguration { @Value("${health.status.order:}") @@ -199,4 +203,33 @@ public class HealthIndicatorAutoConfiguration { } } + @Configuration + @ConditionalOnBean(SolrServer.class) + @ConditionalOnExpression("${health.solr.enabled:true}") + public static class SolrHealthIndicatorConfiguration { + + @Autowired + private HealthAggregator healthAggregator; + + @Autowired + private Map solrServers; + + @Bean + @ConditionalOnMissingBean(name = "solrHealthIndicator") + public HealthIndicator rabbitHealthIndicator() { + if (this.solrServers.size() == 1) { + return new SolrHealthIndicator(this.solrServers.entrySet().iterator() + .next().getValue()); + } + + CompositeHealthIndicator composite = new CompositeHealthIndicator( + this.healthAggregator); + for (Map.Entry entry : this.solrServers.entrySet()) { + composite.addHealthIndicator(entry.getKey(), new SolrHealthIndicator( + entry.getValue())); + } + return composite; + } + } + } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/SolrHealthIndicator.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/SolrHealthIndicator.java new file mode 100644 index 0000000000..8aadabc43b --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/SolrHealthIndicator.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-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.apache.solr.client.solrj.SolrServer; + +/** + * {@link HealthIndicator} for Apache Solr + * + * @author Andy Wilkinson + * @since 1.1.0 + */ +public class SolrHealthIndicator implements HealthIndicator { + + private final SolrServer solrServer; + + public SolrHealthIndicator(SolrServer solrServer) { + this.solrServer = solrServer; + } + + @Override + public Health health() { + Health health = new Health(); + try { + this.solrServer.ping(); + return health.up().withDetail("solrStatus", + this.solrServer.ping().getResponse().get("status")); + } + catch (Exception e) { + return health.down().withException(e); + } + } +} 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 f859eb467d..cf78c32d3c 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,12 +26,14 @@ import org.springframework.boot.actuate.health.MongoHealthIndicator; import org.springframework.boot.actuate.health.RabbitHealthIndicator; import org.springframework.boot.actuate.health.RedisHealthIndicator; import org.springframework.boot.actuate.health.SimpleDataSourceHealthIndicator; +import org.springframework.boot.actuate.health.SolrHealthIndicator; import org.springframework.boot.actuate.health.VanillaHealthIndicator; import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; import org.springframework.boot.autoconfigure.mongo.MongoDataAutoConfiguration; import org.springframework.boot.autoconfigure.redis.RedisAutoConfiguration; +import org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration; import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -39,7 +41,7 @@ import static org.junit.Assert.assertEquals; /** * Tests for {@link HealthIndicatorAutoConfiguration}. - * + * * @author Christian Dupuis */ public class HealthIndicatorAutoConfigurationTests { @@ -130,11 +132,12 @@ public class HealthIndicatorAutoConfigurationTests { public void combinedHealthIndicator() { this.context = new AnnotationConfigApplicationContext(); this.context.register(MongoAutoConfiguration.class, RedisAutoConfiguration.class, - MongoDataAutoConfiguration.class, HealthIndicatorAutoConfiguration.class); + MongoDataAutoConfiguration.class, SolrAutoConfiguration.class, + HealthIndicatorAutoConfiguration.class); this.context.refresh(); Map beans = this.context .getBeansOfType(HealthIndicator.class); - assertEquals(2, beans.size()); + assertEquals(3, beans.size()); } @Test @@ -190,4 +193,31 @@ public class HealthIndicatorAutoConfigurationTests { assertEquals(VanillaHealthIndicator.class, beans.values().iterator().next() .getClass()); } + + @Test + public void solrHeathIndicator() { + this.context = new AnnotationConfigApplicationContext(); + this.context.register(SolrAutoConfiguration.class, + HealthIndicatorAutoConfiguration.class); + this.context.refresh(); + Map beans = this.context + .getBeansOfType(HealthIndicator.class); + assertEquals(1, beans.size()); + assertEquals(SolrHealthIndicator.class, beans.values().iterator().next() + .getClass()); + } + + @Test + public void notSolrHeathIndicator() { + this.context = new AnnotationConfigApplicationContext(); + this.context.register(SolrAutoConfiguration.class, + HealthIndicatorAutoConfiguration.class); + EnvironmentTestUtils.addEnvironment(this.context, "health.solr.enabled:false"); + this.context.refresh(); + Map beans = this.context + .getBeansOfType(HealthIndicator.class); + assertEquals(1, beans.size()); + assertEquals(VanillaHealthIndicator.class, beans.values().iterator().next() + .getClass()); + } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/SolrHealthIndicatorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/SolrHealthIndicatorTests.java new file mode 100644 index 0000000000..e29b7ab5a1 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/SolrHealthIndicatorTests.java @@ -0,0 +1,91 @@ +/* + * Copyright 2012-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 java.io.IOException; + +import org.apache.solr.client.solrj.SolrServer; +import org.apache.solr.client.solrj.response.SolrPingResponse; +import org.apache.solr.common.util.NamedList; +import org.junit.After; +import org.junit.Test; +import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.HealthIndicatorAutoConfiguration; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link SolrHealthIndicator} + * + * @author Andy Wilkinson + */ +public class SolrHealthIndicatorTests { + + private AnnotationConfigApplicationContext context; + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void indicatorExists() { + this.context = new AnnotationConfigApplicationContext( + PropertyPlaceholderAutoConfiguration.class, SolrAutoConfiguration.class, + EndpointAutoConfiguration.class, HealthIndicatorAutoConfiguration.class); + assertEquals(1, this.context.getBeanNamesForType(SolrServer.class).length); + SolrHealthIndicator healthIndicator = this.context + .getBean(SolrHealthIndicator.class); + assertNotNull(healthIndicator); + } + + @Test + public void solrIsUp() throws Exception { + SolrServer solrServer = mock(SolrServer.class); + SolrPingResponse pingResponse = new SolrPingResponse(); + NamedList response = new NamedList(); + response.add("status", "OK"); + pingResponse.setResponse(response); + when(solrServer.ping()).thenReturn(pingResponse); + + SolrHealthIndicator healthIndicator = new SolrHealthIndicator(solrServer); + Health health = healthIndicator.health(); + assertEquals(Status.UP, health.getStatus()); + assertEquals("OK", health.getDetails().get("solrStatus")); + } + + @Test + public void solrIsDown() throws Exception { + SolrServer solrServer = mock(SolrServer.class); + when(solrServer.ping()).thenThrow(new IOException("Connection failed")); + + SolrHealthIndicator healthIndicator = new SolrHealthIndicator(solrServer); + Health health = healthIndicator.health(); + assertEquals(Status.DOWN, health.getStatus()); + assertTrue(((String) health.getDetails().get("error")) + .contains("Connection failed")); + } +}