-
Notifications
You must be signed in to change notification settings - Fork 25.2k
ILM: add searchable snapshot action #52585
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
25cea2b
3c32490
7dbc46e
9881c44
a601dbf
f0a9da1
dfec7c9
a6bc720
271fb54
f6f7d29
7075752
501dbaf
1222902
faa2834
21d67cb
7e0d5f7
eafe3de
bc91ae3
8520c7b
9735649
d20cf52
8a46d05
12c1f5f
3870388
d28557c
357987b
6960187
9af943f
858e6a1
6edfcd0
e4791b0
63194ea
a92463f
0f65ed7
6e779b6
841f6bc
da361b5
5eb524e
8e8ff99
119c439
064b4e7
1cb6480
3a750e0
0a79753
013d4df
aa098d2
520d6bb
5e4cf55
32e3371
ac9e9c3
b68b61c
374e50d
4f25ce9
0f6f371
15d39ed
e76ff6c
ff7623f
3c56f73
06bf054
c3d785e
9d37f78
985477e
2e4bd2b
cd73bd6
0ca1db0
2b467cf
5f506ee
80c9a45
8ed70c8
c38e081
c032d3a
7107b1a
5bc6df2
5b7f599
7f1940d
879e7d6
01222af
64a903d
6e4ede4
864f4c3
9bbb9df
2c61f00
ac574b2
567d067
b766806
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
package org.elasticsearch.xpack.core.ilm; | ||
|
||
import org.elasticsearch.action.ActionListener; | ||
import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest; | ||
import org.elasticsearch.action.support.master.AcknowledgedResponse; | ||
import org.elasticsearch.client.Client; | ||
import org.elasticsearch.cluster.ClusterState; | ||
import org.elasticsearch.cluster.ClusterStateObserver; | ||
import org.elasticsearch.cluster.metadata.IndexMetaData; | ||
import org.elasticsearch.common.Strings; | ||
import org.elasticsearch.snapshots.SnapshotMissingException; | ||
|
||
import static org.elasticsearch.xpack.core.ilm.LifecycleExecutionState.fromIndexMetadata; | ||
|
||
/** | ||
* Deletes the snapshot designated by the snapshot name present in the lifecycle execution state, hosted in the configured repository. | ||
*/ | ||
public class CleanupSnapshotStep extends AsyncActionStep { | ||
public static final String NAME = "cleanup-snapshot"; | ||
|
||
private final String snapshotRepository; | ||
|
||
public CleanupSnapshotStep(StepKey key, StepKey nextStepKey, Client client, String snapshotRepository) { | ||
super(key, nextStepKey, client); | ||
this.snapshotRepository = snapshotRepository; | ||
} | ||
|
||
@Override | ||
public boolean isRetryable() { | ||
return true; | ||
} | ||
|
||
@Override | ||
public void performAction(IndexMetaData indexMetaData, ClusterState currentClusterState, ClusterStateObserver observer, | ||
Listener listener) { | ||
final String indexName = indexMetaData.getIndex().getName(); | ||
|
||
LifecycleExecutionState lifecycleState = fromIndexMetadata(indexMetaData); | ||
final String snapshotName = lifecycleState.getSnapshotName(); | ||
if (Strings.hasText(snapshotName) == false) { | ||
String policyName = indexMetaData.getSettings().get(LifecycleSettings.LIFECYCLE_NAME); | ||
listener.onFailure( | ||
new IllegalStateException("snapshot name was not generated for policy [" + policyName + "] and index [" + indexName + "]") | ||
); | ||
return; | ||
} | ||
DeleteSnapshotRequest deleteSnapshotRequest = new DeleteSnapshotRequest(snapshotRepository, snapshotName); | ||
getClient().admin().cluster().deleteSnapshot(deleteSnapshotRequest, new ActionListener<>() { | ||
|
||
@Override | ||
public void onResponse(AcknowledgedResponse acknowledgedResponse) { | ||
listener.onResponse(true); | ||
dakrone marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
@Override | ||
public void onFailure(Exception e) { | ||
if (e instanceof SnapshotMissingException) { | ||
dakrone marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// during the happy flow we generate a snapshot name and that snapshot doesn't exist in the repository | ||
listener.onResponse(true); | ||
} else { | ||
listener.onFailure(e); | ||
} | ||
} | ||
}); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
package org.elasticsearch.xpack.core.ilm; | ||
|
||
import org.elasticsearch.action.ActionListener; | ||
import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsRequest; | ||
import org.elasticsearch.client.Client; | ||
import org.elasticsearch.cluster.ClusterState; | ||
import org.elasticsearch.cluster.ClusterStateObserver; | ||
import org.elasticsearch.cluster.metadata.IndexMetaData; | ||
import org.elasticsearch.common.settings.Settings; | ||
|
||
import java.util.Objects; | ||
|
||
/** | ||
* Copy the provided settings from the source to the target index. | ||
* <p> | ||
* The target index is derived from the source index using the provided prefix. | ||
* This is useful for actions like shrink or searchable snapshot that create a new index and migrate the ILM execution from the source | ||
* to the target index. | ||
*/ | ||
public class CopySettingsStep extends AsyncActionStep { | ||
public static final String NAME = "copy-settings"; | ||
|
||
private final String[] settingsKeys; | ||
private final String indexPrefix; | ||
|
||
public CopySettingsStep(StepKey key, StepKey nextStepKey, Client client, String indexPrefix, String... settingsKeys) { | ||
super(key, nextStepKey, client); | ||
Objects.nonNull(indexPrefix); | ||
Objects.nonNull(settingsKeys); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These need to be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch (I hadn't even acknowledged the existence of an API that just does |
||
this.indexPrefix = indexPrefix; | ||
this.settingsKeys = settingsKeys; | ||
} | ||
|
||
@Override | ||
public boolean isRetryable() { | ||
return true; | ||
} | ||
|
||
@Override | ||
public void performAction(IndexMetaData indexMetaData, ClusterState currentState, ClusterStateObserver observer, Listener listener) { | ||
String indexName = indexPrefix + indexMetaData.getIndex().getName(); | ||
|
||
Settings.Builder settings = Settings.builder(); | ||
for (String key : settingsKeys) { | ||
String value = indexMetaData.getSettings().get(key); | ||
settings.put(key, value); | ||
} | ||
|
||
UpdateSettingsRequest updateSettingsRequest = new UpdateSettingsRequest(indexName) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently we do this as an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh interesting. I agree it'd be a better avenue |
||
.masterNodeTimeout(getMasterTimeout(currentState)) | ||
.settings(settings); | ||
getClient().admin().indices().updateSettings(updateSettingsRequest, | ||
ActionListener.wrap(response -> listener.onResponse(true), listener::onFailure)); | ||
} | ||
|
||
@Override | ||
public boolean equals(Object o) { | ||
if (this == o) { | ||
return true; | ||
} | ||
if (o == null || getClass() != o.getClass()) { | ||
return false; | ||
} | ||
if (!super.equals(o)) { | ||
return false; | ||
} | ||
CopySettingsStep that = (CopySettingsStep) o; | ||
return Objects.equals(settingsKeys, that.settingsKeys) && | ||
Objects.equals(indexPrefix, that.indexPrefix); | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(super.hashCode(), settingsKeys, indexPrefix); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
package org.elasticsearch.xpack.core.ilm; | ||
|
||
import org.apache.logging.log4j.LogManager; | ||
import org.apache.logging.log4j.Logger; | ||
import org.elasticsearch.action.support.IndicesOptions; | ||
import org.elasticsearch.cluster.ClusterState; | ||
import org.elasticsearch.cluster.metadata.IndexMetaData; | ||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; | ||
import org.elasticsearch.cluster.metadata.MetaData; | ||
import org.elasticsearch.common.UUIDs; | ||
import org.elasticsearch.index.Index; | ||
|
||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.Locale; | ||
|
||
import static org.elasticsearch.xpack.core.ilm.LifecycleExecutionState.ILM_CUSTOM_METADATA_KEY; | ||
import static org.elasticsearch.xpack.core.ilm.LifecycleExecutionState.fromIndexMetadata; | ||
|
||
/** | ||
* Generates a snapshot name for the given index and records it in the index metadata. | ||
*/ | ||
public class GenerateSnapshotNameStep extends ClusterStateActionStep { | ||
|
||
public static final String NAME = "generate-snapshot-name"; | ||
|
||
private static final Logger logger = LogManager.getLogger(TakeSnapshotStep.class); | ||
|
||
private static final IndexNameExpressionResolver.DateMathExpressionResolver DATE_MATH_RESOLVER = | ||
new IndexNameExpressionResolver.DateMathExpressionResolver(); | ||
|
||
public GenerateSnapshotNameStep(StepKey key, StepKey nextStepKey) { | ||
super(key, nextStepKey); | ||
} | ||
|
||
@Override | ||
public ClusterState performAction(Index index, ClusterState clusterState) { | ||
IndexMetaData indexMetaData = clusterState.metaData().index(index); | ||
if (indexMetaData == null) { | ||
// Index must have been since deleted, ignore it | ||
logger.debug("[{}] lifecycle action for index [{}] executed but index no longer exists", getKey().getAction(), index.getName()); | ||
return clusterState; | ||
} | ||
|
||
ClusterState.Builder newClusterStateBuilder = ClusterState.builder(clusterState); | ||
|
||
LifecycleExecutionState lifecycleState = fromIndexMetadata(indexMetaData); | ||
assert lifecycleState.getSnapshotName() == null : "index " + index.getName() + " should not have a snapshot generated by " + | ||
"the ilm policy but has " + lifecycleState.getSnapshotName(); | ||
LifecycleExecutionState.Builder newCustomData = LifecycleExecutionState.builder(lifecycleState); | ||
String policy = indexMetaData.getSettings().get(LifecycleSettings.LIFECYCLE_NAME); | ||
String snapshotName = generateSnapshotName(generateSnapshotName("<{now/M}-" + index.getName() + "-" + policy + ">")); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are we generating the snapshot name twice here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, do we think There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, this should use the same validation that |
||
newCustomData.setSnapshotName(snapshotName); | ||
|
||
IndexMetaData.Builder indexMetadataBuilder = IndexMetaData.builder(indexMetaData); | ||
indexMetadataBuilder.putCustom(ILM_CUSTOM_METADATA_KEY, newCustomData.build().asMap()); | ||
newClusterStateBuilder.metaData(MetaData.builder(clusterState.getMetaData()).put(indexMetadataBuilder)); | ||
return newClusterStateBuilder.build(); | ||
} | ||
|
||
/** | ||
* Since snapshots need to be uniquely named, this method will resolve any date math used in | ||
* the provided name, as well as appending a unique identifier so expressions that may overlap | ||
* still result in unique snapshot names. | ||
*/ | ||
public static String generateSnapshotName(String name) { | ||
return generateSnapshotName(name, new ResolverContext()); | ||
} | ||
|
||
public static String generateSnapshotName(String name, IndexNameExpressionResolver.Context context) { | ||
dakrone marked this conversation as resolved.
Show resolved
Hide resolved
|
||
List<String> candidates = DATE_MATH_RESOLVER.resolve(context, Collections.singletonList(name)); | ||
if (candidates.size() != 1) { | ||
throw new IllegalStateException("resolving snapshot name " + name + " generated more than one candidate: " + candidates); | ||
} | ||
// TODO: we are breaking the rules of UUIDs by lowercasing this here, find an alternative (snapshot names must be lowercase) | ||
return (candidates.get(0) + "-" + UUIDs.randomBase64UUID()).toLowerCase(Locale.ROOT); | ||
} | ||
|
||
/** | ||
* This is a context for the DateMathExpressionResolver, which does not require | ||
* {@code IndicesOptions} or {@code ClusterState} since it only uses the start | ||
* time to resolve expressions | ||
*/ | ||
public static final class ResolverContext extends IndexNameExpressionResolver.Context { | ||
public ResolverContext() { | ||
this(System.currentTimeMillis()); | ||
} | ||
|
||
public ResolverContext(long startTime) { | ||
super(null, null, startTime, false, false); | ||
} | ||
|
||
@Override | ||
public ClusterState getState() { | ||
throw new UnsupportedOperationException("should never be called"); | ||
} | ||
|
||
@Override | ||
public IndicesOptions getOptions() { | ||
throw new UnsupportedOperationException("should never be called"); | ||
} | ||
} | ||
|
||
} |
Uh oh!
There was an error while loading. Please reload this page.