Skip to content

Commit f305122

Browse files
committed
Drop alloc filters on mount of searchable snapshot
Today when a searchable snapshot is mounted it inherits many of the settings of the original index, including any index-level allocation filters that were in place when the snapshot was taken. These filters typically make no sense on the mounted index, so users must explicitly override or drop them. With this commit at mount time we drop all index-level allocation filters that existed on the original index, although we continue to respect any allocation filters that were specified as part of the mount request. Closes #69759
1 parent 5f13de8 commit f305122

File tree

2 files changed

+183
-4
lines changed

2 files changed

+183
-4
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.searchablesnapshots;
9+
10+
import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotResponse;
11+
import org.elasticsearch.common.Nullable;
12+
import org.elasticsearch.common.Strings;
13+
import org.elasticsearch.common.settings.Setting;
14+
import org.elasticsearch.common.settings.Settings;
15+
import org.elasticsearch.index.IndexSettings;
16+
import org.elasticsearch.test.ESIntegTestCase;
17+
import org.elasticsearch.xpack.core.searchablesnapshots.MountSearchableSnapshotAction;
18+
import org.elasticsearch.xpack.core.searchablesnapshots.MountSearchableSnapshotRequest;
19+
20+
import java.util.List;
21+
import java.util.Locale;
22+
23+
import static org.elasticsearch.cluster.metadata.IndexMetadata.INDEX_ROUTING_EXCLUDE_GROUP_SETTING;
24+
import static org.elasticsearch.cluster.metadata.IndexMetadata.INDEX_ROUTING_INCLUDE_GROUP_SETTING;
25+
import static org.elasticsearch.cluster.metadata.IndexMetadata.INDEX_ROUTING_REQUIRE_GROUP_SETTING;
26+
import static org.elasticsearch.cluster.metadata.IndexMetadata.SETTING_NUMBER_OF_REPLICAS;
27+
import static org.elasticsearch.cluster.routing.allocation.decider.FilterAllocationDecider.CLUSTER_ROUTING_EXCLUDE_GROUP_SETTING;
28+
import static org.elasticsearch.index.IndexSettings.INDEX_SOFT_DELETES_SETTING;
29+
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
30+
import static org.hamcrest.Matchers.equalTo;
31+
32+
@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST, numDataNodes = 0)
33+
public class AllocationFilteringIntegTests extends BaseSearchableSnapshotsIntegTestCase {
34+
35+
private MountSearchableSnapshotRequest prepareMountRequest(
36+
Settings.Builder originalIndexSettings,
37+
Settings.Builder mountedIndexSettings
38+
) throws InterruptedException {
39+
40+
final String fsRepoName = randomAlphaOfLength(10);
41+
final String indexName = randomAlphaOfLength(10).toLowerCase(Locale.ROOT);
42+
final String snapshotName = randomAlphaOfLength(10).toLowerCase(Locale.ROOT);
43+
44+
createRepository(fsRepoName, "fs", Settings.builder().put("location", randomRepoPath()));
45+
assertAcked(
46+
prepareCreate(
47+
indexName,
48+
Settings.builder()
49+
.put(INDEX_SOFT_DELETES_SETTING.getKey(), true)
50+
.put(SETTING_NUMBER_OF_REPLICAS, 0)
51+
.put(originalIndexSettings.build())
52+
)
53+
);
54+
populateIndex(indexName, 10);
55+
ensureGreen(indexName);
56+
createFullSnapshot(fsRepoName, snapshotName);
57+
assertAcked(client().admin().indices().prepareDelete(indexName));
58+
59+
final Settings.Builder indexSettingsBuilder = Settings.builder()
60+
.put(SearchableSnapshots.SNAPSHOT_CACHE_ENABLED_SETTING.getKey(), true)
61+
.put(IndexSettings.INDEX_CHECK_ON_STARTUP.getKey(), Boolean.FALSE.toString())
62+
.put(mountedIndexSettings.build());
63+
64+
return new MountSearchableSnapshotRequest(
65+
indexName,
66+
fsRepoName,
67+
snapshotName,
68+
indexName,
69+
indexSettingsBuilder.build(),
70+
Strings.EMPTY_ARRAY,
71+
true,
72+
MountSearchableSnapshotRequest.Storage.FULL_COPY
73+
);
74+
}
75+
76+
public void testRemovesRequireFilter() throws InterruptedException {
77+
runTest(INDEX_ROUTING_REQUIRE_GROUP_SETTING, true, null, true);
78+
}
79+
80+
public void testRemovesIncludeFilter() throws InterruptedException {
81+
runTest(INDEX_ROUTING_INCLUDE_GROUP_SETTING, true, null, true);
82+
}
83+
84+
public void testRemovesExcludeFilter() throws InterruptedException {
85+
runTest(INDEX_ROUTING_EXCLUDE_GROUP_SETTING, false, null, true);
86+
}
87+
88+
public void testReplacesRequireFilter() throws InterruptedException {
89+
runTest(INDEX_ROUTING_REQUIRE_GROUP_SETTING, true, INDEX_ROUTING_REQUIRE_GROUP_SETTING, true);
90+
}
91+
92+
public void testReplacesIncludeFilter() throws InterruptedException {
93+
runTest(INDEX_ROUTING_INCLUDE_GROUP_SETTING, true, INDEX_ROUTING_INCLUDE_GROUP_SETTING, true);
94+
}
95+
96+
public void testReplacesExcludeFilter() throws InterruptedException {
97+
runTest(INDEX_ROUTING_EXCLUDE_GROUP_SETTING, false, INDEX_ROUTING_EXCLUDE_GROUP_SETTING, false);
98+
}
99+
100+
public void testReplacesIncludeFilterWithExcludeFilter() throws InterruptedException {
101+
runTest(INDEX_ROUTING_INCLUDE_GROUP_SETTING, true, INDEX_ROUTING_EXCLUDE_GROUP_SETTING, false);
102+
}
103+
104+
/**
105+
* Starts two nodes, allocates the original index on the first node but then excludes that node and verifies that the index can still be
106+
* mounted and allocated to the other node.
107+
*
108+
* @param indexSetting an allocation filter setting to apply to the original index
109+
* @param mountSetting an (optional) allocation filter setting to apply at mount time
110+
* @param indexSettingIsPositive whether {@code indexSetting} is positive (i.e. include/require) or negative (i.e. exclude)
111+
* @param mountSettingIsPositive whether {@code mountSetting} is positive (i.e. include/require) or negative (i.e. exclude)
112+
*/
113+
private void runTest(
114+
Setting.AffixSetting<String> indexSetting,
115+
boolean indexSettingIsPositive,
116+
@Nullable Setting.AffixSetting<String> mountSetting,
117+
boolean mountSettingIsPositive
118+
) throws InterruptedException {
119+
final List<String> nodes = internalCluster().startNodes(2);
120+
121+
// apply an index setting to restrict the original index to node 0
122+
final Settings.Builder indexSettings = Settings.builder()
123+
.put(indexSetting.getConcreteSettingForNamespace("_name").getKey(), nodes.get(indexSettingIsPositive ? 0 : 1));
124+
125+
final Settings.Builder mountSettings = Settings.builder();
126+
if (mountSetting != null) {
127+
// apply an index setting to restrict the mounted index to node 1
128+
mountSettings.put(mountSetting.getConcreteSettingForNamespace("_name").getKey(), nodes.get(mountSettingIsPositive ? 1 : 0));
129+
}
130+
131+
final MountSearchableSnapshotRequest mountRequest = prepareMountRequest(indexSettings, mountSettings);
132+
133+
// block allocation to node 0 at the cluster level
134+
assertAcked(
135+
client().admin()
136+
.cluster()
137+
.prepareUpdateSettings()
138+
.setPersistentSettings(
139+
Settings.builder()
140+
.put(CLUSTER_ROUTING_EXCLUDE_GROUP_SETTING.getConcreteSettingForNamespace("_name").getKey(), nodes.get(0))
141+
)
142+
);
143+
144+
// mount snapshot and verify it is allocated as expected
145+
final RestoreSnapshotResponse restoreSnapshotResponse = client().execute(MountSearchableSnapshotAction.INSTANCE, mountRequest)
146+
.actionGet();
147+
assertThat(restoreSnapshotResponse.getRestoreInfo().failedShards(), equalTo(0));
148+
ensureGreen(mountRequest.mountedIndexName());
149+
150+
if (mountSetting != null) {
151+
final Settings mountedIndexSettings = client().admin()
152+
.indices()
153+
.prepareGetSettings(mountRequest.mountedIndexName())
154+
.get()
155+
.getIndexToSettings()
156+
.get(mountRequest.mountedIndexName());
157+
assertTrue(mountedIndexSettings.toString(), mountSetting.getConcreteSettingForNamespace("_name").exists(mountedIndexSettings));
158+
}
159+
160+
assertAcked(
161+
client().admin()
162+
.cluster()
163+
.prepareUpdateSettings()
164+
.setPersistentSettings(
165+
Settings.builder().putNull(CLUSTER_ROUTING_EXCLUDE_GROUP_SETTING.getConcreteSettingForNamespace("_name").getKey())
166+
)
167+
);
168+
}
169+
170+
}

