Add configurable timeout for Couchbase health indicator

This commit makes sure to use a configurable timeout to check if the
Couchbase cluster is up, rather than relying on the default that can be
quite long.

Closes gh-13879
pull/14019/merge
Stephane Nicoll 6 years ago
parent 21691f0b20
commit a023bd030a

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2017 the original author or authors. * Copyright 2012-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -32,6 +32,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration; import org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.core.CouchbaseOperations; import org.springframework.data.couchbase.core.CouchbaseOperations;
@ -41,6 +42,7 @@ import org.springframework.data.couchbase.core.CouchbaseOperations;
* {@link CouchbaseHealthIndicator}. * {@link CouchbaseHealthIndicator}.
* *
* @author Eddú Meléndez * @author Eddú Meléndez
* @author Stephane Nicoll
* @since 2.0.0 * @since 2.0.0
*/ */
@Configuration @Configuration
@ -49,14 +51,19 @@ import org.springframework.data.couchbase.core.CouchbaseOperations;
@ConditionalOnEnabledHealthIndicator("couchbase") @ConditionalOnEnabledHealthIndicator("couchbase")
@AutoConfigureBefore(HealthIndicatorAutoConfiguration.class) @AutoConfigureBefore(HealthIndicatorAutoConfiguration.class)
@AutoConfigureAfter(CouchbaseDataAutoConfiguration.class) @AutoConfigureAfter(CouchbaseDataAutoConfiguration.class)
@EnableConfigurationProperties(CouchbaseHealthIndicatorProperties.class)
public class CouchbaseHealthIndicatorAutoConfiguration extends public class CouchbaseHealthIndicatorAutoConfiguration extends
CompositeHealthIndicatorConfiguration<CouchbaseHealthIndicator, CouchbaseOperations> { CompositeHealthIndicatorConfiguration<CouchbaseHealthIndicator, CouchbaseOperations> {
private final Map<String, CouchbaseOperations> couchbaseOperations; private final Map<String, CouchbaseOperations> couchbaseOperations;
private final CouchbaseHealthIndicatorProperties properties;
public CouchbaseHealthIndicatorAutoConfiguration( public CouchbaseHealthIndicatorAutoConfiguration(
Map<String, CouchbaseOperations> couchbaseOperations) { Map<String, CouchbaseOperations> couchbaseOperations,
CouchbaseHealthIndicatorProperties properties) {
this.couchbaseOperations = couchbaseOperations; this.couchbaseOperations = couchbaseOperations;
this.properties = properties;
} }
@Bean @Bean
@ -65,4 +72,11 @@ public class CouchbaseHealthIndicatorAutoConfiguration extends
return createHealthIndicator(this.couchbaseOperations); return createHealthIndicator(this.couchbaseOperations);
} }
@Override
protected CouchbaseHealthIndicator createHealthIndicator(
CouchbaseOperations couchbaseOperations) {
return new CouchbaseHealthIndicator(couchbaseOperations,
this.properties.getTimeout());
}
} }

