Add LDAP health actuator

Provide specific health actuator endpoint to verify if LDAP connection
is valid.

See gh-7905
This commit is contained in:
Eddú Meléndez 2017-01-07 19:40:05 -05:00 committed by Stephane Nicoll
parent db99ed843e
commit b2250f4ad8
6 changed files with 231 additions and 5 deletions

View File

@ -166,6 +166,11 @@
<artifactId>spring-data-couchbase</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-ldap</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2016 the original author or authors.
* Copyright 2012-2017 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.
@ -39,6 +39,7 @@ import org.springframework.boot.actuate.health.DiskSpaceHealthIndicatorPropertie
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.JmsHealthIndicator;
import org.springframework.boot.actuate.health.LdapHealthIndicator;
import org.springframework.boot.actuate.health.MailHealthIndicator;
import org.springframework.boot.actuate.health.MongoHealthIndicator;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
@ -56,6 +57,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration;
import org.springframework.boot.autoconfigure.data.ldap.LdapDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration;
@ -77,6 +79,7 @@ import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.ldap.core.LdapOperations;
import org.springframework.mail.javamail.JavaMailSenderImpl;
/**
@ -96,9 +99,10 @@ import org.springframework.mail.javamail.JavaMailSenderImpl;
CassandraDataAutoConfiguration.class, CouchbaseDataAutoConfiguration.class,
DataSourceAutoConfiguration.class, ElasticsearchAutoConfiguration.class,
JestAutoConfiguration.class, JmsAutoConfiguration.class,
MailSenderAutoConfiguration.class, MongoAutoConfiguration.class,
MongoDataAutoConfiguration.class, RabbitAutoConfiguration.class,
RedisAutoConfiguration.class, SolrAutoConfiguration.class })
LdapDataAutoConfiguration.class, MailSenderAutoConfiguration.class,
MongoAutoConfiguration.class, MongoDataAutoConfiguration.class,
RabbitAutoConfiguration.class, RedisAutoConfiguration.class,
SolrAutoConfiguration.class })
@EnableConfigurationProperties({ HealthIndicatorProperties.class })
@Import({
ElasticsearchHealthIndicatorConfiguration.ElasticsearchClientHealthIndicatorConfiguration.class,
@ -231,6 +235,27 @@ public class HealthIndicatorAutoConfiguration {
}
@Configuration
@ConditionalOnClass(LdapOperations.class)
@ConditionalOnBean(LdapOperations.class)
@ConditionalOnEnabledHealthIndicator("ldap")
public static class LdapHealthIndicatorConfiguration extends
CompositeHealthIndicatorConfiguration<LdapHealthIndicator, LdapOperations> {
private final Map<String, LdapOperations> ldapOperations;
public LdapHealthIndicatorConfiguration(Map<String, LdapOperations> ldapOperations) {
this.ldapOperations = ldapOperations;
}
@Bean
@ConditionalOnMissingBean(name = "ldapHealthIndicator")
public HealthIndicator ldapHealthIndicator() {
return createHealthIndicator(this.ldapOperations);
}
}
@Configuration
@ConditionalOnBean(MongoTemplate.class)
@ConditionalOnEnabledHealthIndicator("mongo")

View File

@ -0,0 +1,52 @@
/*
* Copyright 2012-2017 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 javax.naming.NamingException;
import javax.naming.directory.DirContext;
import org.springframework.ldap.core.ContextExecutor;
import org.springframework.ldap.core.LdapOperations;
import org.springframework.util.Assert;
/**
* {@link HealthIndicator} for configured LDAP server(s).
*
* @author Eddú Meléndez
* @version 1.5.0
*/
public class LdapHealthIndicator extends AbstractHealthIndicator {
private final LdapOperations ldapOperations;
public LdapHealthIndicator(LdapOperations ldapOperations) {
Assert.notNull(ldapOperations, "LdapOperations must not be null");
this.ldapOperations = ldapOperations;
}
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
String version = (String) this.ldapOperations.executeReadOnly(new ContextExecutor<Object>() {
@Override
public Object executeWithContext(DirContext ctx) throws NamingException {
return ctx.getEnvironment().get("java.naming.ldap.version");
}
});
builder.up().withDetail("version", version);
}
}

View File

