|
@@ -11,6 +11,7 @@ package org.elasticsearch.cluster.routing.allocation;
|
|
|
import org.elasticsearch.Version;
|
|
|
import org.elasticsearch.cluster.ClusterName;
|
|
|
import org.elasticsearch.cluster.ClusterState;
|
|
|
+import org.elasticsearch.cluster.metadata.ComposableIndexTemplate;
|
|
|
import org.elasticsearch.cluster.metadata.IndexMetadata;
|
|
|
import org.elasticsearch.cluster.metadata.Metadata;
|
|
|
import org.elasticsearch.cluster.metadata.NodesShutdownMetadata;
|
|
@@ -23,6 +24,7 @@ import org.elasticsearch.cluster.routing.RecoverySource;
|
|
|
import org.elasticsearch.cluster.routing.RoutingTable;
|
|
|
import org.elasticsearch.cluster.routing.ShardRouting;
|
|
|
import org.elasticsearch.cluster.routing.UnassignedInfo;
|
|
|
+import org.elasticsearch.cluster.routing.allocation.ShardsAvailabilityHealthIndicatorService.ShardAllocationStatus;
|
|
|
import org.elasticsearch.cluster.routing.allocation.decider.AwarenessAllocationDecider;
|
|
|
import org.elasticsearch.cluster.routing.allocation.decider.Decision;
|
|
|
import org.elasticsearch.cluster.routing.allocation.decider.EnableAllocationDecider;
|
|
@@ -43,7 +45,12 @@ import org.elasticsearch.health.SimpleHealthIndicatorDetails;
|
|
|
import org.elasticsearch.health.node.HealthInfo;
|
|
|
import org.elasticsearch.index.Index;
|
|
|
import org.elasticsearch.index.shard.ShardId;
|
|
|
+import org.elasticsearch.indices.ExecutorNames;
|
|
|
+import org.elasticsearch.indices.SystemDataStreamDescriptor;
|
|
|
+import org.elasticsearch.indices.SystemIndexDescriptor;
|
|
|
+import org.elasticsearch.indices.SystemIndices;
|
|
|
import org.elasticsearch.test.ESTestCase;
|
|
|
+import org.elasticsearch.threadpool.ThreadPool;
|
|
|
import org.mockito.stubbing.Answer;
|
|
|
|
|
|
import java.util.ArrayList;
|
|
@@ -56,7 +63,10 @@ import java.util.Set;
|
|
|
import java.util.UUID;
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
+import static java.util.Collections.emptyList;
|
|
|
import static java.util.stream.Collectors.toMap;
|
|
|
+import static org.elasticsearch.cluster.metadata.DataStreamTestHelper.createBackingIndex;
|
|
|
+import static org.elasticsearch.cluster.metadata.DataStreamTestHelper.newInstance;
|
|
|
import static org.elasticsearch.cluster.metadata.IndexMetadata.INDEX_ROUTING_INCLUDE_GROUP_PREFIX;
|
|
|
import static org.elasticsearch.cluster.metadata.IndexMetadata.INDEX_ROUTING_REQUIRE_GROUP_PREFIX;
|
|
|
import static org.elasticsearch.cluster.metadata.SingleNodeShutdownMetadata.Type.RESTART;
|
|
@@ -83,14 +93,18 @@ import static org.elasticsearch.cluster.routing.allocation.ShardsAvailabilityHea
|
|
|
import static org.elasticsearch.cluster.routing.allocation.decider.ShardsLimitAllocationDecider.CLUSTER_TOTAL_SHARDS_PER_NODE_SETTING;
|
|
|
import static org.elasticsearch.common.util.CollectionUtils.concatLists;
|
|
|
import static org.elasticsearch.core.TimeValue.timeValueSeconds;
|
|
|
+import static org.elasticsearch.health.Diagnosis.Resource.Type.FEATURE_STATE;
|
|
|
import static org.elasticsearch.health.Diagnosis.Resource.Type.INDEX;
|
|
|
import static org.elasticsearch.health.HealthStatus.GREEN;
|
|
|
import static org.elasticsearch.health.HealthStatus.RED;
|
|
|
import static org.elasticsearch.health.HealthStatus.YELLOW;
|
|
|
import static org.hamcrest.Matchers.contains;
|
|
|
import static org.hamcrest.Matchers.containsInAnyOrder;
|
|
|
+import static org.hamcrest.Matchers.emptyCollectionOf;
|
|
|
import static org.hamcrest.Matchers.equalTo;
|
|
|
+import static org.hamcrest.Matchers.hasItems;
|
|
|
import static org.hamcrest.Matchers.hasSize;
|
|
|
+import static org.hamcrest.Matchers.is;
|
|
|
import static org.mockito.ArgumentMatchers.any;
|
|
|
import static org.mockito.Mockito.mock;
|
|
|
import static org.mockito.Mockito.when;
|
|
@@ -114,8 +128,8 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
|
|
|
GREEN,
|
|
|
"This cluster has all shards available.",
|
|
|
Map.of("started_primaries", 2, "started_replicas", 1),
|
|
|
- Collections.emptyList(),
|
|
|
- Collections.emptyList()
|
|
|
+ emptyList(),
|
|
|
+ emptyList()
|
|
|
)
|
|
|
)
|
|
|
);
|
|
@@ -353,8 +367,8 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
|
|
|
GREEN,
|
|
|
"This cluster has 1 restarting replica shard.",
|
|
|
Map.of("started_primaries", 1, "restarting_replicas", 1),
|
|
|
- Collections.emptyList(),
|
|
|
- Collections.emptyList()
|
|
|
+ emptyList(),
|
|
|
+ emptyList()
|
|
|
)
|
|
|
)
|
|
|
);
|
|
@@ -374,8 +388,8 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
|
|
|
GREEN,
|
|
|
"This cluster has all shards available.",
|
|
|
Map.of("started_primaries", 1),
|
|
|
- Collections.emptyList(),
|
|
|
- Collections.emptyList()
|
|
|
+ emptyList(),
|
|
|
+ emptyList()
|
|
|
)
|
|
|
)
|
|
|
);
|
|
@@ -436,8 +450,8 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
|
|
|
GREEN,
|
|
|
"This cluster has 1 creating primary shard.",
|
|
|
Map.of("creating_primaries", 1),
|
|
|
- Collections.emptyList(),
|
|
|
- Collections.emptyList()
|
|
|
+ emptyList(),
|
|
|
+ emptyList()
|
|
|
)
|
|
|
)
|
|
|
);
|
|
@@ -457,8 +471,8 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
|
|
|
GREEN,
|
|
|
"This cluster has 1 restarting primary shard.",
|
|
|
Map.of("restarting_primaries", 1),
|
|
|
- Collections.emptyList(),
|
|
|
- Collections.emptyList()
|
|
|
+ emptyList(),
|
|
|
+ emptyList()
|
|
|
)
|
|
|
)
|
|
|
);
|
|
@@ -503,7 +517,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- public void testUserActionsNotGeneratedWhenNotDrillingDown() {
|
|
|
+ public void testDiagnosisNotGeneratedWhenNotDrillingDown() {
|
|
|
// Index definition, 1 primary no replicas
|
|
|
IndexMetadata indexMetadata = IndexMetadata.builder("red-index")
|
|
|
.settings(Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT).build())
|
|
@@ -562,6 +576,149 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
|
|
|
assertThat(definitions, contains(ACTION_RESTORE_FROM_SNAPSHOT));
|
|
|
}
|
|
|
|
|
|
+ public void testRestoreFromSnapshotReportsFeatureStates() {
|
|
|
+ // this test adds a mix of regular and system indices and data streams
|
|
|
+ // we'll test the `shards_availability` indicator correctly reports the
|
|
|
+ // affected feature states and indices
|
|
|
+
|
|
|
+ IndexMetadata featureIndex = IndexMetadata.builder(".feature-index")
|
|
|
+ .settings(Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT).build())
|
|
|
+ .numberOfShards(1)
|
|
|
+ .numberOfReplicas(0)
|
|
|
+ .build();
|
|
|
+
|
|
|
+ IndexMetadata regularIndex = IndexMetadata.builder("regular-index")
|
|
|
+ .settings(Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT).build())
|
|
|
+ .numberOfShards(1)
|
|
|
+ .numberOfReplicas(0)
|
|
|
+ .build();
|
|
|
+
|
|
|
+ String featureDataStreamName = ".test-ds-feature";
|
|
|
+ IndexMetadata backingIndex = createBackingIndex(featureDataStreamName, 1).build();
|
|
|
+
|
|
|
+ ShardRouting featureIndexRouting = createShardRouting(
|
|
|
+ new ShardId(featureIndex.getIndex(), 0),
|
|
|
+ true,
|
|
|
+ new ShardAllocation(randomNodeId(), UNAVAILABLE, noShardCopy())
|
|
|
+ );
|
|
|
+
|
|
|
+ ShardRouting regularIndexRouting = createShardRouting(
|
|
|
+ new ShardId(regularIndex.getIndex(), 0),
|
|
|
+ true,
|
|
|
+ new ShardAllocation(randomNodeId(), UNAVAILABLE, noShardCopy())
|
|
|
+ );
|
|
|
+
|
|
|
+ ShardRouting backingIndexRouting = createShardRouting(
|
|
|
+ new ShardId(backingIndex.getIndex(), 0),
|
|
|
+ true,
|
|
|
+ new ShardAllocation(randomNodeId(), UNAVAILABLE, noShardCopy())
|
|
|
+ );
|
|
|
+
|
|
|
+ var clusterState = createClusterStateWith(
|
|
|
+ List.of(featureIndex, regularIndex, backingIndex),
|
|
|
+ List.of(
|
|
|
+ IndexRoutingTable.builder(featureIndex.getIndex()).addShard(featureIndexRouting).build(),
|
|
|
+ IndexRoutingTable.builder(regularIndex.getIndex()).addShard(regularIndexRouting).build(),
|
|
|
+ IndexRoutingTable.builder(backingIndex.getIndex()).addShard(backingIndexRouting).build()
|
|
|
+ ),
|
|
|
+ List.of(),
|
|
|
+ List.of()
|
|
|
+ );
|
|
|
+
|
|
|
+ // add the data stream to the cluster state
|
|
|
+ Metadata.Builder mdBuilder = Metadata.builder(clusterState.metadata())
|
|
|
+ .put(newInstance(featureDataStreamName, List.of(backingIndex.getIndex())));
|
|
|
+ ClusterState state = ClusterState.builder(clusterState).metadata(mdBuilder).build();
|
|
|
+
|
|
|
+ var service = createAllocationHealthIndicatorService(
|
|
|
+ Settings.EMPTY,
|
|
|
+ state,
|
|
|
+ Map.of(),
|
|
|
+ getSystemIndices(featureDataStreamName, ".test-ds-*", ".feature-*")
|
|
|
+ );
|
|
|
+ HealthIndicatorResult result = service.calculate(true, HealthInfo.EMPTY_HEALTH_INFO);
|
|
|
+
|
|
|
+ assertThat(result.status(), is(HealthStatus.RED));
|
|
|
+ assertThat(result.diagnosisList().size(), is(1));
|
|
|
+ Diagnosis diagnosis = result.diagnosisList().get(0);
|
|
|
+ List<Diagnosis.Resource> affectedResources = diagnosis.affectedResources();
|
|
|
+ assertThat("expecting we report a resource of type INDEX and one of type FEATURE_STATE", affectedResources.size(), is(2));
|
|
|
+ for (Diagnosis.Resource resource : affectedResources) {
|
|
|
+ if (resource.getType() == INDEX) {
|
|
|
+ assertThat(resource.getValues(), hasItems("regular-index"));
|
|
|
+ } else {
|
|
|
+ assertThat(resource.getType(), is(FEATURE_STATE));
|
|
|
+ assertThat(resource.getValues(), hasItems("feature-with-system-data-stream", "feature-with-system-index"));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testGetRestoreFromSnapshotAffectedResources() {
|
|
|
+ String featureDataStreamName = ".test-ds-feature";
|
|
|
+ IndexMetadata backingIndex = createBackingIndex(featureDataStreamName, 1).build();
|
|
|
+
|
|
|
+ List<IndexMetadata> indexMetadataList = List.of(
|
|
|
+ IndexMetadata.builder(".feature-index")
|
|
|
+ .settings(Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT).build())
|
|
|
+ .numberOfShards(1)
|
|
|
+ .numberOfReplicas(0)
|
|
|
+ .build(),
|
|
|
+ IndexMetadata.builder("regular-index")
|
|
|
+ .settings(Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT).build())
|
|
|
+ .numberOfShards(1)
|
|
|
+ .numberOfReplicas(0)
|
|
|
+ .build(),
|
|
|
+ backingIndex
|
|
|
+ );
|
|
|
+
|
|
|
+ Metadata.Builder metadataBuilder = Metadata.builder();
|
|
|
+ Map<String, IndexMetadata> indexMetadataMap = new HashMap<>();
|
|
|
+ for (IndexMetadata indexMetadata : indexMetadataList) {
|
|
|
+ indexMetadataMap.put(indexMetadata.getIndex().getName(), indexMetadata);
|
|
|
+ }
|
|
|
+ metadataBuilder.indices(indexMetadataMap);
|
|
|
+ metadataBuilder.put(newInstance(featureDataStreamName, List.of(backingIndex.getIndex())));
|
|
|
+ Metadata metadata = metadataBuilder.build();
|
|
|
+ {
|
|
|
+ List<Diagnosis.Resource> affectedResources = ShardAllocationStatus.getRestoreFromSnapshotAffectedResources(
|
|
|
+ metadata,
|
|
|
+ getSystemIndices(featureDataStreamName, ".test-ds-*", ".feature-*"),
|
|
|
+ Set.of(backingIndex.getIndex().getName(), ".feature-index", "regular-index"),
|
|
|
+ 10
|
|
|
+ );
|
|
|
+
|
|
|
+ assertThat(affectedResources.size(), is(2));
|
|
|
+ for (Diagnosis.Resource resource : affectedResources) {
|
|
|
+ if (resource.getType() == INDEX) {
|
|
|
+ assertThat(resource.getValues(), hasItems("regular-index"));
|
|
|
+ } else {
|
|
|
+ assertThat(resource.getType(), is(FEATURE_STATE));
|
|
|
+ assertThat(resource.getValues(), hasItems("feature-with-system-data-stream", "feature-with-system-index"));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ {
|
|
|
+ List<Diagnosis.Resource> affectedResources = ShardAllocationStatus.getRestoreFromSnapshotAffectedResources(
|
|
|
+ metadata,
|
|
|
+ getSystemIndices(featureDataStreamName, ".test-ds-*", ".feature-*"),
|
|
|
+ Set.of(backingIndex.getIndex().getName(), ".feature-index", "regular-index"),
|
|
|
+ 0
|
|
|
+ );
|
|
|
+
|
|
|
+ assertThat(affectedResources.size(), is(2));
|
|
|
+ for (Diagnosis.Resource resource : affectedResources) {
|
|
|
+ if (resource.getType() == INDEX) {
|
|
|
+ assertThat(resource.getValues(), emptyCollectionOf(String.class));
|
|
|
+ } else {
|
|
|
+ assertThat(resource.getType(), is(FEATURE_STATE));
|
|
|
+ assertThat(resource.getValues(), emptyCollectionOf(String.class));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
public void testDiagnoseUnknownAllocationDeciderIssue() {
|
|
|
// Index definition, 1 primary no replicas
|
|
|
IndexMetadata indexMetadata = IndexMetadata.builder("red-index")
|
|
@@ -1253,6 +1410,53 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Creates the {@link SystemIndices} with one standalone system index and a system data stream
|
|
|
+ */
|
|
|
+ private SystemIndices getSystemIndices(
|
|
|
+ String featureDataStreamName,
|
|
|
+ String systemDataStreamPattern,
|
|
|
+ String standaloneSystemIndexPattern
|
|
|
+ ) {
|
|
|
+ return new SystemIndices(
|
|
|
+ List.of(
|
|
|
+ new SystemIndices.Feature(
|
|
|
+ "feature-with-system-index",
|
|
|
+ "testing",
|
|
|
+ List.of(new SystemIndexDescriptor(standaloneSystemIndexPattern, "feature with index"))
|
|
|
+ ),
|
|
|
+ new SystemIndices.Feature(
|
|
|
+ "feature-with-system-data-stream",
|
|
|
+ "feature with data stream",
|
|
|
+ List.of(),
|
|
|
+ List.of(
|
|
|
+ new SystemDataStreamDescriptor(
|
|
|
+ featureDataStreamName,
|
|
|
+ "description",
|
|
|
+ SystemDataStreamDescriptor.Type.EXTERNAL,
|
|
|
+ new ComposableIndexTemplate(
|
|
|
+ List.of(systemDataStreamPattern),
|
|
|
+ null,
|
|
|
+ null,
|
|
|
+ null,
|
|
|
+ null,
|
|
|
+ null,
|
|
|
+ new ComposableIndexTemplate.DataStreamTemplate()
|
|
|
+ ),
|
|
|
+ Map.of(),
|
|
|
+ List.of("test"),
|
|
|
+ new ExecutorNames(
|
|
|
+ ThreadPool.Names.SYSTEM_CRITICAL_READ,
|
|
|
+ ThreadPool.Names.SYSTEM_READ,
|
|
|
+ ThreadPool.Names.SYSTEM_WRITE
|
|
|
+ )
|
|
|
+ )
|
|
|
+ )
|
|
|
+ )
|
|
|
+ )
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
private HealthIndicatorResult createExpectedResult(
|
|
|
HealthStatus status,
|
|
|
String symptom,
|
|
@@ -1271,7 +1475,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
|
|
|
}
|
|
|
|
|
|
private HealthIndicatorResult createExpectedTruncatedResult(HealthStatus status, String symptom, List<HealthIndicatorImpact> impacts) {
|
|
|
- return new HealthIndicatorResult(NAME, status, symptom, HealthIndicatorDetails.EMPTY, impacts, Collections.emptyList());
|
|
|
+ return new HealthIndicatorResult(NAME, status, symptom, HealthIndicatorDetails.EMPTY, impacts, emptyList());
|
|
|
}
|
|
|
|
|
|
private static ClusterState createClusterStateWith(List<IndexRoutingTable> indexRoutes, List<NodeShutdown> nodeShutdowns) {
|
|
@@ -1534,13 +1738,22 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
|
|
|
ClusterState clusterState,
|
|
|
final Map<ShardRoutingKey, ShardAllocationDecision> decisions
|
|
|
) {
|
|
|
- return createShardsAvailabilityIndicatorService(Settings.EMPTY, clusterState, decisions);
|
|
|
+ return createAllocationHealthIndicatorService(Settings.EMPTY, clusterState, decisions, new SystemIndices(List.of()));
|
|
|
}
|
|
|
|
|
|
private static ShardsAvailabilityHealthIndicatorService createShardsAvailabilityIndicatorService(
|
|
|
Settings nodeSettings,
|
|
|
ClusterState clusterState,
|
|
|
final Map<ShardRoutingKey, ShardAllocationDecision> decisions
|
|
|
+ ) {
|
|
|
+ return createAllocationHealthIndicatorService(nodeSettings, clusterState, decisions, new SystemIndices(List.of()));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static ShardsAvailabilityHealthIndicatorService createAllocationHealthIndicatorService(
|
|
|
+ Settings nodeSettings,
|
|
|
+ ClusterState clusterState,
|
|
|
+ final Map<ShardRoutingKey, ShardAllocationDecision> decisions,
|
|
|
+ SystemIndices systemIndices
|
|
|
) {
|
|
|
var clusterService = mock(ClusterService.class);
|
|
|
when(clusterService.state()).thenReturn(clusterState);
|
|
@@ -1552,6 +1765,6 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
|
|
|
var key = new ShardRoutingKey(shardRouting.getIndexName(), shardRouting.getId(), shardRouting.primary());
|
|
|
return decisions.getOrDefault(key, ShardAllocationDecision.NOT_TAKEN);
|
|
|
});
|
|
|
- return new ShardsAvailabilityHealthIndicatorService(clusterService, allocationService);
|
|
|
+ return new ShardsAvailabilityHealthIndicatorService(clusterService, allocationService, systemIndices);
|
|
|
}
|
|
|
}
|