Skip to content

Commit f8e39d2

Browse files
authored
New setting to prevent automatically importing dangling indices (#49174)
Introduce a new static setting, `gateway.auto_import_dangling_indices`, which prevents dangling indices from being automatically imported. Part of #48366.
1 parent 770a794 commit f8e39d2

File tree

6 files changed

+188
-7
lines changed

6 files changed

+188
-7
lines changed

server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java

+2
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
import org.elasticsearch.discovery.SettingsBasedSeedHostsProvider;
7171
import org.elasticsearch.env.Environment;
7272
import org.elasticsearch.env.NodeEnvironment;
73+
import org.elasticsearch.gateway.DanglingIndicesState;
7374
import org.elasticsearch.gateway.GatewayService;
7475
import org.elasticsearch.gateway.IncrementalClusterStateWriter;
7576
import org.elasticsearch.http.HttpTransportSettings;
@@ -186,6 +187,7 @@ public void apply(Settings value, Settings current, Settings previous) {
186187
BalancedShardsAllocator.THRESHOLD_SETTING,
187188
ClusterRebalanceAllocationDecider.CLUSTER_ROUTING_ALLOCATION_ALLOW_REBALANCE_SETTING,
188189
ConcurrentRebalanceAllocationDecider.CLUSTER_ROUTING_ALLOCATION_CLUSTER_CONCURRENT_REBALANCE_SETTING,
190+
DanglingIndicesState.AUTO_IMPORT_DANGLING_INDICES_SETTING,
189191
EnableAllocationDecider.CLUSTER_ROUTING_ALLOCATION_ENABLE_SETTING,
190192
EnableAllocationDecider.CLUSTER_ROUTING_REBALANCE_ENABLE_SETTING,
191193
FilterAllocationDecider.CLUSTER_ROUTING_INCLUDE_GROUP_SETTING,

server/src/main/java/org/elasticsearch/gateway/DanglingIndicesState.java

+22-2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.elasticsearch.cluster.metadata.MetaData;
3131
import org.elasticsearch.cluster.service.ClusterService;
3232
import org.elasticsearch.common.inject.Inject;
33+
import org.elasticsearch.common.settings.Setting;
3334
import org.elasticsearch.common.util.concurrent.ConcurrentCollections;
3435
import org.elasticsearch.env.NodeEnvironment;
3536
import org.elasticsearch.index.Index;
@@ -55,9 +56,17 @@ public class DanglingIndicesState implements ClusterStateListener {
5556

5657
private static final Logger logger = LogManager.getLogger(DanglingIndicesState.class);
5758

59+
public static final Setting<Boolean> AUTO_IMPORT_DANGLING_INDICES_SETTING = Setting.boolSetting(
60+
"gateway.auto_import_dangling_indices",
61+
true,
62+
Setting.Property.NodeScope,
63+
Setting.Property.Deprecated
64+
);
65+
5866
private final NodeEnvironment nodeEnv;
5967
private final MetaStateService metaStateService;
6068
private final LocalAllocateDangledIndices allocateDangledIndices;
69+
private final boolean isAutoImportDanglingIndicesEnabled;
6170

6271
private final Map<Index, IndexMetaData> danglingIndices = ConcurrentCollections.newConcurrentMap();
6372

@@ -67,7 +76,18 @@ public DanglingIndicesState(NodeEnvironment nodeEnv, MetaStateService metaStateS
6776
this.nodeEnv = nodeEnv;
6877
this.metaStateService = metaStateService;
6978
this.allocateDangledIndices = allocateDangledIndices;
70-
clusterService.addListener(this);
79+
80+
this.isAutoImportDanglingIndicesEnabled = AUTO_IMPORT_DANGLING_INDICES_SETTING.get(clusterService.getSettings());
81+
82+
if (this.isAutoImportDanglingIndicesEnabled) {
83+
clusterService.addListener(this);
84+
} else {
85+
logger.warn(AUTO_IMPORT_DANGLING_INDICES_SETTING.getKey() + " is disabled, dangling indices will not be detected or imported");
86+
}
87+
}
88+
89+
boolean isAutoImportDanglingIndicesEnabled() {
90+
return this.isAutoImportDanglingIndicesEnabled;
7191
}
7292

7393
/**
@@ -171,7 +191,7 @@ private IndexMetaData stripAliases(IndexMetaData indexMetaData) {
171191
* Allocates the provided list of the dangled indices by sending them to the master node
172192
* for allocation.
173193
*/
174-
private void allocateDanglingIndices() {
194+
void allocateDanglingIndices() {
175195
if (danglingIndices.isEmpty()) {
176196
return;
177197
}

server/src/test/java/org/elasticsearch/gateway/DanglingIndicesStateTests.java

+68-3
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,12 @@
3535
import java.nio.file.StandardCopyOption;
3636
import java.util.Map;
3737

38+
import static org.elasticsearch.gateway.DanglingIndicesState.AUTO_IMPORT_DANGLING_INDICES_SETTING;
3839
import static org.hamcrest.Matchers.equalTo;
40+
import static org.mockito.Matchers.any;
3941
import static org.mockito.Mockito.mock;
42+
import static org.mockito.Mockito.verify;
43+
import static org.mockito.Mockito.when;
4044

4145
public class DanglingIndicesStateTests extends ESTestCase {
4246

@@ -46,6 +50,13 @@ public class DanglingIndicesStateTests extends ESTestCase {
4650
.put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT)
4751
.build();
4852

53+
// The setting AUTO_IMPORT_DANGLING_INDICES_SETTING is deprecated, so we must disable
54+
// warning checks or all the tests will fail.
55+
@Override
56+
protected boolean enableWarningsCheck() {
57+
return false;
58+
}
59+
4960
public void testCleanupWhenEmpty() throws Exception {
5061
try (NodeEnvironment env = newNodeEnvironment()) {
5162
MetaStateService metaStateService = new MetaStateService(env, xContentRegistry());
@@ -57,11 +68,11 @@ public void testCleanupWhenEmpty() throws Exception {
5768
assertTrue(danglingState.getDanglingIndices().isEmpty());
5869
}
5970
}
71+
6072
public void testDanglingIndicesDiscovery() throws Exception {
6173
try (NodeEnvironment env = newNodeEnvironment()) {
6274
MetaStateService metaStateService = new MetaStateService(env, xContentRegistry());
6375
DanglingIndicesState danglingState = createDanglingIndicesState(env, metaStateService);
64-
6576
assertTrue(danglingState.getDanglingIndices().isEmpty());
6677
MetaData metaData = MetaData.builder().build();
6778
final Settings.Builder settings = Settings.builder().put(indexSettings).put(IndexMetaData.SETTING_INDEX_UUID, "test1UUID");
@@ -155,7 +166,6 @@ public void testDanglingIndicesNotImportedWhenTombstonePresent() throws Exceptio
155166
final IndexGraveyard graveyard = IndexGraveyard.builder().addTombstone(dangledIndex.getIndex()).build();
156167
final MetaData metaData = MetaData.builder().indexGraveyard(graveyard).build();
157168
assertThat(danglingState.findNewDanglingIndices(metaData).size(), equalTo(0));
158-
159169
}
160170
}
161171

@@ -181,7 +191,62 @@ public void testDanglingIndicesStripAliases() throws Exception {
181191
}
182192
}
183193

194+
public void testDanglingIndicesAreNotAllocatedWhenDisabled() throws Exception {
195+
try (NodeEnvironment env = newNodeEnvironment()) {
196+
MetaStateService metaStateService = new MetaStateService(env, xContentRegistry());
197+
LocalAllocateDangledIndices localAllocateDangledIndices = mock(LocalAllocateDangledIndices.class);
198+
199+
final Settings allocateSettings = Settings.builder().put(AUTO_IMPORT_DANGLING_INDICES_SETTING.getKey(), false).build();
200+
201+
final ClusterService clusterServiceMock = mock(ClusterService.class);
202+
when(clusterServiceMock.getSettings()).thenReturn(allocateSettings);
203+
204+
final DanglingIndicesState danglingIndicesState = new DanglingIndicesState(
205+
env,
206+
metaStateService,
207+
localAllocateDangledIndices,
208+
clusterServiceMock
209+
);
210+
211+
assertFalse("Expected dangling imports to be disabled", danglingIndicesState.isAutoImportDanglingIndicesEnabled());
212+
}
213+
}
214+
215+
public void testDanglingIndicesAreAllocatedWhenEnabled() throws Exception {
216+
try (NodeEnvironment env = newNodeEnvironment()) {
217+
MetaStateService metaStateService = new MetaStateService(env, xContentRegistry());
218+
LocalAllocateDangledIndices localAllocateDangledIndices = mock(LocalAllocateDangledIndices.class);
219+
final Settings allocateSettings = Settings.builder().put(AUTO_IMPORT_DANGLING_INDICES_SETTING.getKey(), true).build();
220+
221+
final ClusterService clusterServiceMock = mock(ClusterService.class);
222+
when(clusterServiceMock.getSettings()).thenReturn(allocateSettings);
223+
224+
DanglingIndicesState danglingIndicesState = new DanglingIndicesState(
225+
env,
226+
metaStateService,
227+
localAllocateDangledIndices, clusterServiceMock
228+
);
229+
230+
assertTrue("Expected dangling imports to be enabled", danglingIndicesState.isAutoImportDanglingIndicesEnabled());
231+
232+
final Settings.Builder settings = Settings.builder().put(indexSettings).put(IndexMetaData.SETTING_INDEX_UUID, "test1UUID");
233+
IndexMetaData dangledIndex = IndexMetaData.builder("test1").settings(settings).build();
234+
metaStateService.writeIndex("test_write", dangledIndex);
235+
236+
danglingIndicesState.findNewAndAddDanglingIndices(MetaData.builder().build());
237+
238+
danglingIndicesState.allocateDanglingIndices();
239+
240+
verify(localAllocateDangledIndices).allocateDangled(any(), any());
241+
}
242+
}
243+
184244
private DanglingIndicesState createDanglingIndicesState(NodeEnvironment env, MetaStateService metaStateService) {
185-
return new DanglingIndicesState(env, metaStateService, null, mock(ClusterService.class));
245+
final Settings allocateSettings = Settings.builder().put(AUTO_IMPORT_DANGLING_INDICES_SETTING.getKey(), true).build();
246+
247+
final ClusterService clusterServiceMock = mock(ClusterService.class);
248+
when(clusterServiceMock.getSettings()).thenReturn(allocateSettings);
249+
250+
return new DanglingIndicesState(env, metaStateService, null, clusterServiceMock);
186251
}
187252
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.indices.recovery;
21+
22+
import org.elasticsearch.common.settings.Settings;
23+
import org.elasticsearch.test.ESIntegTestCase;
24+
import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
25+
import org.elasticsearch.test.InternalTestCluster;
26+
27+
import java.util.concurrent.TimeUnit;
28+
29+
import static org.elasticsearch.cluster.metadata.IndexGraveyard.SETTING_MAX_TOMBSTONES;
30+
import static org.elasticsearch.gateway.DanglingIndicesState.AUTO_IMPORT_DANGLING_INDICES_SETTING;
31+
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
32+
33+
@ClusterScope(numDataNodes = 0, scope = ESIntegTestCase.Scope.TEST)
34+
public class DanglingIndicesIT extends ESIntegTestCase {
35+
private static final String INDEX_NAME = "test-idx-1";
36+
37+
private Settings buildSettings(boolean importDanglingIndices) {
38+
return Settings.builder()
39+
// Don't keep any indices in the graveyard, so that when we delete an index,
40+
// it's definitely considered to be dangling.
41+
.put(SETTING_MAX_TOMBSTONES.getKey(), 0)
42+
.put(AUTO_IMPORT_DANGLING_INDICES_SETTING.getKey(), importDanglingIndices)
43+
.build();
44+
}
45+
46+
/**
47+
* Check that when dangling indices are discovered, then they are recovered into
48+
* the cluster, so long as the recovery setting is enabled.
49+
*/
50+
public void testDanglingIndicesAreRecoveredWhenSettingIsEnabled() throws Exception {
51+
final Settings settings = buildSettings(true);
52+
internalCluster().startNodes(3, settings);
53+
54+
createIndex(INDEX_NAME, Settings.builder().put("number_of_replicas", 2).build());
55+
56+
// Restart node, deleting the index in its absence, so that there is a dangling index to recover
57+
internalCluster().restartRandomDataNode(new InternalTestCluster.RestartCallback() {
58+
59+
@Override
60+
public Settings onNodeStopped(String nodeName) throws Exception {
61+
assertAcked(client().admin().indices().prepareDelete(INDEX_NAME));
62+
return super.onNodeStopped(nodeName);
63+
}
64+
});
65+
66+
assertBusy(() -> assertTrue("Expected dangling index " + INDEX_NAME + " to be recovered", indexExists(INDEX_NAME)));
67+
}
68+
69+
/**
70+
* Check that when dangling indices are discovered, then they are not recovered into
71+
* the cluster when the recovery setting is disabled.
72+
*/
73+
public void testDanglingIndicesAreNotRecoveredWhenSettingIsDisabled() throws Exception {
74+
internalCluster().startNodes(3, buildSettings(false));
75+
76+
createIndex(INDEX_NAME, Settings.builder().put("number_of_replicas", 2).build());
77+
78+
// Restart node, deleting the index in its absence, so that there is a dangling index to recover
79+
internalCluster().restartRandomDataNode(new InternalTestCluster.RestartCallback() {
80+
81+
@Override
82+
public Settings onNodeStopped(String nodeName) throws Exception {
83+
assertAcked(client().admin().indices().prepareDelete(INDEX_NAME));
84+
return super.onNodeStopped(nodeName);
85+
}
86+
});
87+
88+
// Since index recovery is async, we can't prove index recovery will never occur, just that it doesn't occur within some reasonable
89+
// amount of time
90+
assertFalse(
91+
"Did not expect dangling index " + INDEX_NAME + " to be recovered",
92+
waitUntil(() -> indexExists(INDEX_NAME), 1, TimeUnit.SECONDS)
93+
);
94+
}
95+
}

server/src/test/java/org/elasticsearch/indices/recovery/IndexRecoveryIT.java

-1
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,6 @@
142142
public class IndexRecoveryIT extends ESIntegTestCase {
143143

144144
private static final String INDEX_NAME = "test-idx-1";
145-
private static final String INDEX_TYPE = "test-type-1";
146145
private static final String REPO_NAME = "test-repo-1";
147146
private static final String SNAP_NAME = "test-snap-1";
148147

test/framework/src/main/java/org/elasticsearch/test/InternalTestCluster.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1487,7 +1487,7 @@ public synchronized boolean stopRandomDataNode() throws IOException {
14871487
}
14881488

14891489
/**
1490-
* Stops a random node in the cluster that applies to the given filter or non if the non of the nodes applies to the
1490+
* Stops a random node in the cluster that applies to the given filter. Does nothing if none of the nodes match the
14911491
* filter.
14921492
*/
14931493
public synchronized void stopRandomNode(final Predicate<Settings> filter) throws IOException {

0 commit comments

Comments
 (0)