Skip to content

Commit e070991

Browse files
authored
[ILM] More resilient when a policy is added to searchable snapshot (#102741) (#103064)
In this PR we enable ILM to handle the following scenarios: - An ILM policy with the a searchable snapshot action in hot or cold is added on a partially mounted searchable snapshot. - An ILM policy with the a searchable snapshot action in frozen is added on a fully mounted searchable snapshot. The searchable snapshot could have had a previous ILM policy that has been removed via POST <index>/_ilm/remove or it might not have been managed at all.
1 parent bf85643 commit e070991

File tree

5 files changed

+304
-39
lines changed

5 files changed

+304
-39
lines changed

docs/changelog/102741.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 102741
2+
summary: "[ILM] More resilient when a policy is added to searchable snapshot"
3+
area: ILM+SLM
4+
type: bug
5+
issues:
6+
- 101958

docs/reference/ilm/actions/ilm-delete.asciidoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ Defaults to `true`.
1616
This option is applicable when the <<ilm-searchable-snapshot,searchable
1717
snapshot>> action is used in any previous phase.
1818

19+
WARNING: If a policy with a searchable snapshot action is applied on an existing searchable snapshot index,
20+
the snapshot backing this index will NOT be deleted because it was not created by this policy. If you want
21+
to clean this snapshot, please delete it manually after the index is deleted using the <<delete-snapshot-api, delete snapshot API>>, you
22+
can find the repository and snapshot name using the <<indices-get-index, get index API>>.
23+
1924
[[ilm-delete-action-ex]]
2025
==== Example
2126

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

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -68,24 +68,32 @@ void performDuringNoSnapshot(IndexMetadata indexMetadata, ClusterState currentCl
6868
String indexName = indexMetadata.getIndex().getName();
6969

7070
LifecycleExecutionState lifecycleState = indexMetadata.getLifecycleExecutionState();
71+
SearchableSnapshotAction.SearchableSnapshotMetadata searchableSnapshotMetadata = SearchableSnapshotAction
72+
.extractSearchableSnapshotFromSettings(indexMetadata);
7173

7274
String policyName = indexMetadata.getLifecyclePolicyName();
73-
final String snapshotRepository = lifecycleState.snapshotRepository();
75+
String snapshotRepository = lifecycleState.snapshotRepository();
7476
if (Strings.hasText(snapshotRepository) == false) {
75-
listener.onFailure(
76-
new IllegalStateException(
77-
"snapshot repository is not present for policy [" + policyName + "] and index [" + indexName + "]"
78-
)
79-
);
80-
return;
77+
if (searchableSnapshotMetadata == null) {
78+
listener.onFailure(
79+
new IllegalStateException(
80+
"snapshot repository is not present for policy [" + policyName + "] and index [" + indexName + "]"
81+
)
82+
);
83+
return;
84+
} else {
85+
snapshotRepository = searchableSnapshotMetadata.repositoryName();
86+
}
8187
}
8288

83-
final String snapshotName = lifecycleState.snapshotName();
84-
if (Strings.hasText(snapshotName) == false) {
89+
String snapshotName = lifecycleState.snapshotName();
90+
if (Strings.hasText(snapshotName) == false && searchableSnapshotMetadata == null) {
8591
listener.onFailure(
8692
new IllegalStateException("snapshot name was not generated for policy [" + policyName + "] and index [" + indexName + "]")
8793
);
8894
return;
95+
} else if (searchableSnapshotMetadata != null) {
96+
snapshotName = searchableSnapshotMetadata.snapshotName();
8997
}
9098

9199
String mountedIndexName = restoredIndexPrefix + indexName;
@@ -102,16 +110,20 @@ void performDuringNoSnapshot(IndexMetadata indexMetadata, ClusterState currentCl
102110

103111
final String snapshotIndexName = lifecycleState.snapshotIndexName();
104112
if (snapshotIndexName == null) {
105-
// This index had its searchable snapshot created prior to a version where we captured
106-
// the original index name, so make our best guess at the name
107-
indexName = bestEffortIndexNameResolution(indexName);
108-
logger.debug(
109-
"index [{}] using policy [{}] does not have a stored snapshot index name, "
110-
+ "using our best effort guess of [{}] for the original snapshotted index name",
111-
indexMetadata.getIndex().getName(),
112-
policyName,
113-
indexName
114-
);
113+
if (searchableSnapshotMetadata == null) {
114+
// This index had its searchable snapshot created prior to a version where we captured
115+
// the original index name, so make our best guess at the name
116+
indexName = bestEffortIndexNameResolution(indexName);
117+
logger.debug(
118+
"index [{}] using policy [{}] does not have a stored snapshot index name, "
119+
+ "using our best effort guess of [{}] for the original snapshotted index name",
120+
indexMetadata.getIndex().getName(),
121+
policyName,
122+
indexName
123+
);
124+
} else {
125+
indexName = searchableSnapshotMetadata.sourceIndex();
126+
}
115127
} else {
116128
// Use the name of the snapshot as specified in the metadata, because the current index
117129
// name not might not reflect the name of the index actually in the snapshot

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

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.elasticsearch.common.Strings;
1717
import org.elasticsearch.common.io.stream.StreamInput;
1818
import org.elasticsearch.common.io.stream.StreamOutput;
19+
import org.elasticsearch.core.Nullable;
1920
import org.elasticsearch.license.LicenseUtils;
2021
import org.elasticsearch.license.XPackLicenseState;
2122
import org.elasticsearch.xcontent.ConstructingObjectParser;
@@ -32,6 +33,7 @@
3233
import java.util.Objects;
3334

3435
import static org.elasticsearch.snapshots.SearchableSnapshotsSettings.SEARCHABLE_SNAPSHOTS_REPOSITORY_NAME_SETTING_KEY;
36+
import static org.elasticsearch.snapshots.SearchableSnapshotsSettings.SEARCHABLE_SNAPSHOTS_SNAPSHOT_NAME_SETTING_KEY;
3537
import static org.elasticsearch.snapshots.SearchableSnapshotsSettings.SEARCHABLE_SNAPSHOT_PARTIAL_SETTING_KEY;
3638
import static org.elasticsearch.xpack.core.searchablesnapshots.SearchableSnapshotsConstants.SEARCHABLE_SNAPSHOT_FEATURE;
3739

@@ -141,10 +143,12 @@ public List<Step> toSteps(Client client, String phase, StepKey nextStepKey, XPac
141143
IndexMetadata indexMetadata = clusterState.getMetadata().index(index);
142144
assert indexMetadata != null : "index " + index.getName() + " must exist in the cluster state";
143145
String policyName = indexMetadata.getLifecyclePolicyName();
144-
if (indexMetadata.getSettings().get(LifecycleSettings.SNAPSHOT_INDEX_NAME) != null) {
146+
SearchableSnapshotMetadata searchableSnapshotMetadata = extractSearchableSnapshotFromSettings(indexMetadata);
147+
if (searchableSnapshotMetadata != null) {
148+
// TODO: allow this behavior instead of returning false, in this case the index is already a searchable a snapshot
149+
// so the most graceful way of recovery might be to use this repo
145150
// The index is already a searchable snapshot, let's see if the repository matches
146-
String repo = indexMetadata.getSettings().get(SEARCHABLE_SNAPSHOTS_REPOSITORY_NAME_SETTING_KEY);
147-
if (this.snapshotRepository.equals(repo) == false) {
151+
if (this.snapshotRepository.equals(searchableSnapshotMetadata.repositoryName) == false) {
148152
// Okay, different repo, we need to go ahead with the searchable snapshot
149153
logger.debug(
150154
"[{}] action is configured for index [{}] in policy [{}] which is already mounted as a searchable "
@@ -153,15 +157,14 @@ public List<Step> toSteps(Client client, String phase, StepKey nextStepKey, XPac
153157
SearchableSnapshotAction.NAME,
154158
index.getName(),
155159
policyName,
156-
repo,
160+
searchableSnapshotMetadata.repositoryName,
157161
this.snapshotRepository
158162
);
159163
return false;
160164
}
161165

162166
// Check to the storage type to see if we need to convert between full <-> partial
163-
final boolean partial = indexMetadata.getSettings().getAsBoolean(SEARCHABLE_SNAPSHOT_PARTIAL_SETTING_KEY, false);
164-
MountSearchableSnapshotRequest.Storage existingType = partial
167+
MountSearchableSnapshotRequest.Storage existingType = searchableSnapshotMetadata.partial
165168
? MountSearchableSnapshotRequest.Storage.SHARED_CACHE
166169
: MountSearchableSnapshotRequest.Storage.FULL_COPY;
167170
MountSearchableSnapshotRequest.Storage type = getConcreteStorageType(preActionBranchingKey);
@@ -172,7 +175,7 @@ public List<Step> toSteps(Client client, String phase, StepKey nextStepKey, XPac
172175
SearchableSnapshotAction.NAME,
173176
index.getName(),
174177
policyName,
175-
repo,
178+
searchableSnapshotMetadata.repositoryName,
176179
type
177180
);
178181
return true;
@@ -215,7 +218,7 @@ public List<Step> toSteps(Client client, String phase, StepKey nextStepKey, XPac
215218
// When generating a snapshot, we either jump to the force merge step, or we skip the
216219
// forcemerge and go straight to steps for creating the snapshot
217220
StepKey keyForSnapshotGeneration = forceMergeIndex ? forceMergeStepKey : generateSnapshotNameKey;
218-
// Branch, deciding whether there is an existing searchable snapshot snapshot that can be used for mounting the index
221+
// Branch, deciding whether there is an existing searchable snapshot that can be used for mounting the index
219222
// (in which case, skip generating a new name and the snapshot cleanup), or if we need to generate a new snapshot
220223
BranchingStep skipGeneratingSnapshotStep = new BranchingStep(
221224
skipGeneratingSnapshotKey,
@@ -225,7 +228,8 @@ public List<Step> toSteps(Client client, String phase, StepKey nextStepKey, XPac
225228
IndexMetadata indexMetadata = clusterState.getMetadata().index(index);
226229
String policyName = indexMetadata.getLifecyclePolicyName();
227230
LifecycleExecutionState lifecycleExecutionState = indexMetadata.getLifecycleExecutionState();
228-
if (lifecycleExecutionState.snapshotName() == null) {
231+
SearchableSnapshotMetadata searchableSnapshotMetadata = extractSearchableSnapshotFromSettings(indexMetadata);
232+
if (lifecycleExecutionState.snapshotName() == null && searchableSnapshotMetadata == null) {
229233
// No name exists, so it must be generated
230234
logger.trace(
231235
"no snapshot name for index [{}] in policy [{}] exists, so one will be generated",
@@ -234,8 +238,20 @@ public List<Step> toSteps(Client client, String phase, StepKey nextStepKey, XPac
234238
);
235239
return false;
236240
}
241+
String snapshotIndexName;
242+
String snapshotName;
243+
String repoName;
244+
if (lifecycleExecutionState.snapshotName() != null) {
245+
snapshotIndexName = lifecycleExecutionState.snapshotIndexName();
246+
snapshotName = lifecycleExecutionState.snapshotName();
247+
repoName = lifecycleExecutionState.snapshotRepository();
248+
} else {
249+
snapshotIndexName = searchableSnapshotMetadata.sourceIndex;
250+
snapshotName = searchableSnapshotMetadata.snapshotName;
251+
repoName = searchableSnapshotMetadata.repositoryName;
252+
}
237253

238-
if (this.snapshotRepository.equals(lifecycleExecutionState.snapshotRepository()) == false) {
254+
if (this.snapshotRepository.equals(repoName) == false) {
239255
// A different repository is being used
240256
// TODO: allow this behavior instead of throwing an exception
241257
throw new IllegalArgumentException("searchable snapshot indices may be converted only within the same repository");
@@ -244,12 +260,14 @@ public List<Step> toSteps(Client client, String phase, StepKey nextStepKey, XPac
244260
// We can skip the generate, initial cleanup, and snapshot taking for this index, as we already have a generated snapshot.
245261
// This will jump ahead directly to the "mount snapshot" step
246262
logger.debug(
247-
"an existing snapshot [{}] in repository [{}] (index name: [{}]) "
248-
+ "will be used for mounting [{}] as a searchable snapshot",
249-
lifecycleExecutionState.snapshotName(),
250-
lifecycleExecutionState.snapshotRepository(),
251-
lifecycleExecutionState.snapshotIndexName(),
252-
index.getName()
263+
"Policy [{}] will use an existing snapshot [{}] in repository [{}] (index name: [{}]) "
264+
+ "to mount [{}] as a searchable snapshot. This snapshot was found in the {}.",
265+
policyName,
266+
snapshotName,
267+
snapshotRepository,
268+
snapshotIndexName,
269+
index.getName(),
270+
lifecycleExecutionState.snapshotName() != null ? "lifecycle execution state" : "metadata of " + index.getName()
253271
);
254272
return true;
255273
}
@@ -411,4 +429,18 @@ public boolean equals(Object o) {
411429
public int hashCode() {
412430
return Objects.hash(snapshotRepository, forceMergeIndex);
413431
}
432+
433+
@Nullable
434+
static SearchableSnapshotMetadata extractSearchableSnapshotFromSettings(IndexMetadata indexMetadata) {
435+
String indexName = indexMetadata.getSettings().get(LifecycleSettings.SNAPSHOT_INDEX_NAME);
436+
if (indexName == null) {
437+
return null;
438+
}
439+
String snapshotName = indexMetadata.getSettings().get(SEARCHABLE_SNAPSHOTS_SNAPSHOT_NAME_SETTING_KEY);
440+
String repo = indexMetadata.getSettings().get(SEARCHABLE_SNAPSHOTS_REPOSITORY_NAME_SETTING_KEY);
441+
final boolean partial = indexMetadata.getSettings().getAsBoolean(SEARCHABLE_SNAPSHOT_PARTIAL_SETTING_KEY, false);
442+
return new SearchableSnapshotMetadata(indexName, repo, snapshotName, partial);
443+
}
444+
445+
record SearchableSnapshotMetadata(String sourceIndex, String repositoryName, String snapshotName, boolean partial) {};
414446
}

0 commit comments

Comments
 (0)