Move code from spring-boot-actuator to spring-boot-cassandra

This commit is contained in:
Andy Wilkinson
2025-05-09 10:21:22 +01:00
committed by Phillip Webb
parent adec7f2df2
commit ec0370bc2e
12 changed files with 23 additions and 22 deletions

View File

@@ -26,7 +26,6 @@ dependencies {
dockerTestImplementation("org.testcontainers:testcontainers")
optional(project(":spring-boot-project:spring-boot-activemq"))
optional(project(":spring-boot-project:spring-boot-data-cassandra"))
optional(project(":spring-boot-project:spring-boot-data-redis"))
optional(project(":spring-boot-project:spring-boot-flyway"))
optional(project(":spring-boot-project:spring-boot-http-converter"))

View File

@@ -1,62 +0,0 @@
/*
* Copyright 2012-2025 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 java.util.Collection;
import java.util.Optional;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.metadata.Node;
import com.datastax.oss.driver.api.core.metadata.NodeState;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.util.Assert;
/**
* Simple implementation of a {@link HealthIndicator} returning status information for
* Cassandra data stores.
*
* @author Alexandre Dutra
* @author Tomasz Lelek
* @since 2.4.0
*/
public class CassandraDriverHealthIndicator extends AbstractHealthIndicator {
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 {
Collection<Node> nodes = this.session.getMetadata().getNodes().values();
Optional<Node> nodeUp = nodes.stream().filter((node) -> node.getState() == NodeState.UP).findAny();
builder.status(nodeUp.isPresent() ? Status.UP : Status.DOWN);
nodeUp.map(Node::getCassandraVersion).ifPresent((version) -> builder.withDetail("version", version));
}
}

View File

@@ -1,66 +0,0 @@
/*
* Copyright 2012-2025 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 java.util.Collection;
import java.util.Optional;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.metadata.Node;
import com.datastax.oss.driver.api.core.metadata.NodeState;
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.boot.actuate.health.Status;
import org.springframework.util.Assert;
/**
* Simple implementation of a {@link ReactiveHealthIndicator} returning status information
* for Cassandra data stores.
*
* @author Alexandre Dutra
* @author Tomasz Lelek
* @since 2.4.0
*/
public class CassandraDriverReactiveHealthIndicator extends AbstractReactiveHealthIndicator {
private final CqlSession session;
/**
* Create a new {@link CassandraDriverReactiveHealthIndicator} 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<Health> doHealthCheck(Health.Builder builder) {
return Mono.fromSupplier(() -> {
Collection<Node> nodes = this.session.getMetadata().getNodes().values();
Optional<Node> nodeUp = nodes.stream().filter((node) -> node.getState() == NodeState.UP).findAny();
builder.status(nodeUp.isPresent() ? Status.UP : Status.DOWN);
nodeUp.map(Node::getCassandraVersion).ifPresent((version) -> builder.withDetail("version", version));
return builder.build();
});
}
}

View File

@@ -1,20 +0,0 @@
/*
* Copyright 2012-2019 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.
*/
/**
* Actuator support for Cassandra.
*/
package org.springframework.boot.actuate.cassandra;

View File

@@ -1,166 +0,0 @@
/*
* Copyright 2012-2023 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 java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.DriverTimeoutException;
import com.datastax.oss.driver.api.core.Version;
import com.datastax.oss.driver.api.core.metadata.Metadata;
import com.datastax.oss.driver.api.core.metadata.Node;
import com.datastax.oss.driver.api.core.metadata.NodeState;
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.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link CassandraDriverHealthIndicator}.
*
* @author Alexandre Dutra
* @author Stephane Nicoll
*/
class CassandraDriverHealthIndicatorTests {
@Test
void createWhenCqlSessionIsNullShouldThrowException() {
assertThatIllegalArgumentException().isThrownBy(() -> new CassandraDriverHealthIndicator(null));
}
@Test
void healthWithOneHealthyNodeShouldReturnUp() {
CqlSession session = mockCqlSessionWithNodeState(NodeState.UP);
CassandraDriverHealthIndicator healthIndicator = new CassandraDriverHealthIndicator(session);
Health health = healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.UP);
}
@Test
void healthWithOneUnhealthyNodeShouldReturnDown() {
CqlSession session = mockCqlSessionWithNodeState(NodeState.DOWN);
CassandraDriverHealthIndicator healthIndicator = new CassandraDriverHealthIndicator(session);
Health health = healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.DOWN);
}
@Test
void healthWithOneUnknownNodeShouldReturnDown() {
CqlSession session = mockCqlSessionWithNodeState(NodeState.UNKNOWN);
CassandraDriverHealthIndicator healthIndicator = new CassandraDriverHealthIndicator(session);
Health health = healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.DOWN);
}
@Test
void healthWithOneForcedDownNodeShouldReturnDown() {
CqlSession session = mockCqlSessionWithNodeState(NodeState.FORCED_DOWN);
CassandraDriverHealthIndicator healthIndicator = new CassandraDriverHealthIndicator(session);
Health health = healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.DOWN);
}
@Test
void healthWithOneHealthyNodeAndOneUnhealthyNodeShouldReturnUp() {
CqlSession session = mockCqlSessionWithNodeState(NodeState.UP, NodeState.DOWN);
CassandraDriverHealthIndicator healthIndicator = new CassandraDriverHealthIndicator(session);
Health health = healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.UP);
}
@Test
void healthWithOneHealthyNodeAndOneUnknownNodeShouldReturnUp() {
CqlSession session = mockCqlSessionWithNodeState(NodeState.UP, NodeState.UNKNOWN);
CassandraDriverHealthIndicator healthIndicator = new CassandraDriverHealthIndicator(session);
Health health = healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.UP);
}
@Test
void healthWithOneHealthyNodeAndOneForcedDownNodeShouldReturnUp() {
CqlSession session = mockCqlSessionWithNodeState(NodeState.UP, NodeState.FORCED_DOWN);
CassandraDriverHealthIndicator healthIndicator = new CassandraDriverHealthIndicator(session);
Health health = healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.UP);
}
@Test
void healthWithNodeVersionShouldAddVersionDetail() {
CqlSession session = mock(CqlSession.class);
Metadata metadata = mock(Metadata.class);
given(session.getMetadata()).willReturn(metadata);
Node node = mock(Node.class);
given(node.getState()).willReturn(NodeState.UP);
given(node.getCassandraVersion()).willReturn(Version.V4_0_0);
given(metadata.getNodes()).willReturn(createNodesWithRandomUUID(Collections.singletonList(node)));
CassandraDriverHealthIndicator healthIndicator = new CassandraDriverHealthIndicator(session);
Health health = healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.UP);
assertThat(health.getDetails()).containsEntry("version", Version.V4_0_0);
}
@Test
void healthWithoutNodeVersionShouldNotAddVersionDetail() {
CqlSession session = mockCqlSessionWithNodeState(NodeState.UP);
CassandraDriverHealthIndicator healthIndicator = new CassandraDriverHealthIndicator(session);
Health health = healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.UP);
assertThat(health.getDetails()).doesNotContainKey("version");
}
@Test
void healthWithCassandraDownShouldReturnDown() {
CqlSession session = mock(CqlSession.class);
given(session.getMetadata()).willThrow(new DriverTimeoutException("Test Exception"));
CassandraDriverHealthIndicator healthIndicator = new CassandraDriverHealthIndicator(session);
Health health = healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.DOWN);
assertThat(health.getDetails()).containsEntry("error",
DriverTimeoutException.class.getName() + ": Test Exception");
}
private CqlSession mockCqlSessionWithNodeState(NodeState... nodeStates) {
CqlSession session = mock(CqlSession.class);
Metadata metadata = mock(Metadata.class);
List<Node> nodes = new ArrayList<>();
for (NodeState nodeState : nodeStates) {
Node node = mock(Node.class);
given(node.getState()).willReturn(nodeState);
nodes.add(node);
}
given(session.getMetadata()).willReturn(metadata);
given(metadata.getNodes()).willReturn(createNodesWithRandomUUID(nodes));
return session;
}
private Map<UUID, Node> createNodesWithRandomUUID(List<Node> nodes) {
Map<UUID, Node> indexedNodes = new HashMap<>();
nodes.forEach((node) -> indexedNodes.put(UUID.randomUUID(), node));
return indexedNodes;
}
}

