Skip to content

Commit 7255c1c

Browse files
committed
Create new Cassandra health indicators that use CqlSession
Motivation: It is possible to use CassandraAutoConfiguration to create CqlSession beans in an application with just a dependency on the DataStax Java driver, without the requirement to also depend on Spring Data Cassandra. However, it is currently not possible to do so if the application also needs to create Actuator components. Indeed, both CassandraHealthIndicator and CassandraReactiveHealthIndicator have a hard dependency on Spring Data Cassandra and its CassandraOperations bean. Modifications: This commit creates two new health indicators that do not have any dependency on Spring Data Cassandra: CassandraDriverHealthIndicator and CassandraDriverReactiveHealthIndicator; these indicators are automatically used in lieu of CassandraHealthIndicator and CassandraReactiveHealthIndicator when Spring Data Cassandra is not available. Result: Users are now able to create Actuator health components in their applications, even if they use just Spring Boot and the DataStax Java driver, without Spring Data Cassandra.
1 parent 18e0db6 commit 7255c1c

File tree

11 files changed

+597
-0
lines changed

11 files changed

+597
-0
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ dependencies {
3232

3333
optional(platform(project(":spring-boot-project:spring-boot-dependencies")))
3434
optional("ch.qos.logback:logback-classic")
35+
optional("com.datastax.oss:java-driver-core")
3536
optional("com.fasterxml.jackson.dataformat:jackson-dataformat-xml")
3637
optional("com.github.ben-manes.caffeine:caffeine")
3738
optional("com.hazelcast:hazelcast")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.cassandra;
18+
19+
import java.util.Map;
20+
21+
import com.datastax.oss.driver.api.core.CqlSession;
22+
23+
import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfiguration;
24+
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
25+
import org.springframework.boot.actuate.cassandra.CassandraDriverHealthIndicator;
26+
import org.springframework.boot.actuate.health.HealthContributor;
27+
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
28+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
29+
import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
30+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
31+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
32+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
33+
import org.springframework.context.annotation.Bean;
34+
import org.springframework.context.annotation.Configuration;
35+
36+
/**
37+
* {@link EnableAutoConfiguration Auto-configuration} for
38+
* {@link CassandraDriverHealthIndicator}.
39+
*
40+
* @author Alexandre Dutra
41+
* @since 2.4.0
42+
*/
43+
@Configuration(proxyBeanMethods = false)
44+
@ConditionalOnClass(CqlSession.class)
45+
@ConditionalOnBean(CqlSession.class)
46+
@ConditionalOnEnabledHealthIndicator("cassandra")
47+
@AutoConfigureAfter({ CassandraAutoConfiguration.class, CassandraReactiveHealthContributorAutoConfiguration.class,
48+
CassandraHealthContributorAutoConfiguration.class,
49+
CassandraDriverReactiveHealthContributorAutoConfiguration.class })
50+
public class CassandraDriverHealthContributorAutoConfiguration
51+
extends CompositeHealthContributorConfiguration<CassandraDriverHealthIndicator, CqlSession> {
52+
53+
@Bean
54+
@ConditionalOnMissingBean(name = { "cassandraHealthIndicator", "cassandraHealthContributor" })
55+
public HealthContributor cassandraHealthContributor(Map<String, CqlSession> sessions) {
56+
return createContributor(sessions);
57+
}
58+
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.boot.actuate.autoconfigure.cassandra;
17+
18+
import java.util.Map;
19+
20+
import com.datastax.oss.driver.api.core.CqlSession;
21+
import reactor.core.publisher.Flux;
22+
23+
import org.springframework.boot.actuate.autoconfigure.health.CompositeReactiveHealthContributorConfiguration;
24+
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
25+
import org.springframework.boot.actuate.cassandra.CassandraDriverReactiveHealthIndicator;
26+
import org.springframework.boot.actuate.health.ReactiveHealthContributor;
27+
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
28+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
29+
import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
30+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
31+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
32+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
33+
import org.springframework.context.annotation.Bean;
34+
import org.springframework.context.annotation.Configuration;
35+
36+
/**
37+
* {@link EnableAutoConfiguration Auto-configuration} for
38+
* {@link CassandraDriverReactiveHealthIndicator}.
39+
*
40+
* @author Alexandre Dutra
41+
* @since 2.4.0
42+
*/
43+
@Configuration(proxyBeanMethods = false)
44+
@ConditionalOnClass({ CqlSession.class, Flux.class })
45+
@ConditionalOnBean(CqlSession.class)
46+
@ConditionalOnEnabledHealthIndicator("cassandra")
47+
@AutoConfigureAfter({ CassandraAutoConfiguration.class, CassandraReactiveHealthContributorAutoConfiguration.class,
48+
CassandraHealthContributorAutoConfiguration.class })
49+
public class CassandraDriverReactiveHealthContributorAutoConfiguration
50+
extends CompositeReactiveHealthContributorConfiguration<CassandraDriverReactiveHealthIndicator, CqlSession> {
51+
52+
@Bean
53+
@ConditionalOnMissingBean(name = { "cassandraHealthIndicator", "cassandraHealthContributor" })
54+
public ReactiveHealthContributor cassandraHealthContributor(Map<String, CqlSession> sessions) {
55+
return createContributor(sessions);
56+
}
57+
58+
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfigurat
77
org.springframework.boot.actuate.autoconfigure.cache.CachesEndpointAutoConfiguration,\
88
org.springframework.boot.actuate.autoconfigure.cassandra.CassandraHealthContributorAutoConfiguration,\
99
org.springframework.boot.actuate.autoconfigure.cassandra.CassandraReactiveHealthContributorAutoConfiguration,\
10+
org.springframework.boot.actuate.autoconfigure.cassandra.CassandraDriverHealthContributorAutoConfiguration,\
11+
org.springframework.boot.actuate.autoconfigure.cassandra.CassandraDriverReactiveHealthContributorAutoConfiguration,\
1012
org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet.CloudFoundryActuatorAutoConfiguration,\
1113
org.springframework.boot.actuate.autoconfigure.cloudfoundry.reactive.ReactiveCloudFoundryActuatorAutoConfiguration,\
1214
org.springframework.boot.actuate.autoconfigure.condition.ConditionsReportEndpointAutoConfiguration,\
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright 2012-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.cassandra;
18+
19+
import com.datastax.oss.driver.api.core.CqlSession;
20+
import org.junit.jupiter.api.Test;
21+
22+
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
23+
import org.springframework.boot.actuate.cassandra.CassandraDriverHealthIndicator;
24+
import org.springframework.boot.actuate.cassandra.CassandraDriverReactiveHealthIndicator;
25+
import org.springframework.boot.actuate.cassandra.CassandraHealthIndicator;
26+
import org.springframework.boot.actuate.cassandra.CassandraReactiveHealthIndicator;
27+
import org.springframework.boot.autoconfigure.AutoConfigurations;
28+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
29+
import org.springframework.data.cassandra.core.CassandraOperations;
30+
import org.springframework.data.cassandra.core.ReactiveCassandraOperations;
31+
32+
import static org.assertj.core.api.Assertions.assertThat;
33+
import static org.mockito.Mockito.mock;
34+
35+
/**
36+
* Tests for {@link CassandraDriverHealthContributorAutoConfiguration}.
37+
*
38+
* @author Alexandre Dutra
39+
* @since 2.4.0
40+
*/
41+
class CassandraDriverHealthContributorAutoConfigurationTests {
42+
43+
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
44+
.withBean(CqlSession.class, () -> mock(CqlSession.class)).withConfiguration(AutoConfigurations.of(
45+
CassandraDriverHealthContributorAutoConfiguration.class, HealthContributorAutoConfiguration.class));
46+
47+
@Test
48+
void runShouldCreateDriverIndicator() {
49+
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(CassandraDriverHealthIndicator.class)
50+
.hasBean("cassandraHealthContributor").doesNotHaveBean(CassandraHealthIndicator.class)
51+
.doesNotHaveBean(CassandraReactiveHealthIndicator.class)
52+
.doesNotHaveBean(CassandraDriverReactiveHealthIndicator.class));
53+
}
54+
55+
@Test
56+
void runWhenDisabledShouldNotCreateDriverIndicator() {
57+
this.contextRunner.withPropertyValues("management.health.cassandra.enabled:false")
58+
.run((context) -> assertThat(context).doesNotHaveBean(CassandraDriverHealthIndicator.class)
59+
.doesNotHaveBean("cassandraHealthContributor"));
60+
}
61+
62+
@Test
63+
void runWhenSpringDataPresentShouldNotCreateDriverIndicator() {
64+
this.contextRunner.withConfiguration(AutoConfigurations.of(CassandraHealthContributorAutoConfiguration.class))
65+
.withBean(CassandraOperations.class, () -> mock(CassandraOperations.class))
66+
.run((context) -> assertThat(context).doesNotHaveBean(CassandraDriverHealthIndicator.class)
67+
.hasSingleBean(CassandraHealthIndicator.class).hasBean("cassandraHealthContributor"));
68+
}
69+
70+
@Test
71+
void runWhenReactorPresentShouldNotCreateDriverIndicator() {
72+
this.contextRunner
73+
.withConfiguration(
74+
AutoConfigurations.of(CassandraDriverReactiveHealthContributorAutoConfiguration.class))
75+
.run((context) -> assertThat(context).doesNotHaveBean(CassandraDriverHealthIndicator.class)
76+
.hasSingleBean(CassandraDriverReactiveHealthIndicator.class)
77+
.hasBean("cassandraHealthContributor"));
78+
}
79+
80+
@Test
81+
void runWhenSpringDataAndReactorPresentShouldNotCreateDriverIndicator() {
82+
this.contextRunner
83+
.withConfiguration(AutoConfigurations.of(CassandraReactiveHealthContributorAutoConfiguration.class))
84+
.withBean(ReactiveCassandraOperations.class, () -> mock(ReactiveCassandraOperations.class))
85+
.run((context) -> assertThat(context).doesNotHaveBean(CassandraDriverHealthIndicator.class)
86+
.hasSingleBean(CassandraReactiveHealthIndicator.class).hasBean("cassandraHealthContributor"));
87+
}
88+
89+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright 2012-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.cassandra;
18+
19+
import com.datastax.oss.driver.api.core.CqlSession;
20+
import org.junit.jupiter.api.Test;
21+
22+
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
23+
import org.springframework.boot.actuate.cassandra.CassandraDriverReactiveHealthIndicator;
24+
import org.springframework.boot.actuate.cassandra.CassandraHealthIndicator;
25+
import org.springframework.boot.actuate.cassandra.CassandraReactiveHealthIndicator;
26+
import org.springframework.boot.autoconfigure.AutoConfigurations;
27+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
28+
import org.springframework.data.cassandra.core.CassandraOperations;
29+
import org.springframework.data.cassandra.core.ReactiveCassandraOperations;
30+
31+
import static org.assertj.core.api.Assertions.assertThat;
32+
import static org.mockito.Mockito.mock;
33+
34+
/**
35+
* Tests for {@link CassandraDriverReactiveHealthContributorAutoConfiguration}.
36+
*
37+
* @author Alexandre Dutra
38+
* @since 2.4.0
39+
*/
40+
class CassandraDriverReactiveHealthContributorAutoConfigurationTests {
41+
42+
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
43+
.withBean(CqlSession.class, () -> mock(CqlSession.class))
44+
.withConfiguration(AutoConfigurations.of(CassandraDriverReactiveHealthContributorAutoConfiguration.class,
45+
HealthContributorAutoConfiguration.class));
46+
47+
@Test
48+
void runShouldCreateDriverReactiveIndicator() {
49+
this.contextRunner
50+
.run((context) -> assertThat(context).hasSingleBean(CassandraDriverReactiveHealthIndicator.class)
51+
.hasBean("cassandraHealthContributor").doesNotHaveBean(CassandraHealthIndicator.class)
52+
.doesNotHaveBean(CassandraReactiveHealthIndicator.class));
53+
}
54+
55+
@Test
56+
void runWhenDisabledShouldNotCreateDriverReactiveIndicator() {
57+
this.contextRunner.withPropertyValues("management.health.cassandra.enabled:false")
58+
.run((context) -> assertThat(context).doesNotHaveBean(CassandraDriverReactiveHealthIndicator.class)
59+
.doesNotHaveBean("cassandraHealthContributor"));
60+
}
61+
62+
@Test
63+
void runWhenSpringDataPresentShouldNotCreateDriverReactiveIndicator() {
64+
this.contextRunner.withConfiguration(AutoConfigurations.of(CassandraHealthContributorAutoConfiguration.class))
65+
.withBean(CassandraOperations.class, () -> mock(CassandraOperations.class))
66+
.run((context) -> assertThat(context).doesNotHaveBean(CassandraDriverReactiveHealthIndicator.class)
67+
.hasSingleBean(CassandraHealthIndicator.class).hasBean("cassandraHealthContributor"));
68+
}
69+
70+
@Test
71+
void runWhenSpringDataAndReactorPresentShouldNotCreateDriverReactiveIndicator() {
72+
this.contextRunner
73+
.withConfiguration(AutoConfigurations.of(CassandraReactiveHealthContributorAutoConfiguration.class))
74+
.withBean(ReactiveCassandraOperations.class, () -> mock(ReactiveCassandraOperations.class))
75+
.run((context) -> assertThat(context).doesNotHaveBean(CassandraDriverReactiveHealthIndicator.class)
76+
.hasSingleBean(CassandraReactiveHealthIndicator.class).hasBean("cassandraHealthContributor"));
77+
}
78+
79+
}

spring-boot-project/spring-boot-actuator/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ dependencies {
1414
implementation(project(":spring-boot-project:spring-boot"))
1515

1616
optional(platform(project(":spring-boot-project:spring-boot-dependencies")))
17+
optional("com.datastax.oss:java-driver-core")
1718
optional("com.fasterxml.jackson.core:jackson-databind")
1819
optional("com.github.ben-manes.caffeine:caffeine")
1920
optional("com.hazelcast:hazelcast")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.cassandra;
18+
19+
import com.datastax.oss.driver.api.core.ConsistencyLevel;
20+
import com.datastax.oss.driver.api.core.CqlSession;
21+
import com.datastax.oss.driver.api.core.cql.Row;
22+
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
23+
24+
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
25+
import org.springframework.boot.actuate.health.Health;
26+
import org.springframework.boot.actuate.health.HealthIndicator;
27+
import org.springframework.util.Assert;
28+
29+
/**
30+
* Simple implementation of a {@link HealthIndicator} returning status information for
31+
* Cassandra data stores.
32+
*
33+
* This health indicator is automatically used when Spring Data Cassandra is not present,
34+
* but the Cassandra driver is.
35+
*
36+
* @author Alexandre Dutra
37+
* @since 2.4.0
38+
*/
39+
public class CassandraDriverHealthIndicator extends AbstractHealthIndicator {
40+
41+
private static final SimpleStatement SELECT = SimpleStatement
42+
.newInstance("SELECT release_version FROM system.local").setConsistencyLevel(ConsistencyLevel.LOCAL_ONE);
43+
44+
private final CqlSession session;
45+
46+
/**
47+
* Create a new {@link CassandraDriverHealthIndicator} instance.
48+
* @param session the {@link CqlSession}.
49+
*/
50+
public CassandraDriverHealthIndicator(CqlSession session) {
51+
super("Cassandra health check failed");
52+
Assert.notNull(session, "session must not be null");
53+
this.session = session;
54+
}
55+
56+
@Override
57+
protected void doHealthCheck(Health.Builder builder) throws Exception {
58+
Row row = this.session.execute(SELECT).one();
59+
builder.up();
60+
if (row != null && !row.isNull(0)) {
61+
builder.withDetail("version", row.getString(0));
62+
}
63+
}
64+
65+
}

0 commit comments

Comments
 (0)