Merge pull request #13864 from ayudovin:add-health-indicator-for-reactive-cassandra

* pr/13864:
  Polish "Add reactive health indicator for Cassandra"
  Add reactive health indicator for Cassandra
pull/13873/merge
Stephane Nicoll 6 years ago
commit 7bc1863871

@ -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");
* you may not use this file except in compliance with the License.
@ -16,55 +16,39 @@
package org.springframework.boot.actuate.autoconfigure.cassandra;
import java.util.Map;
import com.datastax.driver.core.Cluster;
import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthIndicatorConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.cassandra.CassandraHealthIndicator;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.cassandra.CassandraReactiveHealthIndicator;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.context.annotation.Import;
/**
* {@link EnableAutoConfiguration Auto-configuration} for
* {@link CassandraHealthIndicator}.
* {@link EnableAutoConfiguration Auto-configuration} for {@link CassandraHealthIndicator}
* and {@link CassandraReactiveHealthIndicator}.
*
* @author Julien Dubois
* @author Stephane Nicoll
* @since 2.0.0
*/
@Configuration
@ConditionalOnClass({ CassandraOperations.class, Cluster.class })
@ConditionalOnBean(CassandraOperations.class)
@ConditionalOnClass(Cluster.class)
@ConditionalOnEnabledHealthIndicator("cassandra")
@AutoConfigureBefore(HealthIndicatorAutoConfiguration.class)
@AutoConfigureAfter({ CassandraAutoConfiguration.class,
CassandraDataAutoConfiguration.class })
public class CassandraHealthIndicatorAutoConfiguration extends
CompositeHealthIndicatorConfiguration<CassandraHealthIndicator, CassandraOperations> {
private final Map<String, CassandraOperations> cassandraOperations;
public CassandraHealthIndicatorAutoConfiguration(
Map<String, CassandraOperations> cassandraOperations) {
this.cassandraOperations = cassandraOperations;
}
@Bean
@ConditionalOnMissingBean(name = "cassandraHealthIndicator")
public HealthIndicator cassandraHealthIndicator() {
return createHealthIndicator(this.cassandraOperations);
}
CassandraDataAutoConfiguration.class,
CassandraReactiveDataAutoConfiguration.class })
@Import({ CassandraReactiveHealthIndicatorConfiguration.class,
CassandraHealthIndicatorConfiguration.class })
public class CassandraHealthIndicatorAutoConfiguration {
}

@ -0,0 +1,55 @@
/*
* 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.cassandra;
import java.util.Map;
import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthIndicatorConfiguration;
import org.springframework.boot.actuate.cassandra.CassandraHealthIndicator;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.cassandra.core.CassandraOperations;
/**
* Configuration for {@link CassandraHealthIndicator}.
*
* @author Julien Dubois
*/
@Configuration
@ConditionalOnClass(CassandraOperations.class)
@ConditionalOnBean(CassandraOperations.class)
class CassandraHealthIndicatorConfiguration extends
CompositeHealthIndicatorConfiguration<CassandraHealthIndicator, CassandraOperations> {
private final Map<String, CassandraOperations> cassandraOperations;
CassandraHealthIndicatorConfiguration(
Map<String, CassandraOperations> cassandraOperations) {
this.cassandraOperations = cassandraOperations;
}
@Bean
@ConditionalOnMissingBean(name = "cassandraHealthIndicator")
public HealthIndicator cassandraHealthIndicator() {
return createHealthIndicator(this.cassandraOperations);
}
}

@ -0,0 +1,55 @@
/*
* 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.cassandra;
import java.util.Map;
import org.springframework.boot.actuate.autoconfigure.health.CompositeReactiveHealthIndicatorConfiguration;
import org.springframework.boot.actuate.cassandra.CassandraReactiveHealthIndicator;
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.cassandra.core.ReactiveCassandraOperations;
/**
* Configuration for {@link CassandraReactiveHealthIndicator}.
*
* @author Artsiom Yudovin
* @author Stephane Nicoll
*/
@Configuration
@ConditionalOnClass(ReactiveCassandraOperations.class)
@ConditionalOnBean(ReactiveCassandraOperations.class)
class CassandraReactiveHealthIndicatorConfiguration extends
CompositeReactiveHealthIndicatorConfiguration<CassandraReactiveHealthIndicator, ReactiveCassandraOperations> {
private final Map<String, ReactiveCassandraOperations> reactiveCassandraOperations;
CassandraReactiveHealthIndicatorConfiguration(
Map<String, ReactiveCassandraOperations> reactiveCassandraOperations) {
this.reactiveCassandraOperations = reactiveCassandraOperations;
}
@Bean
@ConditionalOnMissingBean(name = "cassandraReactiveHealthIndicator")
public ReactiveHealthIndicator cassandraHealthIndicator() {
return createHealthIndicator(this.reactiveCassandraOperations);
}
}

