Skip to content

Commit dad23ed

Browse files
Avoid regular indices in frozen tier (#70141)
The frozen tier will be dedicated for partially cached searchable snapshots. This PR ensures that we do not allow allocating regular indices (including fully cached searchable snapshots) to the frozen tier.
1 parent dc78ce4 commit dad23ed

File tree

18 files changed

+316
-44
lines changed

18 files changed

+316
-44
lines changed

build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,9 +189,9 @@ tasks.register("verifyVersions") {
189189
* after the backport of the backcompat code is complete.
190190
*/
191191

192-
boolean bwc_tests_enabled = true
192+
boolean bwc_tests_enabled = false
193193
// place a PR link here when committing bwc changes:
194-
String bwc_tests_disabled_issue = ""
194+
String bwc_tests_disabled_issue = "https://github.com/elastic/elasticsearch/pull/70141"
195195
/*
196196
* FIPS 140-2 behavior was fixed in 7.11.0. Before that there is no way to run elasticsearch in a
197197
* JVM that is properly configured to be in fips mode with BCFIPS. For now we need to disable

x-pack/plugin/core/src/internalClusterTest/java/org/elasticsearch/xpack/cluster/routing/allocation/DataTierIT.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
package org.elasticsearch.xpack.cluster.routing.allocation;
99

10+
import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsRequest;
1011
import org.elasticsearch.action.admin.indices.shrink.ResizeType;
1112
import org.elasticsearch.action.admin.indices.template.put.PutComposableIndexTemplateAction;
1213
import org.elasticsearch.cluster.health.ClusterHealthStatus;
@@ -24,6 +25,7 @@
2425
import java.util.Arrays;
2526
import java.util.Collection;
2627
import java.util.Collections;
28+
import java.util.Map;
2729

2830
import static org.hamcrest.Matchers.equalTo;
2931
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
@@ -267,6 +269,35 @@ public void testTierFilteringIgnoredByFilterAllocationDecider() {
267269
.get();
268270
}
269271

272+
public void testIllegalOnFrozen() {
273+
startDataNode();
274+
275+
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
276+
() -> createIndex(index, Settings.builder()
277+
.put("index.number_of_shards", 1)
278+
.put("index.number_of_replicas", 0)
279+
.put(DataTierAllocationDecider.INDEX_ROUTING_PREFER, DataTier.DATA_FROZEN)
280+
.build()));
281+
assertThat(e.getMessage(), equalTo("[data_frozen] tier can only be used for partial searchable snapshots"));
282+
283+
String initialTier = randomFrom(DataTier.DATA_HOT, DataTier.DATA_WARM, DataTier.DATA_COLD);
284+
createIndex(index, Settings.builder()
285+
.put("index.number_of_shards", 1)
286+
.put("index.number_of_replicas", 0)
287+
.put(DataTierAllocationDecider.INDEX_ROUTING_PREFER, initialTier)
288+
.build());
289+
290+
IllegalArgumentException e2 = expectThrows(IllegalArgumentException.class, () -> updatePreference(DataTier.DATA_FROZEN));
291+
assertThat(e2.getMessage(), equalTo("[data_frozen] tier can only be used for partial searchable snapshots"));
292+
293+
updatePreference(randomValueOtherThan(initialTier, () -> randomFrom(DataTier.DATA_HOT, DataTier.DATA_WARM, DataTier.DATA_COLD)));
294+
}
295+
296+
private void updatePreference(String tier) {
297+
client().admin().indices().updateSettings(new UpdateSettingsRequest(index)
298+
.settings(Map.of(DataTierAllocationDecider.INDEX_ROUTING_PREFER, tier))).actionGet();
299+
}
300+
270301
private DataTiersFeatureSetUsage getUsage() {
271302
XPackUsageResponse usages = new XPackUsageRequestBuilder(client()).execute().actionGet();
272303
return usages.getUsages().stream()

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/cluster/routing/allocation/DataTierAllocationDecider.java

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,21 @@
2121
import org.elasticsearch.common.settings.ClusterSettings;
2222
import org.elasticsearch.common.settings.Setting;
2323
import org.elasticsearch.common.settings.Settings;
24+
import org.elasticsearch.index.IndexModule;
2425
import org.elasticsearch.xpack.core.DataTier;
26+
import org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshotsConstants;
2527

2628
import java.util.Arrays;
29+
import java.util.Collection;
30+
import java.util.Iterator;
31+
import java.util.List;
32+
import java.util.Map;
2733
import java.util.Optional;
2834
import java.util.Set;
2935
import java.util.stream.Collectors;
3036

37+
import static org.elasticsearch.xpack.core.DataTier.DATA_FROZEN;
38+
3139
/**
3240
* The {@code DataTierAllocationDecider} is a custom allocation decider that behaves similar to the
3341
* {@link org.elasticsearch.cluster.routing.allocation.decider.FilterAllocationDecider}, however it
@@ -45,20 +53,21 @@ public class DataTierAllocationDecider extends AllocationDecider {
4553
public static final String INDEX_ROUTING_PREFER = "index.routing.allocation.include._tier_preference";
4654
public static final String INDEX_ROUTING_EXCLUDE = "index.routing.allocation.exclude._tier";
4755

56+
private static final DataTierValidator VALIDATOR = new DataTierValidator();
4857
public static final Setting<String> CLUSTER_ROUTING_REQUIRE_SETTING = Setting.simpleString(CLUSTER_ROUTING_REQUIRE,
4958
DataTierAllocationDecider::validateTierSetting, Setting.Property.Dynamic, Setting.Property.NodeScope);
5059
public static final Setting<String> CLUSTER_ROUTING_INCLUDE_SETTING = Setting.simpleString(CLUSTER_ROUTING_INCLUDE,
5160
DataTierAllocationDecider::validateTierSetting, Setting.Property.Dynamic, Setting.Property.NodeScope);
5261
public static final Setting<String> CLUSTER_ROUTING_EXCLUDE_SETTING = Setting.simpleString(CLUSTER_ROUTING_EXCLUDE,
5362
DataTierAllocationDecider::validateTierSetting, Setting.Property.Dynamic, Setting.Property.NodeScope);
5463
public static final Setting<String> INDEX_ROUTING_REQUIRE_SETTING = Setting.simpleString(INDEX_ROUTING_REQUIRE,
55-
DataTierAllocationDecider::validateTierSetting, Setting.Property.Dynamic, Setting.Property.IndexScope);
64+
VALIDATOR, Setting.Property.Dynamic, Setting.Property.IndexScope);
5665
public static final Setting<String> INDEX_ROUTING_INCLUDE_SETTING = Setting.simpleString(INDEX_ROUTING_INCLUDE,
57-
DataTierAllocationDecider::validateTierSetting, Setting.Property.Dynamic, Setting.Property.IndexScope);
66+
VALIDATOR, Setting.Property.Dynamic, Setting.Property.IndexScope);
5867
public static final Setting<String> INDEX_ROUTING_EXCLUDE_SETTING = Setting.simpleString(INDEX_ROUTING_EXCLUDE,
59-
DataTierAllocationDecider::validateTierSetting, Setting.Property.Dynamic, Setting.Property.IndexScope);
68+
VALIDATOR, Setting.Property.Dynamic, Setting.Property.IndexScope);
6069
public static final Setting<String> INDEX_ROUTING_PREFER_SETTING = Setting.simpleString(INDEX_ROUTING_PREFER,
61-
DataTierAllocationDecider::validateTierSetting, Setting.Property.Dynamic, Setting.Property.IndexScope);
70+
VALIDATOR, Setting.Property.Dynamic, Setting.Property.IndexScope);
6271

6372
private static void validateTierSetting(String setting) {
6473
if (Strings.hasText(setting)) {
@@ -71,6 +80,31 @@ private static void validateTierSetting(String setting) {
7180
}
7281
}
7382

83+
private static class DataTierValidator implements Setting.Validator<String> {
84+
private static final Collection<Setting<?>> dependencies = List.of(IndexModule.INDEX_STORE_TYPE_SETTING,
85+
SearchableSnapshotsConstants.SNAPSHOT_PARTIAL_SETTING);
86+
87+
@Override
88+
public void validate(String value) {
89+
validateTierSetting(value);
90+
}
91+
92+
@Override
93+
public void validate(String value, Map<Setting<?>, Object> settings) {
94+
if (Strings.hasText(value) && SearchableSnapshotsConstants.isPartialSearchableSnapshotIndex(settings) == false) {
95+
String[] split = value.split(",");
96+
if (Arrays.stream(split).anyMatch(DATA_FROZEN::equals)) {
97+
throw new IllegalArgumentException("[" + DATA_FROZEN + "] tier can only be used for partial searchable snapshots");
98+
}
99+
}
100+
}
101+
102+
@Override
103+
public Iterator<Setting<?>> settings() {
104+
return dependencies.iterator();
105+
}
106+
}
107+
74108
private volatile String clusterRequire;
75109
private volatile String clusterInclude;
76110
private volatile String clusterExclude;

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshotsConstants.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,41 @@
77
package org.elasticsearch.xpack.searchablesnapshots;
88

99
import org.elasticsearch.Version;
10+
import org.elasticsearch.common.settings.Setting;
1011
import org.elasticsearch.common.settings.Settings;
1112

13+
import java.util.Map;
14+
1215
import static org.elasticsearch.index.IndexModule.INDEX_STORE_TYPE_SETTING;
1316

1417
public class SearchableSnapshotsConstants {
1518
public static final String SNAPSHOT_DIRECTORY_FACTORY_KEY = "snapshot";
1619

1720
public static final String SNAPSHOT_RECOVERY_STATE_FACTORY_KEY = "snapshot_prewarm";
21+
public static final Setting<Boolean> SNAPSHOT_PARTIAL_SETTING = Setting.boolSetting(
22+
"index.store.snapshot.partial",
23+
false,
24+
Setting.Property.IndexScope,
25+
Setting.Property.PrivateIndex,
26+
Setting.Property.NotCopyableOnResize
27+
);
1828

1929
public static boolean isSearchableSnapshotStore(Settings indexSettings) {
2030
return SNAPSHOT_DIRECTORY_FACTORY_KEY.equals(INDEX_STORE_TYPE_SETTING.get(indexSettings));
2131
}
2232

33+
/**
34+
* Based on a map from setting to value, do the settings represent a partial searchable snapshot index?
35+
*
36+
* Both index.store.type and index.store.snapshot.partial must be supplied.
37+
*/
38+
public static boolean isPartialSearchableSnapshotIndex(Map<Setting<?>, Object> indexSettings) {
39+
assert indexSettings.containsKey(INDEX_STORE_TYPE_SETTING) : "must include store type in map";
40+
assert indexSettings.get(SNAPSHOT_PARTIAL_SETTING) != null : "partial setting must be non-null in map (has default value)";
41+
return SNAPSHOT_DIRECTORY_FACTORY_KEY.equals(indexSettings.get(INDEX_STORE_TYPE_SETTING))
42+
&& (boolean) indexSettings.get(SNAPSHOT_PARTIAL_SETTING);
43+
}
44+
2345
public static final String CACHE_FETCH_ASYNC_THREAD_POOL_NAME = "searchable_snapshots_cache_fetch_async";
2446
public static final String CACHE_FETCH_ASYNC_THREAD_POOL_SETTING = "xpack.searchable_snapshots.cache_fetch_async_thread_pool";
2547

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/cluster/routing/allocation/DataTierAllocationDeciderTests.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
package org.elasticsearch.xpack.cluster.routing.allocation;
99

10+
import joptsimple.internal.Strings;
1011
import org.elasticsearch.Version;
1112
import org.elasticsearch.cluster.ClusterState;
1213
import org.elasticsearch.cluster.ESAllocationTestCase;
@@ -27,21 +28,26 @@
2728
import org.elasticsearch.cluster.routing.allocation.decider.Decision;
2829
import org.elasticsearch.cluster.routing.allocation.decider.ReplicaAfterPrimaryActiveAllocationDecider;
2930
import org.elasticsearch.cluster.routing.allocation.decider.SameShardAllocationDecider;
31+
import org.elasticsearch.common.Randomness;
3032
import org.elasticsearch.common.settings.ClusterSettings;
3133
import org.elasticsearch.common.settings.Setting;
3234
import org.elasticsearch.common.settings.Settings;
35+
import org.elasticsearch.index.IndexModule;
3336
import org.elasticsearch.index.shard.ShardId;
3437
import org.elasticsearch.snapshots.EmptySnapshotsInfoService;
3538
import org.elasticsearch.test.gateway.TestGatewayAllocator;
3639
import org.elasticsearch.xpack.core.DataTier;
40+
import org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshotsConstants;
3741

42+
import java.util.ArrayList;
3843
import java.util.Arrays;
3944
import java.util.Collections;
4045
import java.util.HashSet;
4146
import java.util.List;
4247
import java.util.Optional;
4348
import java.util.Set;
4449

50+
import static org.elasticsearch.xpack.core.DataTier.DATA_FROZEN;
4551
import static org.hamcrest.Matchers.containsString;
4652
import static org.hamcrest.Matchers.equalTo;
4753

@@ -704,6 +710,53 @@ public void testExistedClusterFilters() {
704710
"tier filters [data_hot,data_warm]"));
705711
}
706712

