If multiple configuration are present, the health-check fail as it will have multiple candidates for the CqlSession.
We can quickly handle it by having a constructor with a list of CqlSession instead
/*
* Copyright 2017-2020 original 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 io.micronaut.cassandra.health
import java.net.InetSocketAddress;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import com.datastax.oss.driver.api.core.CqlIdentifier;
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 com.datastax.oss.driver.api.core.session.Session;
import io.micronaut.context.annotation.Replaces;
import io.micronaut.context.annotation.Requires;
import io.micronaut.health.HealthStatus;
import io.micronaut.management.endpoint.health.HealthEndpoint;
import io.micronaut.management.health.indicator.AbstractHealthIndicator;
import jakarta.inject.Singleton;
/**
* A {@link io.micronaut.management.health.indicator.HealthIndicator} for Cassandra, handling multiple Configurations.
*
* @author Ilkin Ashrafli
* @since 2.2.0
*/
@Requires(property = HealthEndpoint.PREFIX + ".cassandra.enabled", notEquals = "false")
@Requires(beans = { HealthEndpoint.class, CqlSession.class })
@Singleton
public class CassandraHealthIndicator extends AbstractHealthIndicator<Map<String, Object>> {
private final List<CqlSession> cqlSessions;
/**
* Default constructor.
*
* @param cqlSessions The list of cassandra {@link CqlSession} to query for details
*/
public CassandraHealthIndicator(final List<CqlSession> cqlSessions) {
this.cqlSessions = cqlSessions;
}
@Override
protected Map<String, Object> getHealthInformation() {
return cqlSessions.stream()
.collect(Collectors.toMap(Session::getName, this::getHealthInformation, (a, b) -> b, LinkedHashMap::new));
}
private Map<String, Object> getHealthInformation(final CqlSession cqlSession) {
final Map<String, Object> detail = new LinkedHashMap<>();
final Map<UUID, Node> nodes = cqlSession.getMetadata().getNodes();
detail.put("session", cqlSession.isClosed() ? "CLOSED" : "OPEN");
final Optional<String> opClusterName = cqlSession.getMetadata().getClusterName();
opClusterName.ifPresent(s -> detail.put("cluster_name", s));
final Optional<CqlIdentifier> opKeyspace = cqlSession.getKeyspace();
opKeyspace.ifPresent(cqlIdentifier -> detail.put("keyspace", cqlIdentifier.asInternal()));
detail.put("nodes_count", nodes.keySet().size());
final Map<UUID, Map<String, Object>> nodesMap = new HashMap<>();
final Map<NodeState, Integer> nodeStateMap = new EnumMap<>(NodeState.class);
boolean up = false;
int i = 0;
for (final Map.Entry<UUID, Node> entry : nodes.entrySet()) {
final UUID uuid = entry.getKey();
final Node node = entry.getValue();
nodeStateMap.merge(node.getState(), 1, Integer::sum);
if (node.getState() == NodeState.UP) {
up = true;
}
if (i++ < 10) {
final Map<String, Object> nodeMap = new HashMap<>();
final Optional<InetSocketAddress> opBroadcastAddress = node.getBroadcastAddress();
opBroadcastAddress.ifPresent(inetSocketAddress -> nodeMap.put("broadcast_address", inetSocketAddress.getAddress()));
nodeMap.put("endpoint", node.getEndPoint());
nodeMap.put("state", node.getState());
nodeMap.put("distance", node.getDistance());
nodeMap.put("open_connections", node.getOpenConnections());
nodeMap.put("cassandra_version", node.getCassandraVersion());
nodeMap.put("datacenter", node.getDatacenter());
nodeMap.put("rack", node.getRack());
nodeMap.put("uptime_ms", node.getUpSinceMillis());
nodeMap.put("is_reconnecting", node.isReconnecting());
nodesMap.put(uuid, nodeMap);
}
}
detail.put("nodes_state", nodeStateMap);
if (nodesMap.size() > 0) {
detail.put("nodes (10 max.)", nodesMap);
}
healthStatus = up ? HealthStatus.UP : HealthStatus.DOWN;
return detail;
}
@Override
protected String getName() {
return "cassandra";
}
}
also, quick fix for the keyspace, as passing the cqlIdentifier directly lead to displaying an empty value for the keyspace
=> opKeyspace.ifPresent(cqlIdentifier -> detail.put("keyspace", cqlIdentifier.asInternal())); will display it
Feature description
If multiple configuration are present, the health-check fail as it will have multiple candidates for the
CqlSession
.We can quickly handle it by having a constructor with a list of
CqlSession
insteadalso, quick fix for the keyspace, as passing the
cqlIdentifier
directly lead to displaying an empty value for the keyspace =>opKeyspace.ifPresent(cqlIdentifier -> detail.put("keyspace", cqlIdentifier.asInternal()));
will display it