diff --git a/server/src/internalClusterTest/java/org/opensearch/cluster/metadata/ClusterIndexRefreshIntervalIT.java b/server/src/internalClusterTest/java/org/opensearch/cluster/metadata/ClusterIndexRefreshIntervalIT.java new file mode 100644 index 0000000000000..54824b67b7abc --- /dev/null +++ b/server/src/internalClusterTest/java/org/opensearch/cluster/metadata/ClusterIndexRefreshIntervalIT.java @@ -0,0 +1,338 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you 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 + * + * http://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. + */ + +/* + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.cluster.metadata; + +import org.opensearch.action.admin.indices.get.GetIndexRequest; +import org.opensearch.action.admin.indices.get.GetIndexResponse; +import org.opensearch.action.admin.indices.settings.put.UpdateSettingsRequest; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.unit.TimeValue; +import org.opensearch.core.index.Index; +import org.opensearch.index.IndexService; +import org.opensearch.index.IndexSettings; +import org.opensearch.indices.IndicesService; +import org.opensearch.test.OpenSearchIntegTestCase; +import org.junit.Before; + +import java.util.List; +import java.util.concurrent.ExecutionException; + +import static org.opensearch.indices.IndicesService.CLUSTER_DEFAULT_INDEX_REFRESH_INTERVAL_SETTING; +import static org.opensearch.indices.IndicesService.CLUSTER_MINIMUM_INDEX_REFRESH_INTERVAL_SETTING; + +@OpenSearchIntegTestCase.ClusterScope(scope = OpenSearchIntegTestCase.Scope.TEST, numDataNodes = 0) +public class ClusterIndexRefreshIntervalIT extends OpenSearchIntegTestCase { + + public static final String INDEX_NAME = "test-index"; + + public static final String OTHER_INDEX_NAME = "other-test-index"; + + @Override + public Settings indexSettings() { + return Settings.builder().put(super.indexSettings()).put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 1).build(); + } + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + internalCluster().startClusterManagerOnlyNode(); + } + + public void testDefaultRefreshIntervalWithUpdateClusterAndIndexSettings() throws Exception { + String clusterManagerName = internalCluster().getClusterManagerName(); + List dataNodes = internalCluster().startDataOnlyNodes(2); + createIndex(INDEX_NAME); + ensureYellowAndNoInitializingShards(INDEX_NAME); + ensureGreen(INDEX_NAME); + GetIndexResponse getIndexResponse = client(clusterManagerName).admin().indices().getIndex(new GetIndexRequest()).get(); + IndicesService indicesService = internalCluster().getInstance(IndicesService.class, randomFrom(dataNodes)); + String uuid = getIndexResponse.getSettings().get(INDEX_NAME).get(IndexMetadata.SETTING_INDEX_UUID); + IndexService indexService = indicesService.indexService(new Index(INDEX_NAME, uuid)); + assertEquals(getDefaultRefreshInterval(), indexService.getRefreshTaskInterval()); + + // Update the cluster.default.index.refresh_interval setting to another value and validate the index refresh interval + TimeValue refreshInterval = TimeValue.timeValueMillis(randomIntBetween(10, 90) * 1000L); + client(clusterManagerName).admin() + .cluster() + .prepareUpdateSettings() + .setTransientSettings(Settings.builder().put(CLUSTER_DEFAULT_INDEX_REFRESH_INTERVAL_SETTING.getKey(), refreshInterval)) + .get(); + assertEquals(refreshInterval, indexService.getRefreshTaskInterval()); + + // Update of cluster.minimum.index.refresh_interval setting to value less than refreshInterval above will fail + TimeValue invalidMinimumRefreshInterval = TimeValue.timeValueMillis(refreshInterval.millis() + randomIntBetween(1, 1000)); + IllegalArgumentException exceptionDuringMinUpdate = assertThrows( + IllegalArgumentException.class, + () -> client(clusterManagerName).admin() + .cluster() + .prepareUpdateSettings() + .setTransientSettings( + Settings.builder().put(CLUSTER_MINIMUM_INDEX_REFRESH_INTERVAL_SETTING.getKey(), invalidMinimumRefreshInterval) + ) + .get() + ); + assertEquals( + "cluster minimum index refresh interval [" + + invalidMinimumRefreshInterval + + "] more than cluster default index refresh interval [" + + refreshInterval + + "]", + exceptionDuringMinUpdate.getMessage() + ); + + // Update the cluster.minimum.index.refresh_interval setting to a valid value, this will succeed. + TimeValue validMinimumRefreshInterval = TimeValue.timeValueMillis(refreshInterval.millis() - randomIntBetween(1, 1000)); + client(clusterManagerName).admin() + .cluster() + .prepareUpdateSettings() + .setTransientSettings( + Settings.builder().put(CLUSTER_MINIMUM_INDEX_REFRESH_INTERVAL_SETTING.getKey(), validMinimumRefreshInterval) + ) + .get(); + + // Update with invalid index setting index.refresh_interval, this will fail. + TimeValue invalidRefreshInterval = TimeValue.timeValueMillis(validMinimumRefreshInterval.millis() - randomIntBetween(1, 1000)); + String expectedMessage = "invalid index.refresh_interval [" + + invalidRefreshInterval + + "]: cannot be smaller than cluster.minimum.index.refresh_interval [" + + validMinimumRefreshInterval + + "]"; + + IllegalArgumentException exceptionDuringUpdateSettings = assertThrows( + IllegalArgumentException.class, + () -> client(clusterManagerName).admin() + .indices() + .updateSettings( + new UpdateSettingsRequest(INDEX_NAME).settings( + Settings.builder().put(IndexSettings.INDEX_REFRESH_INTERVAL_SETTING.getKey(), invalidRefreshInterval) + ) + ) + .actionGet() + ); + assertEquals(expectedMessage, exceptionDuringUpdateSettings.getMessage()); + + // Create another index with invalid index setting index.refresh_interval, this fails. + Settings indexSettings = Settings.builder() + .put(indexSettings()) + .put(IndexSettings.INDEX_REFRESH_INTERVAL_SETTING.getKey(), invalidRefreshInterval) + .build(); + IllegalArgumentException exceptionDuringCreateIndex = assertThrows( + IllegalArgumentException.class, + () -> createIndex(OTHER_INDEX_NAME, indexSettings) + ); + assertEquals(expectedMessage, exceptionDuringCreateIndex.getMessage()); + + // Update with valid index setting index.refresh_interval, this will succeed now. + TimeValue validRefreshInterval = TimeValue.timeValueMillis(validMinimumRefreshInterval.millis() + randomIntBetween(1, 1000)); + client(clusterManagerName).admin() + .indices() + .updateSettings( + new UpdateSettingsRequest(INDEX_NAME).settings( + Settings.builder().put(IndexSettings.INDEX_REFRESH_INTERVAL_SETTING.getKey(), validRefreshInterval) + ) + ) + .get(); + // verify refresh task interval is updated. + assertEquals(validRefreshInterval, indexService.getRefreshTaskInterval()); + + // Try to create another index with valid index setting index.refresh_interval, this will pass. + createIndex( + OTHER_INDEX_NAME, + Settings.builder().put(indexSettings).put(IndexSettings.INDEX_REFRESH_INTERVAL_SETTING.getKey(), validRefreshInterval).build() + ); + getIndexResponse = client(clusterManagerName).admin().indices().getIndex(new GetIndexRequest()).get(); + String otherUuid = getIndexResponse.getSettings().get(INDEX_NAME).get(IndexMetadata.SETTING_INDEX_UUID); + assertEquals(validRefreshInterval, indicesService.indexService(new Index(OTHER_INDEX_NAME, otherUuid)).getRefreshTaskInterval()); + + // Update the cluster.default.index.refresh_interval & cluster.minimum.index.refresh_interval setting to null + client(clusterManagerName).admin() + .cluster() + .prepareUpdateSettings() + .setTransientSettings( + Settings.builder() + .putNull(CLUSTER_DEFAULT_INDEX_REFRESH_INTERVAL_SETTING.getKey()) + .putNull(CLUSTER_MINIMUM_INDEX_REFRESH_INTERVAL_SETTING.getKey()) + ) + .get(); + // verify the index is still using the refresh interval passed in the update settings call + assertEquals(validRefreshInterval, indexService.getRefreshTaskInterval()); + + // Remove the index setting as well now, it should reset the refresh task interval to the default refresh interval + client(clusterManagerName).admin() + .indices() + .updateSettings( + new UpdateSettingsRequest(INDEX_NAME).settings( + Settings.builder().putNull(IndexSettings.INDEX_REFRESH_INTERVAL_SETTING.getKey()) + ) + ) + .get(); + assertEquals(getDefaultRefreshInterval(), indexService.getRefreshTaskInterval()); + } + + public void testRefreshIntervalDisabled() throws ExecutionException, InterruptedException { + TimeValue clusterMinimumRefreshInterval = client().settings() + .getAsTime(IndicesService.CLUSTER_MINIMUM_INDEX_REFRESH_INTERVAL_SETTING.getKey(), TimeValue.MINUS_ONE); + boolean createIndexSuccess = clusterMinimumRefreshInterval.equals(TimeValue.MINUS_ONE); + String clusterManagerName = internalCluster().getClusterManagerName(); + List dataNodes = internalCluster().startDataOnlyNodes(2); + Settings settings = Settings.builder() + .put(indexSettings()) + .put(IndexSettings.INDEX_REFRESH_INTERVAL_SETTING.getKey(), IndexSettings.MINIMUM_REFRESH_INTERVAL) + .build(); + if (createIndexSuccess) { + createIndex(INDEX_NAME, settings); + ensureYellowAndNoInitializingShards(INDEX_NAME); + ensureGreen(INDEX_NAME); + GetIndexResponse getIndexResponse = client(clusterManagerName).admin().indices().getIndex(new GetIndexRequest()).get(); + IndicesService indicesService = internalCluster().getInstance(IndicesService.class, randomFrom(dataNodes)); + String uuid = getIndexResponse.getSettings().get(INDEX_NAME).get(IndexMetadata.SETTING_INDEX_UUID); + IndexService indexService = indicesService.indexService(new Index(INDEX_NAME, uuid)); + assertEquals(IndexSettings.MINIMUM_REFRESH_INTERVAL, indexService.getRefreshTaskInterval()); + } else { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> createIndex(INDEX_NAME, settings)); + assertEquals( + "invalid index.refresh_interval [-1]: cannot be smaller than cluster.minimum.index.refresh_interval [" + + getMinRefreshIntervalForRefreshDisabled() + + "]", + exception.getMessage() + ); + } + } + + protected TimeValue getMinRefreshIntervalForRefreshDisabled() { + throw new RuntimeException("This is not expected to be called here, but for the implementor"); + } + + public void testInvalidRefreshInterval() { + String invalidRefreshInterval = "-10s"; + internalCluster().startDataOnlyNodes(2); + Settings settings = Settings.builder() + .put(indexSettings()) + .put(IndexSettings.INDEX_REFRESH_INTERVAL_SETTING.getKey(), invalidRefreshInterval) + .build(); + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> createIndex(INDEX_NAME, settings)); + assertEquals( + "failed to parse setting [index.refresh_interval] with value [" + + invalidRefreshInterval + + "] as a time value: negative durations are not supported", + exception.getMessage() + ); + } + + public void testCreateIndexWithExplicitNullRefreshInterval() throws ExecutionException, InterruptedException { + List dataNodes = internalCluster().startDataOnlyNodes(2); + Settings indexSettings = Settings.builder() + .put(indexSettings()) + .putNull(IndexSettings.INDEX_REFRESH_INTERVAL_SETTING.getKey()) + .build(); + createIndex(INDEX_NAME, indexSettings); + ensureYellowAndNoInitializingShards(INDEX_NAME); + ensureGreen(INDEX_NAME); + + GetIndexResponse getIndexResponse = client(internalCluster().getClusterManagerName()).admin() + .indices() + .getIndex(new GetIndexRequest()) + .get(); + String uuid = getIndexResponse.getSettings().get(INDEX_NAME).get(IndexMetadata.SETTING_INDEX_UUID); + + IndicesService indicesService = internalCluster().getInstance(IndicesService.class, randomFrom(dataNodes)); + IndexService indexService = indicesService.indexService(new Index(INDEX_NAME, uuid)); + + assertEquals(IndexSettings.DEFAULT_REFRESH_INTERVAL, indexService.getRefreshTaskInterval()); + } + + /** + * In this test we check the case where an index is created with index setting `index.refresh_interval` with the value + * being lesser than the `cluster.minimum.index.refresh_interval`. Later we change the cluster minimum to be more than + * the index setting. The underlying index should continue to use the same refresh interval as earlier. + */ + public void testClusterMinimumChangeOnIndexWithCustomRefreshInterval() throws ExecutionException, InterruptedException { + List dataNodes = internalCluster().startDataOnlyNodes(2); + TimeValue customRefreshInterval = TimeValue.timeValueSeconds(getDefaultRefreshInterval().getSeconds() + randomIntBetween(1, 5)); + Settings indexSettings = Settings.builder() + .put(indexSettings()) + .put(IndexSettings.INDEX_REFRESH_INTERVAL_SETTING.getKey(), customRefreshInterval) + .build(); + createIndex(INDEX_NAME, indexSettings); + + ensureYellowAndNoInitializingShards(INDEX_NAME); + ensureGreen(INDEX_NAME); + + GetIndexResponse getIndexResponse = client(internalCluster().getClusterManagerName()).admin() + .indices() + .getIndex(new GetIndexRequest()) + .get(); + String uuid = getIndexResponse.getSettings().get(INDEX_NAME).get(IndexMetadata.SETTING_INDEX_UUID); + + IndicesService indicesService = internalCluster().getInstance(IndicesService.class, randomFrom(dataNodes)); + IndexService indexService = indicesService.indexService(new Index(INDEX_NAME, uuid)); + + assertEquals(customRefreshInterval, indexService.getRefreshTaskInterval()); + + // Update the cluster.minimum.index.refresh_interval setting to a valid value higher the custom refresh interval. + // At the same time, due to certain degree of randomness in the test, we update the cluster.default.refresh_interval + // to a valid value as well to be deterministic in test behaviour. + TimeValue clusterMinimum = TimeValue.timeValueSeconds(customRefreshInterval.getSeconds() + randomIntBetween(1, 5)); + TimeValue clusterDefault = TimeValue.timeValueSeconds(customRefreshInterval.getSeconds() + 6); + String clusterManagerName = internalCluster().getClusterManagerName(); + client(clusterManagerName).admin() + .cluster() + .prepareUpdateSettings() + .setTransientSettings( + Settings.builder() + .put(CLUSTER_DEFAULT_INDEX_REFRESH_INTERVAL_SETTING.getKey(), clusterDefault) + .put(CLUSTER_MINIMUM_INDEX_REFRESH_INTERVAL_SETTING.getKey(), clusterMinimum) + ) + .get(); + + // Validate that the index refresh interval is still the existing one that was used during index creation + assertEquals(customRefreshInterval, indexService.getRefreshTaskInterval()); + + // Update index setting to a value >= current cluster minimum and this should happen successfully. + customRefreshInterval = TimeValue.timeValueSeconds(clusterMinimum.getSeconds() + randomIntBetween(1, 5)); + client(clusterManagerName).admin() + .indices() + .updateSettings( + new UpdateSettingsRequest(INDEX_NAME).settings( + Settings.builder().put(IndexSettings.INDEX_REFRESH_INTERVAL_SETTING.getKey(), customRefreshInterval) + ) + ) + .get(); + assertEquals(customRefreshInterval, indexService.getRefreshTaskInterval()); + } + + protected TimeValue getDefaultRefreshInterval() { + return IndexSettings.DEFAULT_REFRESH_INTERVAL; + } +} diff --git a/server/src/internalClusterTest/java/org/opensearch/cluster/metadata/ClusterIndexRefreshIntervalWithNodeSettingsIT.java b/server/src/internalClusterTest/java/org/opensearch/cluster/metadata/ClusterIndexRefreshIntervalWithNodeSettingsIT.java new file mode 100644 index 0000000000000..5fc7bfcbcd442 --- /dev/null +++ b/server/src/internalClusterTest/java/org/opensearch/cluster/metadata/ClusterIndexRefreshIntervalWithNodeSettingsIT.java @@ -0,0 +1,38 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.cluster.metadata; + +import org.opensearch.common.settings.Settings; +import org.opensearch.common.unit.TimeValue; +import org.opensearch.indices.IndicesService; + +public class ClusterIndexRefreshIntervalWithNodeSettingsIT extends ClusterIndexRefreshIntervalIT { + + @Override + protected Settings nodeSettings(int nodeOrdinal) { + return Settings.builder() + .put(super.nodeSettings(nodeOrdinal)) + .put(IndicesService.CLUSTER_DEFAULT_INDEX_REFRESH_INTERVAL_SETTING.getKey(), getDefaultRefreshInterval()) + .put( + IndicesService.CLUSTER_MINIMUM_INDEX_REFRESH_INTERVAL_SETTING.getKey(), + getMinRefreshIntervalForRefreshDisabled().toString() + ) + .build(); + } + + @Override + protected TimeValue getMinRefreshIntervalForRefreshDisabled() { + return TimeValue.timeValueSeconds(1); + } + + @Override + protected TimeValue getDefaultRefreshInterval() { + return TimeValue.timeValueSeconds(5); + } +} diff --git a/server/src/main/java/org/opensearch/cluster/metadata/MetadataCreateIndexService.java b/server/src/main/java/org/opensearch/cluster/metadata/MetadataCreateIndexService.java index 2cc88c9fbc05e..ec63f762bea9f 100644 --- a/server/src/main/java/org/opensearch/cluster/metadata/MetadataCreateIndexService.java +++ b/server/src/main/java/org/opensearch/cluster/metadata/MetadataCreateIndexService.java @@ -68,9 +68,11 @@ import org.opensearch.common.compress.CompressedXContent; import org.opensearch.common.io.PathUtils; import org.opensearch.common.logging.DeprecationLogger; +import org.opensearch.common.settings.ClusterSettings; import org.opensearch.common.settings.IndexScopedSettings; import org.opensearch.common.settings.Setting; import org.opensearch.common.settings.Settings; +import org.opensearch.common.unit.TimeValue; import org.opensearch.common.xcontent.XContentHelper; import org.opensearch.core.action.ActionListener; import org.opensearch.core.common.Strings; @@ -572,7 +574,8 @@ private ClusterState applyCreateIndexRequestWithV1Templates( settings, indexScopedSettings, shardLimitValidator, - indexSettingProviders + indexSettingProviders, + clusterService.getClusterSettings() ); int routingNumShards = getIndexNumberOfRoutingShards(aggregatedIndexSettings, null); IndexMetadata tmpImd = buildAndValidateTemporaryIndexMetadata(currentState, aggregatedIndexSettings, request, routingNumShards); @@ -636,7 +639,8 @@ private ClusterState applyCreateIndexRequestWithV2Template( settings, indexScopedSettings, shardLimitValidator, - indexSettingProviders + indexSettingProviders, + clusterService.getClusterSettings() ); int routingNumShards = getIndexNumberOfRoutingShards(aggregatedIndexSettings, null); IndexMetadata tmpImd = buildAndValidateTemporaryIndexMetadata(currentState, aggregatedIndexSettings, request, routingNumShards); @@ -716,7 +720,8 @@ private ClusterState applyCreateIndexRequestWithExistingMetadata( settings, indexScopedSettings, shardLimitValidator, - indexSettingProviders + indexSettingProviders, + clusterService.getClusterSettings() ); final int routingNumShards = getIndexNumberOfRoutingShards(aggregatedIndexSettings, sourceMetadata); IndexMetadata tmpImd = buildAndValidateTemporaryIndexMetadata(currentState, aggregatedIndexSettings, request, routingNumShards); @@ -799,7 +804,8 @@ static Settings aggregateIndexSettings( Settings settings, IndexScopedSettings indexScopedSettings, ShardLimitValidator shardLimitValidator, - Set indexSettingProviders + Set indexSettingProviders, + ClusterSettings clusterSettings ) { // Create builders for the template and request settings. We transform these into builders // because we may want settings to be "removed" from these prior to being set on the new @@ -914,6 +920,7 @@ static Settings aggregateIndexSettings( } validateTranslogRetentionSettings(indexSettings); validateStoreTypeSettings(indexSettings); + validateRefreshIntervalSettings(request.settings(), clusterSettings); return indexSettings; } @@ -1468,4 +1475,27 @@ public static void validateTranslogRetentionSettings(Settings indexSettings) { } } } + + /** + * Validates {@code index.refresh_interval} is equal or below the {@code cluster.minimum.index.refresh_interval}. + * + * @param requestSettings settings passed in during index create request + * @param clusterSettings cluster setting + */ + static void validateRefreshIntervalSettings(Settings requestSettings, ClusterSettings clusterSettings) { + if (IndexSettings.INDEX_REFRESH_INTERVAL_SETTING.exists(requestSettings) == false) { + return; + } + TimeValue requestRefreshInterval = IndexSettings.INDEX_REFRESH_INTERVAL_SETTING.get(requestSettings); + TimeValue clusterMinimumRefreshInterval = clusterSettings.get(IndicesService.CLUSTER_MINIMUM_INDEX_REFRESH_INTERVAL_SETTING); + if (requestRefreshInterval.millis() < clusterMinimumRefreshInterval.millis()) { + throw new IllegalArgumentException( + "invalid index.refresh_interval [" + + requestRefreshInterval + + "]: cannot be smaller than cluster.minimum.index.refresh_interval [" + + clusterMinimumRefreshInterval + + "]" + ); + } + } } diff --git a/server/src/main/java/org/opensearch/cluster/metadata/MetadataUpdateSettingsService.java b/server/src/main/java/org/opensearch/cluster/metadata/MetadataUpdateSettingsService.java index 0221e8ec6636d..d1e9642596cea 100644 --- a/server/src/main/java/org/opensearch/cluster/metadata/MetadataUpdateSettingsService.java +++ b/server/src/main/java/org/opensearch/cluster/metadata/MetadataUpdateSettingsService.java @@ -125,6 +125,9 @@ public void updateSettings( .put(request.settings()) .normalizePrefix(IndexMetadata.INDEX_SETTING_PREFIX) .build(); + + MetadataCreateIndexService.validateRefreshIntervalSettings(normalizedSettings, clusterService.getClusterSettings()); + Settings.Builder settingsForClosedIndices = Settings.builder(); Settings.Builder settingsForOpenIndices = Settings.builder(); final Set skippedSettings = new HashSet<>(); diff --git a/server/src/main/java/org/opensearch/common/settings/ClusterSettings.java b/server/src/main/java/org/opensearch/common/settings/ClusterSettings.java index e5db580a4a354..32d14a3519659 100644 --- a/server/src/main/java/org/opensearch/common/settings/ClusterSettings.java +++ b/server/src/main/java/org/opensearch/common/settings/ClusterSettings.java @@ -260,6 +260,8 @@ public void apply(Settings value, Settings current, Settings previous) { IndicesQueryCache.INDICES_CACHE_QUERY_SIZE_SETTING, IndicesQueryCache.INDICES_CACHE_QUERY_COUNT_SETTING, IndicesQueryCache.INDICES_QUERIES_CACHE_ALL_SEGMENTS_SETTING, + IndicesService.CLUSTER_DEFAULT_INDEX_REFRESH_INTERVAL_SETTING, + IndicesService.CLUSTER_MINIMUM_INDEX_REFRESH_INTERVAL_SETTING, IndicesService.INDICES_ID_FIELD_DATA_ENABLED_SETTING, IndicesService.WRITE_DANGLING_INDICES_INFO_SETTING, IndicesService.CLUSTER_REPLICATION_TYPE_SETTING, diff --git a/server/src/main/java/org/opensearch/index/IndexModule.java b/server/src/main/java/org/opensearch/index/IndexModule.java index de4b36ddbe39b..b4f0e474430f2 100644 --- a/server/src/main/java/org/opensearch/index/IndexModule.java +++ b/server/src/main/java/org/opensearch/index/IndexModule.java @@ -52,6 +52,7 @@ import org.opensearch.common.settings.Setting; import org.opensearch.common.settings.Setting.Property; import org.opensearch.common.settings.Settings; +import org.opensearch.common.unit.TimeValue; import org.opensearch.common.util.BigArrays; import org.opensearch.common.util.io.IOUtils; import org.opensearch.core.common.io.stream.NamedWriteableRegistry; @@ -598,7 +599,8 @@ public IndexService newIndexService( BooleanSupplier idFieldDataEnabled, ValuesSourceRegistry valuesSourceRegistry, IndexStorePlugin.DirectoryFactory remoteDirectoryFactory, - BiFunction translogFactorySupplier + BiFunction translogFactorySupplier, + Supplier clusterDefaultRefreshIntervalSupplier ) throws IOException { final IndexEventListener eventListener = freeze(); Function> readerWrapperFactory = indexReaderWrapper @@ -654,7 +656,8 @@ public IndexService newIndexService( expressionResolver, valuesSourceRegistry, recoveryStateFactory, - translogFactorySupplier + translogFactorySupplier, + clusterDefaultRefreshIntervalSupplier ); success = true; return indexService; diff --git a/server/src/main/java/org/opensearch/index/IndexService.java b/server/src/main/java/org/opensearch/index/IndexService.java index 4d2ee3ca37487..811768fc1540e 100644 --- a/server/src/main/java/org/opensearch/index/IndexService.java +++ b/server/src/main/java/org/opensearch/index/IndexService.java @@ -176,6 +176,7 @@ public class IndexService extends AbstractIndexComponent implements IndicesClust private final Supplier indexSortSupplier; private final ValuesSourceRegistry valuesSourceRegistry; private final BiFunction translogFactorySupplier; + private final Supplier clusterDefaultRefreshIntervalSupplier; public IndexService( IndexSettings indexSettings, @@ -208,7 +209,8 @@ public IndexService( IndexNameExpressionResolver expressionResolver, ValuesSourceRegistry valuesSourceRegistry, IndexStorePlugin.RecoveryStateFactory recoveryStateFactory, - BiFunction translogFactorySupplier + BiFunction translogFactorySupplier, + Supplier clusterDefaultRefreshIntervalSupplier ) { super(indexSettings); this.allowExpensiveQueries = allowExpensiveQueries; @@ -275,6 +277,7 @@ public IndexService( this.readerWrapper = wrapperFactory.apply(this); this.searchOperationListeners = Collections.unmodifiableList(searchOperationListeners); this.indexingOperationListeners = Collections.unmodifiableList(indexingOperationListeners); + this.clusterDefaultRefreshIntervalSupplier = clusterDefaultRefreshIntervalSupplier; // kick off async ops for the first shard in this index this.refreshTask = new AsyncRefreshTask(this); this.trimTranslogTask = new AsyncTrimTranslogTask(this); @@ -895,36 +898,47 @@ public synchronized void updateMetadata(final IndexMetadata currentIndexMetadata ); } } - if (refreshTask.getInterval().equals(indexSettings.getRefreshInterval()) == false) { - // once we change the refresh interval we schedule yet another refresh - // to ensure we are in a clean and predictable state. - // it doesn't matter if we move from or to -1 in both cases we want - // docs to become visible immediately. This also flushes all pending indexing / search requests - // that are waiting for a refresh. - threadPool.executor(ThreadPool.Names.REFRESH).execute(new AbstractRunnable() { - @Override - public void onFailure(Exception e) { - logger.warn("forced refresh failed after interval change", e); - } - - @Override - protected void doRun() throws Exception { - maybeRefreshEngine(true); - } - - @Override - public boolean isForceExecution() { - return true; - } - }); - rescheduleRefreshTasks(); - } + onRefreshIntervalChange(); updateFsyncTaskIfNecessary(); } metadataListeners.forEach(c -> c.accept(newIndexMetadata)); } + /** + * Called whenever the refresh interval changes. This can happen in 2 cases - + * 1. {@code cluster.default.index.refresh_interval} cluster setting changes. The change would only happen for + * indexes relying on cluster default. + * 2. {@code index.refresh_interval} index setting changes. + */ + public void onRefreshIntervalChange() { + if (refreshTask.getInterval().equals(getRefreshInterval())) { + return; + } + // once we change the refresh interval we schedule yet another refresh + // to ensure we are in a clean and predictable state. + // it doesn't matter if we move from or to -1 in both cases we want + // docs to become visible immediately. This also flushes all pending indexing / search requests + // that are waiting for a refresh. + threadPool.executor(ThreadPool.Names.REFRESH).execute(new AbstractRunnable() { + @Override + public void onFailure(Exception e) { + logger.warn("forced refresh failed after interval change", e); + } + + @Override + protected void doRun() throws Exception { + maybeRefreshEngine(true); + } + + @Override + public boolean isForceExecution() { + return true; + } + }); + rescheduleRefreshTasks(); + } + private void updateFsyncTaskIfNecessary() { if (indexSettings.getTranslogDurability() == Translog.Durability.REQUEST) { try { @@ -989,7 +1003,7 @@ private void maybeFSyncTranslogs() { } private void maybeRefreshEngine(boolean force) { - if (indexSettings.getRefreshInterval().millis() > 0 || force) { + if (getRefreshInterval().millis() > 0 || force) { for (IndexShard shard : this.shards.values()) { try { shard.scheduledRefresh(); @@ -1060,6 +1074,17 @@ private void sync(final Consumer sync, final String source) { } } + /** + * Gets the refresh interval seen by the index service. Index setting overrides takes the highest precedence. + * @return the refresh interval. + */ + private TimeValue getRefreshInterval() { + if (getIndexSettings().isExplicitRefresh()) { + return getIndexSettings().getRefreshInterval(); + } + return clusterDefaultRefreshIntervalSupplier.get(); + } + /** * Base asynchronous task * @@ -1120,7 +1145,7 @@ public String toString() { final class AsyncRefreshTask extends BaseAsyncTask { AsyncRefreshTask(IndexService indexService) { - super(indexService, indexService.getIndexSettings().getRefreshInterval()); + super(indexService, indexService.getRefreshInterval()); } @Override @@ -1242,6 +1267,11 @@ AsyncRefreshTask getRefreshTask() { // for tests return refreshTask; } + // Visible for test + public TimeValue getRefreshTaskInterval() { + return refreshTask.getInterval(); + } + AsyncTranslogFSync getFsyncTask() { // for tests return fsyncTask; } diff --git a/server/src/main/java/org/opensearch/index/IndexSettings.java b/server/src/main/java/org/opensearch/index/IndexSettings.java index 72be28d1ed93a..9ceb03974166f 100644 --- a/server/src/main/java/org/opensearch/index/IndexSettings.java +++ b/server/src/main/java/org/opensearch/index/IndexSettings.java @@ -299,10 +299,11 @@ public final class IndexSettings { Property.Deprecated ); public static final TimeValue DEFAULT_REFRESH_INTERVAL = new TimeValue(1, TimeUnit.SECONDS); + public static final TimeValue MINIMUM_REFRESH_INTERVAL = new TimeValue(-1, TimeUnit.MILLISECONDS); public static final Setting INDEX_REFRESH_INTERVAL_SETTING = Setting.timeSetting( "index.refresh_interval", DEFAULT_REFRESH_INTERVAL, - new TimeValue(-1, TimeUnit.MILLISECONDS), + MINIMUM_REFRESH_INTERVAL, Property.Dynamic, Property.IndexScope ); diff --git a/server/src/main/java/org/opensearch/indices/IndicesService.java b/server/src/main/java/org/opensearch/indices/IndicesService.java index c2d6f43688eb1..297beff981722 100644 --- a/server/src/main/java/org/opensearch/indices/IndicesService.java +++ b/server/src/main/java/org/opensearch/indices/IndicesService.java @@ -272,6 +272,37 @@ public class IndicesService extends AbstractLifecycleComponent Property.Final ); + /** + * This setting is used to set the refresh interval when the {@code index.refresh_interval} index setting is not + * provided during index creation or when the existing {@code index.refresh_interval} index setting is set as null. + * This comes handy when the user wants to set a default refresh interval across all indexes created in a cluster + * which is different from 1s and also at the same time have searchIdle feature supported. The setting can only be + * as low as the {@code cluster.minimum.index.refresh_interval}. + */ + public static final Setting CLUSTER_DEFAULT_INDEX_REFRESH_INTERVAL_SETTING = Setting.timeSetting( + "cluster.default.index.refresh_interval", + IndexSettings.DEFAULT_REFRESH_INTERVAL, + IndexSettings.MINIMUM_REFRESH_INTERVAL, + new ClusterDefaultRefreshIntervalValidator(), + Property.NodeScope, + Property.Dynamic + ); + + /** + * This setting is used to set the minimum refresh interval applicable for all indexes in a cluster. The + * {@code cluster.default.index.refresh_interval} setting value needs to be higher than this setting's value. Index + * creation will fail if the index setting {@code index.refresh_interval} is supplied with a value lower than the + * cluster minimum refresh interval. + */ + public static final Setting CLUSTER_MINIMUM_INDEX_REFRESH_INTERVAL_SETTING = Setting.timeSetting( + "cluster.minimum.index.refresh_interval", + IndexSettings.MINIMUM_REFRESH_INTERVAL, + IndexSettings.MINIMUM_REFRESH_INTERVAL, + new ClusterMinimumRefreshIntervalValidator(), + Property.NodeScope, + Property.Dynamic + ); + /** * The node's settings. */ @@ -317,7 +348,7 @@ public class IndicesService extends AbstractLifecycleComponent private final ValuesSourceRegistry valuesSourceRegistry; private final IndexStorePlugin.DirectoryFactory remoteDirectoryFactory; private final BiFunction translogFactorySupplier; - + private volatile TimeValue clusterDefaultRefreshInterval; private final FileCacheCleaner fileCacheCleaner; @Override @@ -439,6 +470,25 @@ protected void closeInternal() { clusterService.getClusterSettings().addSettingsUpdateConsumer(ALLOW_EXPENSIVE_QUERIES, this::setAllowExpensiveQueries); this.remoteDirectoryFactory = remoteDirectoryFactory; this.translogFactorySupplier = getTranslogFactorySupplier(repositoriesServiceSupplier, threadPool); + this.clusterDefaultRefreshInterval = CLUSTER_DEFAULT_INDEX_REFRESH_INTERVAL_SETTING.get(clusterService.getSettings()); + clusterService.getClusterSettings() + .addSettingsUpdateConsumer(CLUSTER_DEFAULT_INDEX_REFRESH_INTERVAL_SETTING, this::onRefreshIntervalUpdate); + } + + /** + * The changes to dynamic cluster setting {@code cluster.default.index.refresh_interval} needs to be updated. This + * method gets called whenever the setting changes. We set the instance variable with the updated value as this is + * also a supplier to all IndexService that have been created on the node. We also notify the change to all + * IndexService instances that are created on this node. + * + * @param clusterDefaultRefreshInterval the updated cluster default refresh interval. + */ + private void onRefreshIntervalUpdate(TimeValue clusterDefaultRefreshInterval) { + this.clusterDefaultRefreshInterval = clusterDefaultRefreshInterval; + for (Map.Entry entry : indices.entrySet()) { + IndexService indexService = entry.getValue(); + indexService.onRefreshIntervalChange(); + } } private static BiFunction getTranslogFactorySupplier( @@ -816,7 +866,8 @@ private synchronized IndexService createIndexService( this::isIdFieldDataEnabled, valuesSourceRegistry, remoteDirectoryFactory, - translogFactorySupplier + translogFactorySupplier, + this::getClusterDefaultRefreshInterval ); } @@ -1858,4 +1909,76 @@ public boolean allPendingDanglingIndicesWritten() { return nodeWriteDanglingIndicesInfo == false || (danglingIndicesToWrite.isEmpty() && danglingIndicesThreadPoolExecutor.getActiveCount() == 0); } + + /** + * Validates the cluster default index refresh interval. + * + * @opensearch.internal + */ + private static final class ClusterDefaultRefreshIntervalValidator implements Setting.Validator { + + @Override + public void validate(TimeValue value) { + + } + + @Override + public void validate(final TimeValue defaultRefreshInterval, final Map, Object> settings) { + final TimeValue minimumRefreshInterval = (TimeValue) settings.get(CLUSTER_MINIMUM_INDEX_REFRESH_INTERVAL_SETTING); + validateRefreshIntervalSettings(minimumRefreshInterval, defaultRefreshInterval); + } + + @Override + public Iterator> settings() { + final List> settings = List.of(CLUSTER_MINIMUM_INDEX_REFRESH_INTERVAL_SETTING); + return settings.iterator(); + } + } + + /** + * Validates the cluster minimum index refresh interval. + * + * @opensearch.internal + */ + private static final class ClusterMinimumRefreshIntervalValidator implements Setting.Validator { + + @Override + public void validate(TimeValue value) { + + } + + @Override + public void validate(final TimeValue minimumRefreshInterval, final Map, Object> settings) { + final TimeValue defaultRefreshInterval = (TimeValue) settings.get(CLUSTER_DEFAULT_INDEX_REFRESH_INTERVAL_SETTING); + validateRefreshIntervalSettings(minimumRefreshInterval, defaultRefreshInterval); + } + + @Override + public Iterator> settings() { + final List> settings = List.of(CLUSTER_DEFAULT_INDEX_REFRESH_INTERVAL_SETTING); + return settings.iterator(); + } + } + + /** + * Validates that the cluster minimum refresh interval is not more than the cluster default refresh interval. + * + * @param minimumRefreshInterval value of cluster minimum index refresh interval setting + * @param defaultRefreshInterval value of cluster default index refresh interval setting + */ + private static void validateRefreshIntervalSettings(TimeValue minimumRefreshInterval, TimeValue defaultRefreshInterval) { + if (minimumRefreshInterval.compareTo(defaultRefreshInterval) > 0) { + throw new IllegalArgumentException( + "cluster minimum index refresh interval [" + + minimumRefreshInterval + + "] more than cluster default index refresh interval [" + + defaultRefreshInterval + + "]" + ); + } + } + + private TimeValue getClusterDefaultRefreshInterval() { + return this.clusterDefaultRefreshInterval; + } } diff --git a/server/src/test/java/org/opensearch/cluster/metadata/MetadataCreateIndexServiceTests.java b/server/src/test/java/org/opensearch/cluster/metadata/MetadataCreateIndexServiceTests.java index 8e0a29dae688e..a9f3e97e64e39 100644 --- a/server/src/test/java/org/opensearch/cluster/metadata/MetadataCreateIndexServiceTests.java +++ b/server/src/test/java/org/opensearch/cluster/metadata/MetadataCreateIndexServiceTests.java @@ -86,6 +86,7 @@ import org.opensearch.threadpool.TestThreadPool; import org.opensearch.threadpool.ThreadPool; import org.hamcrest.Matchers; +import org.junit.After; import org.junit.Before; import java.io.IOException; @@ -130,8 +131,11 @@ import static org.opensearch.cluster.metadata.MetadataCreateIndexService.getIndexNumberOfRoutingShards; import static org.opensearch.cluster.metadata.MetadataCreateIndexService.parseV1Mappings; import static org.opensearch.cluster.metadata.MetadataCreateIndexService.resolveAndValidateAliases; +import static org.opensearch.index.IndexSettings.INDEX_REFRESH_INTERVAL_SETTING; import static org.opensearch.index.IndexSettings.INDEX_REMOTE_TRANSLOG_BUFFER_INTERVAL_SETTING; import static org.opensearch.index.IndexSettings.INDEX_SOFT_DELETES_SETTING; +import static org.opensearch.indices.IndicesService.CLUSTER_DEFAULT_INDEX_REFRESH_INTERVAL_SETTING; +import static org.opensearch.indices.IndicesService.CLUSTER_MINIMUM_INDEX_REFRESH_INTERVAL_SETTING; import static org.opensearch.indices.IndicesService.CLUSTER_REMOTE_SEGMENT_STORE_REPOSITORY_SETTING; import static org.opensearch.indices.IndicesService.CLUSTER_REMOTE_STORE_ENABLED_SETTING; import static org.opensearch.indices.IndicesService.CLUSTER_REMOTE_TRANSLOG_REPOSITORY_SETTING; @@ -153,6 +157,13 @@ public class MetadataCreateIndexServiceTests extends OpenSearchTestCase { private AliasValidator aliasValidator; private CreateIndexClusterStateUpdateRequest request; private QueryShardContext queryShardContext; + private ClusterSettings clusterSettings; + + @Before + public void setup() throws Exception { + super.setUp(); + clusterSettings = new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS); + } @Before public void setupCreateIndexRequestAndAliasValidator() { @@ -818,7 +829,8 @@ public void testAggregateSettingsAppliesSettingsFromTemplatesAndRequest() { Settings.EMPTY, IndexScopedSettings.DEFAULT_SCOPED_SETTINGS, randomShardLimitService(), - Collections.emptySet() + Collections.emptySet(), + clusterSettings ); assertThat(aggregatedIndexSettings.get("template_setting"), equalTo("value1")); @@ -880,7 +892,8 @@ public void testRequestDataHavePriorityOverTemplateData() throws Exception { Settings.EMPTY, IndexScopedSettings.DEFAULT_SCOPED_SETTINGS, randomShardLimitService(), - Collections.emptySet() + Collections.emptySet(), + clusterSettings ); assertThat(resolvedAliases.get(0).getSearchRouting(), equalTo("fromRequest")); @@ -902,7 +915,8 @@ public void testDefaultSettings() { Settings.EMPTY, IndexScopedSettings.DEFAULT_SCOPED_SETTINGS, randomShardLimitService(), - Collections.emptySet() + Collections.emptySet(), + clusterSettings ); assertThat(aggregatedIndexSettings.get(SETTING_NUMBER_OF_SHARDS), equalTo("1")); @@ -917,7 +931,8 @@ public void testSettingsFromClusterState() { Settings.builder().put(SETTING_NUMBER_OF_SHARDS, 15).build(), IndexScopedSettings.DEFAULT_SCOPED_SETTINGS, randomShardLimitService(), - Collections.emptySet() + Collections.emptySet(), + clusterSettings ); assertThat(aggregatedIndexSettings.get(SETTING_NUMBER_OF_SHARDS), equalTo("15")); @@ -954,7 +969,8 @@ public void testTemplateOrder() throws Exception { Settings.EMPTY, IndexScopedSettings.DEFAULT_SCOPED_SETTINGS, randomShardLimitService(), - Collections.emptySet() + Collections.emptySet(), + clusterSettings ); List resolvedAliases = resolveAndValidateAliases( request.index(), @@ -993,7 +1009,8 @@ public void testAggregateIndexSettingsIgnoresTemplatesOnCreateFromSourceIndex() Settings.EMPTY, IndexScopedSettings.DEFAULT_SCOPED_SETTINGS, randomShardLimitService(), - Collections.emptySet() + Collections.emptySet(), + clusterSettings ); assertThat(aggregatedIndexSettings.get("templateSetting"), is(nullValue())); @@ -1215,7 +1232,8 @@ public void testRemoteStoreNoUserOverrideExceptReplicationTypeSegmentIndexSettin settings, IndexScopedSettings.DEFAULT_SCOPED_SETTINGS, randomShardLimitService(), - Collections.emptySet() + Collections.emptySet(), + clusterSettings ); verifyRemoteStoreIndexSettings( indexSettings, @@ -1245,7 +1263,8 @@ public void testRemoteStoreNoUserOverrideIndexSettings() { settings, IndexScopedSettings.DEFAULT_SCOPED_SETTINGS, randomShardLimitService(), - Collections.emptySet() + Collections.emptySet(), + clusterSettings ); verifyRemoteStoreIndexSettings( indexSettings, @@ -1426,7 +1445,8 @@ public void testSoftDeletesDisabledIsRejected() { Settings.EMPTY, IndexScopedSettings.DEFAULT_SCOPED_SETTINGS, randomShardLimitService(), - Collections.emptySet() + Collections.emptySet(), + clusterSettings ); }); assertThat( @@ -1455,7 +1475,8 @@ public void testValidateTranslogRetentionSettings() { Settings.EMPTY, IndexScopedSettings.DEFAULT_SCOPED_SETTINGS, randomShardLimitService(), - Collections.emptySet() + Collections.emptySet(), + clusterSettings ); assertWarnings( "Translog retention settings [index.translog.retention.age] " @@ -1502,7 +1523,8 @@ public void testDeprecatedSimpleFSStoreSettings() { Settings.EMPTY, IndexScopedSettings.DEFAULT_SCOPED_SETTINGS, randomShardLimitService(), - Collections.emptySet() + Collections.emptySet(), + clusterSettings ); assertWarnings( "[simplefs] is deprecated and will be removed in 2.0. Use [niofs], which offers equal " @@ -1521,7 +1543,8 @@ public void testClusterReplicationSetting() { settings, IndexScopedSettings.DEFAULT_SCOPED_SETTINGS, randomShardLimitService(), - Collections.emptySet() + Collections.emptySet(), + clusterSettings ); assertEquals(ReplicationType.SEGMENT.toString(), indexSettings.get(SETTING_REPLICATION_TYPE)); } @@ -1541,12 +1564,127 @@ public void testIndexSettingOverridesClusterReplicationSetting() { settings, IndexScopedSettings.DEFAULT_SCOPED_SETTINGS, randomShardLimitService(), - Collections.emptySet() + Collections.emptySet(), + clusterSettings ); // Verify if index setting overrides cluster replication setting assertEquals(ReplicationType.DOCUMENT.toString(), indexSettings.get(SETTING_REPLICATION_TYPE)); } + public void testRefreshIntervalValidationWithNoIndexSetting() { + // This checks that aggregateIndexSetting works for the case where there are no index setting + // `index.refresh_interval` in the cluster state update request. + request = new CreateIndexClusterStateUpdateRequest("create index", "test", "test"); + aggregateIndexSettings( + ClusterState.EMPTY_STATE, + request, + Settings.EMPTY, + null, + Settings.EMPTY, + IndexScopedSettings.DEFAULT_SCOPED_SETTINGS, + randomShardLimitService(), + Collections.emptySet(), + clusterSettings + ); + } + + public void testRefreshIntervalValidationSuccessWithIndexSettingEqualToClusterMinimum() { + // This checks that aggregateIndexSettings works for the case when the index setting `index.refresh_interval` + // is set to a value that is equal to the `cluster.default.index.refresh_interval` value. + TimeValue refreshInterval = TimeValue.timeValueSeconds(10); + Settings settings = Settings.builder() + .put(CLUSTER_MINIMUM_INDEX_REFRESH_INTERVAL_SETTING.getKey(), refreshInterval) + .put(CLUSTER_DEFAULT_INDEX_REFRESH_INTERVAL_SETTING.getKey(), refreshInterval) + .build(); + request = new CreateIndexClusterStateUpdateRequest("create index", "test", "test"); + final Settings.Builder requestSettings = Settings.builder(); + // Set index setting refresh interval the same value as the cluster minimum refresh interval + requestSettings.put(INDEX_REFRESH_INTERVAL_SETTING.getKey(), refreshInterval); + request.settings(requestSettings.build()); + Settings indexSettings = aggregateIndexSettings( + ClusterState.EMPTY_STATE, + request, + Settings.EMPTY, + null, + settings, + IndexScopedSettings.DEFAULT_SCOPED_SETTINGS, + randomShardLimitService(), + Collections.emptySet(), + clusterSettings + ); + // Verify that the value is the same as set as earlier and the validation was successful + assertEquals(refreshInterval, INDEX_REFRESH_INTERVAL_SETTING.get(indexSettings)); + } + + public void testRefreshIntervalValidationSuccessWithIndexSettingGreaterThanClusterMinimum() { + // This checks that aggregateIndexSettings works for the case when the index setting `index.refresh_interval` + // is set to a value that is greater than the `cluster.default.index.refresh_interval` value. + int clusterMinRefreshTimeMs = 10 * 1000; + TimeValue clusterMinRefreshTime = TimeValue.timeValueSeconds(clusterMinRefreshTimeMs); + Settings settings = Settings.builder() + .put(CLUSTER_MINIMUM_INDEX_REFRESH_INTERVAL_SETTING.getKey(), clusterMinRefreshTime) + .put(CLUSTER_DEFAULT_INDEX_REFRESH_INTERVAL_SETTING.getKey(), clusterMinRefreshTime) + .build(); + request = new CreateIndexClusterStateUpdateRequest("create index", "test", "test"); + final Settings.Builder requestSettings = Settings.builder(); + // Set index setting refresh interval the same value as the cluster minimum refresh interval + TimeValue indexRefreshTime = TimeValue.timeValueMillis(clusterMinRefreshTimeMs + randomNonNegativeLong()); + requestSettings.put(INDEX_REFRESH_INTERVAL_SETTING.getKey(), indexRefreshTime); + request.settings(requestSettings.build()); + Settings indexSettings = aggregateIndexSettings( + ClusterState.EMPTY_STATE, + request, + Settings.EMPTY, + null, + settings, + IndexScopedSettings.DEFAULT_SCOPED_SETTINGS, + randomShardLimitService(), + Collections.emptySet(), + clusterSettings + ); + // Verify that the value is the same as set as earlier and the validation was successful + assertEquals(indexRefreshTime, INDEX_REFRESH_INTERVAL_SETTING.get(indexSettings)); + } + + public void testRefreshIntervalValidationFailureWithIndexSetting() { + // This checks that aggregateIndexSettings works for the case when the index setting `index.refresh_interval` + // is set to a value that is below the `cluster.default.index.refresh_interval` value. + int clusterMinRefreshTimeMs = 10 * 1000; + TimeValue clusterMinRefreshTime = TimeValue.timeValueMillis(clusterMinRefreshTimeMs); + Settings settings = Settings.builder() + .put(CLUSTER_MINIMUM_INDEX_REFRESH_INTERVAL_SETTING.getKey(), clusterMinRefreshTime) + .put(CLUSTER_DEFAULT_INDEX_REFRESH_INTERVAL_SETTING.getKey(), clusterMinRefreshTime) + .build(); + clusterSettings = new ClusterSettings(settings, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS); + request = new CreateIndexClusterStateUpdateRequest("create index", "test", "test"); + final Settings.Builder requestSettings = Settings.builder(); + // Set index setting refresh interval the same value as the cluster minimum refresh interval + TimeValue indexRefreshTime = TimeValue.timeValueMillis(clusterMinRefreshTimeMs - randomIntBetween(1, clusterMinRefreshTimeMs - 1)); + requestSettings.put(INDEX_REFRESH_INTERVAL_SETTING.getKey(), indexRefreshTime); + request.settings(requestSettings.build()); + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, + () -> aggregateIndexSettings( + ClusterState.EMPTY_STATE, + request, + Settings.EMPTY, + null, + Settings.EMPTY, + IndexScopedSettings.DEFAULT_SCOPED_SETTINGS, + randomShardLimitService(), + Collections.emptySet(), + clusterSettings + ) + ); + // verify that the message is as expected + assertEquals( + "invalid index.refresh_interval [" + + indexRefreshTime + + "]: cannot be smaller than cluster.minimum.index.refresh_interval [10s]", + exception.getMessage() + ); + } + private IndexTemplateMetadata addMatchingTemplate(Consumer configurator) { IndexTemplateMetadata.Builder builder = templateMetadataBuilder("template1", "te*"); configurator.accept(builder); @@ -1606,4 +1744,9 @@ private void verifyRemoteStoreIndexSettings( assertEquals(translogBufferInterval, INDEX_REMOTE_TRANSLOG_BUFFER_INTERVAL_SETTING.get(indexSettings)); } + @After + public void shutdown() throws Exception { + clusterSettings = null; + } + } diff --git a/server/src/test/java/org/opensearch/index/IndexModuleTests.java b/server/src/test/java/org/opensearch/index/IndexModuleTests.java index e9716ff8f1837..3b43ede3c0a6d 100644 --- a/server/src/test/java/org/opensearch/index/IndexModuleTests.java +++ b/server/src/test/java/org/opensearch/index/IndexModuleTests.java @@ -254,7 +254,8 @@ private IndexService newIndexService(IndexModule module) throws IOException { () -> false, null, new RemoteSegmentStoreDirectoryFactory(() -> repositoriesService, threadPool), - translogFactorySupplier + translogFactorySupplier, + () -> IndexSettings.DEFAULT_REFRESH_INTERVAL ); }