Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,29 @@

package org.springframework.boot.actuate.cassandra;

import com.datastax.oss.driver.api.core.ConsistencyLevel;
import java.util.Collection;
import java.util.Optional;

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 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 static final SimpleStatement SELECT = SimpleStatement
.newInstance("SELECT release_version FROM system.local").setConsistencyLevel(ConsistencyLevel.LOCAL_ONE);

private final CqlSession session;

/**
Expand All @@ -52,11 +53,10 @@ public CassandraDriverHealthIndicator(CqlSession 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));
}
Collection<Node> nodes = this.session.getMetadata().getNodes().values();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic is still duplicated twice. Do you think it's worth extracting it to a separate, shared component?

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));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,30 @@
*/
package org.springframework.boot.actuate.cassandra;

import com.datastax.oss.driver.api.core.ConsistencyLevel;
import java.util.Collection;
import java.util.Optional;

import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
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 static final SimpleStatement SELECT = SimpleStatement
.newInstance("SELECT release_version FROM system.local").setConsistencyLevel(ConsistencyLevel.LOCAL_ONE);

private final CqlSession session;

/**
Expand All @@ -51,8 +53,13 @@ public CassandraDriverReactiveHealthIndicator(CqlSession session) {

@Override
protected Mono<Health> doHealthCheck(Health.Builder builder) {
return Mono.from(this.session.executeReactive(SELECT))
.map((row) -> builder.up().withDetail("version", row.getString(0)).build());
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();
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
* @author Julien Dubois
* @author Alexandre Dutra
* @since 2.0.0
* @deprecated since 2.4.0 in favor of {@link CassandraDriverHealthIndicator}
*/
@Deprecated
public class CassandraHealthIndicator extends AbstractHealthIndicator {

private static final SimpleStatement SELECT = SimpleStatement
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
*
* @author Artsiom Yudovin
* @since 2.1.0
* @deprecated since 2.4.0 in favor of {@link CassandraDriverHealthIndicator}
*/
@Deprecated
public class CassandraReactiveHealthIndicator extends AbstractReactiveHealthIndicator {

private static final SimpleStatement SELECT = SimpleStatement
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,32 @@

package org.springframework.boot.actuate.cassandra;

import java.util.HashMap;
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.cql.ResultSet;
import com.datastax.oss.driver.api.core.cql.Row;
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
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.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.BDDMockito.mock;
import static org.mockito.BDDMockito.when;

/**
* Tests for {@link CassandraDriverHealthIndicator}.
*
* @author Alexandre Dutra
* @author Tomasz Lelek
* @since 2.4.0
*/
class CassandraDriverHealthIndicatorTests {
Expand All @@ -46,29 +52,150 @@ void createWhenCqlSessionIsNullShouldThrowException() {
}

@Test
void healthWithCassandraUp() {
void oneHealthyNodeShouldReturnUp() {
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");
Metadata metadata = mock(Metadata.class);
Node healthyNode = mock(Node.class);
given(healthyNode.getState()).willReturn(NodeState.UP);
given(session.getMetadata()).willReturn(metadata);
given(metadata.getNodes()).willReturn(createNodesMap(healthyNode));
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 oneUnhealthyNodeShouldReturnDown() {
CqlSession session = mock(CqlSession.class);
Metadata metadata = mock(Metadata.class);
Node unhealthyNode = mock(Node.class);
given(unhealthyNode.getState()).willReturn(NodeState.DOWN);
given(session.getMetadata()).willReturn(metadata);
given(metadata.getNodes()).willReturn(createNodesMap(unhealthyNode));
CassandraDriverHealthIndicator healthIndicator = new CassandraDriverHealthIndicator(session);
Health health = healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.DOWN);
}

@Test
void oneUnknownNodeShouldReturnDown() {
CqlSession session = mock(CqlSession.class);
Metadata metadata = mock(Metadata.class);
Node unknownNode = mock(Node.class);
given(unknownNode.getState()).willReturn(NodeState.UNKNOWN);
given(session.getMetadata()).willReturn(metadata);
given(metadata.getNodes()).willReturn(createNodesMap(unknownNode));
CassandraDriverHealthIndicator healthIndicator = new CassandraDriverHealthIndicator(session);
Health health = healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.DOWN);
}

@Test
void oneForcedDownNodeShouldReturnDown() {
CqlSession session = mock(CqlSession.class);
Metadata metadata = mock(Metadata.class);
Node forcedDownNode = mock(Node.class);
given(forcedDownNode.getState()).willReturn(NodeState.FORCED_DOWN);
given(session.getMetadata()).willReturn(metadata);
given(metadata.getNodes()).willReturn(createNodesMap(forcedDownNode));
CassandraDriverHealthIndicator healthIndicator = new CassandraDriverHealthIndicator(session);
Health health = healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.DOWN);
}

@Test
void oneHealthyNodeAndOneUnhealthyNodeShouldReturnUp() {
CqlSession session = mock(CqlSession.class);
Metadata metadata = mock(Metadata.class);
Node healthyNode = mock(Node.class);
Node unhealthyNode = mock(Node.class);
given(healthyNode.getState()).willReturn(NodeState.UP);
given(unhealthyNode.getState()).willReturn(NodeState.DOWN);
given(session.getMetadata()).willReturn(metadata);
given(metadata.getNodes()).willReturn(createNodesMap(healthyNode, unhealthyNode));
CassandraDriverHealthIndicator healthIndicator = new CassandraDriverHealthIndicator(session);
Health health = healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.UP);
}

@Test
void oneHealthyNodeAndOneUnknownNodeShouldReturnUp() {
CqlSession session = mock(CqlSession.class);
Metadata metadata = mock(Metadata.class);
Node healthyNode = mock(Node.class);
Node unknownNode = mock(Node.class);
given(healthyNode.getState()).willReturn(NodeState.UP);
given(unknownNode.getState()).willReturn(NodeState.UNKNOWN);
given(session.getMetadata()).willReturn(metadata);
given(metadata.getNodes()).willReturn(createNodesMap(healthyNode, unknownNode));
CassandraDriverHealthIndicator healthIndicator = new CassandraDriverHealthIndicator(session);
Health health = healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.UP);
}

