diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClientPlugin.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClientPlugin.java index 3e33a9ac51e9a..9e5a40a0f742b 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClientPlugin.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClientPlugin.java @@ -52,13 +52,14 @@ import org.elasticsearch.xpack.core.indexlifecycle.RolloverAction; import org.elasticsearch.xpack.core.indexlifecycle.ShrinkAction; import org.elasticsearch.xpack.core.indexlifecycle.TimeseriesLifecycleType; -import org.elasticsearch.xpack.core.indexlifecycle.action.SetPolicyForIndexAction; import org.elasticsearch.xpack.core.indexlifecycle.action.DeleteLifecycleAction; import org.elasticsearch.xpack.core.indexlifecycle.action.ExplainLifecycleAction; import org.elasticsearch.xpack.core.indexlifecycle.action.GetLifecycleAction; import org.elasticsearch.xpack.core.indexlifecycle.action.MoveToStepAction; import org.elasticsearch.xpack.core.indexlifecycle.action.PutLifecycleAction; +import org.elasticsearch.xpack.core.indexlifecycle.action.RemovePolicyForIndexAction; import org.elasticsearch.xpack.core.indexlifecycle.action.RetryAction; +import org.elasticsearch.xpack.core.indexlifecycle.action.SetPolicyForIndexAction; import org.elasticsearch.xpack.core.logstash.LogstashFeatureSetUsage; import org.elasticsearch.xpack.core.ml.MachineLearningFeatureSetUsage; import org.elasticsearch.xpack.core.ml.MlMetadata; @@ -333,6 +334,7 @@ public List getClientActions() { PutLifecycleAction.INSTANCE, ExplainLifecycleAction.INSTANCE, SetPolicyForIndexAction.INSTANCE, + RemovePolicyForIndexAction.INSTANCE, MoveToStepAction.INSTANCE, RetryAction.INSTANCE ); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/action/RemovePolicyForIndexAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/action/RemovePolicyForIndexAction.java new file mode 100644 index 0000000000000..ed03c731e0c8a --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/action/RemovePolicyForIndexAction.java @@ -0,0 +1,185 @@ +/* + * 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.indexlifecycle.action; + +import org.elasticsearch.action.Action; +import org.elasticsearch.action.ActionRequestValidationException; +import org.elasticsearch.action.ActionResponse; +import org.elasticsearch.action.IndicesRequest; +import org.elasticsearch.action.support.IndicesOptions; +import org.elasticsearch.action.support.master.AcknowledgedRequest; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +public class RemovePolicyForIndexAction extends Action { + public static final RemovePolicyForIndexAction INSTANCE = new RemovePolicyForIndexAction(); + public static final String NAME = "indices:admin/xpack/index_lifecycle/remove_policy"; + + protected RemovePolicyForIndexAction() { + super(NAME); + } + + @Override + public RemovePolicyForIndexAction.Response newResponse() { + return new Response(); + } + + public static class Response extends ActionResponse implements ToXContentObject { + + public static final ParseField HAS_FAILURES_FIELD = new ParseField("has_failures"); + public static final ParseField FAILED_INDEXES_FIELD = new ParseField("failed_indexes"); + @SuppressWarnings("unchecked") + public static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( + "change_policy_for_index_response", a -> new Response((List) a[0])); + static { + PARSER.declareStringArray(ConstructingObjectParser.constructorArg(), FAILED_INDEXES_FIELD); + // Needs to be declared but not used in constructing the response object + PARSER.declareBoolean(ConstructingObjectParser.constructorArg(), HAS_FAILURES_FIELD); + } + + private List failedIndexes; + + public Response() { + } + + public Response(List failedIndexes) { + if (failedIndexes == null) { + throw new IllegalArgumentException(FAILED_INDEXES_FIELD.getPreferredName() + " cannot be null"); + } + this.failedIndexes = failedIndexes; + } + + public List getFailedIndexes() { + return failedIndexes; + } + + public boolean hasFailures() { + return failedIndexes.isEmpty() == false; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field(HAS_FAILURES_FIELD.getPreferredName(), hasFailures()); + builder.field(FAILED_INDEXES_FIELD.getPreferredName(), failedIndexes); + builder.endObject(); + return builder; + } + + @Override + public void readFrom(StreamInput in) throws IOException { + super.readFrom(in); + failedIndexes = in.readList(StreamInput::readString); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeStringList(failedIndexes); + } + + @Override + public int hashCode() { + return Objects.hash(failedIndexes); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Response other = (Response) obj; + return Objects.equals(failedIndexes, other.failedIndexes); + } + + } + + public static class Request extends AcknowledgedRequest implements IndicesRequest.Replaceable { + + private String[] indices; + private IndicesOptions indicesOptions = IndicesOptions.strictExpandOpen(); + + public Request() { + } + + public Request(String... indices) { + if (indices == null) { + throw new IllegalArgumentException("indices cannot be null"); + } + this.indices = indices; + } + + @Override + public Request indices(String... indices) { + this.indices = indices; + return this; + } + + @Override + public String[] indices() { + return indices; + } + + public void indicesOptions(IndicesOptions indicesOptions) { + this.indicesOptions = indicesOptions; + } + + public IndicesOptions indicesOptions() { + return indicesOptions; + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + @Override + public void readFrom(StreamInput in) throws IOException { + super.readFrom(in); + indices = in.readStringArray(); + indicesOptions = IndicesOptions.readIndicesOptions(in); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeStringArray(indices); + indicesOptions.writeIndicesOptions(out); + } + + @Override + public int hashCode() { + return Objects.hash(Arrays.hashCode(indices), indicesOptions); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Request other = (Request) obj; + return Objects.deepEquals(indices, other.indices) && + Objects.equals(indicesOptions, other.indicesOptions); + } + + } +} diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/action/RemovePolicyForIndexRequestTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/action/RemovePolicyForIndexRequestTests.java new file mode 100644 index 0000000000000..b4fde97df0a5b --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/action/RemovePolicyForIndexRequestTests.java @@ -0,0 +1,68 @@ +/* + * 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.indexlifecycle.action; + +import org.elasticsearch.action.support.IndicesOptions; +import org.elasticsearch.test.AbstractStreamableTestCase; +import org.elasticsearch.xpack.core.indexlifecycle.action.RemovePolicyForIndexAction.Request; + +import java.io.IOException; +import java.util.Arrays; + +public class RemovePolicyForIndexRequestTests extends AbstractStreamableTestCase { + + @Override + protected Request createTestInstance() { + Request request = new Request(generateRandomStringArray(20, 20, false)); + if (randomBoolean()) { + IndicesOptions indicesOptions = IndicesOptions.fromOptions(randomBoolean(), randomBoolean(), randomBoolean(), randomBoolean(), + randomBoolean(), randomBoolean(), randomBoolean()); + request.indicesOptions(indicesOptions); + } + if (randomBoolean()) { + request.indices(generateRandomStringArray(20, 20, false)); + } + return request; + } + + @Override + protected Request createBlankInstance() { + return new Request(); + } + + @Override + protected Request mutateInstance(Request instance) throws IOException { + String[] indices = instance.indices(); + IndicesOptions indicesOptions = instance.indicesOptions(); + switch (between(0, 1)) { + case 0: + indices = randomValueOtherThanMany(i -> Arrays.equals(i, instance.indices()), + () -> generateRandomStringArray(20, 20, false)); + break; + case 1: + indicesOptions = randomValueOtherThan(indicesOptions, () -> IndicesOptions.fromOptions(randomBoolean(), randomBoolean(), + randomBoolean(), randomBoolean(), randomBoolean(), randomBoolean(), randomBoolean())); + break; + default: + throw new AssertionError("Illegal randomisation branch"); + } + Request newRequest = new Request(indices); + newRequest.indicesOptions(indicesOptions); + return newRequest; + } + + public void testNullIndices() { + IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, + () -> new Request((String[]) null)); + assertEquals("indices cannot be null", exception.getMessage()); + } + + public void testValidate() { + Request request = createTestInstance(); + assertNull(request.validate()); + } +} diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/action/RemovePolicyForIndexResponseTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/action/RemovePolicyForIndexResponseTests.java new file mode 100644 index 0000000000000..48c9affecd9de --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/action/RemovePolicyForIndexResponseTests.java @@ -0,0 +1,69 @@ +/* + * 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.indexlifecycle.action; + +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.test.AbstractStreamableXContentTestCase; +import org.elasticsearch.xpack.core.indexlifecycle.action.RemovePolicyForIndexAction.Response; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class RemovePolicyForIndexResponseTests extends AbstractStreamableXContentTestCase { + + @Override + protected Response createBlankInstance() { + return new Response(); + } + + @Override + protected Response createTestInstance() { + List failedIndexes = Arrays.asList(generateRandomStringArray(20, 20, false)); + return new Response(failedIndexes); + } + + @Override + protected Response mutateInstance(Response instance) throws IOException { + List failedIndices = randomValueOtherThan(instance.getFailedIndexes(), + () -> Arrays.asList(generateRandomStringArray(20, 20, false))); + return new Response(failedIndices); + } + + @Override + protected Response doParseInstance(XContentParser parser) throws IOException { + return Response.PARSER.apply(parser, null); + } + + @Override + protected boolean supportsUnknownFields() { + return false; + } + + public void testNullFailedIndices() { + IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> new Response((List) null)); + assertEquals("failed_indexes cannot be null", exception.getMessage()); + } + + public void testHasFailures() { + Response response = new Response(new ArrayList<>()); + assertFalse(response.hasFailures()); + assertEquals(Collections.emptyList(), response.getFailedIndexes()); + + int size = randomIntBetween(1, 10); + List failedIndexes = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + failedIndexes.add(randomAlphaOfLength(20)); + } + response = new Response(failedIndexes); + assertTrue(response.hasFailures()); + assertEquals(failedIndexes, response.getFailedIndexes()); + } + +} diff --git a/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycle.java b/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycle.java index ad8a2e55c46f0..c7dc7512e5a26 100644 --- a/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycle.java +++ b/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycle.java @@ -34,27 +34,30 @@ import org.elasticsearch.xpack.core.XPackSettings; import org.elasticsearch.xpack.core.indexlifecycle.LifecycleSettings; import org.elasticsearch.xpack.core.indexlifecycle.RolloverAction; -import org.elasticsearch.xpack.core.indexlifecycle.action.SetPolicyForIndexAction; import org.elasticsearch.xpack.core.indexlifecycle.action.DeleteLifecycleAction; import org.elasticsearch.xpack.core.indexlifecycle.action.ExplainLifecycleAction; import org.elasticsearch.xpack.core.indexlifecycle.action.GetLifecycleAction; import org.elasticsearch.xpack.core.indexlifecycle.action.MoveToStepAction; import org.elasticsearch.xpack.core.indexlifecycle.action.PutLifecycleAction; +import org.elasticsearch.xpack.core.indexlifecycle.action.RemovePolicyForIndexAction; import org.elasticsearch.xpack.core.indexlifecycle.action.RetryAction; -import org.elasticsearch.xpack.indexlifecycle.action.RestSetPolicyForIndexAction; +import org.elasticsearch.xpack.core.indexlifecycle.action.SetPolicyForIndexAction; import org.elasticsearch.xpack.indexlifecycle.action.RestDeleteLifecycleAction; import org.elasticsearch.xpack.indexlifecycle.action.RestExplainLifecycleAction; import org.elasticsearch.xpack.indexlifecycle.action.RestGetLifecycleAction; import org.elasticsearch.xpack.indexlifecycle.action.RestMoveToStepAction; import org.elasticsearch.xpack.indexlifecycle.action.RestPutLifecycleAction; +import org.elasticsearch.xpack.indexlifecycle.action.RestRemovePolicyForIndexAction; import org.elasticsearch.xpack.indexlifecycle.action.RestRetryAction; -import org.elasticsearch.xpack.indexlifecycle.action.TransportSetPolicyForIndexAction; +import org.elasticsearch.xpack.indexlifecycle.action.RestSetPolicyForIndexAction; import org.elasticsearch.xpack.indexlifecycle.action.TransportDeleteLifecycleAction; import org.elasticsearch.xpack.indexlifecycle.action.TransportExplainLifecycleAction; import org.elasticsearch.xpack.indexlifecycle.action.TransportGetLifecycleAction; import org.elasticsearch.xpack.indexlifecycle.action.TransportMoveToStepAction; import org.elasticsearch.xpack.indexlifecycle.action.TransportPutLifecycleAction; +import org.elasticsearch.xpack.indexlifecycle.action.TransportRemovePolicyForIndexAction; import org.elasticsearch.xpack.indexlifecycle.action.TransportRetryAction; +import org.elasticsearch.xpack.indexlifecycle.action.TransportSetPolicyForIndexAction; import java.time.Clock; import java.util.ArrayList; @@ -151,6 +154,7 @@ public List getRestHandlers(Settings settings, RestController restC new RestDeleteLifecycleAction(settings, restController), new RestExplainLifecycleAction(settings, restController), new RestSetPolicyForIndexAction(settings, restController), + new RestRemovePolicyForIndexAction(settings, restController), new RestMoveToStepAction(settings, restController), new RestRetryAction(settings, restController) ); @@ -167,6 +171,7 @@ public List getRestHandlers(Settings settings, RestController restC new ActionHandler<>(DeleteLifecycleAction.INSTANCE, TransportDeleteLifecycleAction.class), new ActionHandler<>(ExplainLifecycleAction.INSTANCE, TransportExplainLifecycleAction.class), new ActionHandler<>(SetPolicyForIndexAction.INSTANCE, TransportSetPolicyForIndexAction.class), + new ActionHandler<>(RemovePolicyForIndexAction.INSTANCE, TransportRemovePolicyForIndexAction.class), new ActionHandler<>(MoveToStepAction.INSTANCE, TransportMoveToStepAction.class), new ActionHandler<>(RetryAction.INSTANCE, TransportRetryAction.class)); } diff --git a/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycleRunner.java b/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycleRunner.java index 455776ce71648..118855ccc4669 100644 --- a/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycleRunner.java +++ b/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycleRunner.java @@ -28,6 +28,7 @@ import org.elasticsearch.xpack.core.indexlifecycle.InitializePolicyContextStep; import org.elasticsearch.xpack.core.indexlifecycle.LifecyclePolicy; import org.elasticsearch.xpack.core.indexlifecycle.LifecycleSettings; +import org.elasticsearch.xpack.core.indexlifecycle.RolloverAction; import org.elasticsearch.xpack.core.indexlifecycle.Step; import org.elasticsearch.xpack.core.indexlifecycle.Step.StepKey; import org.elasticsearch.xpack.core.indexlifecycle.TerminalPolicyStep; @@ -340,4 +341,66 @@ private static boolean canSetPolicy(StepKey currentStepKey, String currentPolicy return true; } } + + public static ClusterState removePolicyForIndexes(final Index[] indices, ClusterState currentState, List failedIndexes) { + MetaData.Builder newMetadata = MetaData.builder(currentState.getMetaData()); + boolean clusterStateChanged = false; + for (Index index : indices) { + IndexMetaData indexMetadata = currentState.getMetaData().index(index); + if (indexMetadata == null) { + // Index doesn't exist so fail it + failedIndexes.add(index.getName()); + } else { + IndexMetaData.Builder newIdxMetadata = IndexLifecycleRunner.removePolicyForIndex(index, indexMetadata, failedIndexes); + if (newIdxMetadata != null) { + newMetadata.put(newIdxMetadata); + clusterStateChanged = true; + } + } + } + if (clusterStateChanged) { + ClusterState.Builder newClusterState = ClusterState.builder(currentState); + newClusterState.metaData(newMetadata); + return newClusterState.build(); + } else { + return currentState; + } + } + + private static IndexMetaData.Builder removePolicyForIndex(Index index, IndexMetaData indexMetadata, List failedIndexes) { + Settings idxSettings = indexMetadata.getSettings(); + Settings.Builder newSettings = Settings.builder().put(idxSettings); + String currentPolicy = LifecycleSettings.LIFECYCLE_NAME_SETTING.get(idxSettings); + StepKey currentStepKey = IndexLifecycleRunner.getCurrentStepKey(idxSettings); + + if (canRemovePolicy(currentStepKey, currentPolicy)) { + newSettings.remove(LifecycleSettings.LIFECYCLE_NAME_SETTING.getKey()); + newSettings.remove(LifecycleSettings.LIFECYCLE_PHASE_SETTING.getKey()); + newSettings.remove(LifecycleSettings.LIFECYCLE_PHASE_TIME_SETTING.getKey()); + newSettings.remove(LifecycleSettings.LIFECYCLE_ACTION_SETTING.getKey()); + newSettings.remove(LifecycleSettings.LIFECYCLE_ACTION_TIME_SETTING.getKey()); + newSettings.remove(LifecycleSettings.LIFECYCLE_STEP_SETTING.getKey()); + newSettings.remove(LifecycleSettings.LIFECYCLE_STEP_TIME_SETTING.getKey()); + newSettings.remove(LifecycleSettings.LIFECYCLE_STEP_INFO_SETTING.getKey()); + newSettings.remove(LifecycleSettings.LIFECYCLE_FAILED_STEP_SETTING.getKey()); + newSettings.remove(LifecycleSettings.LIFECYCLE_INDEX_CREATION_DATE_SETTING.getKey()); + newSettings.remove(LifecycleSettings.LIFECYCLE_SKIP_SETTING.getKey()); + newSettings.remove(RolloverAction.LIFECYCLE_ROLLOVER_ALIAS_SETTING.getKey()); + return IndexMetaData.builder(indexMetadata).settings(newSettings); + } else { + failedIndexes.add(index.getName()); + return null; + } + } + + private static boolean canRemovePolicy(StepKey currentStepKey, String currentPolicyName) { + if (Strings.hasLength(currentPolicyName)) { + // Can't remove policy if the index is currently in the Shrink + // action + return ShrinkAction.NAME.equals(currentStepKey.getAction()) == false; + } else { + // Index not previously managed by ILM + return true; + } + } } diff --git a/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/action/RestRemovePolicyForIndexAction.java b/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/action/RestRemovePolicyForIndexAction.java new file mode 100644 index 0000000000000..d2900d6363682 --- /dev/null +++ b/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/action/RestRemovePolicyForIndexAction.java @@ -0,0 +1,41 @@ +/* + * 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.indexlifecycle.action; + +import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.rest.BaseRestHandler; +import org.elasticsearch.rest.RestController; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.action.RestToXContentListener; +import org.elasticsearch.xpack.core.indexlifecycle.action.RemovePolicyForIndexAction; +import org.elasticsearch.xpack.indexlifecycle.IndexLifecycle; + +import java.io.IOException; + +public class RestRemovePolicyForIndexAction extends BaseRestHandler { + + public RestRemovePolicyForIndexAction(Settings settings, RestController controller) { + super(settings); + controller.registerHandler(RestRequest.Method.PUT, "_" + IndexLifecycle.NAME + "/remove_policy", this); + controller.registerHandler(RestRequest.Method.PUT, "{index}/_" + IndexLifecycle.NAME + "/remove_policy", this); + } + + @Override + public String getName() { + return "xpack_remove_policy_for_index_action"; + } + + @Override + protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient client) throws IOException { + String[] indexes = Strings.splitStringByCommaToArray(restRequest.param("index")); + RemovePolicyForIndexAction.Request changePolicyRequest = new RemovePolicyForIndexAction.Request(indexes); + changePolicyRequest.masterNodeTimeout(restRequest.paramAsTime("master_timeout", changePolicyRequest.masterNodeTimeout())); + + return channel -> client.execute(RemovePolicyForIndexAction.INSTANCE, changePolicyRequest, new RestToXContentListener<>(channel)); + } +} \ No newline at end of file diff --git a/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/action/TransportRemovePolicyForIndexAction.java b/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/action/TransportRemovePolicyForIndexAction.java new file mode 100644 index 0000000000000..7a5428249d6f8 --- /dev/null +++ b/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/action/TransportRemovePolicyForIndexAction.java @@ -0,0 +1,80 @@ +/* + * 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.indexlifecycle.action; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.support.ActionFilters; +import org.elasticsearch.action.support.master.TransportMasterNodeAction; +import org.elasticsearch.cluster.AckedClusterStateUpdateTask; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.block.ClusterBlockException; +import org.elasticsearch.cluster.block.ClusterBlockLevel; +import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.index.Index; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.TransportService; +import org.elasticsearch.xpack.core.indexlifecycle.action.RemovePolicyForIndexAction; +import org.elasticsearch.xpack.core.indexlifecycle.action.RemovePolicyForIndexAction.Request; +import org.elasticsearch.xpack.core.indexlifecycle.action.RemovePolicyForIndexAction.Response; +import org.elasticsearch.xpack.indexlifecycle.IndexLifecycleRunner; + +import java.util.ArrayList; +import java.util.List; + +public class TransportRemovePolicyForIndexAction extends TransportMasterNodeAction { + + @Inject + public TransportRemovePolicyForIndexAction(Settings settings, TransportService transportService, ClusterService clusterService, + ThreadPool threadPool, ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver) { + super(settings, RemovePolicyForIndexAction.NAME, transportService, clusterService, threadPool, actionFilters, + indexNameExpressionResolver, Request::new); + } + + @Override + protected String executor() { + return ThreadPool.Names.SAME; + } + + @Override + protected Response newResponse() { + return new Response(); + } + + @Override + protected ClusterBlockException checkBlock(Request request, ClusterState state) { + return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_WRITE); + } + + @Override + protected void masterOperation(Request request, ClusterState state, ActionListener listener) throws Exception { + final Index[] indices = indexNameExpressionResolver.concreteIndices(state, request.indicesOptions(), request.indices()); + clusterService.submitStateUpdateTask("remove-lifecycle-for-index", + new AckedClusterStateUpdateTask(request, listener) { + + private final List failedIndexes = new ArrayList<>(); + + @Override + public ClusterState execute(ClusterState currentState) throws Exception { + return IndexLifecycleRunner.removePolicyForIndexes(indices, currentState, failedIndexes); + } + + @Override + public void onFailure(String source, Exception e) { + listener.onFailure(e); + } + + @Override + protected Response newResponse(boolean acknowledged) { + return new Response(failedIndexes); + } + }); + } + +} diff --git a/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycleRunnerTests.java b/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycleRunnerTests.java index 9f156034fc5b8..dc833388043f3 100644 --- a/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycleRunnerTests.java +++ b/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycleRunnerTests.java @@ -33,6 +33,7 @@ import org.elasticsearch.xpack.core.indexlifecycle.LifecycleSettings; import org.elasticsearch.xpack.core.indexlifecycle.MockStep; import org.elasticsearch.xpack.core.indexlifecycle.RandomStepInfo; +import org.elasticsearch.xpack.core.indexlifecycle.RolloverAction; import org.elasticsearch.xpack.core.indexlifecycle.Step; import org.elasticsearch.xpack.core.indexlifecycle.Step.StepKey; import org.elasticsearch.xpack.core.indexlifecycle.TerminalPolicyStep; @@ -910,6 +911,100 @@ public void testSetPolicyForIndexIndexInShrink() { assertSame(clusterState, newClusterState); } + public void testRemovePolicyForIndex() { + String indexName = randomAlphaOfLength(10); + String oldPolicyName = "old_policy"; + StepKey currentStep = AbstractStepTestCase.randomStepKey(); + Settings.Builder indexSettingsBuilder = Settings.builder().put(LifecycleSettings.LIFECYCLE_NAME, oldPolicyName) + .put(LifecycleSettings.LIFECYCLE_PHASE, currentStep.getPhase()) + .put(LifecycleSettings.LIFECYCLE_ACTION, currentStep.getAction()) + .put(LifecycleSettings.LIFECYCLE_STEP, currentStep.getName()).put(LifecycleSettings.LIFECYCLE_SKIP, true); + ClusterState clusterState = buildClusterState(indexName, indexSettingsBuilder); + Index index = clusterState.metaData().index(indexName).getIndex(); + Index[] indices = new Index[] { index }; + List failedIndexes = new ArrayList<>(); + + ClusterState newClusterState = IndexLifecycleRunner.removePolicyForIndexes(indices, clusterState, failedIndexes); + + assertTrue(failedIndexes.isEmpty()); + assertIndexNotManagedByILM(newClusterState, index); + } + + public void testRemovePolicyForIndexNoCurrentPolicy() { + String indexName = randomAlphaOfLength(10); + Settings.Builder indexSettingsBuilder = Settings.builder(); + ClusterState clusterState = buildClusterState(indexName, indexSettingsBuilder); + Index index = clusterState.metaData().index(indexName).getIndex(); + Index[] indices = new Index[] { index }; + List failedIndexes = new ArrayList<>(); + + ClusterState newClusterState = IndexLifecycleRunner.removePolicyForIndexes(indices, clusterState, failedIndexes); + + assertTrue(failedIndexes.isEmpty()); + assertIndexNotManagedByILM(newClusterState, index); + } + + public void testRemovePolicyForIndexIndexDoesntExist() { + String indexName = randomAlphaOfLength(10); + String oldPolicyName = "old_policy"; + StepKey currentStep = AbstractStepTestCase.randomStepKey(); + Settings.Builder indexSettingsBuilder = Settings.builder().put(LifecycleSettings.LIFECYCLE_NAME, oldPolicyName) + .put(LifecycleSettings.LIFECYCLE_PHASE, currentStep.getPhase()) + .put(LifecycleSettings.LIFECYCLE_ACTION, currentStep.getAction()) + .put(LifecycleSettings.LIFECYCLE_STEP, currentStep.getName()).put(LifecycleSettings.LIFECYCLE_SKIP, true); + ClusterState clusterState = buildClusterState(indexName, indexSettingsBuilder); + Index index = new Index("doesnt_exist", "im_not_here"); + Index[] indices = new Index[] { index }; + List failedIndexes = new ArrayList<>(); + + ClusterState newClusterState = IndexLifecycleRunner.removePolicyForIndexes(indices, clusterState, failedIndexes); + + assertEquals(1, failedIndexes.size()); + assertEquals("doesnt_exist", failedIndexes.get(0)); + assertSame(clusterState, newClusterState); + } + + public void testRemovePolicyForIndexIndexInShrink() { + String indexName = randomAlphaOfLength(10); + String oldPolicyName = "old_policy"; + StepKey currentStep = new StepKey(randomAlphaOfLength(10), ShrinkAction.NAME, randomAlphaOfLength(10)); + Settings.Builder indexSettingsBuilder = Settings.builder().put(LifecycleSettings.LIFECYCLE_NAME, oldPolicyName) + .put(LifecycleSettings.LIFECYCLE_PHASE, currentStep.getPhase()) + .put(LifecycleSettings.LIFECYCLE_ACTION, currentStep.getAction()) + .put(LifecycleSettings.LIFECYCLE_STEP, currentStep.getName()).put(LifecycleSettings.LIFECYCLE_SKIP, true); + ClusterState clusterState = buildClusterState(indexName, indexSettingsBuilder); + Index index = clusterState.metaData().index(indexName).getIndex(); + Index[] indices = new Index[] { index }; + List failedIndexes = new ArrayList<>(); + + ClusterState newClusterState = IndexLifecycleRunner.removePolicyForIndexes(indices, clusterState, failedIndexes); + + assertEquals(1, failedIndexes.size()); + assertEquals(index.getName(), failedIndexes.get(0)); + assertSame(clusterState, newClusterState); + } + + public static void assertIndexNotManagedByILM(ClusterState clusterState, Index index) { + MetaData metadata = clusterState.metaData(); + assertNotNull(metadata); + IndexMetaData indexMetadata = metadata.getIndexSafe(index); + assertNotNull(indexMetadata); + Settings indexSettings = indexMetadata.getSettings(); + assertNotNull(indexSettings); + assertFalse(LifecycleSettings.LIFECYCLE_NAME_SETTING.exists(indexSettings)); + assertFalse(LifecycleSettings.LIFECYCLE_PHASE_SETTING.exists(indexSettings)); + assertFalse(LifecycleSettings.LIFECYCLE_PHASE_TIME_SETTING.exists(indexSettings)); + assertFalse(LifecycleSettings.LIFECYCLE_ACTION_SETTING.exists(indexSettings)); + assertFalse(LifecycleSettings.LIFECYCLE_ACTION_TIME_SETTING.exists(indexSettings)); + assertFalse(LifecycleSettings.LIFECYCLE_STEP_SETTING.exists(indexSettings)); + assertFalse(LifecycleSettings.LIFECYCLE_STEP_TIME_SETTING.exists(indexSettings)); + assertFalse(LifecycleSettings.LIFECYCLE_STEP_INFO_SETTING.exists(indexSettings)); + assertFalse(LifecycleSettings.LIFECYCLE_FAILED_STEP_SETTING.exists(indexSettings)); + assertFalse(LifecycleSettings.LIFECYCLE_INDEX_CREATION_DATE_SETTING.exists(indexSettings)); + assertFalse(LifecycleSettings.LIFECYCLE_SKIP_SETTING.exists(indexSettings)); + assertFalse(RolloverAction.LIFECYCLE_ROLLOVER_ALIAS_SETTING.exists(indexSettings)); + } + public static void assertClusterStateOnPolicy(ClusterState oldClusterState, Index index, String expectedPolicy, StepKey previousStep, StepKey expectedStep, ClusterState newClusterState, long now) { assertNotSame(oldClusterState, newClusterState); diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/api/xpack.index_lifecycle.remove_policy.json b/x-pack/plugin/src/test/resources/rest-api-spec/api/xpack.index_lifecycle.remove_policy.json new file mode 100644 index 0000000000000..bf9cf5649fef8 --- /dev/null +++ b/x-pack/plugin/src/test/resources/rest-api-spec/api/xpack.index_lifecycle.remove_policy.json @@ -0,0 +1,19 @@ +{ + "xpack.index_lifecycle.remove_policy": { + "documentation": "http://www.elastic.co/guide/en/index_lifecycle/current/index_lifecycle.html", + "methods": [ "PUT" ], + "url": { + "path": "/{index}/_index_lifecycle/remove_policy", + "paths": ["/{index}/_index_lifecycle/remove_policy", "/_index_lifecycle/remove_policy"], + "parts": { + "index": { + "type" : "string", + "description" : "The name of the index to remove policy on" + } + }, + "params": { + } + }, + "body": null + } +} diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/test/index_lifecycle/60_remove_policy_for_index.yml b/x-pack/plugin/src/test/resources/rest-api-spec/test/index_lifecycle/60_remove_policy_for_index.yml new file mode 100644 index 0000000000000..0a683ec6cba80 --- /dev/null +++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/index_lifecycle/60_remove_policy_for_index.yml @@ -0,0 +1,219 @@ +--- +setup: + - do: + cluster.health: + wait_for_status: yellow + - do: + acknowlege: true + xpack.index_lifecycle.put_lifecycle: + lifecycle: "my_moveable_timeseries_lifecycle" + body: | + { + "policy": { + "type": "timeseries", + "phases": { + "warm": { + "after": "1000s", + "actions": { + "forcemerge": { + "max_num_segments": 10000 + } + } + }, + "hot": { + "after": "1000s", + "actions": { } + } + } + } + } + + - do: + acknowledge: true + xpack.index_lifecycle.get_lifecycle: + lifecycle: "my_moveable_timeseries_lifecycle" + + - do: + acknowlege: true + xpack.index_lifecycle.put_lifecycle: + lifecycle: "my_alternative_timeseries_lifecycle" + body: | + { + "policy": { + "type": "timeseries", + "phases": { + "warm": { + "after": "1000s", + "actions": { + "forcemerge": { + "max_num_segments": 10000 + } + } + }, + "hot": { + "after": "1000s", + "actions": { } + } + } + } + } + + - do: + acknowledge: true + xpack.index_lifecycle.get_lifecycle: + lifecycle: "my_alternative_timeseries_lifecycle" + + - do: + indices.create: + index: my_index + body: + settings: + index.lifecycle.name: "my_moveable_timeseries_lifecycle" + + - do: + indices.create: + index: my_index2 + body: + settings: + index.lifecycle.name: "my_moveable_timeseries_lifecycle" + + - do: + indices.create: + index: another_index + body: + settings: + index.lifecycle.name: "my_moveable_timeseries_lifecycle" + + - do: + indices.create: + index: unmanaged_index + body: + settings: {} + + - do: + indices.create: + index: my_index_no_policy + +--- +teardown: + + - do: + acknowledge: true + indices.delete: + index: my_index + - do: + acknowledge: true + indices.delete: + index: my_index2 + - do: + acknowledge: true + indices.delete: + index: another_index + - do: + acknowledge: true + indices.delete: + index: unmanaged_index + + - do: + acknowledge: true + indices.delete: + index: my_index_no_policy + + - do: + acknowledge: true + xpack.index_lifecycle.delete_lifecycle: + lifecycle: "my_moveable_timeseries_lifecycle" + + - do: + catch: missing + xpack.index_lifecycle.get_lifecycle: + lifecycle: "my_moveable_timeseries_lifecycle" + + - do: + acknowledge: true + xpack.index_lifecycle.delete_lifecycle: + lifecycle: "my_alternative_timeseries_lifecycle" + + - do: + catch: missing + xpack.index_lifecycle.get_lifecycle: + lifecycle: "my_alternative_timeseries_lifecycle" + +--- +"Test Remove Policy Single Index": + + - do: + indices.get_settings: + index: "another_index" + + - match: { another_index.settings.index.lifecycle.name: my_moveable_timeseries_lifecycle } + + - do: + acknowledge: true + xpack.index_lifecycle.remove_policy: + index: "another_index" + + - is_false: has_failures + - length: { failed_indexes: 0 } + + - do: + indices.get_settings: + index: "another_index" + + - is_false: another_index.settings.index.lifecycle + +--- +"Test Remove Policy Index Pattern": + + - do: + indices.get_settings: + index: "my_*" + + - match: { my_index.settings.index.lifecycle.name: my_moveable_timeseries_lifecycle } + - match: { my_index2.settings.index.lifecycle.name: my_moveable_timeseries_lifecycle } + + - do: + acknowledge: true + xpack.index_lifecycle.remove_policy: + index: "my_*" + + - is_false: has_failures + - length: { failed_indexes: 0 } + + - do: + indices.get_settings: + index: "my_*" + + - is_false: my_index.settings.index.lifecycle + - is_false: my_index2.settings.index.lifecycle + +--- +"Test Remove Policy Unmanaged Index": + + - do: + indices.get_settings: + index: "unmanaged_index" + + - is_false: unmanaged_index.settings.index.lifecycle.name + + - do: + acknowledge: true + xpack.index_lifecycle.remove_policy: + index: "unmanaged_index" + + - is_false: has_failures + - length: { failed_indexes: 0 } + + - do: + indices.get_settings: + index: "unmanaged_index" + + - is_false: unmanaged_index.settings.index.lifecycle + +--- +"Test Remove Policy Index Does Not Exist": + + - do: + catch: missing + xpack.index_lifecycle.remove_policy: + index: "doesnt_exist"