@ -0,0 +1,73 @@
/*
* 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.cassandra;
import org.junit.Test;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.cassandra.CassandraHealthIndicator;
import org.springframework.boot.actuate.cassandra.CassandraReactiveHealthIndicator;
import org.springframework.boot.actuate.health.ApplicationHealthIndicator;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.cassandra.core.ReactiveCassandraOperations;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link CassandraReactiveHealthIndicatorConfiguration}.
*
* @author Artsiom Yudovin
* @author Stephane Nicoll
*/
public class CassandraReactiveHealthIndicatorConfigurationTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withUserConfiguration(CassandraMockConfiguration.class).withConfiguration(
AutoConfigurations.of(CassandraHealthIndicatorAutoConfiguration.class,
HealthIndicatorAutoConfiguration.class));
@Test
public void runShouldCreateIndicator() {
this.contextRunner.run((context) -> assertThat(context)
.hasSingleBean(CassandraReactiveHealthIndicator.class)
.doesNotHaveBean(CassandraHealthIndicator.class)
.doesNotHaveBean(ApplicationHealthIndicator.class));
}
@Test
public void runWhenDisabledShouldNotCreateIndicator() {
this.contextRunner.withPropertyValues("management.health.cassandra.enabled:false")
.run((context) -> assertThat(context)
.doesNotHaveBean(CassandraReactiveHealthIndicator.class)
.hasSingleBean(ApplicationHealthIndicator.class));
}
@Configuration
protected static class CassandraMockConfiguration {
@Bean
public ReactiveCassandraOperations cassandraOperations() {
return mock(ReactiveCassandraOperations.class);
}
}
}

@ -0,0 +1,58 @@
/*
* 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.cassandra;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.querybuilder.Select;
import reactor.core.publisher.Mono;
import org.springframework.boot.actuate.health.AbstractReactiveHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
import org.springframework.data.cassandra.core.ReactiveCassandraOperations;
import org.springframework.util.Assert;
/**
* A {@link ReactiveHealthIndicator} for Cassandra.
*
* @author Artsiom Yudovin
* @since 2.1.0
*/
public class CassandraReactiveHealthIndicator extends AbstractReactiveHealthIndicator {
private final ReactiveCassandraOperations reactiveCassandraOperations;
/**
* Create a new {@link CassandraHealthIndicator} instance.
* @param reactiveCassandraOperations the Cassandra operations
*/
public CassandraReactiveHealthIndicator(
ReactiveCassandraOperations reactiveCassandraOperations) {
Assert.notNull(reactiveCassandraOperations,
"ReactiveCassandraOperations must not be null");
this.reactiveCassandraOperations = reactiveCassandraOperations;
}
@Override
protected Mono<Health> doHealthCheck(Health.Builder builder) {
Select select = QueryBuilder.select("release_version").from("system", "local");
return this.reactiveCassandraOperations.getReactiveCqlOperations()
.queryForObject(select, String.class)
.map((version) -> builder.up().withDetail("version", version).build())
.single();
}
}

@ -0,0 +1,80 @@
/*
* 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.cassandra;
import com.datastax.driver.core.querybuilder.Select;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.Status;
import org.springframework.data.cassandra.CassandraInternalException;
import org.springframework.data.cassandra.core.ReactiveCassandraOperations;
import org.springframework.data.cassandra.core.cql.ReactiveCqlOperations;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link CassandraReactiveHealthIndicatorTests}.
*
* @author Artsiom Yudovin
*/
public class CassandraReactiveHealthIndicatorTests {
@Test
public void testCassandraIsUp() {
ReactiveCqlOperations reactiveCqlOperations = mock(ReactiveCqlOperations.class);
given(reactiveCqlOperations.queryForObject(any(Select.class), eq(String.class)))
.willReturn(Mono.just("6.0.0"));
ReactiveCassandraOperations reactiveCassandraOperations = mock(
ReactiveCassandraOperations.class);
given(reactiveCassandraOperations.getReactiveCqlOperations())
.willReturn(reactiveCqlOperations);
CassandraReactiveHealthIndicator cassandraReactiveHealthIndicator = new CassandraReactiveHealthIndicator(
reactiveCassandraOperations);
Mono<Health> health = cassandraReactiveHealthIndicator.health();
StepVerifier.create(health).consumeNextWith((h) -> {
assertThat(h.getStatus()).isEqualTo(Status.UP);
assertThat(h.getDetails()).containsOnlyKeys("version");
assertThat(h.getDetails().get("version")).isEqualTo("6.0.0");
}).verifyComplete();
}
@Test
public void testCassandraIsDown() {
ReactiveCassandraOperations reactiveCassandraOperations = mock(
ReactiveCassandraOperations.class);
given(reactiveCassandraOperations.getReactiveCqlOperations())
.willThrow(new CassandraInternalException("Connection failed"));
CassandraReactiveHealthIndicator cassandraReactiveHealthIndicator = new CassandraReactiveHealthIndicator(
reactiveCassandraOperations);
Mono<Health> health = cassandraReactiveHealthIndicator.health();
StepVerifier.create(health).consumeNextWith((h) -> {
assertThat(h.getStatus()).isEqualTo(Status.DOWN);
assertThat(h.getDetails()).containsOnlyKeys("error");
assertThat(h.getDetails().get("error")).isEqualTo(
CassandraInternalException.class.getName() + ": Connection failed");
}).verifyComplete();
}
}

@ -921,6 +921,9 @@ appropriate:
|===
|Name |Description
|{sc-spring-boot-actuator}/cassandra/CassandraReactiveHealthIndicator.{sc-ext}[`CassandraReactiveHealthIndicator`]
|Checks that a Cassandra database is up.
|{sc-spring-boot-actuator}/mongo/MongoReactiveHealthIndicator.{sc-ext}[`MongoReactiveHealthIndicator`]
|Checks that a Mongo database is up.

Loading…
Cancel
Save