diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle b/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle index 61b07a921f6..7eb842d0e50 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle @@ -31,6 +31,7 @@ dependencies { optional(platform(project(":spring-boot-project:spring-boot-dependencies"))) optional("ch.qos.logback:logback-classic") + optional("com.datastax.oss:java-driver-core") optional("com.fasterxml.jackson.dataformat:jackson-dataformat-xml") optional("com.github.ben-manes.caffeine:caffeine") optional("com.hazelcast:hazelcast") diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cassandra/CassandraHealthContributorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cassandra/CassandraHealthContributorAutoConfiguration.java index 0a10ca472c2..c15a049c188 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cassandra/CassandraHealthContributorAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cassandra/CassandraHealthContributorAutoConfiguration.java @@ -16,24 +16,19 @@ package org.springframework.boot.actuate.autoconfigure.cassandra; -import java.util.Map; - import com.datastax.oss.driver.api.core.CqlSession; -import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfiguration; +import org.springframework.boot.actuate.autoconfigure.cassandra.CassandraHealthContributorConfigurations.CassandraDriverConfiguration; +import org.springframework.boot.actuate.autoconfigure.cassandra.CassandraHealthContributorConfigurations.CassandraOperationsConfiguration; import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator; import org.springframework.boot.actuate.cassandra.CassandraHealthIndicator; -import org.springframework.boot.actuate.health.HealthContributor; import org.springframework.boot.autoconfigure.AutoConfigureAfter; 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.context.annotation.Configuration; -import org.springframework.data.cassandra.core.CassandraOperations; +import org.springframework.context.annotation.Import; /** * {@link EnableAutoConfiguration Auto-configuration} for @@ -44,18 +39,11 @@ import org.springframework.data.cassandra.core.CassandraOperations; * @since 2.1.0 */ @Configuration(proxyBeanMethods = false) -@ConditionalOnClass({ CqlSession.class, CassandraOperations.class }) -@ConditionalOnBean(CassandraOperations.class) +@ConditionalOnClass(CqlSession.class) @ConditionalOnEnabledHealthIndicator("cassandra") @AutoConfigureAfter({ CassandraAutoConfiguration.class, CassandraDataAutoConfiguration.class, CassandraReactiveHealthContributorAutoConfiguration.class }) -public class CassandraHealthContributorAutoConfiguration - extends CompositeHealthContributorConfiguration { - - @Bean - @ConditionalOnMissingBean(name = { "cassandraHealthIndicator", "cassandraHealthContributor" }) - public HealthContributor cassandraHealthContributor(Map cassandraOperations) { - return createContributor(cassandraOperations); - } +@Import({ CassandraOperationsConfiguration.class, CassandraDriverConfiguration.class }) +public class CassandraHealthContributorAutoConfiguration { } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cassandra/CassandraHealthContributorConfigurations.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cassandra/CassandraHealthContributorConfigurations.java new file mode 100644 index 00000000000..a618380a4f7 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cassandra/CassandraHealthContributorConfigurations.java @@ -0,0 +1,101 @@ +/* + * Copyright 2012-2020 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 + * + * https://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 com.datastax.oss.driver.api.core.CqlSession; + +import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.CompositeReactiveHealthContributorConfiguration; +import org.springframework.boot.actuate.cassandra.CassandraDriverHealthIndicator; +import org.springframework.boot.actuate.cassandra.CassandraDriverReactiveHealthIndicator; +import org.springframework.boot.actuate.cassandra.CassandraHealthIndicator; +import org.springframework.boot.actuate.cassandra.CassandraReactiveHealthIndicator; +import org.springframework.boot.actuate.health.HealthContributor; +import org.springframework.boot.actuate.health.ReactiveHealthContributor; +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; +import org.springframework.data.cassandra.core.ReactiveCassandraOperations; + +/** + * Health contributor options for Cassandra. + * + * @author Stephane Nicoll + */ +class CassandraHealthContributorConfigurations { + + @Configuration(proxyBeanMethods = false) + @ConditionalOnBean(CqlSession.class) + static class CassandraDriverConfiguration + extends CompositeHealthContributorConfiguration { + + @Bean + @ConditionalOnMissingBean(name = { "cassandraHealthIndicator", "cassandraHealthContributor" }) + HealthContributor cassandraHealthContributor(Map sessions) { + return createContributor(sessions); + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(CassandraOperations.class) + @ConditionalOnBean(CassandraOperations.class) + static class CassandraOperationsConfiguration + extends CompositeHealthContributorConfiguration { + + @Bean + @ConditionalOnMissingBean(name = { "cassandraHealthIndicator", "cassandraHealthContributor" }) + HealthContributor cassandraHealthContributor(Map cassandraOperations) { + return createContributor(cassandraOperations); + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnBean(CqlSession.class) + static class CassandraReactiveDriverConfiguration extends + CompositeReactiveHealthContributorConfiguration { + + @Bean + @ConditionalOnMissingBean(name = { "cassandraHealthIndicator", "cassandraHealthContributor" }) + ReactiveHealthContributor cassandraHealthContributor(Map sessions) { + return createContributor(sessions); + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(ReactiveCassandraOperations.class) + @ConditionalOnBean(ReactiveCassandraOperations.class) + static class CassandraReactiveOperationsConfiguration extends + CompositeReactiveHealthContributorConfiguration { + + @Bean + @ConditionalOnMissingBean(name = { "cassandraHealthIndicator", "cassandraHealthContributor" }) + ReactiveHealthContributor cassandraHealthContributor( + Map reactiveCassandraOperations) { + return createContributor(reactiveCassandraOperations); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cassandra/CassandraReactiveHealthContributorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cassandra/CassandraReactiveHealthContributorAutoConfiguration.java index fbe71367b8f..b6fbfdd7c82 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cassandra/CassandraReactiveHealthContributorAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cassandra/CassandraReactiveHealthContributorAutoConfiguration.java @@ -15,24 +15,19 @@ */ package org.springframework.boot.actuate.autoconfigure.cassandra; -import java.util.Map; - import com.datastax.oss.driver.api.core.CqlSession; import reactor.core.publisher.Flux; -import org.springframework.boot.actuate.autoconfigure.health.CompositeReactiveHealthContributorConfiguration; +import org.springframework.boot.actuate.autoconfigure.cassandra.CassandraHealthContributorConfigurations.CassandraReactiveDriverConfiguration; +import org.springframework.boot.actuate.autoconfigure.cassandra.CassandraHealthContributorConfigurations.CassandraReactiveOperationsConfiguration; import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator; import org.springframework.boot.actuate.cassandra.CassandraReactiveHealthIndicator; -import org.springframework.boot.actuate.health.ReactiveHealthContributor; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -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.CassandraReactiveDataAutoConfiguration; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.data.cassandra.core.ReactiveCassandraOperations; +import org.springframework.context.annotation.Import; /** * {@link EnableAutoConfiguration Auto-configuration} for @@ -43,18 +38,10 @@ import org.springframework.data.cassandra.core.ReactiveCassandraOperations; * @since 2.1.0 */ @Configuration(proxyBeanMethods = false) -@ConditionalOnClass({ CqlSession.class, ReactiveCassandraOperations.class, Flux.class }) -@ConditionalOnBean(ReactiveCassandraOperations.class) +@ConditionalOnClass({ CqlSession.class, Flux.class }) @ConditionalOnEnabledHealthIndicator("cassandra") @AutoConfigureAfter(CassandraReactiveDataAutoConfiguration.class) -public class CassandraReactiveHealthContributorAutoConfiguration extends - CompositeReactiveHealthContributorConfiguration { - - @Bean - @ConditionalOnMissingBean(name = { "cassandraHealthIndicator", "cassandraHealthContributor" }) - public ReactiveHealthContributor cassandraHealthContributor( - Map reactiveCassandraOperations) { - return createContributor(reactiveCassandraOperations); - } +@Import({ CassandraReactiveOperationsConfiguration.class, CassandraReactiveDriverConfiguration.class }) +public class CassandraReactiveHealthContributorAutoConfiguration { } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cassandra/CassandraHealthContributorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cassandra/CassandraHealthContributorAutoConfigurationTests.java index 65baa9377b6..3b96f1a2019 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cassandra/CassandraHealthContributorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cassandra/CassandraHealthContributorAutoConfigurationTests.java @@ -16,15 +16,15 @@ package org.springframework.boot.actuate.autoconfigure.cassandra; +import com.datastax.oss.driver.api.core.CqlSession; import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; +import org.springframework.boot.actuate.cassandra.CassandraDriverHealthIndicator; import org.springframework.boot.actuate.cassandra.CassandraHealthIndicator; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.test.context.FilteredClassLoader; 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.CassandraOperations; import static org.assertj.core.api.Assertions.assertThat; @@ -34,33 +34,50 @@ import static org.mockito.Mockito.mock; * Tests for {@link CassandraHealthContributorAutoConfiguration}. * * @author Phillip Webb + * @author Stephane Nicoll */ class CassandraHealthContributorAutoConfigurationTests { - private ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(CassandraConfiguration.class, - CassandraHealthContributorAutoConfiguration.class, HealthContributorAutoConfiguration.class)); + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(CassandraHealthContributorAutoConfiguration.class, + HealthContributorAutoConfiguration.class)); @Test - void runShouldCreateIndicator() { - this.contextRunner.run((context) -> assertThat(context).hasSingleBean(CassandraHealthIndicator.class)); + void runWithoutCqlSessionOrCassandraOperationsShouldNotCreateIndicator() { + this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean("cassandraHealthContributor") + .doesNotHaveBean(CassandraHealthIndicator.class).doesNotHaveBean(CassandraDriverHealthIndicator.class)); } @Test - void runWhenDisabledShouldNotCreateIndicator() { - this.contextRunner.withPropertyValues("management.health.cassandra.enabled:false") - .run((context) -> assertThat(context).doesNotHaveBean(CassandraHealthIndicator.class)); + void runWithCassandraOperationsShouldCreateRegularIndicator() { + this.contextRunner.withBean(CqlSession.class, () -> mock(CqlSession.class)) + .withBean(CassandraOperations.class, () -> mock(CassandraOperations.class)) + .run((context) -> assertThat(context).hasSingleBean(CassandraHealthIndicator.class) + .doesNotHaveBean(CassandraDriverHealthIndicator.class)); } - @Configuration(proxyBeanMethods = false) - @AutoConfigureBefore(CassandraHealthContributorAutoConfiguration.class) - static class CassandraConfiguration { + @Test + void runWithCqlSessionOnlyShouldCreateDriverIndicator() { + this.contextRunner.withBean(CqlSession.class, () -> mock(CqlSession.class)).run((context) -> assertThat(context) + .hasSingleBean(CassandraDriverHealthIndicator.class).doesNotHaveBean(CassandraHealthIndicator.class)); + } - @Bean - CassandraOperations cassandraOperations() { - return mock(CassandraOperations.class); - } + @Test + void runWithCqlSessionAndSpringDataAbsentShouldCreateDriverIndicator() { + this.contextRunner.withBean(CqlSession.class, () -> mock(CqlSession.class)) + .withClassLoader(new FilteredClassLoader("org.springframework.data")) + .run((context) -> assertThat(context).hasSingleBean(CassandraDriverHealthIndicator.class) + .doesNotHaveBean(CassandraHealthIndicator.class)); + } + @Test + void runWhenDisabledShouldNotCreateIndicator() { + this.contextRunner.withBean(CqlSession.class, () -> mock(CqlSession.class)) + .withBean(CassandraOperations.class, () -> mock(CassandraOperations.class)) + .withPropertyValues("management.health.cassandra.enabled:false") + .run((context) -> assertThat(context).doesNotHaveBean("cassandraHealthContributor") + .doesNotHaveBean(CassandraHealthIndicator.class) + .doesNotHaveBean(CassandraDriverHealthIndicator.class)); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cassandra/CassandraReactiveHealthContributorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cassandra/CassandraReactiveHealthContributorAutoConfigurationTests.java index 94d5495135f..2b28ae87060 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cassandra/CassandraReactiveHealthContributorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cassandra/CassandraReactiveHealthContributorAutoConfigurationTests.java @@ -16,14 +16,18 @@ package org.springframework.boot.actuate.autoconfigure.cassandra; +import com.datastax.oss.driver.api.core.CqlSession; import org.junit.jupiter.api.Test; -import org.springframework.boot.actuate.autoconfigure.cassandra.CassandraHealthContributorAutoConfigurationTests.CassandraConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; +import org.springframework.boot.actuate.cassandra.CassandraDriverHealthIndicator; +import org.springframework.boot.actuate.cassandra.CassandraDriverReactiveHealthIndicator; import org.springframework.boot.actuate.cassandra.CassandraHealthIndicator; import org.springframework.boot.actuate.cassandra.CassandraReactiveHealthIndicator; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.data.cassandra.core.CassandraOperations; import org.springframework.data.cassandra.core.ReactiveCassandraOperations; import static org.assertj.core.api.Assertions.assertThat; @@ -37,31 +41,53 @@ import static org.mockito.Mockito.mock; */ class CassandraReactiveHealthContributorAutoConfigurationTests { - private ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withBean(ReactiveCassandraOperations.class, () -> mock(ReactiveCassandraOperations.class)) + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(CassandraReactiveHealthContributorAutoConfiguration.class, - HealthContributorAutoConfiguration.class)); + CassandraHealthContributorAutoConfiguration.class, HealthContributorAutoConfiguration.class)); @Test - void runShouldCreateIndicator() { - this.contextRunner.run((context) -> assertThat(context).hasSingleBean(CassandraReactiveHealthIndicator.class) - .hasBean("cassandraHealthContributor")); + void runWithoutCqlSessionOrReactiveCassandraOperationsShouldNotCreateIndicator() { + this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean("cassandraHealthContributor") + .doesNotHaveBean(CassandraReactiveHealthIndicator.class) + .doesNotHaveBean(CassandraDriverReactiveHealthIndicator.class)); } @Test - void runWithRegularIndicatorShouldOnlyCreateReactiveIndicator() { - this.contextRunner - .withConfiguration(AutoConfigurations.of(CassandraConfiguration.class, - CassandraHealthContributorAutoConfiguration.class)) - .run((context) -> assertThat(context).hasSingleBean(CassandraReactiveHealthIndicator.class) - .hasBean("cassandraHealthContributor").doesNotHaveBean(CassandraHealthIndicator.class)); + void runWithReactiveCassandraOperationsShouldOnlyCreateReactiveIndicator() { + this.contextRunner.withBean(CqlSession.class, () -> mock(CqlSession.class)) + .withBean(ReactiveCassandraOperations.class, () -> mock(ReactiveCassandraOperations.class)) + .withBean(CassandraOperations.class, () -> mock(CassandraOperations.class)) + .run((context) -> assertThat(context).hasBean("cassandraHealthContributor") + .hasSingleBean(CassandraReactiveHealthIndicator.class) + .doesNotHaveBean(CassandraDriverReactiveHealthIndicator.class) + .doesNotHaveBean(CassandraHealthIndicator.class) + .doesNotHaveBean(CassandraDriverHealthIndicator.class)); + } + + @Test + void runWithCqlSessionShouldCreateDriverIndicator() { + this.contextRunner.withBean(CqlSession.class, () -> mock(CqlSession.class)) + .run((context) -> assertThat(context).hasBean("cassandraHealthContributor") + .hasSingleBean(CassandraDriverReactiveHealthIndicator.class) + .doesNotHaveBean(CassandraReactiveHealthIndicator.class)); + } + + @Test + void runWithCqlSessionAndSpringDataAbsentShouldACreateDriverIndicator() { + this.contextRunner.withBean(CqlSession.class, () -> mock(CqlSession.class)) + .withClassLoader(new FilteredClassLoader("org.springframework.data")) + .run((context) -> assertThat(context).hasBean("cassandraHealthContributor") + .hasSingleBean(CassandraDriverReactiveHealthIndicator.class) + .doesNotHaveBean(CassandraReactiveHealthIndicator.class)); } @Test void runWhenDisabledShouldNotCreateIndicator() { - this.contextRunner.withPropertyValues("management.health.cassandra.enabled:false") - .run((context) -> assertThat(context).doesNotHaveBean(CassandraReactiveHealthIndicator.class) - .doesNotHaveBean("cassandraHealthContributor")); + this.contextRunner.withBean(CqlSession.class, () -> mock(CqlSession.class)) + .withBean(ReactiveCassandraOperations.class, () -> mock(ReactiveCassandraOperations.class)) + .withPropertyValues("management.health.cassandra.enabled:false") + .run((context) -> assertThat(context).doesNotHaveBean("cassandraHealthContributor") + .doesNotHaveBean(CassandraReactiveHealthIndicator.class)); } } diff --git a/spring-boot-project/spring-boot-actuator/build.gradle b/spring-boot-project/spring-boot-actuator/build.gradle index 26598287084..f9f27e92b47 100644 --- a/spring-boot-project/spring-boot-actuator/build.gradle +++ b/spring-boot-project/spring-boot-actuator/build.gradle @@ -13,6 +13,7 @@ dependencies { api(project(":spring-boot-project:spring-boot")) optional(platform(project(":spring-boot-project:spring-boot-dependencies"))) + optional("com.datastax.oss:java-driver-core") optional("com.fasterxml.jackson.core:jackson-databind") optional("com.github.ben-manes.caffeine:caffeine") optional("com.hazelcast:hazelcast") diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cassandra/CassandraDriverHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cassandra/CassandraDriverHealthIndicator.java new file mode 100644 index 00000000000..bbb1468c0e7 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cassandra/CassandraDriverHealthIndicator.java @@ -0,0 +1,62 @@ +/* + * Copyright 2012-2020 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 + * + * https://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.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; + +import org.springframework.boot.actuate.health.AbstractHealthIndicator; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.util.Assert; + +/** + * Simple implementation of a {@link HealthIndicator} returning status information for + * Cassandra data stores. + * + * @author Alexandre Dutra + * @since 2.4.0 + */ +public class CassandraDriverHealthIndicator extends AbstractHealthIndicator { + + private static final SimpleStatement SELECT = SimpleStatement + .newInstance("SELECT release_version FROM system.local").setConsistencyLevel(ConsistencyLevel.LOCAL_ONE); + + private final CqlSession session; + + /** + * Create a new {@link CassandraDriverHealthIndicator} instance. + * @param session the {@link CqlSession}. + */ + public CassandraDriverHealthIndicator(CqlSession session) { + super("Cassandra health check failed"); + Assert.notNull(session, "session must not be null"); + this.session = session; + } + + @Override + protected void doHealthCheck(Health.Builder builder) throws Exception { + Row row = this.session.execute(SELECT).one(); + builder.up(); + if (row != null && !row.isNull(0)) { + builder.withDetail("version", row.getString(0)); + } + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cassandra/CassandraDriverReactiveHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cassandra/CassandraDriverReactiveHealthIndicator.java new file mode 100644 index 00000000000..40483ca04ef --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cassandra/CassandraDriverReactiveHealthIndicator.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012-2020 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 + * + * https://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.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +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.util.Assert; + +/** + * Simple implementation of a {@link ReactiveHealthIndicator} returning status information + * for Cassandra data stores. + * + * @author Alexandre Dutra + * @since 2.4.0 + */ +public class CassandraDriverReactiveHealthIndicator extends AbstractReactiveHealthIndicator { + + private static final SimpleStatement SELECT = SimpleStatement + .newInstance("SELECT release_version FROM system.local").setConsistencyLevel(ConsistencyLevel.LOCAL_ONE); + + private final CqlSession session; + + /** + * Create a new {@link CassandraHealthIndicator} instance. + * @param session the {@link CqlSession}. + */ + public CassandraDriverReactiveHealthIndicator(CqlSession session) { + super("Cassandra health check failed"); + Assert.notNull(session, "session must not be null"); + this.session = session; + } + + @Override + protected Mono doHealthCheck(Health.Builder builder) { + return Mono.from(this.session.executeReactive(SELECT)) + .map((row) -> builder.up().withDetail("version", row.getString(0)).build()); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cassandra/CassandraDriverHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cassandra/CassandraDriverHealthIndicatorTests.java new file mode 100644 index 00000000000..875edd64c34 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cassandra/CassandraDriverHealthIndicatorTests.java @@ -0,0 +1,74 @@ +/* + * Copyright 2012-2020 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 + * + * https://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.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.DriverTimeoutException; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.Row; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.Status; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link CassandraDriverHealthIndicator}. + * + * @author Alexandre Dutra + * @since 2.4.0 + */ +class CassandraDriverHealthIndicatorTests { + + @Test + void createWhenCqlSessionIsNullShouldThrowException() { + assertThatIllegalArgumentException().isThrownBy(() -> new CassandraDriverHealthIndicator(null)); + } + + @Test + void healthWithCassandraUp() { + CqlSession session = mock(CqlSession.class); + ResultSet resultSet = mock(ResultSet.class); + Row row = mock(Row.class); + given(session.execute(any(SimpleStatement.class))).willReturn(resultSet); + given(resultSet.one()).willReturn(row); + given(row.isNull(0)).willReturn(false); + given(row.getString(0)).willReturn("1.0.0"); + CassandraDriverHealthIndicator healthIndicator = new CassandraDriverHealthIndicator(session); + Health health = healthIndicator.health(); + assertThat(health.getStatus()).isEqualTo(Status.UP); + assertThat(health.getDetails().get("version")).isEqualTo("1.0.0"); + } + + @Test + void healthWithCassandraDown() { + CqlSession session = mock(CqlSession.class); + given(session.execute(any(SimpleStatement.class))).willThrow(new DriverTimeoutException("Test Exception")); + CassandraDriverHealthIndicator healthIndicator = new CassandraDriverHealthIndicator(session); + Health health = healthIndicator.health(); + assertThat(health.getStatus()).isEqualTo(Status.DOWN); + assertThat(health.getDetails().get("error")) + .isEqualTo(DriverTimeoutException.class.getName() + ": Test Exception"); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cassandra/CassandraDriverReactiveHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cassandra/CassandraDriverReactiveHealthIndicatorTests.java new file mode 100644 index 00000000000..6cf98f77ec4 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cassandra/CassandraDriverReactiveHealthIndicatorTests.java @@ -0,0 +1,106 @@ +/* + * Copyright 2012-2020 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 + * + * https://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.dse.driver.api.core.cql.reactive.ReactiveResultSet; +import com.datastax.dse.driver.api.core.cql.reactive.ReactiveRow; +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.DriverTimeoutException; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import org.junit.jupiter.api.Test; +import org.mockito.stubbing.Answer; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.Status; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.doAnswer; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.mock; + +/** + * Tests for {@link CassandraDriverReactiveHealthIndicator}. + * + * @author Alexandre Dutra + * @since 2.4.0 + */ +class CassandraDriverReactiveHealthIndicatorTests { + + @Test + void createWhenCqlSessionIsNullShouldThrowException() { + assertThatIllegalArgumentException().isThrownBy(() -> new CassandraDriverReactiveHealthIndicator(null)); + } + + @Test + void testCassandraIsUp() { + CqlSession session = mock(CqlSession.class); + ReactiveResultSet results = mock(ReactiveResultSet.class); + ReactiveRow row = mock(ReactiveRow.class); + given(session.executeReactive(any(SimpleStatement.class))).willReturn(results); + doAnswer(mockReactiveResultSetBehavior(row)).when(results).subscribe(any()); + given(row.getString(0)).willReturn("6.0.0"); + CassandraDriverReactiveHealthIndicator cassandraReactiveHealthIndicator = new CassandraDriverReactiveHealthIndicator( + session); + Mono 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 + void testCassandraIsDown() { + CqlSession session = mock(CqlSession.class); + given(session.executeReactive(any(SimpleStatement.class))) + .willThrow(new DriverTimeoutException("Test Exception")); + CassandraDriverReactiveHealthIndicator cassandraReactiveHealthIndicator = new CassandraDriverReactiveHealthIndicator( + session); + Mono 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(DriverTimeoutException.class.getName() + ": Test Exception"); + }).verifyComplete(); + } + + private Answer mockReactiveResultSetBehavior(ReactiveRow row) { + return (invocation) -> { + Subscriber subscriber = invocation.getArgument(0); + Subscription s = new Subscription() { + @Override + public void request(long n) { + subscriber.onNext(row); + subscriber.onComplete(); + } + + @Override + public void cancel() { + } + }; + subscriber.onSubscribe(s); + return null; + }; + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/production-ready-features.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/production-ready-features.adoc index a7488c79ddf..446a5eb5baa 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/production-ready-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/production-ready-features.adoc @@ -655,7 +655,7 @@ The following `HealthIndicators` are auto-configured by Spring Boot when appropr |=== | Name | Description -| {spring-boot-actuator-module-code}/cassandra/CassandraHealthIndicator.java[`CassandraHealthIndicator`] +| {spring-boot-actuator-module-code}/cassandra/CassandraHealthIndicator.java[`CassandraHealthIndicator`] or {spring-boot-actuator-module-code}/cassandra/CassandraDriverHealthIndicator.java[`CassandraDriverHealthIndicator`] | Checks that a Cassandra database is up. | {spring-boot-actuator-module-code}/couchbase/CouchbaseHealthIndicator.java[`CouchbaseHealthIndicator`] @@ -830,7 +830,7 @@ The following `ReactiveHealthIndicators` are auto-configured by Spring Boot when |=== | Name | Description -| {spring-boot-actuator-module-code}/cassandra/CassandraReactiveHealthIndicator.java[`CassandraReactiveHealthIndicator`] +| {spring-boot-actuator-module-code}/cassandra/CassandraReactiveHealthIndicator.java[`CassandraReactiveHealthIndicator`] or {spring-boot-actuator-module-code}/cassandra/CassandraDriverReactiveHealthIndicator.java[`CassandraDriverReactiveHealthIndicator`] | Checks that a Cassandra database is up. | {spring-boot-actuator-module-code}/couchbase/CouchbaseReactiveHealthIndicator.java[`CouchbaseReactiveHealthIndicator`]