@ -139,6 +139,12 @@
"description": "Enable JMS health check.",
"defaultValue": true
},
{
"name": "management.health.ldap.enabled",
"type": "java.lang.Boolean",
"description": "Enable LDAP health check.",
"defaultValue": true
},
{
"name": "management.health.mongo.enabled",
"type": "java.lang.Boolean",

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2016 the original author or authors.
* Copyright 2012-2017 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.
@ -35,6 +35,7 @@ import org.springframework.boot.actuate.health.ElasticsearchJestHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.JmsHealthIndicator;
import org.springframework.boot.actuate.health.LdapHealthIndicator;
import org.springframework.boot.actuate.health.MailHealthIndicator;
import org.springframework.boot.actuate.health.MongoHealthIndicator;
import org.springframework.boot.actuate.health.RabbitHealthIndicator;
@ -63,6 +64,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.couchbase.core.CouchbaseOperations;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.ldap.core.LdapOperations;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@ -535,6 +537,35 @@ public class HealthIndicatorAutoConfigurationTests {
.isEqualTo(ApplicationHealthIndicator.class);
}
@Test
public void ldapHealthIndicator() throws Exception {
EnvironmentTestUtils.addEnvironment(this.context,
"management.health.diskspace.enabled:false");
this.context.register(LdapConfiguration.class,
ManagementServerProperties.class, HealthIndicatorAutoConfiguration.class);
this.context.refresh();
Map<String, HealthIndicator> beans = this.context
.getBeansOfType(HealthIndicator.class);
assertThat(beans.size()).isEqualTo(1);
assertThat(beans.values().iterator().next().getClass())
.isEqualTo(LdapHealthIndicator.class);
}
@Test
public void notLdapHealthIndicator() throws Exception {
EnvironmentTestUtils.addEnvironment(this.context,
"management.health.diskspace.enabled:false",
"management.health.ldap.enabled:false");
this.context.register(LdapConfiguration.class,
ManagementServerProperties.class, HealthIndicatorAutoConfiguration.class);
this.context.refresh();
Map<String, HealthIndicator> beans = this.context
.getBeansOfType(HealthIndicator.class);
assertThat(beans.size()).isEqualTo(1);
assertThat(beans.values().iterator().next().getClass())
.isEqualTo(ApplicationHealthIndicator.class);
}
@Configuration
@EnableConfigurationProperties
protected static class DataSourceConfig {
@ -605,4 +636,15 @@ public class HealthIndicatorAutoConfigurationTests {
}
@Configuration
protected static class LdapConfiguration {
@Bean
public LdapOperations ldapOperations() {
LdapOperations operations = mock(LdapOperations.class);
return operations;
}
}
}

View File

@ -0,0 +1,96 @@
/*
* Copyright 2012-2017 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.junit.After;
import org.junit.Before;
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.data.ldap.LdapDataAutoConfiguration;
import org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.ldap.CommunicationException;
import org.springframework.ldap.core.ContextExecutor;
import org.springframework.ldap.core.LdapTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link LdapHealthIndicator}
*
* @author Eddú Meléndez
*/
public class LdapHealthIndicatorTests {
private AnnotationConfigApplicationContext context;
@Before
public void setup() {
this.context = new AnnotationConfigApplicationContext();
}
@After
public void close() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void indicatorExist() {
this.context.register(LdapAutoConfiguration.class, LdapDataAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class, EndpointAutoConfiguration.class,
HealthIndicatorAutoConfiguration.class);
this.context.refresh();
LdapTemplate ldapTemplate = this.context.getBean(LdapTemplate.class);
assertThat(ldapTemplate).isNotNull();
LdapHealthIndicator healthIndicator = this.context.getBean(LdapHealthIndicator.class);
assertThat(healthIndicator).isNotNull();
}
@Test
public void ldapIsUp() {
LdapTemplate ldapTemplate = mock(LdapTemplate.class);
given(ldapTemplate.executeReadOnly(any(ContextExecutor.class))).willReturn("3");
LdapHealthIndicator healthIndicator = new LdapHealthIndicator(ldapTemplate);
Health health = healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.UP);
assertThat(health.getDetails().get("version")).isEqualTo("3");
verify(ldapTemplate).executeReadOnly(any(ContextExecutor.class));
}
@Test
public void ldapIsDown() {
LdapTemplate ldapTemplate = mock(LdapTemplate.class);
given(ldapTemplate.executeReadOnly(any(ContextExecutor.class)))
.willThrow(new CommunicationException(new javax.naming.CommunicationException("Connection failed")));
LdapHealthIndicator healthIndicator = new LdapHealthIndicator(ldapTemplate);
Health health = healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.DOWN);
assertThat((String) health.getDetails().get("error"))
.contains("Connection failed");
verify(ldapTemplate).executeReadOnly(any(ContextExecutor.class));
}
}