浏览代码

Add option to filter ILM explain response (#44777)

In order to make it easier to interpret the output of the ILM Explain
API, this commit adds two request parameters to that API:

- `only_managed`, which causes the response to only contain indices
  which have `index.lifecycle.name` set
- `only_errors`, which causes the response to contain only indices in an
  ILM error state

"Error state" is defined as either being in the `ERROR` step or having
`index.lifecycle.name` set to a policy that does not exist.
Gordon Brown 6 年之前
父节点
当前提交
5f0861aac0

+ 9 - 0
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

+ 43 - 3
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<ExplainLifecycleRequest> {
+    private static final Version FILTERS_INTRODUCED_VERSION = Version.V_8_0_0;
+
+    private boolean onlyErrors = false;
+    private boolean onlyManaged = false;
 
     public ExplainLifecycleRequest() {
         super();
@@ -28,6 +34,37 @@ public class ExplainLifecycleRequest extends ClusterInfoRequest<ExplainLifecycle
 
     public ExplainLifecycleRequest(StreamInput in) throws IOException {
         super(in);
+        if (in.getVersion().onOrAfter(FILTERS_INTRODUCED_VERSION)) {
+            onlyErrors = in.readBoolean();
+            onlyManaged = in.readBoolean();
+        }
+    }
+
+    @Override
+    public void writeTo(StreamOutput out) throws IOException {
+        super.writeTo(out);
+        if (out.getVersion().onOrAfter(FILTERS_INTRODUCED_VERSION)) {
+            out.writeBoolean(onlyErrors);
+            out.writeBoolean(onlyManaged);
+        }
+    }
+
+    public boolean onlyErrors() {
+        return onlyErrors;
+    }
+
+    public ExplainLifecycleRequest onlyErrors(boolean onlyErrors) {
+        this.onlyErrors = onlyErrors;
+        return this;
+    }
+
+    public boolean onlyManaged() {
+        return onlyManaged;
+    }
+
+    public ExplainLifecycleRequest onlyManaged(boolean onlyManaged) {
+        this.onlyManaged = onlyManaged;
+        return this;
     }
 
     @Override
@@ -37,7 +74,7 @@ public class ExplainLifecycleRequest extends ClusterInfoRequest<ExplainLifecycle
 
     @Override
     public int hashCode() {
-        return Objects.hash(Arrays.hashCode(indices()), indicesOptions());
+        return Objects.hash(Arrays.hashCode(indices()), indicesOptions(), onlyErrors, onlyManaged);
     }
 
     @Override
@@ -50,12 +87,15 @@ public class ExplainLifecycleRequest extends ClusterInfoRequest<ExplainLifecycle
         }
         ExplainLifecycleRequest other = (ExplainLifecycleRequest) obj;
         return Objects.deepEquals(indices(), other.indices()) &&
-                Objects.equals(indicesOptions(), other.indicesOptions());
+                Objects.equals(indicesOptions(), other.indicesOptions()) &&
+                Objects.equals(onlyErrors(), other.onlyErrors()) &&
+                Objects.equals(onlyManaged(), other.onlyManaged());
     }
 
     @Override
     public String toString() {
-        return "ExplainLifecycleRequest [indices()=" + Arrays.toString(indices()) + ", indicesOptions()=" + indicesOptions() + "]";
+        return "ExplainLifecycleRequest [indices()=" + Arrays.toString(indices()) + ", indicesOptions()=" + indicesOptions() +
+            ", onlyErrors()=" + onlyErrors() + ", onlyManaged()=" + onlyManaged() + "]";
     }
 
 }

+ 25 - 9
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/ExplainLifecycleRequestTests.java

@@ -26,6 +26,12 @@ public class ExplainLifecycleRequestTests extends AbstractWireSerializingTestCas
                     randomBoolean(), randomBoolean(), randomBoolean(), randomBoolean());
             request.indicesOptions(indicesOptions);
         }
+        if (randomBoolean()) {
+            request.onlyErrors(randomBoolean());
+        }
+        if (randomBoolean()) {
+            request.onlyManaged(randomBoolean());
+        }
         return request;
     }
 
@@ -33,21 +39,31 @@ public class ExplainLifecycleRequestTests extends AbstractWireSerializingTestCas
     protected ExplainLifecycleRequest mutateInstance(ExplainLifecycleRequest 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()),
+        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;
     }
 

+ 50 - 3
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;
 
@@ -829,6 +831,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<String, Map<String, Object>> explainResponse = explain(index + "*", false, false);
+            assertNotNull(explainResponse);
+            assertThat(explainResponse,
+                allOf(hasKey(goodIndex), hasKey(errorIndex), hasKey(nonexistantPolicyIndex), hasKey(unmanagedIndex)));
+
+            Map<String, Map<String, Object>> onlyManagedResponse = explain(index + "*", false, true);
+            assertNotNull(onlyManagedResponse);
+            assertThat(onlyManagedResponse, allOf(hasKey(goodIndex), hasKey(errorIndex), hasKey(nonexistantPolicyIndex)));
+            assertThat(onlyManagedResponse, not(hasKey(unmanagedIndex)));
+
+            Map<String, Map<String, Object>> 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<String, LifecycleAction> hotActions = new HashMap<>();
         hotActions.put(SetPriorityAction.NAME, new SetPriorityAction(100));
@@ -948,15 +989,21 @@ public class TimeSeriesLifecycleActionsIT extends ESRestTestCase {
     }
 
     private Map<String, Object> explainIndex(String indexName) throws IOException {
-        Request explainRequest = new Request("GET", indexName + "/_ilm/explain");
+        return explain(indexName, false, false).get(indexName);
+    }
+
+    private Map<String, Map<String, Object>> 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<String, Object> responseMap;
         try (InputStream is = response.getEntity().getContent()) {
             responseMap = XContentHelper.convertToMap(XContentType.JSON.xContent(), is, true);
         }
 
-        @SuppressWarnings("unchecked") Map<String, Object> indexResponse = ((Map<String, Map<String, Object>>) responseMap.get("indices"))
-            .get(indexName);
+        @SuppressWarnings("unchecked") Map<String, Map<String, Object>> indexResponse =
+            ((Map<String, Map<String, Object>>) responseMap.get("indices"));
         return indexResponse;
     }
 

+ 53 - 0
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

+ 4 - 0
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.
      *

+ 3 - 1
x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/action/RestExplainLifecycleAction.java

@@ -10,11 +10,11 @@ import org.elasticsearch.action.support.IndicesOptions;
 import org.elasticsearch.client.node.NodeClient;
 import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.settings.Settings;
-import org.elasticsearch.xpack.core.indexlifecycle.ExplainLifecycleRequest;
 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.ExplainLifecycleRequest;
 import org.elasticsearch.xpack.core.indexlifecycle.action.ExplainLifecycleAction;
 
 import java.io.IOException;
@@ -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);

+ 31 - 16
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<ExplainLifecycleRequest, ExplainLifecycleResponse> {
 
     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<ExplainLifecycleResponse> listener) {
-        Map<String, IndexLifecycleExplainResponse> indexReponses = new HashMap<>();
+        Map<String, IndexLifecycleExplainResponse> 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));
     }
 
 }

+ 10 - 1
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
   }