@ -0,0 +1,46 @@
/*
* Copyright 2012-2018 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.couchbase;
import java.time.Duration;
import org.springframework.boot.actuate.couchbase.CouchbaseHealthIndicator;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Configuration properties for {@link CouchbaseHealthIndicator}.
*
* @author Stephane Nicoll
* @since 2.0.5
*/
@ConfigurationProperties(prefix = "management.health.couchbase")
public class CouchbaseHealthIndicatorProperties {
/**
* Timeout for getting the Bucket information from the server.
*/
private Duration timeout = Duration.ofMillis(1000);
public Duration getTimeout() {
return this.timeout;
}
public void setTimeout(Duration timeout) {
this.timeout = timeout;
}
}

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2017 the original author or authors. * Copyright 2012-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -27,6 +27,7 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.core.CouchbaseOperations; import org.springframework.data.couchbase.core.CouchbaseOperations;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@ -35,6 +36,7 @@ import static org.mockito.Mockito.mock;
* Tests for {@link CouchbaseHealthIndicatorAutoConfiguration}. * Tests for {@link CouchbaseHealthIndicatorAutoConfiguration}.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Stephane Nicoll
*/ */
public class CouchbaseHealthIndicatorAutoConfigurationTests { public class CouchbaseHealthIndicatorAutoConfigurationTests {
@ -50,6 +52,17 @@ public class CouchbaseHealthIndicatorAutoConfigurationTests {
.doesNotHaveBean(ApplicationHealthIndicator.class)); .doesNotHaveBean(ApplicationHealthIndicator.class));
} }
@Test
public void runWithCustomTimeoutShouldCreateIndicator() {
this.contextRunner.withPropertyValues("management.health.couchbase.timeout=2s")
.run((context) -> {
assertThat(context).hasSingleBean(CouchbaseHealthIndicator.class);
assertThat(ReflectionTestUtils.getField(
context.getBean(CouchbaseHealthIndicator.class), "timeout"))
.isEqualTo(2000L);
});
}
@Test @Test
public void runWhenDisabledShouldNotCreateIndicator() { public void runWhenDisabledShouldNotCreateIndicator() {
this.contextRunner.withPropertyValues("management.health.couchbase.enabled:false") this.contextRunner.withPropertyValues("management.health.couchbase.enabled:false")

@ -16,6 +16,10 @@
package org.springframework.boot.actuate.couchbase; package org.springframework.boot.actuate.couchbase;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import com.couchbase.client.java.bucket.BucketInfo; import com.couchbase.client.java.bucket.BucketInfo;
import com.couchbase.client.java.cluster.ClusterInfo; import com.couchbase.client.java.cluster.ClusterInfo;
@ -35,26 +39,57 @@ import org.springframework.util.StringUtils;
*/ */
public class CouchbaseHealthIndicator extends AbstractHealthIndicator { public class CouchbaseHealthIndicator extends AbstractHealthIndicator {
private CouchbaseOperations operations; private final CouchbaseOperations operations;
public CouchbaseHealthIndicator() { private final long timeout;
super("Couchbase health check failed");
}
public CouchbaseHealthIndicator(CouchbaseOperations couchbaseOperations) { /**
* Create an indicator with the specified {@link CouchbaseOperations} and
* {@code timeout}.
* @param couchbaseOperations the couchbase operations
* @param timeout the request timeout
*/
public CouchbaseHealthIndicator(CouchbaseOperations couchbaseOperations,
Duration timeout) {
super("Couchbase health check failed"); super("Couchbase health check failed");
Assert.notNull(couchbaseOperations, "CouchbaseOperations must not be null"); Assert.notNull(couchbaseOperations, "CouchbaseOperations must not be null");
Assert.notNull(timeout, "Timeout must not be null");
this.operations = couchbaseOperations; this.operations = couchbaseOperations;
this.timeout = timeout.toMillis();
}
/**
* Create an indicator with the specified {@link CouchbaseOperations}.
* @param couchbaseOperations the couchbase operations
* @deprecated as of 2.0.5 in favour of
* {@link #CouchbaseHealthIndicator(CouchbaseOperations, Duration)}
*/
@Deprecated
public CouchbaseHealthIndicator(CouchbaseOperations couchbaseOperations) {
this(couchbaseOperations, Duration.ofSeconds(1));
} }
@Override @Override
protected void doHealthCheck(Health.Builder builder) throws Exception { protected void doHealthCheck(Health.Builder builder) throws Exception {
ClusterInfo cluster = this.operations.getCouchbaseClusterInfo(); ClusterInfo cluster = this.operations.getCouchbaseClusterInfo();
BucketInfo bucket = this.operations.getCouchbaseBucket().bucketManager().info(); BucketInfo bucket = getBucketInfo();
String versions = StringUtils String versions = StringUtils
.collectionToCommaDelimitedString(cluster.getAllVersions()); .collectionToCommaDelimitedString(cluster.getAllVersions());
String nodes = StringUtils.collectionToCommaDelimitedString(bucket.nodeList()); String nodes = StringUtils.collectionToCommaDelimitedString(bucket.nodeList());
builder.up().withDetail("versions", versions).withDetail("nodes", nodes); builder.up().withDetail("versions", versions).withDetail("nodes", nodes);
} }
private BucketInfo getBucketInfo() throws Exception {
try {
return this.operations.getCouchbaseBucket().bucketManager().info(this.timeout,
TimeUnit.MILLISECONDS);
}
catch (RuntimeException ex) {
if (ex.getCause() instanceof TimeoutException) {
throw (TimeoutException) ex.getCause();
}
throw ex;
}
}
} }

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2017 the original author or authors. * Copyright 2012-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -18,7 +18,10 @@ package org.springframework.boot.actuate.couchbase;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.time.Duration;
import java.util.Collections; import java.util.Collections;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import com.couchbase.client.java.Bucket; import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.bucket.BucketInfo; import com.couchbase.client.java.bucket.BucketInfo;
@ -51,7 +54,7 @@ public class CouchbaseHealthIndicatorTests {
given(bucketInfo.nodeList()).willReturn( given(bucketInfo.nodeList()).willReturn(
Collections.singletonList(InetAddress.getByName("127.0.0.1"))); Collections.singletonList(InetAddress.getByName("127.0.0.1")));
BucketManager bucketManager = mock(BucketManager.class); BucketManager bucketManager = mock(BucketManager.class);
given(bucketManager.info()).willReturn(bucketInfo); given(bucketManager.info(2000, TimeUnit.MILLISECONDS)).willReturn(bucketInfo);
Bucket bucket = mock(Bucket.class); Bucket bucket = mock(Bucket.class);
given(bucket.bucketManager()).willReturn(bucketManager); given(bucket.bucketManager()).willReturn(bucketManager);
ClusterInfo clusterInfo = mock(ClusterInfo.class); ClusterInfo clusterInfo = mock(ClusterInfo.class);
@ -61,7 +64,7 @@ public class CouchbaseHealthIndicatorTests {
given(couchbaseOperations.getCouchbaseBucket()).willReturn(bucket); given(couchbaseOperations.getCouchbaseBucket()).willReturn(bucket);
given(couchbaseOperations.getCouchbaseClusterInfo()).willReturn(clusterInfo); given(couchbaseOperations.getCouchbaseClusterInfo()).willReturn(clusterInfo);
CouchbaseHealthIndicator healthIndicator = new CouchbaseHealthIndicator( CouchbaseHealthIndicator healthIndicator = new CouchbaseHealthIndicator(
couchbaseOperations); couchbaseOperations, Duration.ofSeconds(2));
Health health = healthIndicator.health(); Health health = healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.UP); assertThat(health.getStatus()).isEqualTo(Status.UP);
assertThat(health.getDetails()).containsOnly(entry("versions", "1.2.3"), assertThat(health.getDetails()).containsOnly(entry("versions", "1.2.3"),
@ -70,13 +73,29 @@ public class CouchbaseHealthIndicatorTests {
verify(bucketInfo).nodeList(); verify(bucketInfo).nodeList();
} }
@Test
public void couchbaseTimeout() {
BucketManager bucketManager = mock(BucketManager.class);
given(bucketManager.info(1500, TimeUnit.MILLISECONDS)).willThrow(
new RuntimeException(new TimeoutException("timeout, expected")));
Bucket bucket = mock(Bucket.class);
given(bucket.bucketManager()).willReturn(bucketManager);
CouchbaseOperations couchbaseOperations = mock(CouchbaseOperations.class);
given(couchbaseOperations.getCouchbaseBucket()).willReturn(bucket);
CouchbaseHealthIndicator healthIndicator = new CouchbaseHealthIndicator(
couchbaseOperations, Duration.ofMillis(1500));
Health health = healthIndicator.health();
assertThat((String) health.getDetails().get("error"))
.contains("timeout, expected");
}
@Test @Test
public void couchbaseIsDown() { public void couchbaseIsDown() {
CouchbaseOperations couchbaseOperations = mock(CouchbaseOperations.class); CouchbaseOperations couchbaseOperations = mock(CouchbaseOperations.class);
given(couchbaseOperations.getCouchbaseClusterInfo()) given(couchbaseOperations.getCouchbaseClusterInfo())
.willThrow(new IllegalStateException("test, expected")); .willThrow(new IllegalStateException("test, expected"));
CouchbaseHealthIndicator healthIndicator = new CouchbaseHealthIndicator( CouchbaseHealthIndicator healthIndicator = new CouchbaseHealthIndicator(
couchbaseOperations); couchbaseOperations, Duration.ofSeconds(1));
Health health = healthIndicator.health(); Health health = healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.DOWN); assertThat(health.getStatus()).isEqualTo(Status.DOWN);
assertThat((String) health.getDetails().get("error")).contains("test, expected"); assertThat((String) health.getDetails().get("error")).contains("test, expected");

@ -1276,6 +1276,7 @@ content into your application. Rather, pick only the properties that you need.
management.health.db.enabled=true # Whether to enable database health check. management.health.db.enabled=true # Whether to enable database health check.
management.health.cassandra.enabled=true # Whether to enable Cassandra health check. management.health.cassandra.enabled=true # Whether to enable Cassandra health check.
management.health.couchbase.enabled=true # Whether to enable Couchbase health check. management.health.couchbase.enabled=true # Whether to enable Couchbase health check.
management.health.couchbase.timeout=1000ms # Timeout for getting the Bucket information from the server.
management.health.defaults.enabled=true # Whether to enable default health indicators. management.health.defaults.enabled=true # Whether to enable default health indicators.
management.health.diskspace.enabled=true # Whether to enable disk space health check. management.health.diskspace.enabled=true # Whether to enable disk space health check.
management.health.diskspace.path= # Path used to compute the available disk space. management.health.diskspace.path= # Path used to compute the available disk space.

Loading…
Cancel
Save