x-pack/plugin/searchable-snapshots/src/main/java/org/elasticsearch/xpack/searchablesnapshots/action/TransportMountSearchableSnapshotAction.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,12 @@
4343
import org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshotsConstants;
4444

4545
import java.util.Arrays;
46+
import java.util.LinkedHashSet;
4647
import java.util.Locale;
4748
import java.util.Map;
4849
import java.util.Objects;
4950
import java.util.Optional;
51+
import java.util.Set;
5052

5153
import static org.elasticsearch.index.IndexModule.INDEX_RECOVERY_TYPE_SETTING;
5254
import static org.elasticsearch.index.IndexModule.INDEX_STORE_TYPE_SETTING;
@@ -180,9 +182,6 @@ protected void masterOperation(
180182
}
181183
final SnapshotId snapshotId = matchingSnapshotId.get();
182184

183-
final String[] ignoreIndexSettings = Arrays.copyOf(request.ignoreIndexSettings(), request.ignoreIndexSettings().length + 1);
184-
ignoreIndexSettings[ignoreIndexSettings.length - 1] = IndexMetadata.SETTING_DATA_PATH;
185-
186185
final IndexMetadata indexMetadata = repository.getSnapshotIndexMetaData(repoData, snapshotId, indexId);
187186
if (isSearchableSnapshotStore(indexMetadata.getSettings())) {
188187
throw new IllegalArgumentException(
@@ -202,6 +201,16 @@ protected void masterOperation(
202201
);
203202
}
204203

204+
final Set<String> ignoreIndexSettings = new LinkedHashSet<>(Arrays.asList(request.ignoreIndexSettings()));
205+
ignoreIndexSettings.add(IndexMetadata.SETTING_DATA_PATH);
206+
for (final String indexSettingKey : indexMetadata.getSettings().keySet()) {
207+
if (indexSettingKey.startsWith(IndexMetadata.INDEX_ROUTING_REQUIRE_GROUP_PREFIX)
208+
|| indexSettingKey.startsWith(IndexMetadata.INDEX_ROUTING_INCLUDE_GROUP_PREFIX)
209+
|| indexSettingKey.startsWith(IndexMetadata.INDEX_ROUTING_EXCLUDE_GROUP_PREFIX)) {
210+
ignoreIndexSettings.add(indexSettingKey);
211+
}
212+
}
213+
205214
client.admin()
206215
.cluster()
207216
.restoreSnapshot(
@@ -224,7 +233,7 @@ protected void masterOperation(
224233
.build()
225234
)
226235
// Pass through ignored index settings
227-
.ignoreIndexSettings(ignoreIndexSettings)
236+
.ignoreIndexSettings(ignoreIndexSettings.toArray(new String[0]))
228237
// Don't include global state
229238
.includeGlobalState(false)
230239
// Don't include aliases

0 commit comments

Comments
 (0)