Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.elasticsearch.action.support.PlainActionFuture;
import org.elasticsearch.cluster.health.ClusterHealthStatus;
import org.elasticsearch.cluster.metadata.IndexMetadata;
import org.elasticsearch.cluster.metadata.Metadata;
import org.elasticsearch.cluster.routing.UnassignedInfo;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.Priority;
Expand Down Expand Up @@ -56,8 +57,14 @@ public void testSimpleLocalHealth() {
}

public void testHealth() {
logger.info("--> running cluster health for '_all' on an empty cluster");
ClusterHealthResponse healthResponse = clusterAdmin().prepareHealth(TEST_REQUEST_TIMEOUT, Metadata.ALL).get();
assertThat(healthResponse.isTimedOut(), equalTo(false));
assertThat(healthResponse.getStatus(), equalTo(ClusterHealthStatus.GREEN));
assertThat(healthResponse.getIndices().isEmpty(), equalTo(true));

logger.info("--> running cluster health on an index that does not exists");
ClusterHealthResponse healthResponse = clusterAdmin().prepareHealth(TEST_REQUEST_TIMEOUT, "test1")
healthResponse = clusterAdmin().prepareHealth(TEST_REQUEST_TIMEOUT, "test1")
.setWaitForYellowStatus()
.setTimeout(TimeValue.timeValueSeconds(1))
.get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.common.Strings;
import org.elasticsearch.index.mapper.extras.MapperExtrasPlugin;
import org.elasticsearch.indices.IndexCreationException;
import org.elasticsearch.plugins.Plugin;
Expand Down Expand Up @@ -316,11 +317,7 @@ public void testCreateSynonymsWithYellowSynonymsIndex() throws Exception {
@Override
void checkSynonymsIndexHealth(ActionListener<ClusterHealthResponse> listener) {
ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT).build();
ClusterHealthResponse response = new ClusterHealthResponse(
randomIdentifier(),
new String[] { SynonymsManagementAPIService.SYNONYMS_INDEX_CONCRETE_NAME },
clusterState
);
ClusterHealthResponse response = new ClusterHealthResponse(randomIdentifier(), Strings.EMPTY_ARRAY, clusterState);
response.setTimedOut(true);
listener.onResponse(response);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,29 @@ public ClusterIndexHealth(final IndexMetadata indexMetadata, final IndexRoutingT
this.numberOfReplicas = indexMetadata.getNumberOfReplicas();

shards = new HashMap<>();
for (int i = 0; i < indexRoutingTable.size(); i++) {
IndexShardRoutingTable shardRoutingTable = indexRoutingTable.shard(i);
int shardId = shardRoutingTable.shardId().id();
shards.put(shardId, new ClusterShardHealth(shardId, shardRoutingTable));
if (indexRoutingTable != null) {
for (int i = 0; i < indexRoutingTable.size(); i++) {
IndexShardRoutingTable shardRoutingTable = indexRoutingTable.shard(i);
int shardId = shardRoutingTable.shardId().id();
shards.put(shardId, new ClusterShardHealth(shardId, shardRoutingTable));
}
} else {
for (int shardId = 0; shardId < numberOfShards; shardId++) {
// Create a shard health representing completely unassigned shard
// All replicas for this shard are unassigned, including the primary
int replicasCount = numberOfReplicas + 1;
ClusterShardHealth clusterShardHealth = new ClusterShardHealth(
shardId,
ClusterHealthStatus.RED,
0,
0,
0,
replicasCount,
1,
false
);
shards.put(shardId, clusterShardHealth);
}
}

// update the index status
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,16 @@ public ClusterStateHealth(
int totalShardCount = 0;

for (String index : concreteIndices) {
IndexRoutingTable indexRoutingTable = routingTable.index(index);
IndexMetadata indexMetadata = project.index(index);
if (indexRoutingTable == null) {
if (indexMetadata == null) {
// should not happen, concreteIndices ought to have been resolved against the project metadata
assert false : "concrete index [" + index + "] not found in project [" + project.id() + "]";
computeStatus = ClusterHealthStatus.RED;
continue;
}

IndexRoutingTable indexRoutingTable = routingTable.index(index);

ClusterIndexHealth indexHealth = new ClusterIndexHealth(indexMetadata, indexRoutingTable);
indices.put(indexHealth.getIndex(), indexHealth);

Expand Down Expand Up @@ -121,7 +125,7 @@ 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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import org.elasticsearch.cluster.health.ClusterIndexHealth;
import org.elasticsearch.cluster.health.ClusterIndexHealthTests;
import org.elasticsearch.cluster.health.ClusterStateHealth;
import org.elasticsearch.cluster.metadata.Metadata;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.Writeable;
Expand Down Expand Up @@ -161,7 +160,7 @@ public void testClusterHealth() throws IOException {
TimeValue pendingTaskInQueueTime = TimeValue.timeValueMillis(randomIntBetween(1000, 100000));
ClusterHealthResponse clusterHealth = new ClusterHealthResponse(
"bla",
new String[] { Metadata.ALL },
new String[0], // Use empty array since clusterState has no indices
clusterState,
clusterState.metadata().getProject().id(),
pendingTasks,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.elasticsearch.cluster.routing.ShardRoutingState;
import org.elasticsearch.cluster.routing.TestShardRouting;
import org.elasticsearch.common.Randomness;
import org.elasticsearch.common.Strings;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.core.Tuple;
import org.elasticsearch.index.IndexVersion;
Expand Down Expand Up @@ -75,7 +76,7 @@ public void testWaitForAllShards() {

clusterState = ClusterState.builder(ClusterName.DEFAULT).build();
project = clusterState.metadata().getProject(Metadata.DEFAULT_PROJECT_ID);
response = createResponse(indices, clusterState, project);
response = createResponse(Strings.EMPTY_ARRAY /* no indices */ , clusterState, project);
assertThat(TransportClusterHealthAction.prepareResponse(request, response, project, null), equalTo(1));
}

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 @@ -633,6 +637,64 @@ private boolean primaryInactiveDueToRecovery(final String indexName, final Clust
return true;
}

/**
* Tests the case where indices exist in metadata but their routing tables are missing.
* This happens during cluster restart where metadata is loaded but routing table is not yet built.
* All shards should be considered completely unassigned and the cluster should be RED.
*/
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())
.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);

// The cluster should be RED because all shards are unassigned
assertThat(clusterStateHealth.getStatus(), equalTo(ClusterHealthStatus.RED));

// All shards are unassigned, so activeShardsPercent should be 0.0
assertThat(
"activeShardsPercent should be 0.0 when all shards are unassigned",
clusterStateHealth.getActiveShardsPercent(),
equalTo(0.0)
);

// Verify that totalShardCount is correctly calculated
int expectedTotalShards = indexMetadata.getTotalNumberOfShards();
assertThat("All shards should be counted as unassigned", clusterStateHealth.getUnassignedShards(), equalTo(expectedTotalShards));

// All primary shards should be unassigned
assertThat(
"All primary shards should be unassigned",
clusterStateHealth.getUnassignedPrimaryShards(),
equalTo(indexMetadata.getNumberOfShards())
);

// No active shards
assertThat(clusterStateHealth.getActiveShards(), equalTo(0));
assertThat(clusterStateHealth.getActivePrimaryShards(), equalTo(0));
}

private void assertClusterHealth(ClusterStateHealth clusterStateHealth, RoutingTableGenerator.ShardCounter counter) {
assertThat(clusterStateHealth.getStatus(), equalTo(counter.status()));
assertThat(clusterStateHealth.getActiveShards(), equalTo(counter.active));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ public void testBuildTable() {
IndexStats indexStats = indicesStats.get(indexName);
IndexMetadata indexMetadata = project.index(indexName);

if (indexHealth != null) {
IndexRoutingTable indexRoutingTable = clusterState.routingTable(project.id()).index(indexName);
if (indexRoutingTable != null) {
assertThat(row.get(0).value, equalTo(indexHealth.getStatus().toString().toLowerCase(Locale.ROOT)));
} else if (indexStats != null) {
assertThat(row.get(0).value, equalTo("red*"));
Expand Down