mirror of
https://github.com/honeymoose/OpenSearch.git
synced 2025-02-25 22:36:20 +00:00
Check shard limit after applying index templates (#44619)
Today when creating an index and checking cluster shard limits, we check the number of shards before applying index templates. At this point, we do not know the actual number of shards that will be used to create the index. In a case when the defaults are used and a template would override, we could be grossly underestimating the number of shards that would be created, and thus incorrectly applying the limits. This commit addresses this by checking the shard limits after applying index templates.
This commit is contained in:
parent
e2c8f8dfa3
commit
6928a315c4
@ -20,8 +20,8 @@
|
|||||||
package org.elasticsearch.cluster.metadata;
|
package org.elasticsearch.cluster.metadata;
|
||||||
|
|
||||||
import com.carrotsearch.hppc.cursors.ObjectObjectCursor;
|
import com.carrotsearch.hppc.cursors.ObjectObjectCursor;
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.apache.logging.log4j.message.ParameterizedMessage;
|
import org.apache.logging.log4j.message.ParameterizedMessage;
|
||||||
import org.elasticsearch.ElasticsearchException;
|
import org.elasticsearch.ElasticsearchException;
|
||||||
import org.elasticsearch.ResourceAlreadyExistsException;
|
import org.elasticsearch.ResourceAlreadyExistsException;
|
||||||
@ -438,6 +438,13 @@ public class MetaDataCreateIndexService {
|
|||||||
indexScopedSettings);
|
indexScopedSettings);
|
||||||
}
|
}
|
||||||
final Settings actualIndexSettings = indexSettingsBuilder.build();
|
final Settings actualIndexSettings = indexSettingsBuilder.build();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We can not check the shard limit until we have applied templates, otherwise we do not know the actual number of shards
|
||||||
|
* that will be used to create this index.
|
||||||
|
*/
|
||||||
|
checkShardLimit(actualIndexSettings, currentState);
|
||||||
|
|
||||||
tmpImdBuilder.settings(actualIndexSettings);
|
tmpImdBuilder.settings(actualIndexSettings);
|
||||||
|
|
||||||
if (recoverFromIndex != null) {
|
if (recoverFromIndex != null) {
|
||||||
@ -593,7 +600,7 @@ public class MetaDataCreateIndexService {
|
|||||||
assert Version.CURRENT.major == 7;
|
assert Version.CURRENT.major == 7;
|
||||||
final int numberOfShards;
|
final int numberOfShards;
|
||||||
final Version indexVersionCreated =
|
final Version indexVersionCreated =
|
||||||
Version.fromId(Integer.parseInt(indexSettingsBuilder.get(IndexMetaData.SETTING_INDEX_VERSION_CREATED.getKey())));
|
Version.fromId(Integer.parseInt(indexSettingsBuilder.get(IndexMetaData.SETTING_INDEX_VERSION_CREATED.getKey())));
|
||||||
if (indexVersionCreated.before(Version.V_7_0_0)) {
|
if (indexVersionCreated.before(Version.V_7_0_0)) {
|
||||||
numberOfShards = 5;
|
numberOfShards = 5;
|
||||||
} else {
|
} else {
|
||||||
@ -602,6 +609,10 @@ public class MetaDataCreateIndexService {
|
|||||||
return numberOfShards;
|
return numberOfShards;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void checkShardLimit(final Settings settings, final ClusterState clusterState) {
|
||||||
|
MetaDataCreateIndexService.checkShardLimit(settings, clusterState);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(String source, Exception e) {
|
public void onFailure(String source, Exception e) {
|
||||||
if (e instanceof ResourceAlreadyExistsException) {
|
if (e instanceof ResourceAlreadyExistsException) {
|
||||||
@ -622,9 +633,6 @@ public class MetaDataCreateIndexService {
|
|||||||
final boolean forbidPrivateIndexSettings) throws IndexCreationException {
|
final boolean forbidPrivateIndexSettings) throws IndexCreationException {
|
||||||
List<String> validationErrors = getIndexSettingsValidationErrors(settings, forbidPrivateIndexSettings);
|
List<String> validationErrors = getIndexSettingsValidationErrors(settings, forbidPrivateIndexSettings);
|
||||||
|
|
||||||
Optional<String> shardAllocation = checkShardLimit(settings, clusterState);
|
|
||||||
shardAllocation.ifPresent(validationErrors::add);
|
|
||||||
|
|
||||||
if (validationErrors.isEmpty() == false) {
|
if (validationErrors.isEmpty() == false) {
|
||||||
ValidationException validationException = new ValidationException();
|
ValidationException validationException = new ValidationException();
|
||||||
validationException.addValidationErrors(validationErrors);
|
validationException.addValidationErrors(validationErrors);
|
||||||
@ -635,15 +643,21 @@ public class MetaDataCreateIndexService {
|
|||||||
/**
|
/**
|
||||||
* Checks whether an index can be created without going over the cluster shard limit.
|
* Checks whether an index can be created without going over the cluster shard limit.
|
||||||
*
|
*
|
||||||
* @param settings The settings of the index to be created.
|
* @param settings the settings of the index to be created
|
||||||
* @param clusterState The current cluster state.
|
* @param clusterState the current cluster state
|
||||||
* @return If present, an error message to be used to reject index creation. If empty, a signal that this operation may be carried out.
|
* @throws ValidationException if creating this index would put the cluster over the cluster shard limit
|
||||||
*/
|
*/
|
||||||
static Optional<String> checkShardLimit(Settings settings, ClusterState clusterState) {
|
public static void checkShardLimit(final Settings settings, final ClusterState clusterState) {
|
||||||
int shardsToCreate = IndexMetaData.INDEX_NUMBER_OF_SHARDS_SETTING.get(settings)
|
final int numberOfShards = IndexMetaData.INDEX_NUMBER_OF_SHARDS_SETTING.get(settings);
|
||||||
* (1 + IndexMetaData.INDEX_NUMBER_OF_REPLICAS_SETTING.get(settings));
|
final int numberOfReplicas = IndexMetaData.INDEX_NUMBER_OF_REPLICAS_SETTING.get(settings);
|
||||||
|
final int shardsToCreate = numberOfShards * (1 + numberOfReplicas);
|
||||||
|
|
||||||
return IndicesService.checkShardLimit(shardsToCreate, clusterState);
|
final Optional<String> shardLimit = IndicesService.checkShardLimit(shardsToCreate, clusterState);
|
||||||
|
if (shardLimit.isPresent()) {
|
||||||
|
final ValidationException e = new ValidationException();
|
||||||
|
e.addValidationError(shardLimit.get());
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> getIndexSettingsValidationErrors(final Settings settings, final boolean forbidPrivateIndexSettings) {
|
List<String> getIndexSettingsValidationErrors(final Settings settings, final boolean forbidPrivateIndexSettings) {
|
||||||
|
@ -283,6 +283,7 @@ public class RestoreService implements ClusterStateApplier {
|
|||||||
indexMdBuilder.settings(Settings.builder()
|
indexMdBuilder.settings(Settings.builder()
|
||||||
.put(snapshotIndexMetaData.getSettings())
|
.put(snapshotIndexMetaData.getSettings())
|
||||||
.put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()));
|
.put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()));
|
||||||
|
MetaDataCreateIndexService.checkShardLimit(snapshotIndexMetaData.getSettings(), currentState);
|
||||||
if (!request.includeAliases() && !snapshotIndexMetaData.getAliases().isEmpty()) {
|
if (!request.includeAliases() && !snapshotIndexMetaData.getAliases().isEmpty()) {
|
||||||
// Remove all aliases - they shouldn't be restored
|
// Remove all aliases - they shouldn't be restored
|
||||||
indexMdBuilder.removeAllAliases();
|
indexMdBuilder.removeAllAliases();
|
||||||
|
@ -429,7 +429,14 @@ public class IndexCreationTaskTests extends ESTestCase {
|
|||||||
setupRequest();
|
setupRequest();
|
||||||
final MetaDataCreateIndexService.IndexCreationTask task = new MetaDataCreateIndexService.IndexCreationTask(
|
final MetaDataCreateIndexService.IndexCreationTask task = new MetaDataCreateIndexService.IndexCreationTask(
|
||||||
logger, allocationService, request, listener, indicesService, aliasValidator, xContentRegistry, clusterStateSettings.build(),
|
logger, allocationService, request, listener, indicesService, aliasValidator, xContentRegistry, clusterStateSettings.build(),
|
||||||
validator, IndexScopedSettings.DEFAULT_SCOPED_SETTINGS);
|
validator, IndexScopedSettings.DEFAULT_SCOPED_SETTINGS) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void checkShardLimit(final Settings settings, final ClusterState clusterState) {
|
||||||
|
// we have to make this a no-op since we are not mocking enough for this method to be able to execute
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
return task.execute(state);
|
return task.execute(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ import org.elasticsearch.cluster.routing.allocation.decider.AllocationDeciders;
|
|||||||
import org.elasticsearch.cluster.routing.allocation.decider.MaxRetryAllocationDecider;
|
import org.elasticsearch.cluster.routing.allocation.decider.MaxRetryAllocationDecider;
|
||||||
import org.elasticsearch.cluster.shards.ClusterShardLimitIT;
|
import org.elasticsearch.cluster.shards.ClusterShardLimitIT;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.logging.DeprecationLogger;
|
import org.elasticsearch.common.ValidationException;
|
||||||
import org.elasticsearch.common.settings.IndexScopedSettings;
|
import org.elasticsearch.common.settings.IndexScopedSettings;
|
||||||
import org.elasticsearch.common.settings.Setting;
|
import org.elasticsearch.common.settings.Setting;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
@ -53,7 +53,7 @@ import java.util.Collections;
|
|||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -65,8 +65,10 @@ import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF
|
|||||||
import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_VERSION_CREATED;
|
import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_VERSION_CREATED;
|
||||||
import static org.elasticsearch.cluster.shards.ClusterShardLimitIT.ShardCounts.forDataNodeCount;
|
import static org.elasticsearch.cluster.shards.ClusterShardLimitIT.ShardCounts.forDataNodeCount;
|
||||||
import static org.elasticsearch.indices.IndicesServiceTests.createClusterForShardLimitTest;
|
import static org.elasticsearch.indices.IndicesServiceTests.createClusterForShardLimitTest;
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.Matchers.endsWith;
|
import static org.hamcrest.Matchers.endsWith;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.hamcrest.Matchers.hasToString;
|
||||||
|
|
||||||
public class MetaDataCreateIndexServiceTests extends ESTestCase {
|
public class MetaDataCreateIndexServiceTests extends ESTestCase {
|
||||||
|
|
||||||
@ -486,14 +488,19 @@ public class MetaDataCreateIndexServiceTests extends ESTestCase {
|
|||||||
.put(SETTING_NUMBER_OF_REPLICAS, counts.getFailingIndexReplicas())
|
.put(SETTING_NUMBER_OF_REPLICAS, counts.getFailingIndexReplicas())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
DeprecationLogger deprecationLogger = new DeprecationLogger(logger);
|
final ValidationException e = expectThrows(
|
||||||
Optional<String> errorMessage = MetaDataCreateIndexService.checkShardLimit(indexSettings, state);
|
ValidationException.class,
|
||||||
|
() -> MetaDataCreateIndexService.checkShardLimit(indexSettings, state));
|
||||||
int totalShards = counts.getFailingIndexShards() * (1 + counts.getFailingIndexReplicas());
|
int totalShards = counts.getFailingIndexShards() * (1 + counts.getFailingIndexReplicas());
|
||||||
int currentShards = counts.getFirstIndexShards() * (1 + counts.getFirstIndexReplicas());
|
int currentShards = counts.getFirstIndexShards() * (1 + counts.getFirstIndexReplicas());
|
||||||
int maxShards = counts.getShardsPerNode() * nodesInCluster;
|
int maxShards = counts.getShardsPerNode() * nodesInCluster;
|
||||||
assertTrue(errorMessage.isPresent());
|
final String expectedMessage = String.format(
|
||||||
assertEquals("this action would add [" + totalShards + "] total shards, but this cluster currently has [" + currentShards
|
Locale.ROOT,
|
||||||
+ "]/[" + maxShards + "] maximum shards open", errorMessage.get());
|
"this action would add [%d] total shards, but this cluster currently has [%d]/[%d] maximum shards open",
|
||||||
|
totalShards,
|
||||||
|
currentShards,
|
||||||
|
maxShards);
|
||||||
|
assertThat(e, hasToString(containsString(expectedMessage)));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,7 @@ import org.elasticsearch.snapshots.SnapshotInfo;
|
|||||||
import org.elasticsearch.snapshots.SnapshotState;
|
import org.elasticsearch.snapshots.SnapshotState;
|
||||||
import org.elasticsearch.test.ESIntegTestCase;
|
import org.elasticsearch.test.ESIntegTestCase;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_REPLICAS;
|
import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_REPLICAS;
|
||||||
@ -101,6 +102,51 @@ public class ClusterShardLimitIT extends ESIntegTestCase {
|
|||||||
assertFalse(clusterState.getMetaData().hasIndex("should-fail"));
|
assertFalse(clusterState.getMetaData().hasIndex("should-fail"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testIndexCreationOverLimitFromTemplate() {
|
||||||
|
int dataNodes = client().admin().cluster().prepareState().get().getState().getNodes().getDataNodes().size();
|
||||||
|
|
||||||
|
final ShardCounts counts;
|
||||||
|
{
|
||||||
|
final ShardCounts temporaryCounts = ShardCounts.forDataNodeCount(dataNodes);
|
||||||
|
/*
|
||||||
|
* We are going to create an index that will bring us up to one below the limit; we go one below the limit to ensure the
|
||||||
|
* template is used instead of one shard.
|
||||||
|
*/
|
||||||
|
counts = new ShardCounts(
|
||||||
|
temporaryCounts.shardsPerNode,
|
||||||
|
temporaryCounts.firstIndexShards - 1,
|
||||||
|
temporaryCounts.firstIndexReplicas,
|
||||||
|
temporaryCounts.failingIndexShards + 1,
|
||||||
|
temporaryCounts.failingIndexReplicas);
|
||||||
|
}
|
||||||
|
setShardsPerNode(counts.getShardsPerNode());
|
||||||
|
|
||||||
|
if (counts.firstIndexShards > 0) {
|
||||||
|
createIndex(
|
||||||
|
"test",
|
||||||
|
Settings.builder()
|
||||||
|
.put(indexSettings())
|
||||||
|
.put(SETTING_NUMBER_OF_SHARDS, counts.getFirstIndexShards())
|
||||||
|
.put(SETTING_NUMBER_OF_REPLICAS, counts.getFirstIndexReplicas()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertAcked(client().admin()
|
||||||
|
.indices()
|
||||||
|
.preparePutTemplate("should-fail*")
|
||||||
|
.setPatterns(Collections.singletonList("should-fail"))
|
||||||
|
.setOrder(1)
|
||||||
|
.setSettings(Settings.builder()
|
||||||
|
.put(SETTING_NUMBER_OF_SHARDS, counts.getFailingIndexShards())
|
||||||
|
.put(SETTING_NUMBER_OF_REPLICAS, counts.getFailingIndexReplicas()))
|
||||||
|
.get());
|
||||||
|
|
||||||
|
final IllegalArgumentException e =
|
||||||
|
expectThrows(IllegalArgumentException.class, () -> client().admin().indices().prepareCreate("should-fail").get());
|
||||||
|
verifyException(dataNodes, counts, e);
|
||||||
|
ClusterState clusterState = client().admin().cluster().prepareState().get().getState();
|
||||||
|
assertFalse(clusterState.getMetaData().hasIndex("should-fail"));
|
||||||
|
}
|
||||||
|
|
||||||
public void testIncreaseReplicasOverLimit() {
|
public void testIncreaseReplicasOverLimit() {
|
||||||
int dataNodes = client().admin().cluster().prepareState().get().getState().getNodes().getDataNodes().size();
|
int dataNodes = client().admin().cluster().prepareState().get().getState().getNodes().getDataNodes().size();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user