Skip to content

Commit a541306

Browse files
committed
Added support for feature flags in opensearch.yml (opensearch-project#4959)
This change introduces a static store "settings" in FeatureFlags.java file to enable isEnabled method to fetch flag settings defined in opensearch.yml. Signed-off-by: Nagaraj Tantri <nagarajtantri@gmail.com> (cherry picked from commit 241bd42)
1 parent 785e4a6 commit a541306

9 files changed

Lines changed: 600 additions & 30 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
1717
- Support for labels on version bump PRs, skip label support for changelog verifier ([#4391](https://github.com/opensearch-project/OpenSearch/pull/4391))
1818
- Add a new node role 'search' which is dedicated to provide search capability ([#4689](https://github.com/opensearch-project/OpenSearch/pull/4689))
1919
- Introduce experimental searchable snapshot API ([#4680](https://github.com/opensearch-project/OpenSearch/pull/4680))
20+
- Added support for feature flags in opensearch.yml ([#4959](https://github.com/opensearch-project/OpenSearch/pull/4959))
21+
2022
### Dependencies
2123
- Bumps `com.diffplug.spotless` from 6.9.1 to 6.10.0
2224
- Bumps `xmlbeans` from 5.1.0 to 5.1.1

distribution/src/config/opensearch.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,26 @@ ${path.logs}
8686
# Require explicit names when deleting indices:
8787
#
8888
#action.destructive_requires_name: true
89+
#
90+
# ---------------------------------- Experimental Features -----------------------------------
91+
#
92+
# Gates the visibility of the index setting that allows changing of replication type.
93+
# Once the feature is ready for production release, this feature flag can be removed.
94+
#
95+
#opensearch.experimental.feature.replication_type.enabled: false
96+
#
97+
#
98+
# Gates the visibility of the index setting that allows persisting data to remote store along with local disk.
99+
# Once the feature is ready for production release, this feature flag can be removed.
100+
#
101+
#opensearch.experimental.feature.remote_store.enabled: false
102+
#
103+
#
104+
# Gates the functionality of a new parameter to the snapshot restore API
105+
# that allows for creation of a new index type that searches a snapshot
106+
# directly in a remote repository without restoring all index data to disk
107+
# ahead of time.
108+
#
109+
#opensearch.experimental.feature.searchable_snapshot.enabled: false
110+
#
111+
#

server/src/internalClusterTest/java/org/opensearch/indices/replication/SegmentReplicationIT.java

Lines changed: 194 additions & 29 deletions
Large diffs are not rendered by default.
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
package org.opensearch.snapshots;
10+
11+
import com.carrotsearch.randomizedtesting.RandomizedTest;
12+
import org.opensearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse;
13+
import org.opensearch.action.admin.cluster.snapshots.restore.RestoreSnapshotRequestBuilder;
14+
import org.opensearch.action.admin.cluster.snapshots.restore.RestoreSnapshotResponse;
15+
import org.opensearch.action.admin.indices.delete.DeleteIndexRequest;
16+
import org.opensearch.action.admin.indices.settings.get.GetSettingsRequest;
17+
import org.opensearch.action.admin.indices.settings.get.GetSettingsResponse;
18+
import org.opensearch.action.search.SearchResponse;
19+
import org.opensearch.cluster.metadata.IndexMetadata;
20+
import org.opensearch.common.settings.Settings;
21+
import org.opensearch.common.util.FeatureFlags;
22+
import org.opensearch.index.query.QueryBuilders;
23+
import org.opensearch.indices.replication.common.ReplicationType;
24+
import org.opensearch.rest.RestStatus;
25+
import org.opensearch.test.BackgroundIndexer;
26+
import org.opensearch.test.InternalTestCluster;
27+
import org.opensearch.test.OpenSearchIntegTestCase;
28+
29+
import java.nio.file.Path;
30+
import java.util.ArrayList;
31+
import java.util.List;
32+
33+
import static org.hamcrest.Matchers.equalTo;
34+
import static org.opensearch.test.hamcrest.OpenSearchAssertions.assertAcked;
35+
import static org.opensearch.test.hamcrest.OpenSearchAssertions.assertHitCount;
36+
37+
@OpenSearchIntegTestCase.ClusterScope(scope = OpenSearchIntegTestCase.Scope.TEST, numDataNodes = 0)
38+
public class SegmentReplicationSnapshotIT extends AbstractSnapshotIntegTestCase {
39+
private static final String INDEX_NAME = "test-segrep-idx";
40+
private static final String RESTORED_INDEX_NAME = INDEX_NAME + "-restored";
41+
private static final int SHARD_COUNT = 1;
42+
private static final int REPLICA_COUNT = 1;
43+
private static final int DOC_COUNT = 1010;
44+
45+
private static final String REPOSITORY_NAME = "test-segrep-repo";
46+
private static final String SNAPSHOT_NAME = "test-segrep-snapshot";
47+
48+
@Override
49+
protected Settings featureFlagSettings() {
50+
return Settings.builder().put(super.featureFlagSettings()).put(FeatureFlags.REPLICATION_TYPE, "true").build();
51+
}
52+
53+
public Settings segRepEnableIndexSettings() {
54+
return getShardSettings().put(IndexMetadata.SETTING_REPLICATION_TYPE, ReplicationType.SEGMENT).build();
55+
}
56+
57+
public Settings docRepEnableIndexSettings() {
58+
return getShardSettings().put(IndexMetadata.SETTING_REPLICATION_TYPE, ReplicationType.DOCUMENT).build();
59+
}
60+
61+
public Settings.Builder getShardSettings() {
62+
return Settings.builder()
63+
.put(super.indexSettings())
64+
.put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, SHARD_COUNT)
65+
.put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, REPLICA_COUNT);
66+
}
67+
68+
public Settings restoreIndexSegRepSettings() {
69+
return Settings.builder().put(IndexMetadata.SETTING_REPLICATION_TYPE, ReplicationType.SEGMENT).build();
70+
}
71+
72+
public Settings restoreIndexDocRepSettings() {
73+
return Settings.builder().put(IndexMetadata.SETTING_REPLICATION_TYPE, ReplicationType.DOCUMENT).build();
74+
}
75+
76+
@Override
77+
protected boolean addMockInternalEngine() {
78+
return false;
79+
}
80+
81+
public void ingestData(int docCount, String indexName) throws Exception {
82+
try (
83+
BackgroundIndexer indexer = new BackgroundIndexer(
84+
indexName,
85+
"_doc",
86+
client(),
87+
-1,
88+
RandomizedTest.scaledRandomIntBetween(2, 5),
89+
false,
90+
random()
91+
)
92+
) {
93+
indexer.start(docCount);
94+
waitForDocs(docCount, indexer);
95+
refresh(indexName);
96+
}
97+
}
98+
99+
// Start cluster with provided settings and return the node names as list
100+
public List<String> startClusterWithSettings(Settings indexSettings, int replicaCount) throws Exception {
101+
// Start primary
102+
final String primaryNode = internalCluster().startNode(featureFlagSettings());
103+
List<String> nodeNames = new ArrayList<>();
104+
nodeNames.add(primaryNode);
105+
for (int i = 0; i < replicaCount; i++) {
106+
nodeNames.add(internalCluster().startNode(featureFlagSettings()));
107+
}
108+
createIndex(INDEX_NAME, indexSettings);
109+
ensureGreen(INDEX_NAME);
110+
// Ingest data
111+
ingestData(DOC_COUNT, INDEX_NAME);
112+
return nodeNames;
113+
}
114+
115+
public void createSnapshot() {
116+
// Snapshot declaration
117+
Path absolutePath = randomRepoPath().toAbsolutePath();
118+
// Create snapshot
119+
createRepository(REPOSITORY_NAME, "fs", absolutePath);
120+
CreateSnapshotResponse createSnapshotResponse = client().admin()
121+
.cluster()
122+
.prepareCreateSnapshot(REPOSITORY_NAME, SNAPSHOT_NAME)
123+
.setWaitForCompletion(true)
124+
.setIndices(INDEX_NAME)
125+
.get();
126+
assertThat(
127+
createSnapshotResponse.getSnapshotInfo().successfulShards(),
128+
equalTo(createSnapshotResponse.getSnapshotInfo().totalShards())
129+
);
130+
assertThat(createSnapshotResponse.getSnapshotInfo().state(), equalTo(SnapshotState.SUCCESS));
131+
}
132+
133+
public RestoreSnapshotResponse restoreSnapshotWithSettings(Settings indexSettings) {
134+
RestoreSnapshotRequestBuilder builder = client().admin()
135+
.cluster()
136+
.prepareRestoreSnapshot(REPOSITORY_NAME, SNAPSHOT_NAME)
137+
.setWaitForCompletion(false)
138+
.setRenamePattern(INDEX_NAME)
139+
.setRenameReplacement(RESTORED_INDEX_NAME);
140+
if (indexSettings != null) {
141+
builder.setIndexSettings(indexSettings);
142+
}
143+
return builder.get();
144+
}
145+
146+
public void testRestoreOnSegRep() throws Exception {
147+
// Start cluster with one primary and one replica node
148+
startClusterWithSettings(segRepEnableIndexSettings(), 1);
149+
createSnapshot();
150+
// Delete index
151+
assertAcked(client().admin().indices().delete(new DeleteIndexRequest(INDEX_NAME)).get());
152+
assertFalse("index [" + INDEX_NAME + "] should have been deleted", indexExists(INDEX_NAME));
153+
154+
RestoreSnapshotResponse restoreSnapshotResponse = restoreSnapshotWithSettings(null);
155+
156+
// Assertions
157+
assertThat(restoreSnapshotResponse.status(), equalTo(RestStatus.ACCEPTED));
158+
ensureGreen(RESTORED_INDEX_NAME);
159+
GetSettingsResponse settingsResponse = client().admin()
160+
.indices()
161+
.getSettings(new GetSettingsRequest().indices(RESTORED_INDEX_NAME))
162+
.get();
163+
assertEquals(settingsResponse.getSetting(RESTORED_INDEX_NAME, "index.replication.type"), "SEGMENT");
164+
SearchResponse resp = client().prepareSearch(RESTORED_INDEX_NAME).setQuery(QueryBuilders.matchAllQuery()).get();
165+
assertHitCount(resp, DOC_COUNT);
166+
}
167+
168+
public void testSnapshotOnSegRep_RestoreOnSegRepDuringIngestion() throws Exception {
169+
startClusterWithSettings(segRepEnableIndexSettings(), 1);
170+
createSnapshot();
171+
// Delete index
172+
assertAcked(client().admin().indices().delete(new DeleteIndexRequest(INDEX_NAME)).get());
173+
assertFalse("index [" + INDEX_NAME + "] should have been deleted", indexExists(INDEX_NAME));
174+
175+
RestoreSnapshotResponse restoreSnapshotResponse = restoreSnapshotWithSettings(null);
176+
177+
// Assertions
178+
assertThat(restoreSnapshotResponse.status(), equalTo(RestStatus.ACCEPTED));
179+
ingestData(5000, RESTORED_INDEX_NAME);
180+
ensureGreen(RESTORED_INDEX_NAME);
181+
GetSettingsResponse settingsResponse = client().admin()
182+
.indices()
183+
.getSettings(new GetSettingsRequest().indices(RESTORED_INDEX_NAME))
184+
.get();
185+
assertEquals(settingsResponse.getSetting(RESTORED_INDEX_NAME, "index.replication.type"), "SEGMENT");
186+
SearchResponse resp = client().prepareSearch(RESTORED_INDEX_NAME).setQuery(QueryBuilders.matchAllQuery()).get();
187+
assertHitCount(resp, DOC_COUNT + 5000);
188+
}
189+
190+
public void testSnapshotOnDocRep_RestoreOnSegRep() throws Exception {
191+
startClusterWithSettings(docRepEnableIndexSettings(), 1);
192+
createSnapshot();
193+
// Delete index
194+
assertAcked(client().admin().indices().delete(new DeleteIndexRequest(INDEX_NAME)).get());
195+
196+
RestoreSnapshotResponse restoreSnapshotResponse = restoreSnapshotWithSettings(restoreIndexSegRepSettings());
197+
198+
// Assertions
199+
assertThat(restoreSnapshotResponse.status(), equalTo(RestStatus.ACCEPTED));
200+
ensureGreen(RESTORED_INDEX_NAME);
201+
GetSettingsResponse settingsResponse = client().admin()
202+
.indices()
203+
.getSettings(new GetSettingsRequest().indices(RESTORED_INDEX_NAME))
204+
.get();
205+
assertEquals(settingsResponse.getSetting(RESTORED_INDEX_NAME, "index.replication.type"), "SEGMENT");
206+
207+
SearchResponse resp = client().prepareSearch(RESTORED_INDEX_NAME).setQuery(QueryBuilders.matchAllQuery()).get();
208+
assertHitCount(resp, DOC_COUNT);
209+
}
210+
211+
public void testSnapshotOnSegRep_RestoreOnDocRep() throws Exception {
212+
// Start a cluster with one primary and one replica
213+
startClusterWithSettings(segRepEnableIndexSettings(), 1);
214+
createSnapshot();
215+
// Delete index
216+
assertAcked(client().admin().indices().delete(new DeleteIndexRequest(INDEX_NAME)).get());
217+
218+
RestoreSnapshotResponse restoreSnapshotResponse = restoreSnapshotWithSettings(restoreIndexDocRepSettings());
219+
220+
// Assertions
221+
assertThat(restoreSnapshotResponse.status(), equalTo(RestStatus.ACCEPTED));
222+
ensureGreen(RESTORED_INDEX_NAME);
223+
GetSettingsResponse settingsResponse = client().admin()
224+
.indices()
225+
.getSettings(new GetSettingsRequest().indices(RESTORED_INDEX_NAME))
226+
.get();
227+
assertEquals(settingsResponse.getSetting(RESTORED_INDEX_NAME, "index.replication.type"), "DOCUMENT");
228+
SearchResponse resp = client().prepareSearch(RESTORED_INDEX_NAME).setQuery(QueryBuilders.matchAllQuery()).get();
229+
assertHitCount(resp, DOC_COUNT);
230+
}
231+
232+
public void testSnapshotOnDocRep_RestoreOnDocRep() throws Exception {
233+
startClusterWithSettings(docRepEnableIndexSettings(), 1);
234+
createSnapshot();
235+
// Delete index
236+
assertAcked(client().admin().indices().delete(new DeleteIndexRequest(INDEX_NAME)).get());
237+
238+
RestoreSnapshotResponse restoreSnapshotResponse = restoreSnapshotWithSettings(restoreIndexDocRepSettings());
239+
240+
// Assertions
241+
assertThat(restoreSnapshotResponse.status(), equalTo(RestStatus.ACCEPTED));
242+
ensureGreen(RESTORED_INDEX_NAME);
243+
GetSettingsResponse settingsResponse = client().admin()
244+
.indices()
245+
.getSettings(new GetSettingsRequest().indices(RESTORED_INDEX_NAME))
246+
.get();
247+
assertEquals(settingsResponse.getSetting(RESTORED_INDEX_NAME, "index.replication.type"), "DOCUMENT");
248+
249+
SearchResponse resp = client().prepareSearch(RESTORED_INDEX_NAME).setQuery(QueryBuilders.matchAllQuery()).get();
250+
assertHitCount(resp, DOC_COUNT);
251+
}
252+
253+
public void testRestoreOnReplicaNode() throws Exception {
254+
List<String> nodeNames = startClusterWithSettings(segRepEnableIndexSettings(), 1);
255+
final String primaryNode = nodeNames.get(0);
256+
createSnapshot();
257+
// Delete index
258+
assertAcked(client().admin().indices().delete(new DeleteIndexRequest(INDEX_NAME)).get());
259+
assertFalse("index [" + INDEX_NAME + "] should have been deleted", indexExists(INDEX_NAME));
260+
261+
// stop the primary node so that restoration happens on replica node
262+
internalCluster().stopRandomNode(InternalTestCluster.nameFilter(primaryNode));
263+
264+
RestoreSnapshotResponse restoreSnapshotResponse = restoreSnapshotWithSettings(null);
265+
266+
// Assertions
267+
assertThat(restoreSnapshotResponse.status(), equalTo(RestStatus.ACCEPTED));
268+
internalCluster().startNode(featureFlagSettings());
269+
ensureGreen(RESTORED_INDEX_NAME);
270+
GetSettingsResponse settingsResponse = client().admin()
271+
.indices()
272+
.getSettings(new GetSettingsRequest().indices(RESTORED_INDEX_NAME))
273+
.get();
274+
assertEquals(settingsResponse.getSetting(RESTORED_INDEX_NAME, "index.replication.type"), "SEGMENT");
275+
SearchResponse resp = client().prepareSearch(RESTORED_INDEX_NAME).setQuery(QueryBuilders.matchAllQuery()).get();
276+
assertHitCount(resp, DOC_COUNT);
277+
}
278+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
package org.opensearch.common.settings;
10+
11+
import java.util.Arrays;
12+
import java.util.Collections;
13+
import java.util.HashSet;
14+
import java.util.Set;
15+
import org.opensearch.common.settings.Setting.Property;
16+
import org.opensearch.common.util.FeatureFlags;
17+
18+
/**
19+
* Encapsulates all valid feature flag level settings.
20+
*
21+
* @opensearch.internal
22+
*/
23+
public class FeatureFlagSettings extends AbstractScopedSettings {
24+
25+
protected FeatureFlagSettings(
26+
Settings settings,
27+
Set<Setting<?>> settingsSet,
28+
Set<SettingUpgrader<?>> settingUpgraders,
29+
Property scope
30+
) {
31+
super(settings, settingsSet, settingUpgraders, scope);
32+
}
33+
34+
public static final Set<Setting<?>> BUILT_IN_FEATURE_FLAGS = Collections.unmodifiableSet(
35+
new HashSet<>(
36+
Arrays.asList(
37+
FeatureFlags.REPLICATION_TYPE_SETTING,
38+
FeatureFlags.REMOTE_STORE_SETTING,
39+
FeatureFlags.SEARCHABLE_SNAPSHOT_SETTING,
40+
FeatureFlags.EXTENSIONS_SETTING
41+
)
42+
)
43+
);
44+
}

server/src/main/java/org/opensearch/common/settings/SettingsModule.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ public SettingsModule(
8787
for (Setting<?> setting : IndexScopedSettings.BUILT_IN_INDEX_SETTINGS) {
8888
registerSetting(setting);
8989
}
90+
for (Setting<?> setting : FeatureFlagSettings.BUILT_IN_FEATURE_FLAGS) {
91+
registerSetting(setting);
92+
}
9093

9194
for (Map.Entry<String, List<Setting>> featureFlaggedSetting : IndexScopedSettings.FEATURE_FLAGGED_INDEX_SETTINGS.entrySet()) {
9295
if (FeatureFlags.isEnabled(featureFlaggedSetting.getKey())) {

0 commit comments

Comments
 (0)