@Test
void oneHealthyNodeAndOneForcedDownNodeShouldReturnUp() {
CqlSession session = mock(CqlSession.class);
Metadata metadata = mock(Metadata.class);
Node healthyNode = mock(Node.class);
Node forcedDownNode = mock(Node.class);
given(healthyNode.getState()).willReturn(NodeState.UP);
given(forcedDownNode.getState()).willReturn(NodeState.FORCED_DOWN);
given(session.getMetadata()).willReturn(metadata);
given(metadata.getNodes()).willReturn(createNodesMap(healthyNode, forcedDownNode));
CassandraDriverHealthIndicator healthIndicator = new CassandraDriverHealthIndicator(session);
Health health = healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.UP);
}

@Test
void addVersionToDetailsIfReportedNotNull() {
CqlSession session = mock(CqlSession.class);
Metadata metadata = mock(Metadata.class);
when(session.getMetadata()).thenReturn(metadata);
Node node = mock(Node.class);
when(node.getState()).thenReturn(NodeState.UP);
when(node.getCassandraVersion()).thenReturn(Version.V4_0_0);
when(metadata.getNodes()).thenReturn(createNodesMap(node));

CassandraDriverHealthIndicator healthIndicator = new CassandraDriverHealthIndicator(session);
Health health = healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.UP);
assertThat(health.getDetails().get("version")).isEqualTo(Version.V4_0_0);
}

@Test
void doNotAddVersionToDetailsIfReportedNull() {
CqlSession session = mock(CqlSession.class);
Metadata metadata = mock(Metadata.class);
when(session.getMetadata()).thenReturn(metadata);
Node node = mock(Node.class);
when(node.getState()).thenReturn(NodeState.UP);
when(metadata.getNodes()).thenReturn(createNodesMap(node));

CassandraDriverHealthIndicator healthIndicator = new CassandraDriverHealthIndicator(session);
Health health = healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.UP);
assertThat(health.getDetails().get("version")).isNull();
}

@Test
void healthWithCassandraDown() {
CqlSession session = mock(CqlSession.class);
given(session.execute(any(SimpleStatement.class))).willThrow(new DriverTimeoutException("Test Exception"));
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().get("error"))
.isEqualTo(DriverTimeoutException.class.getName() + ": Test Exception");
}

private static Map<UUID, Node> createNodesMap(Node... nodes) {
Map<UUID, Node> nodesMap = new HashMap<>();
for (Node n : nodes) {
nodesMap.put(UUID.randomUUID(), n);
}
return nodesMap;
}

}
Loading