Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ public ClusterStateHealth(

// shortcut on green
if (computeStatus.equals(ClusterHealthStatus.GREEN)) {
this.activeShardsPercent = 100;
this.activeShardsPercent = 100.0;
} else {
this.activeShardsPercent = (((double) this.activeShards) / totalShardCount) * 100;
this.activeShardsPercent = totalShardCount == 0 ? 0.0 : (((double) this.activeShards) / totalShardCount) * 100;
Copy link
Member

Choose a reason for hiding this comment

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

I think that perhaps we should represent this as 100%, rather than 0%, since the cluster would be totally healthy if there were no shards. 0% would indicate otherwise to me.

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.TestShardRoutingRoleStrategies;
import org.elasticsearch.cluster.block.ClusterBlock;
import org.elasticsearch.cluster.block.ClusterBlockLevel;
import org.elasticsearch.cluster.block.ClusterBlocks;
import org.elasticsearch.cluster.metadata.IndexMetadata;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.metadata.Metadata;
Expand All @@ -41,6 +44,7 @@
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.index.IndexVersion;
import org.elasticsearch.indices.TestIndexNameExpressionResolver;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.tasks.CancellableTask;
import org.elasticsearch.tasks.TaskId;
import org.elasticsearch.test.ESTestCase;
Expand Down Expand Up @@ -71,6 +75,7 @@
import static org.elasticsearch.test.ClusterServiceUtils.setState;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.lessThanOrEqualTo;
Expand Down Expand Up @@ -633,6 +638,48 @@ private boolean primaryInactiveDueToRecovery(final String indexName, final Clust
return true;
}

/**
* Tests the case where totalShardCount == 0 during cluster health calculation.
* This happens when indices exist in metadata but their routing tables are missing,
* causing totalShardCount to remain 0 and triggering the division by zero fix.
*/
public void testActiveShardsPercentDuringClusterRestart() {
final String indexName = "test-idx";
ProjectId projectId = randomUniqueProjectId();

final IndexMetadata indexMetadata = IndexMetadata.builder(indexName)
.settings(settings(IndexVersion.current()).put(IndexMetadata.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()))
.numberOfShards(3)
.numberOfReplicas(1)
.build();

// Create cluster state with index metadata but WITHOUT routing table entry
// This simulates cluster restart where metadata is loaded but routing table is not yet built
final var mdBuilder = Metadata.builder().put(ProjectMetadata.builder(projectId).put(indexMetadata, true).build());
final var rtBuilder = GlobalRoutingTable.builder().put(projectId, RoutingTable.EMPTY_ROUTING_TABLE);

ClusterState clusterState = ClusterState.builder(new ClusterName("test_cluster"))
.metadata(mdBuilder.build())
.routingTable(rtBuilder.build())
// Add global block to force RED status while keeping totalShardCount == 0
.blocks(
ClusterBlocks.builder()
.addGlobalBlock(new ClusterBlock(1, "test", true, true, true, RestStatus.SERVICE_UNAVAILABLE, ClusterBlockLevel.ALL))
)
.build();

String[] concreteIndices = new String[] { indexName };
ClusterStateHealth clusterStateHealth = new ClusterStateHealth(clusterState, concreteIndices, projectId);

assertThat(clusterStateHealth.getStatus(), not(equalTo(ClusterHealthStatus.GREEN)));

assertThat(
"activeShardsPercent should be 0.0 when totalShardCount is 0",
clusterStateHealth.getActiveShardsPercent(),
equalTo(0.0)
);
}

private void assertClusterHealth(ClusterStateHealth clusterStateHealth, RoutingTableGenerator.ShardCounter counter) {
assertThat(clusterStateHealth.getStatus(), equalTo(counter.status()));
assertThat(clusterStateHealth.getActiveShards(), equalTo(counter.active));
Expand Down