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.
This commit is contained in:
parent
3915d4c055
commit
b1ff74f652
|
@ -77,6 +77,7 @@ import org.elasticsearch.discovery.zen.FaultDetection;
|
||||||
import org.elasticsearch.discovery.zen.ZenDiscovery;
|
import org.elasticsearch.discovery.zen.ZenDiscovery;
|
||||||
import org.elasticsearch.env.Environment;
|
import org.elasticsearch.env.Environment;
|
||||||
import org.elasticsearch.env.NodeEnvironment;
|
import org.elasticsearch.env.NodeEnvironment;
|
||||||
|
import org.elasticsearch.gateway.DanglingIndicesState;
|
||||||
import org.elasticsearch.gateway.GatewayService;
|
import org.elasticsearch.gateway.GatewayService;
|
||||||
import org.elasticsearch.gateway.IncrementalClusterStateWriter;
|
import org.elasticsearch.gateway.IncrementalClusterStateWriter;
|
||||||
import org.elasticsearch.http.HttpTransportSettings;
|
import org.elasticsearch.http.HttpTransportSettings;
|
||||||
|
@ -199,6 +200,7 @@ public final class ClusterSettings extends AbstractScopedSettings {
|
||||||
BalancedShardsAllocator.THRESHOLD_SETTING,
|
BalancedShardsAllocator.THRESHOLD_SETTING,
|
||||||
ClusterRebalanceAllocationDecider.CLUSTER_ROUTING_ALLOCATION_ALLOW_REBALANCE_SETTING,
|
ClusterRebalanceAllocationDecider.CLUSTER_ROUTING_ALLOCATION_ALLOW_REBALANCE_SETTING,
|
||||||
ConcurrentRebalanceAllocationDecider.CLUSTER_ROUTING_ALLOCATION_CLUSTER_CONCURRENT_REBALANCE_SETTING,
|
ConcurrentRebalanceAllocationDecider.CLUSTER_ROUTING_ALLOCATION_CLUSTER_CONCURRENT_REBALANCE_SETTING,
|
||||||
|
DanglingIndicesState.AUTO_IMPORT_DANGLING_INDICES_SETTING,
|
||||||
EnableAllocationDecider.CLUSTER_ROUTING_ALLOCATION_ENABLE_SETTING,
|
EnableAllocationDecider.CLUSTER_ROUTING_ALLOCATION_ENABLE_SETTING,
|
||||||
EnableAllocationDecider.CLUSTER_ROUTING_REBALANCE_ENABLE_SETTING,
|
EnableAllocationDecider.CLUSTER_ROUTING_REBALANCE_ENABLE_SETTING,
|
||||||
FilterAllocationDecider.CLUSTER_ROUTING_INCLUDE_GROUP_SETTING,
|
FilterAllocationDecider.CLUSTER_ROUTING_INCLUDE_GROUP_SETTING,
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.elasticsearch.cluster.metadata.IndexMetaData;
|
||||||
import org.elasticsearch.cluster.metadata.MetaData;
|
import org.elasticsearch.cluster.metadata.MetaData;
|
||||||
import org.elasticsearch.cluster.service.ClusterService;
|
import org.elasticsearch.cluster.service.ClusterService;
|
||||||
import org.elasticsearch.common.inject.Inject;
|
import org.elasticsearch.common.inject.Inject;
|
||||||
|
import org.elasticsearch.common.settings.Setting;
|
||||||
import org.elasticsearch.common.util.concurrent.ConcurrentCollections;
|
import org.elasticsearch.common.util.concurrent.ConcurrentCollections;
|
||||||
import org.elasticsearch.env.NodeEnvironment;
|
import org.elasticsearch.env.NodeEnvironment;
|
||||||
import org.elasticsearch.index.Index;
|
import org.elasticsearch.index.Index;
|
||||||
|
@ -56,9 +57,17 @@ public class DanglingIndicesState implements ClusterStateListener {
|
||||||
|
|
||||||
private static final Logger logger = LogManager.getLogger(DanglingIndicesState.class);
|
private static final Logger logger = LogManager.getLogger(DanglingIndicesState.class);
|
||||||
|
|
||||||
|
public static final Setting<Boolean> AUTO_IMPORT_DANGLING_INDICES_SETTING = Setting.boolSetting(
|
||||||
|
"gateway.auto_import_dangling_indices",
|
||||||
|
true,
|
||||||
|
Setting.Property.NodeScope,
|
||||||
|
Setting.Property.Deprecated
|
||||||
|
);
|
||||||
|
|
||||||
private final NodeEnvironment nodeEnv;
|
private final NodeEnvironment nodeEnv;
|
||||||
private final MetaStateService metaStateService;
|
private final MetaStateService metaStateService;
|
||||||
private final LocalAllocateDangledIndices allocateDangledIndices;
|
private final LocalAllocateDangledIndices allocateDangledIndices;
|
||||||
|
private final boolean isAutoImportDanglingIndicesEnabled;
|
||||||
|
|
||||||
private final Map<Index, IndexMetaData> danglingIndices = ConcurrentCollections.newConcurrentMap();
|
private final Map<Index, IndexMetaData> danglingIndices = ConcurrentCollections.newConcurrentMap();
|
||||||
|
|
||||||
|
@ -68,7 +77,18 @@ public class DanglingIndicesState implements ClusterStateListener {
|
||||||
this.nodeEnv = nodeEnv;
|
this.nodeEnv = nodeEnv;
|
||||||
this.metaStateService = metaStateService;
|
this.metaStateService = metaStateService;
|
||||||
this.allocateDangledIndices = allocateDangledIndices;
|
this.allocateDangledIndices = allocateDangledIndices;
|
||||||
|
|
||||||
|
this.isAutoImportDanglingIndicesEnabled = AUTO_IMPORT_DANGLING_INDICES_SETTING.get(clusterService.getSettings());
|
||||||
|
|
||||||
|
if (this.isAutoImportDanglingIndicesEnabled) {
|
||||||
clusterService.addListener(this);
|
clusterService.addListener(this);
|
||||||
|
} else {
|
||||||
|
logger.warn(AUTO_IMPORT_DANGLING_INDICES_SETTING.getKey() + " is disabled, dangling indices will not be detected or imported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isAutoImportDanglingIndicesEnabled() {
|
||||||
|
return this.isAutoImportDanglingIndicesEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -172,7 +192,7 @@ public class DanglingIndicesState implements ClusterStateListener {
|
||||||
* Allocates the provided list of the dangled indices by sending them to the master node
|
* Allocates the provided list of the dangled indices by sending them to the master node
|
||||||
* for allocation.
|
* for allocation.
|
||||||
*/
|
*/
|
||||||
private void allocateDanglingIndices() {
|
void allocateDanglingIndices() {
|
||||||
if (danglingIndices.isEmpty()) {
|
if (danglingIndices.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,8 +35,12 @@ import java.nio.file.Path;
|
||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.elasticsearch.gateway.DanglingIndicesState.AUTO_IMPORT_DANGLING_INDICES_SETTING;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.mockito.Matchers.any;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
public class DanglingIndicesStateTests extends ESTestCase {
|
public class DanglingIndicesStateTests extends ESTestCase {
|
||||||
|
|
||||||
|
@ -46,6 +50,13 @@ public class DanglingIndicesStateTests extends ESTestCase {
|
||||||
.put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT)
|
.put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
// The setting AUTO_IMPORT_DANGLING_INDICES_SETTING is deprecated, so we must disable
|
||||||
|
// warning checks or all the tests will fail.
|
||||||
|
@Override
|
||||||
|
protected boolean enableWarningsCheck() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public void testCleanupWhenEmpty() throws Exception {
|
public void testCleanupWhenEmpty() throws Exception {
|
||||||
try (NodeEnvironment env = newNodeEnvironment()) {
|
try (NodeEnvironment env = newNodeEnvironment()) {
|
||||||
MetaStateService metaStateService = new MetaStateService(env, xContentRegistry());
|
MetaStateService metaStateService = new MetaStateService(env, xContentRegistry());
|
||||||
|
@ -57,11 +68,11 @@ public class DanglingIndicesStateTests extends ESTestCase {
|
||||||
assertTrue(danglingState.getDanglingIndices().isEmpty());
|
assertTrue(danglingState.getDanglingIndices().isEmpty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testDanglingIndicesDiscovery() throws Exception {
|
public void testDanglingIndicesDiscovery() throws Exception {
|
||||||
try (NodeEnvironment env = newNodeEnvironment()) {
|
try (NodeEnvironment env = newNodeEnvironment()) {
|
||||||
MetaStateService metaStateService = new MetaStateService(env, xContentRegistry());
|
MetaStateService metaStateService = new MetaStateService(env, xContentRegistry());
|
||||||
DanglingIndicesState danglingState = createDanglingIndicesState(env, metaStateService);
|
DanglingIndicesState danglingState = createDanglingIndicesState(env, metaStateService);
|
||||||
|
|
||||||
assertTrue(danglingState.getDanglingIndices().isEmpty());
|
assertTrue(danglingState.getDanglingIndices().isEmpty());
|
||||||
MetaData metaData = MetaData.builder().build();
|
MetaData metaData = MetaData.builder().build();
|
||||||
final Settings.Builder settings = Settings.builder().put(indexSettings).put(IndexMetaData.SETTING_INDEX_UUID, "test1UUID");
|
final Settings.Builder settings = Settings.builder().put(indexSettings).put(IndexMetaData.SETTING_INDEX_UUID, "test1UUID");
|
||||||
|
@ -155,7 +166,6 @@ public class DanglingIndicesStateTests extends ESTestCase {
|
||||||
final IndexGraveyard graveyard = IndexGraveyard.builder().addTombstone(dangledIndex.getIndex()).build();
|
final IndexGraveyard graveyard = IndexGraveyard.builder().addTombstone(dangledIndex.getIndex()).build();
|
||||||
final MetaData metaData = MetaData.builder().indexGraveyard(graveyard).build();
|
final MetaData metaData = MetaData.builder().indexGraveyard(graveyard).build();
|
||||||
assertThat(danglingState.findNewDanglingIndices(metaData).size(), equalTo(0));
|
assertThat(danglingState.findNewDanglingIndices(metaData).size(), equalTo(0));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,7 +191,62 @@ public class DanglingIndicesStateTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testDanglingIndicesAreNotAllocatedWhenDisabled() throws Exception {
|
||||||
|
try (NodeEnvironment env = newNodeEnvironment()) {
|
||||||
|
MetaStateService metaStateService = new MetaStateService(env, xContentRegistry());
|
||||||
|
LocalAllocateDangledIndices localAllocateDangledIndices = mock(LocalAllocateDangledIndices.class);
|
||||||
|
|
||||||
|
final Settings allocateSettings = Settings.builder().put(AUTO_IMPORT_DANGLING_INDICES_SETTING.getKey(), false).build();
|
||||||
|
|
||||||
|
final ClusterService clusterServiceMock = mock(ClusterService.class);
|
||||||
|
when(clusterServiceMock.getSettings()).thenReturn(allocateSettings);
|
||||||
|
|
||||||
|
final DanglingIndicesState danglingIndicesState = new DanglingIndicesState(
|
||||||
|
env,
|
||||||
|
metaStateService,
|
||||||
|
localAllocateDangledIndices,
|
||||||
|
clusterServiceMock
|
||||||
|
);
|
||||||
|
|
||||||
|
assertFalse("Expected dangling imports to be disabled", danglingIndicesState.isAutoImportDanglingIndicesEnabled());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testDanglingIndicesAreAllocatedWhenEnabled() throws Exception {
|
||||||
|
try (NodeEnvironment env = newNodeEnvironment()) {
|
||||||
|
MetaStateService metaStateService = new MetaStateService(env, xContentRegistry());
|
||||||
|
LocalAllocateDangledIndices localAllocateDangledIndices = mock(LocalAllocateDangledIndices.class);
|
||||||
|
final Settings allocateSettings = Settings.builder().put(AUTO_IMPORT_DANGLING_INDICES_SETTING.getKey(), true).build();
|
||||||
|
|
||||||
|
final ClusterService clusterServiceMock = mock(ClusterService.class);
|
||||||
|
when(clusterServiceMock.getSettings()).thenReturn(allocateSettings);
|
||||||
|
|
||||||
|
DanglingIndicesState danglingIndicesState = new DanglingIndicesState(
|
||||||
|
env,
|
||||||
|
metaStateService,
|
||||||
|
localAllocateDangledIndices, clusterServiceMock
|
||||||
|
);
|
||||||
|
|
||||||
|
assertTrue("Expected dangling imports to be enabled", danglingIndicesState.isAutoImportDanglingIndicesEnabled());
|
||||||
|
|
||||||
|
final Settings.Builder settings = Settings.builder().put(indexSettings).put(IndexMetaData.SETTING_INDEX_UUID, "test1UUID");
|
||||||
|
IndexMetaData dangledIndex = IndexMetaData.builder("test1").settings(settings).build();
|
||||||
|
metaStateService.writeIndex("test_write", dangledIndex);
|
||||||
|
|
||||||
|
danglingIndicesState.findNewAndAddDanglingIndices(MetaData.builder().build());
|
||||||
|
|
||||||
|
danglingIndicesState.allocateDanglingIndices();
|
||||||
|
|
||||||
|
verify(localAllocateDangledIndices).allocateDangled(any(), any());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private DanglingIndicesState createDanglingIndicesState(NodeEnvironment env, MetaStateService metaStateService) {
|
private DanglingIndicesState createDanglingIndicesState(NodeEnvironment env, MetaStateService metaStateService) {
|
||||||
return new DanglingIndicesState(env, metaStateService, null, mock(ClusterService.class));
|
final Settings allocateSettings = Settings.builder().put(AUTO_IMPORT_DANGLING_INDICES_SETTING.getKey(), true).build();
|
||||||
|
|
||||||
|
final ClusterService clusterServiceMock = mock(ClusterService.class);
|
||||||
|
when(clusterServiceMock.getSettings()).thenReturn(allocateSettings);
|
||||||
|
|
||||||
|
return new DanglingIndicesState(env, metaStateService, null, clusterServiceMock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.elasticsearch.indices.recovery;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.test.ESIntegTestCase;
|
||||||
|
import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
|
||||||
|
import org.elasticsearch.test.InternalTestCluster;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static org.elasticsearch.cluster.metadata.IndexGraveyard.SETTING_MAX_TOMBSTONES;
|
||||||
|
import static org.elasticsearch.gateway.DanglingIndicesState.AUTO_IMPORT_DANGLING_INDICES_SETTING;
|
||||||
|
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
|
||||||
|
|
||||||
|
@ClusterScope(numDataNodes = 0, scope = ESIntegTestCase.Scope.TEST)
|
||||||
|
public class DanglingIndicesIT extends ESIntegTestCase {
|
||||||
|
private static final String INDEX_NAME = "test-idx-1";
|
||||||
|
|
||||||
|
private Settings buildSettings(boolean importDanglingIndices) {
|
||||||
|
return Settings.builder()
|
||||||
|
// Don't keep any indices in the graveyard, so that when we delete an index,
|
||||||
|
// it's definitely considered to be dangling.
|
||||||
|
.put(SETTING_MAX_TOMBSTONES.getKey(), 0)
|
||||||
|
.put(AUTO_IMPORT_DANGLING_INDICES_SETTING.getKey(), importDanglingIndices)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that when dangling indices are discovered, then they are recovered into
|
||||||
|
* the cluster, so long as the recovery setting is enabled.
|
||||||
|
*/
|
||||||
|
public void testDanglingIndicesAreRecoveredWhenSettingIsEnabled() throws Exception {
|
||||||
|
final Settings settings = buildSettings(true);
|
||||||
|
internalCluster().startNodes(3, settings);
|
||||||
|
|
||||||
|
createIndex(INDEX_NAME, Settings.builder().put("number_of_replicas", 2).build());
|
||||||
|
|
||||||
|
// Restart node, deleting the index in its absence, so that there is a dangling index to recover
|
||||||
|
internalCluster().restartRandomDataNode(new InternalTestCluster.RestartCallback() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Settings onNodeStopped(String nodeName) throws Exception {
|
||||||
|
assertAcked(client().admin().indices().prepareDelete(INDEX_NAME));
|
||||||
|
return super.onNodeStopped(nodeName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assertBusy(() -> assertTrue("Expected dangling index " + INDEX_NAME + " to be recovered", indexExists(INDEX_NAME)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that when dangling indices are discovered, then they are not recovered into
|
||||||
|
* the cluster when the recovery setting is disabled.
|
||||||
|
*/
|
||||||
|
public void testDanglingIndicesAreNotRecoveredWhenSettingIsDisabled() throws Exception {
|
||||||
|
internalCluster().startNodes(3, buildSettings(false));
|
||||||
|
|
||||||
|
createIndex(INDEX_NAME, Settings.builder().put("number_of_replicas", 2).build());
|
||||||
|
|
||||||
|
// Restart node, deleting the index in its absence, so that there is a dangling index to recover
|
||||||
|
internalCluster().restartRandomDataNode(new InternalTestCluster.RestartCallback() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Settings onNodeStopped(String nodeName) throws Exception {
|
||||||
|
assertAcked(client().admin().indices().prepareDelete(INDEX_NAME));
|
||||||
|
return super.onNodeStopped(nodeName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Since index recovery is async, we can't prove index recovery will never occur, just that it doesn't occur within some reasonable
|
||||||
|
// amount of time
|
||||||
|
assertFalse(
|
||||||
|
"Did not expect dangling index " + INDEX_NAME + " to be recovered",
|
||||||
|
waitUntil(() -> indexExists(INDEX_NAME), 1, TimeUnit.SECONDS)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1635,7 +1635,7 @@ public final class InternalTestCluster extends TestCluster {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops a random node in the cluster that applies to the given filter or non if the non of the nodes applies to the
|
* Stops a random node in the cluster that applies to the given filter. Does nothing if none of the nodes match the
|
||||||
* filter.
|
* filter.
|
||||||
*/
|
*/
|
||||||
public synchronized void stopRandomNode(final Predicate<Settings> filter) throws IOException {
|
public synchronized void stopRandomNode(final Predicate<Settings> filter) throws IOException {
|
||||||
|
|
Loading…
Reference in New Issue