713+
public void testFrozenIllegalForRegularIndices() {
714+
List<String> tierList = new ArrayList<>(randomSubsetOf(DataTier.ALL_DATA_TIERS));
715+
if (tierList.contains(DATA_FROZEN) == false) {
716+
tierList.add(DATA_FROZEN);
717+
}
718+
Randomness.shuffle(tierList);
719+
720+
String value = Strings.join(tierList, ",");
721+
Setting<String> setting = randomTierSetting();
722+
Settings.Builder builder = Settings.builder().put(setting.getKey(), value);
723+
if (randomBoolean()) {
724+
builder.put(IndexModule.INDEX_STORE_TYPE_SETTING.getKey(), SearchableSnapshotsConstants.SNAPSHOT_DIRECTORY_FACTORY_KEY);
725+
}
726+
727+
Settings settings = builder.build();
728+
IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> setting.get(settings));
729+
assertThat(exception.getMessage(), equalTo("[data_frozen] tier can only be used for partial searchable snapshots"));
730+
}
731+
732+
public void testFrozenLegalForPartialSnapshot() {
733+
List<String> tierList = new ArrayList<>(randomSubsetOf(DataTier.ALL_DATA_TIERS));
734+
if (tierList.contains(DATA_FROZEN) == false) {
735+
tierList.add(DATA_FROZEN);
736+
}
737+
Randomness.shuffle(tierList);
738+
739+
String value = Strings.join(tierList, ",");
740+
Setting<String> setting = randomTierSetting();
741+
Settings.Builder builder = Settings.builder().put(setting.getKey(), value);
742+
builder.put(IndexModule.INDEX_STORE_TYPE_SETTING.getKey(), SearchableSnapshotsConstants.SNAPSHOT_DIRECTORY_FACTORY_KEY);
743+
builder.put(SearchableSnapshotsConstants.SNAPSHOT_PARTIAL_SETTING.getKey(), true);
744+
745+
Settings settings = builder.build();
746+
747+
// validate do not throw
748+
assertThat(setting.get(settings), equalTo(value));
749+
}
750+
751+
public Setting<String> randomTierSetting() {
752+
//noinspection unchecked
753+
return randomFrom(
754+
DataTierAllocationDecider.INDEX_ROUTING_EXCLUDE_SETTING,
755+
DataTierAllocationDecider.INDEX_ROUTING_INCLUDE_SETTING,
756+
DataTierAllocationDecider.INDEX_ROUTING_REQUIRE_SETTING,
757+
DataTierAllocationDecider.INDEX_ROUTING_PREFER_SETTING);
758+
}
759+
707760
private ClusterState prepareState(ClusterState initialState) {
708761
return prepareState(initialState, Settings.EMPTY);
709762
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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.index.IndexModule;
11+
import org.elasticsearch.test.ESTestCase;
12+
13+
import java.util.Map;
14+
15+
import static org.hamcrest.Matchers.is;
16+
17+
public class SearchableSnapshotsConstantsTests extends ESTestCase {
18+
19+
public void testIsPartialSearchableSnapshotIndex() {
20+
assertThat(SearchableSnapshotsConstants.isPartialSearchableSnapshotIndex(
21+
Map.of(IndexModule.INDEX_STORE_TYPE_SETTING, SearchableSnapshotsConstants.SNAPSHOT_DIRECTORY_FACTORY_KEY,
22+
SearchableSnapshotsConstants.SNAPSHOT_PARTIAL_SETTING, false)),
23+
is(false));
24+
25+
assertThat(SearchableSnapshotsConstants.isPartialSearchableSnapshotIndex(
26+
Map.of(IndexModule.INDEX_STORE_TYPE_SETTING, "abc",
27+
SearchableSnapshotsConstants.SNAPSHOT_PARTIAL_SETTING, randomBoolean())),
28+
is(false));
29+
30+
assertThat(SearchableSnapshotsConstants.isPartialSearchableSnapshotIndex(
31+
Map.of(IndexModule.INDEX_STORE_TYPE_SETTING, SearchableSnapshotsConstants.SNAPSHOT_DIRECTORY_FACTORY_KEY,
32+
SearchableSnapshotsConstants.SNAPSHOT_PARTIAL_SETTING, true)),
33+
is(true));
34+
}
35+
}

x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/BaseSearchableSnapshotsIntegTestCase.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ protected void mountSnapshot(
143143
storage
144144
);
145145

146-
final RestoreSnapshotResponse restoreResponse = client().execute(MountSearchableSnapshotAction.INSTANCE, mountRequest).get();
146+
final RestoreSnapshotResponse restoreResponse = client().execute(MountSearchableSnapshotAction.INSTANCE, mountRequest).actionGet();
147147
assertThat(restoreResponse.getRestoreInfo().successfulShards(), equalTo(getNumShards(restoredIndexName).numPrimaries));
148148
assertThat(restoreResponse.getRestoreInfo().failedShards(), equalTo(0));
149149
}

0 commit comments

Comments
 (0)