View File

@@ -1,199 +0,0 @@
/*
* Copyright 2012-2023 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 java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.DriverTimeoutException;
import com.datastax.oss.driver.api.core.Version;
import com.datastax.oss.driver.api.core.metadata.Metadata;
import com.datastax.oss.driver.api.core.metadata.Node;
import com.datastax.oss.driver.api.core.metadata.NodeState;
import org.junit.jupiter.api.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 static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link CassandraDriverReactiveHealthIndicator}.
*
* @author Alexandre Dutra
* @author Stephane Nicoll
*/
class CassandraDriverReactiveHealthIndicatorTests {
@Test
void createWhenCqlSessionIsNullShouldThrowException() {
assertThatIllegalArgumentException().isThrownBy(() -> new CassandraDriverReactiveHealthIndicator(null));
}
@Test
void healthWithOneHealthyNodeShouldReturnUp() {
CqlSession session = mockCqlSessionWithNodeState(NodeState.UP);
CassandraDriverReactiveHealthIndicator healthIndicator = new CassandraDriverReactiveHealthIndicator(session);
Mono<Health> health = healthIndicator.health();
StepVerifier.create(health)
.consumeNextWith((h) -> assertThat(h.getStatus()).isEqualTo(Status.UP))
.expectComplete()
.verify(Duration.ofSeconds(30));
}
@Test
void healthWithOneUnhealthyNodeShouldReturnDown() {
CqlSession session = mockCqlSessionWithNodeState(NodeState.DOWN);
CassandraDriverReactiveHealthIndicator healthIndicator = new CassandraDriverReactiveHealthIndicator(session);
Mono<Health> health = healthIndicator.health();
StepVerifier.create(health)
.consumeNextWith((h) -> assertThat(h.getStatus()).isEqualTo(Status.DOWN))
.expectComplete()
.verify(Duration.ofSeconds(30));
}
@Test
void healthWithOneUnknownNodeShouldReturnDown() {
CqlSession session = mockCqlSessionWithNodeState(NodeState.UNKNOWN);
CassandraDriverReactiveHealthIndicator healthIndicator = new CassandraDriverReactiveHealthIndicator(session);
Mono<Health> health = healthIndicator.health();
StepVerifier.create(health)
.consumeNextWith((h) -> assertThat(h.getStatus()).isEqualTo(Status.DOWN))
.expectComplete()
.verify(Duration.ofSeconds(30));
}
@Test
void healthWithOneForcedDownNodeShouldReturnDown() {
CqlSession session = mockCqlSessionWithNodeState(NodeState.FORCED_DOWN);
CassandraDriverReactiveHealthIndicator healthIndicator = new CassandraDriverReactiveHealthIndicator(session);
Mono<Health> health = healthIndicator.health();
StepVerifier.create(health)
.consumeNextWith((h) -> assertThat(h.getStatus()).isEqualTo(Status.DOWN))
.expectComplete()
.verify(Duration.ofSeconds(30));
}
@Test
void healthWithOneHealthyNodeAndOneUnhealthyNodeShouldReturnUp() {
CqlSession session = mockCqlSessionWithNodeState(NodeState.UP, NodeState.DOWN);
CassandraDriverReactiveHealthIndicator healthIndicator = new CassandraDriverReactiveHealthIndicator(session);
Mono<Health> health = healthIndicator.health();
StepVerifier.create(health)
.consumeNextWith((h) -> assertThat(h.getStatus()).isEqualTo(Status.UP))
.expectComplete()
.verify(Duration.ofSeconds(30));
}
@Test
void healthWithOneHealthyNodeAndOneUnknownNodeShouldReturnUp() {
CqlSession session = mockCqlSessionWithNodeState(NodeState.UP, NodeState.UNKNOWN);
CassandraDriverReactiveHealthIndicator healthIndicator = new CassandraDriverReactiveHealthIndicator(session);
Mono<Health> health = healthIndicator.health();
StepVerifier.create(health)
.consumeNextWith((h) -> assertThat(h.getStatus()).isEqualTo(Status.UP))
.expectComplete()
.verify(Duration.ofSeconds(30));
}
@Test
void healthWithOneHealthyNodeAndOneForcedDownNodeShouldReturnUp() {
CqlSession session = mockCqlSessionWithNodeState(NodeState.UP, NodeState.FORCED_DOWN);
CassandraDriverReactiveHealthIndicator healthIndicator = new CassandraDriverReactiveHealthIndicator(session);
Mono<Health> health = healthIndicator.health();
StepVerifier.create(health)
.consumeNextWith((h) -> assertThat(h.getStatus()).isEqualTo(Status.UP))
.expectComplete()
.verify(Duration.ofSeconds(30));
}
@Test
void healthWithNodeVersionShouldAddVersionDetail() {
CqlSession session = mock(CqlSession.class);
Metadata metadata = mock(Metadata.class);
given(session.getMetadata()).willReturn(metadata);
Node node = mock(Node.class);
given(node.getState()).willReturn(NodeState.UP);
given(node.getCassandraVersion()).willReturn(Version.V4_0_0);
given(metadata.getNodes()).willReturn(createNodesWithRandomUUID(Collections.singletonList(node)));
CassandraDriverReactiveHealthIndicator healthIndicator = new CassandraDriverReactiveHealthIndicator(session);
Mono<Health> health = healthIndicator.health();
StepVerifier.create(health).consumeNextWith((h) -> {
assertThat(h.getStatus()).isEqualTo(Status.UP);
assertThat(h.getDetails()).containsOnlyKeys("version");
assertThat(h.getDetails()).containsEntry("version", Version.V4_0_0);
}).expectComplete().verify(Duration.ofSeconds(30));
}
@Test
void healthWithoutNodeVersionShouldNotAddVersionDetail() {
CqlSession session = mockCqlSessionWithNodeState(NodeState.UP);
CassandraDriverReactiveHealthIndicator healthIndicator = new CassandraDriverReactiveHealthIndicator(session);
Mono<Health> health = healthIndicator.health();
StepVerifier.create(health).consumeNextWith((h) -> {
assertThat(h.getStatus()).isEqualTo(Status.UP);
assertThat(h.getDetails()).doesNotContainKey("version");
}).expectComplete().verify(Duration.ofSeconds(30));
}
@Test
void healthWithCassandraDownShouldReturnDown() {
CqlSession session = mock(CqlSession.class);
given(session.getMetadata()).willThrow(new DriverTimeoutException("Test Exception"));
CassandraDriverReactiveHealthIndicator cassandraReactiveHealthIndicator = new CassandraDriverReactiveHealthIndicator(
session);
Mono<Health> health = cassandraReactiveHealthIndicator.health();
StepVerifier.create(health).consumeNextWith((h) -> {
assertThat(h.getStatus()).isEqualTo(Status.DOWN);
assertThat(h.getDetails()).containsOnlyKeys("error");
assertThat(h.getDetails()).containsEntry("error",
DriverTimeoutException.class.getName() + ": Test Exception");
}).expectComplete().verify(Duration.ofSeconds(30));
}
private CqlSession mockCqlSessionWithNodeState(NodeState... nodeStates) {
CqlSession session = mock(CqlSession.class);
Metadata metadata = mock(Metadata.class);
List<Node> nodes = new ArrayList<>();
for (NodeState nodeState : nodeStates) {
Node node = mock(Node.class);
given(node.getState()).willReturn(nodeState);
nodes.add(node);
}
given(session.getMetadata()).willReturn(metadata);
given(metadata.getNodes()).willReturn(createNodesWithRandomUUID(nodes));
return session;
}
private Map<UUID, Node> createNodesWithRandomUUID(List<Node> nodes) {
Map<UUID, Node> indexedNodes = new HashMap<>();
nodes.forEach((node) -> indexedNodes.put(UUID.randomUUID(), node));
return indexedNodes;
}
}