diff --git a/docs/reference/ilm/apis/explain.asciidoc b/docs/reference/ilm/apis/explain.asciidoc index 85855d18bea..3aa2572175e 100644 --- a/docs/reference/ilm/apis/explain.asciidoc +++ b/docs/reference/ilm/apis/explain.asciidoc @@ -26,6 +26,15 @@ about any failures. ==== Request Parameters +`only_managed`:: + (boolean) Filters the returned indices to only indices that are managed by + ILM. + +`only_errors`:: + (boolean) Filters the returned indices to only indices that are managed by + ILM and are in an error state, either due to an encountering an error while + executing the policy, or attempting to use a policy that does not exist. + include::{docdir}/rest-api/timeoutparms.asciidoc[] ==== Authorization diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/ExplainLifecycleRequest.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/ExplainLifecycleRequest.java index 037de2d5052..0a4d65d3e01 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/ExplainLifecycleRequest.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/ExplainLifecycleRequest.java @@ -6,9 +6,11 @@ package org.elasticsearch.xpack.core.indexlifecycle; +import org.elasticsearch.Version; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.support.master.info.ClusterInfoRequest; import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; import java.io.IOException; import java.util.Arrays; @@ -21,6 +23,10 @@ import java.util.Objects; * {@link #indices(String...)} method */ public class ExplainLifecycleRequest extends ClusterInfoRequest { + private static final Version FILTERS_INTRODUCED_VERSION = Version.V_7_4_0; + + private boolean onlyErrors = false; + private boolean onlyManaged = false; public ExplainLifecycleRequest() { super(); @@ -28,6 +34,37 @@ public class ExplainLifecycleRequest extends ClusterInfoRequest Arrays.equals(i, instance.indices()), + boolean onlyErrors = instance.onlyErrors(); + boolean onlyManaged = instance.onlyManaged(); + switch (between(0, 3)) { + case 0: + indices = randomValueOtherThanMany(i -> Arrays.equals(i, instance.indices()), () -> generateRandomStringArray(20, 10, false, false)); - break; - case 1: - indicesOptions = randomValueOtherThan(indicesOptions, () -> IndicesOptions.fromOptions(randomBoolean(), randomBoolean(), + break; + case 1: + indicesOptions = randomValueOtherThan(indicesOptions, () -> IndicesOptions.fromOptions(randomBoolean(), randomBoolean(), randomBoolean(), randomBoolean(), randomBoolean(), randomBoolean(), randomBoolean(), randomBoolean())); - break; - default: - throw new AssertionError("Illegal randomisation branch"); + break; + case 2: + onlyErrors = !onlyErrors; + break; + case 3: + onlyManaged = !onlyManaged; + break; + default: + throw new AssertionError("Illegal randomisation branch"); } ExplainLifecycleRequest newRequest = new ExplainLifecycleRequest(); newRequest.indices(indices); newRequest.indicesOptions(indicesOptions); + newRequest.onlyErrors(onlyErrors); + newRequest.onlyManaged(onlyManaged); return newRequest; } diff --git a/x-pack/plugin/ilm/qa/multi-node/src/test/java/org/elasticsearch/xpack/ilm/TimeSeriesLifecycleActionsIT.java b/x-pack/plugin/ilm/qa/multi-node/src/test/java/org/elasticsearch/xpack/ilm/TimeSeriesLifecycleActionsIT.java index d0129291fc0..b0183fa5f3d 100644 --- a/x-pack/plugin/ilm/qa/multi-node/src/test/java/org/elasticsearch/xpack/ilm/TimeSeriesLifecycleActionsIT.java +++ b/x-pack/plugin/ilm/qa/multi-node/src/test/java/org/elasticsearch/xpack/ilm/TimeSeriesLifecycleActionsIT.java @@ -54,9 +54,11 @@ import java.util.function.Supplier; import static java.util.Collections.singletonMap; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.hasKey; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; @@ -838,6 +840,45 @@ public class TimeSeriesLifecycleActionsIT extends ESRestTestCase { assertOK(client().performRequest(startILMReqest)); } + public void testExplainFilters() throws Exception { + String goodIndex = index + "-good-000001"; + String errorIndex = index + "-error"; + String nonexistantPolicyIndex = index + "-nonexistant-policy"; + String unmanagedIndex = index + "-unmanaged"; + + createFullPolicy(TimeValue.ZERO); + + createIndexWithSettings(goodIndex, Settings.builder() + .put(RolloverAction.LIFECYCLE_ROLLOVER_ALIAS, "alias") + .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0) + .put(LifecycleSettings.LIFECYCLE_NAME, policy)); + createIndexWithSettingsNoAlias(errorIndex, Settings.builder() + .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0) + .put(LifecycleSettings.LIFECYCLE_NAME, policy)); + createIndexWithSettingsNoAlias(nonexistantPolicyIndex, Settings.builder() + .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0) + .put(LifecycleSettings.LIFECYCLE_NAME, randomValueOtherThan(policy, () -> randomAlphaOfLengthBetween(3,10)))); + createIndexWithSettingsNoAlias(unmanagedIndex, Settings.builder() + .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0)); + + assertBusy(() -> { + Map> explainResponse = explain(index + "*", false, false); + assertNotNull(explainResponse); + assertThat(explainResponse, + allOf(hasKey(goodIndex), hasKey(errorIndex), hasKey(nonexistantPolicyIndex), hasKey(unmanagedIndex))); + + Map> onlyManagedResponse = explain(index + "*", false, true); + assertNotNull(onlyManagedResponse); + assertThat(onlyManagedResponse, allOf(hasKey(goodIndex), hasKey(errorIndex), hasKey(nonexistantPolicyIndex))); + assertThat(onlyManagedResponse, not(hasKey(unmanagedIndex))); + + Map> onlyErrorsResponse = explain(index + "*", true, randomBoolean()); + assertNotNull(onlyErrorsResponse); + assertThat(onlyErrorsResponse, allOf(hasKey(errorIndex), hasKey(nonexistantPolicyIndex))); + assertThat(onlyErrorsResponse, allOf(not(hasKey(goodIndex)), not(hasKey(unmanagedIndex)))); + }); + } + private void createFullPolicy(TimeValue hotTime) throws IOException { Map hotActions = new HashMap<>(); hotActions.put(SetPriorityAction.NAME, new SetPriorityAction(100)); @@ -957,15 +998,21 @@ public class TimeSeriesLifecycleActionsIT extends ESRestTestCase { } private Map explainIndex(String indexName) throws IOException { - Request explainRequest = new Request("GET", indexName + "/_ilm/explain"); + return explain(indexName, false, false).get(indexName); + } + + private Map> explain(String indexPattern, boolean onlyErrors, boolean onlyManaged) throws IOException { + Request explainRequest = new Request("GET", indexPattern + "/_ilm/explain"); + explainRequest.addParameter("only_errors", Boolean.toString(onlyErrors)); + explainRequest.addParameter("only_managed", Boolean.toString(onlyManaged)); Response response = client().performRequest(explainRequest); Map responseMap; try (InputStream is = response.getEntity().getContent()) { responseMap = XContentHelper.convertToMap(XContentType.JSON.xContent(), is, true); } - @SuppressWarnings("unchecked") Map indexResponse = ((Map>) responseMap.get("indices")) - .get(indexName); + @SuppressWarnings("unchecked") Map> indexResponse = + ((Map>) responseMap.get("indices")); return indexResponse; } diff --git a/x-pack/plugin/ilm/qa/rest/src/test/resources/rest-api-spec/test/ilm/40_explain_lifecycle.yml b/x-pack/plugin/ilm/qa/rest/src/test/resources/rest-api-spec/test/ilm/40_explain_lifecycle.yml index baa051103d5..81f7064f8ce 100644 --- a/x-pack/plugin/ilm/qa/rest/src/test/resources/rest-api-spec/test/ilm/40_explain_lifecycle.yml +++ b/x-pack/plugin/ilm/qa/rest/src/test/resources/rest-api-spec/test/ilm/40_explain_lifecycle.yml @@ -60,6 +60,12 @@ setup: - do: indices.create: index: my_index_no_policy + - do: + indices.create: + index: index_with_policy_that_doesnt_exist + body: + settings: + index.lifecycle.name: "a_policy_that_doesnt_exist" --- teardown: @@ -81,6 +87,10 @@ teardown: indices.delete: index: my_index_no_policy + - do: + indices.delete: + index: index_with_policy_that_doesnt_exist + - do: ilm.delete_lifecycle: policy: "my_moveable_timeseries_lifecycle" @@ -112,6 +122,7 @@ teardown: - is_false: indices.my_index2 - is_false: indices.another_index - is_false: indices.unmanaged_index + - is_false: indices.index_with_policy_that_doesnt_exist --- "Test Wildcard Index Lifecycle Explain": @@ -146,6 +157,7 @@ teardown: - is_false: indices.another_index - is_false: indices.unmanaged_index + - is_false: indices.index_with_policy_that_doesnt_exist --- @@ -201,6 +213,16 @@ teardown: - is_false: indices.another_index.failed_step - is_false: indices.another_index.step_info + - match: { indices.index_with_policy_that_doesnt_exist.index: "index_with_policy_that_doesnt_exist" } + - match: { indices.index_with_policy_that_doesnt_exist.policy: "a_policy_that_doesnt_exist" } + - match: { indices.index_with_policy_that_doesnt_exist.step_info.reason: "policy [a_policy_that_doesnt_exist] does not exist" } + - is_true: indices.index_with_policy_that_doesnt_exist.managed + - is_false: indices.index_with_policy_that_doesnt_exist.phase + - is_false: indices.index_with_policy_that_doesnt_exist.action + - is_false: indices.index_with_policy_that_doesnt_exist.step + - is_false: indices.index_with_policy_that_doesnt_exist.age + - is_false: indices.index_with_policy_that_doesnt_exist.failed_step + --- "Test Unmanaged Index Lifecycle Explain": @@ -221,3 +243,34 @@ teardown: - is_false: indices.my_index - is_false: indices.my_index2 - is_false: indices.another_index + - is_false: indices.index_with_policy_that_doesnt_exist + +--- +"Test filter for only managed indices": + + - do: + ilm.explain_lifecycle: + index: "*" + only_managed: true + + - match: { indices.my_index.index: "my_index" } + - match: { indices.my_index2.index: "my_index2" } + - match: { indices.another_index.index: "another_index" } + - match: { indices.index_with_policy_that_doesnt_exist.index: "index_with_policy_that_doesnt_exist" } + - is_false: indices.unmanaged_index + - is_false: indices.my_index_no_policy + +--- +"Test filter for only error indices": + + - do: + ilm.explain_lifecycle: + index: "*" + only_errors: true + + - match: { indices.index_with_policy_that_doesnt_exist.index: "index_with_policy_that_doesnt_exist" } + - is_false: indices.unmanaged_index + - is_false: indices.my_index_no_policy + - is_false: indices.my_index + - is_false: indices.my_index2 + - is_false: indices.another_index diff --git a/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/IndexLifecycleService.java b/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/IndexLifecycleService.java index 97b4bf5504e..c38419fa992 100644 --- a/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/IndexLifecycleService.java +++ b/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/IndexLifecycleService.java @@ -214,6 +214,10 @@ public class IndexLifecycleService } } + public boolean policyExists(String policyId) { + return policyRegistry.policyExists(policyId); + } + /** * executes the policy execution on the appropriate indices by running cluster-state tasks per index. * diff --git a/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/action/RestExplainLifecycleAction.java b/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/action/RestExplainLifecycleAction.java index 9473ab615d2..85807a77dfb 100644 --- a/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/action/RestExplainLifecycleAction.java +++ b/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/action/RestExplainLifecycleAction.java @@ -37,6 +37,8 @@ public class RestExplainLifecycleAction extends BaseRestHandler { ExplainLifecycleRequest explainLifecycleRequest = new ExplainLifecycleRequest(); explainLifecycleRequest.indices(indexes); explainLifecycleRequest.indicesOptions(IndicesOptions.fromRequest(restRequest, IndicesOptions.strictExpandOpen())); + explainLifecycleRequest.onlyManaged(restRequest.paramAsBoolean("only_managed", false)); + explainLifecycleRequest.onlyErrors(restRequest.paramAsBoolean("only_errors", false)); String masterNodeTimeout = restRequest.param("master_timeout"); if (masterNodeTimeout != null) { explainLifecycleRequest.masterNodeTimeout(masterNodeTimeout); diff --git a/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/action/TransportExplainLifecycleAction.java b/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/action/TransportExplainLifecycleAction.java index e5e2e051acf..460764768df 100644 --- a/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/action/TransportExplainLifecycleAction.java +++ b/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/action/TransportExplainLifecycleAction.java @@ -27,6 +27,7 @@ import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; +import org.elasticsearch.xpack.core.indexlifecycle.ErrorStep; import org.elasticsearch.xpack.core.indexlifecycle.ExplainLifecycleRequest; import org.elasticsearch.xpack.core.indexlifecycle.ExplainLifecycleResponse; import org.elasticsearch.xpack.core.indexlifecycle.IndexLifecycleExplainResponse; @@ -34,6 +35,7 @@ import org.elasticsearch.xpack.core.indexlifecycle.LifecycleExecutionState; import org.elasticsearch.xpack.core.indexlifecycle.LifecycleSettings; import org.elasticsearch.xpack.core.indexlifecycle.PhaseExecutionInfo; import org.elasticsearch.xpack.core.indexlifecycle.action.ExplainLifecycleAction; +import org.elasticsearch.xpack.ilm.IndexLifecycleService; import java.io.IOException; import java.util.HashMap; @@ -43,14 +45,16 @@ public class TransportExplainLifecycleAction extends TransportClusterInfoAction { private final NamedXContentRegistry xContentRegistry; + private final IndexLifecycleService indexLifecycleService; @Inject public TransportExplainLifecycleAction(TransportService transportService, ClusterService clusterService, ThreadPool threadPool, ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver, - NamedXContentRegistry xContentRegistry) { + NamedXContentRegistry xContentRegistry, IndexLifecycleService indexLifecycleService) { super(ExplainLifecycleAction.NAME, transportService, clusterService, threadPool, actionFilters, ExplainLifecycleRequest::new, indexNameExpressionResolver); this.xContentRegistry = xContentRegistry; + this.indexLifecycleService = indexLifecycleService; } @Override @@ -73,7 +77,7 @@ public class TransportExplainLifecycleAction @Override protected void doMasterOperation(ExplainLifecycleRequest request, String[] concreteIndices, ClusterState state, ActionListener listener) { - Map indexReponses = new HashMap<>(); + Map indexResponses = new HashMap<>(); for (String index : concreteIndices) { IndexMetaData idxMetadata = state.metaData().index(index); Settings idxSettings = idxMetadata.getSettings(); @@ -100,23 +104,34 @@ public class TransportExplainLifecycleAction } final IndexLifecycleExplainResponse indexResponse; if (Strings.hasLength(policyName)) { - indexResponse = IndexLifecycleExplainResponse.newManagedIndexResponse(index, policyName, - lifecycleState.getLifecycleDate(), - lifecycleState.getPhase(), - lifecycleState.getAction(), - lifecycleState.getStep(), - lifecycleState.getFailedStep(), - lifecycleState.getPhaseTime(), - lifecycleState.getActionTime(), - lifecycleState.getStepTime(), - stepInfoBytes, - phaseExecutionInfo); - } else { + // If this is requesting only errors, only include indices in the error step or which are using a nonexistent policy + if (request.onlyErrors() == false + || (ErrorStep.NAME.equals(lifecycleState.getStep()) || indexLifecycleService.policyExists(policyName) == false)) { + indexResponse = IndexLifecycleExplainResponse.newManagedIndexResponse(index, policyName, + lifecycleState.getLifecycleDate(), + lifecycleState.getPhase(), + lifecycleState.getAction(), + lifecycleState.getStep(), + lifecycleState.getFailedStep(), + lifecycleState.getPhaseTime(), + lifecycleState.getActionTime(), + lifecycleState.getStepTime(), + stepInfoBytes, + phaseExecutionInfo); + } else { + indexResponse = null; + } + } else if (request.onlyManaged() == false && request.onlyErrors() == false) { indexResponse = IndexLifecycleExplainResponse.newUnmanagedIndexResponse(index); + } else { + indexResponse = null; + } + + if (indexResponse != null) { + indexResponses.put(indexResponse.getIndex(), indexResponse); } - indexReponses.put(indexResponse.getIndex(), indexResponse); } - listener.onResponse(new ExplainLifecycleResponse(indexReponses)); + listener.onResponse(new ExplainLifecycleResponse(indexResponses)); } } diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/api/ilm.explain_lifecycle.json b/x-pack/plugin/src/test/resources/rest-api-spec/api/ilm.explain_lifecycle.json index 8c48b22a5eb..9b80525946f 100644 --- a/x-pack/plugin/src/test/resources/rest-api-spec/api/ilm.explain_lifecycle.json +++ b/x-pack/plugin/src/test/resources/rest-api-spec/api/ilm.explain_lifecycle.json @@ -11,7 +11,16 @@ "description" : "The name of the index to explain" } }, - "params": {} + "params": { + "only_managed": { + "type": "boolean", + "description": "filters the indices included in the response to ones managed by ILM" + }, + "only_errors": { + "type": "boolean", + "description": "filters the indices included in the response to ones in an ILM error state, implies only_managed" + } + } }, "body": null }