Skip to content

Commit f69cb27

Browse files
authored
[7.x] Restrict ILM frozen phase to searchable snapshot actions only (#70158) (#70170)
Backports the following commits to 7.x: Restrict ILM frozen phase to searchable snapshot actions only (#70158) Remove "frozen" from read only tests (#70173)
1 parent ce03589 commit f69cb27

File tree

11 files changed

+118
-211
lines changed

11 files changed

+118
-211
lines changed

build.gradle

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

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

docs/reference/ilm/actions/ilm-searchable-snapshot.asciidoc

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ To keep the snapshot, set `delete_searchable_snapshot` to `false` in the delete
3636
`snapshot_repository`::
3737
(Required, string)
3838
Specifies where to store the snapshot.
39-
See <<snapshots-register-repository>> for more information.
39+
See <<snapshots-register-repository>> for more information. In non-frozen phases the snapshot will
40+
be mounted as a `full_copy`, and in frozen phases mounted with the `shared_cache` storage type.
4041

4142
`force_merge_index`::
4243
(Optional, Boolean)
@@ -52,12 +53,6 @@ the shards are relocating, in which case they will not be merged.
5253
The `searchable_snapshot` action will continue executing even if not all shards
5354
are force merged.
5455

55-
`storage`::
56-
(Optional, string)
57-
Specifies the type of snapshot that should be mounted for a searchable snapshot. This corresponds to
58-
the <<searchable-snapshots-api-mount-query-params, `storage` option when mounting a snapshot>>.
59-
Defaults to `full_copy` in non-frozen phases, or `shared_cache` in the frozen phase.
60-
6156
[[ilm-searchable-snapshot-ex]]
6257
==== Examples
6358
[source,console]
@@ -69,8 +64,7 @@ PUT _ilm/policy/my_policy
6964
"cold": {
7065
"actions": {
7166
"searchable_snapshot" : {
72-
"snapshot_repository" : "backing_repo",
73-
"storage": "shared_cache"
67+
"snapshot_repository" : "backing_repo"
7468
}
7569
}
7670
}

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/SearchableSnapshotAction.java

Lines changed: 10 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import org.elasticsearch.cluster.health.ClusterHealthStatus;
1414
import org.elasticsearch.cluster.metadata.IndexAbstraction;
1515
import org.elasticsearch.cluster.metadata.IndexMetadata;
16-
import org.elasticsearch.common.Nullable;
1716
import org.elasticsearch.common.ParseField;
1817
import org.elasticsearch.common.Strings;
1918
import org.elasticsearch.common.io.stream.StreamInput;
@@ -44,7 +43,6 @@ public class SearchableSnapshotAction implements LifecycleAction {
4443

4544
public static final ParseField SNAPSHOT_REPOSITORY = new ParseField("snapshot_repository");
4645
public static final ParseField FORCE_MERGE_INDEX = new ParseField("force_merge_index");
47-
public static final ParseField STORAGE = new ParseField("storage");
4846
public static final String CONDITIONAL_DATASTREAM_CHECK_KEY = BranchingStep.NAME + "-on-datastream-check";
4947
public static final String CONDITIONAL_SKIP_ACTION_STEP = BranchingStep.NAME + "-check-prerequisites";
5048
public static final String CONDITIONAL_SKIP_GENERATE_AND_CLEAN = BranchingStep.NAME + "-check-existing-snapshot";
@@ -53,21 +51,11 @@ public class SearchableSnapshotAction implements LifecycleAction {
5351
public static final String PARTIAL_RESTORED_INDEX_PREFIX = "partial-";
5452

5553
private static final ConstructingObjectParser<SearchableSnapshotAction, Void> PARSER = new ConstructingObjectParser<>(NAME,
56-
a -> {
57-
String storageName = (String) a[2];
58-
final MountSearchableSnapshotRequest.Storage storageType;
59-
if (storageName == null) {
60-
storageType = null;
61-
} else {
62-
storageType = MountSearchableSnapshotRequest.Storage.fromString(storageName);
63-
}
64-
return new SearchableSnapshotAction((String) a[0], a[1] == null || (boolean) a[1], storageType);
65-
});
54+
a -> new SearchableSnapshotAction((String) a[0], a[1] == null || (boolean) a[1]));
6655

6756
static {
6857
PARSER.declareString(ConstructingObjectParser.constructorArg(), SNAPSHOT_REPOSITORY);
6958
PARSER.declareBoolean(ConstructingObjectParser.optionalConstructorArg(), FORCE_MERGE_INDEX);
70-
PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), STORAGE);
7159
}
7260

7361

@@ -77,21 +65,17 @@ public static SearchableSnapshotAction parse(XContentParser parser) {
7765

7866
private final String snapshotRepository;
7967
private final boolean forceMergeIndex;
80-
@Nullable
81-
private final MountSearchableSnapshotRequest.Storage storageType;
8268

83-
public SearchableSnapshotAction(String snapshotRepository, boolean forceMergeIndex,
84-
@Nullable MountSearchableSnapshotRequest.Storage type) {
69+
public SearchableSnapshotAction(String snapshotRepository, boolean forceMergeIndex) {
8570
if (Strings.hasText(snapshotRepository) == false) {
8671
throw new IllegalArgumentException("the snapshot repository must be specified");
8772
}
8873
this.snapshotRepository = snapshotRepository;
8974
this.forceMergeIndex = forceMergeIndex;
90-
this.storageType = type;
9175
}
9276

9377
public SearchableSnapshotAction(String snapshotRepository) {
94-
this(snapshotRepository, true, null);
78+
this(snapshotRepository, true);
9579
}
9680

9781
public SearchableSnapshotAction(StreamInput in) throws IOException {
@@ -101,22 +85,12 @@ public SearchableSnapshotAction(StreamInput in) throws IOException {
10185
} else {
10286
this.forceMergeIndex = true;
10387
}
104-
if (in.getVersion().onOrAfter(Version.V_7_12_0)) {
105-
this.storageType = in.readOptionalEnum(MountSearchableSnapshotRequest.Storage.class);
106-
} else {
107-
this.storageType = null;
108-
}
10988
}
11089

11190
boolean isForceMergeIndex() {
11291
return forceMergeIndex;
11392
}
11493

115-
@Nullable
116-
public MountSearchableSnapshotRequest.Storage getStorageType() {
117-
return storageType;
118-
}
119-
12094
public String getSnapshotRepository() {
12195
return snapshotRepository;
12296
}
@@ -290,28 +264,15 @@ public List<Step> toSteps(Client client, String phase, StepKey nextStepKey) {
290264
* Resolves the prefix to be used for the mounted index depending on the provided key
291265
*/
292266
String getRestoredIndexPrefix(StepKey currentKey) {
293-
if (storageType == null) {
294-
if (currentKey.getPhase().equals(TimeseriesLifecycleType.FROZEN_PHASE)) {
295-
return PARTIAL_RESTORED_INDEX_PREFIX;
296-
} else {
297-
return FULL_RESTORED_INDEX_PREFIX;
298-
}
299-
}
300-
switch (storageType) {
301-
case FULL_COPY:
302-
return FULL_RESTORED_INDEX_PREFIX;
303-
case SHARED_CACHE:
304-
return PARTIAL_RESTORED_INDEX_PREFIX;
305-
default:
306-
throw new IllegalArgumentException("unexpected storage type: " + storageType);
267+
if (currentKey.getPhase().equals(TimeseriesLifecycleType.FROZEN_PHASE)) {
268+
return PARTIAL_RESTORED_INDEX_PREFIX;
269+
} else {
270+
return FULL_RESTORED_INDEX_PREFIX;
307271
}
308272
}
309273

310-
// Resolves the storage type from a Nullable to non-Nullable type
274+
// Resolves the storage type depending on which phase the index is in
311275
MountSearchableSnapshotRequest.Storage getConcreteStorageType(StepKey currentKey) {
312-
if (storageType != null) {
313-
return storageType;
314-
}
315276
if (currentKey.getPhase().equals(TimeseriesLifecycleType.FROZEN_PHASE)) {
316277
return MountSearchableSnapshotRequest.Storage.SHARED_CACHE;
317278
} else {
@@ -335,19 +296,13 @@ public void writeTo(StreamOutput out) throws IOException {
335296
if (out.getVersion().onOrAfter(Version.V_7_10_0)) {
336297
out.writeBoolean(forceMergeIndex);
337298
}
338-
if (out.getVersion().onOrAfter(Version.V_7_12_0)) {
339-
out.writeOptionalEnum(storageType);
340-
}
341299
}
342300

343301
@Override
344302
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
345303
builder.startObject();
346304
builder.field(SNAPSHOT_REPOSITORY.getPreferredName(), snapshotRepository);
347305
builder.field(FORCE_MERGE_INDEX.getPreferredName(), forceMergeIndex);
348-
if (storageType != null) {
349-
builder.field(STORAGE.getPreferredName(), storageType);
350-
}
351306
builder.endObject();
352307
return builder;
353308
}
@@ -361,12 +316,11 @@ public boolean equals(Object o) {
361316
return false;
362317
}
363318
SearchableSnapshotAction that = (SearchableSnapshotAction) o;
364-
return Objects.equals(snapshotRepository, that.snapshotRepository) &&
365-
Objects.equals(storageType, that.storageType);
319+
return Objects.equals(snapshotRepository, that.snapshotRepository);
366320
}
367321

368322
@Override
369323
public int hashCode() {
370-
return Objects.hash(snapshotRepository, storageType);
324+
return Objects.hash(snapshotRepository);
371325
}
372326
}

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/TimeseriesLifecycleType.java

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,7 @@ public class TimeseriesLifecycleType implements LifecycleType {
5050
static final List<String> ORDERED_VALID_WARM_ACTIONS = Arrays.asList(SetPriorityAction.NAME, UnfollowAction.NAME, ReadOnlyAction.NAME,
5151
AllocateAction.NAME, MigrateAction.NAME, ShrinkAction.NAME, ForceMergeAction.NAME);
5252
static final List<String> ORDERED_VALID_COLD_ACTIONS;
53-
static final List<String> ORDERED_VALID_FROZEN_ACTIONS = Arrays.asList(SetPriorityAction.NAME, UnfollowAction.NAME,
54-
ReadOnlyAction.NAME, SearchableSnapshotAction.NAME, AllocateAction.NAME, MigrateAction.NAME, FreezeAction.NAME);
53+
static final List<String> ORDERED_VALID_FROZEN_ACTIONS = Collections.singletonList(SearchableSnapshotAction.NAME);
5554
static final List<String> ORDERED_VALID_DELETE_ACTIONS = Arrays.asList(WaitForSnapshotAction.NAME, DeleteAction.NAME);
5655
static final Set<String> VALID_HOT_ACTIONS;
5756
static final Set<String> VALID_WARM_ACTIONS = Sets.newHashSet(ORDERED_VALID_WARM_ACTIONS);
@@ -136,10 +135,8 @@ static boolean shouldInjectMigrateStepForPhase(Phase phase) {
136135
}
137136
}
138137

139-
if (phase.getActions().get(SearchableSnapshotAction.NAME) != null && phase.getName().equals(FROZEN_PHASE) == false) {
140-
// the `searchable_snapshot` action defines migration rules itself, so no need to inject a migrate action, unless we're in the
141-
// frozen phase (as the migrate action would also include the `data_frozen` role which is not guaranteed to be included by all
142-
// types of searchable snapshots)
138+
if (phase.getActions().get(SearchableSnapshotAction.NAME) != null) {
139+
// Searchable snapshots automatically set their own allocation rules, no need to configure them with a migrate step.
143140
return false;
144141
}
145142

@@ -301,6 +298,7 @@ public void validate(Collection<Phase> phases) {
301298

302299
validateActionsFollowingSearchableSnapshot(phases);
303300
validateAllSearchableSnapshotActionsUseSameRepository(phases);
301+
validateFrozenPhaseHasSearchableSnapshotAction(phases);
304302
}
305303

306304
static void validateActionsFollowingSearchableSnapshot(Collection<Phase> phases) {
@@ -413,6 +411,22 @@ public static String validateMonotonicallyIncreasingPhaseTimings(Collection<Phas
413411
return Strings.collectionToCommaDelimitedString(errors);
414412
}
415413

414+
/**
415+
* Require that the "frozen" phase configured in a policy has a searchable snapshot action.
416+
*/
417+
static void validateFrozenPhaseHasSearchableSnapshotAction(Collection<Phase> phases) {
418+
Optional<Phase> maybeFrozenPhase = phases.stream()
419+
.filter(p -> FROZEN_PHASE.equals(p.getName()))
420+
.findFirst();
421+
422+
maybeFrozenPhase.ifPresent(p -> {
423+
if (p.getActions().containsKey(SearchableSnapshotAction.NAME) == false) {
424+
throw new IllegalArgumentException("policy specifies the [" + FROZEN_PHASE + "] phase without a corresponding [" +
425+
SearchableSnapshotAction.NAME + "] action, but a searchable snapshot action is required in the frozen phase");
426+
}
427+
});
428+
}
429+
416430
private static boolean definesAllocationRules(AllocateAction action) {
417431
return action.getRequire().isEmpty() == false || action.getInclude().isEmpty() == false || action.getExclude().isEmpty() == false;
418432
}

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/LifecyclePolicyTests.java

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@
2929
import java.util.Map;
3030
import java.util.Set;
3131
import java.util.function.Function;
32+
import java.util.stream.Collectors;
3233

33-
import static org.elasticsearch.xpack.core.ilm.SearchableSnapshotActionTests.randomStorageType;
3434
import static org.hamcrest.Matchers.equalTo;
3535
import static org.hamcrest.Matchers.instanceOf;
3636
import static org.mockito.Mockito.mock;
@@ -128,7 +128,10 @@ public static LifecyclePolicy randomTimeseriesLifecyclePolicyWithAllPhases(@Null
128128

129129
public static LifecyclePolicy randomTimeseriesLifecyclePolicy(@Nullable String lifecycleName) {
130130
List<String> phaseNames = randomSubsetOf(
131-
between(0, TimeseriesLifecycleType.ORDERED_VALID_PHASES.size() - 1), TimeseriesLifecycleType.ORDERED_VALID_PHASES);
131+
between(0, TimeseriesLifecycleType.ORDERED_VALID_PHASES.size() - 1), TimeseriesLifecycleType.ORDERED_VALID_PHASES).stream()
132+
// Remove the frozen phase, we'll randomly re-add it later
133+
.filter(pn -> TimeseriesLifecycleType.FROZEN_PHASE.equals(pn) == false)
134+
.collect(Collectors.toList());
132135
Map<String, Phase> phases = new HashMap<>(phaseNames.size());
133136
Function<String, Set<String>> validActions = getPhaseToValidActions();
134137
Function<String, LifecycleAction> randomAction = getNameToActionFunction();
@@ -189,6 +192,17 @@ public static LifecyclePolicy randomTimeseriesLifecyclePolicy(@Nullable String l
189192
}
190193
phases.put(phase, new Phase(phase, after, actions));
191194
}
195+
// Add a frozen phase if neither the hot nor cold phase contains a searchable snapshot action
196+
if (hotPhaseContainsSearchableSnap == false && coldPhaseContainsSearchableSnap == false && randomBoolean()) {
197+
TimeValue frozenTime = prev == null ? TimeValue.parseTimeValue(randomTimeValue(0, 100000, "s", "m", "h", "d"), "test") :
198+
TimeValue.timeValueSeconds(prev.seconds() + randomIntBetween(60, 600));
199+
phases.put(TimeseriesLifecycleType.FROZEN_PHASE,
200+
new Phase(TimeseriesLifecycleType.FROZEN_PHASE, frozenTime,
201+
Collections.singletonMap(SearchableSnapshotAction.NAME,
202+
new SearchableSnapshotAction(randomAlphaOfLength(10), randomBoolean()))));
203+
} else {
204+
phases.remove(TimeseriesLifecycleType.FROZEN_PHASE);
205+
}
192206
return new LifecyclePolicy(TimeseriesLifecycleType.INSTANCE, lifecycleName, phases);
193207
}
194208

@@ -234,7 +248,7 @@ private static Function<String, LifecycleAction> getNameToActionFunction() {
234248
case UnfollowAction.NAME:
235249
return new UnfollowAction();
236250
case SearchableSnapshotAction.NAME:
237-
return new SearchableSnapshotAction("repo", randomBoolean(), randomStorageType());
251+
return new SearchableSnapshotAction("repo", randomBoolean());
238252
case MigrateAction.NAME:
239253
return new MigrateAction(false);
240254
case RollupILMAction.NAME:
@@ -269,8 +283,16 @@ protected LifecyclePolicy mutateInstance(LifecyclePolicy instance) throws IOExce
269283
name = name + randomAlphaOfLengthBetween(1, 5);
270284
break;
271285
case 1:
286+
// Remove the frozen phase, because it makes a lot of invalid phases when randomly mutating an existing policy
287+
phases.remove(TimeseriesLifecycleType.FROZEN_PHASE);
288+
// Remove a random phase
289+
if (phases.size() > 0) {
290+
phases.remove(new ArrayList<>(phases.keySet()).remove(randomIntBetween(0, phases.size() - 1)));
291+
}
272292
String phaseName = randomValueOtherThanMany(phases::containsKey,
273-
() -> randomFrom(TimeseriesLifecycleType.ORDERED_VALID_PHASES));
293+
() -> randomFrom(TimeseriesLifecycleType.ORDERED_VALID_PHASES.stream()
294+
.filter(pn -> TimeseriesLifecycleType.FROZEN_PHASE.equals(pn) == false)
295+
.collect(Collectors.toList())));
274296
phases = new LinkedHashMap<>(phases);
275297
phases.put(phaseName, new Phase(phaseName, null, Collections.emptyMap()));
276298
break;

0 commit comments

Comments
 (0)