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.
This commit is contained in:
Gordon Brown 2019-07-26 11:57:38 -04:00 committed by GitHub
parent 97177a3b4f
commit d4b2d21339
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 227 additions and 32 deletions

View File

@ -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

View File

@ -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_7_4_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() + "]";
}
}

View File

@ -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,7 +39,9 @@ public class ExplainLifecycleRequestTests extends AbstractWireSerializingTestCas
protected ExplainLifecycleRequest mutateInstance(ExplainLifecycleRequest instance) throws IOException {
String[] indices = instance.indices();
IndicesOptions indicesOptions = instance.indicesOptions();
switch (between(0, 1)) {
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));
@ -42,12 +50,20 @@ public class ExplainLifecycleRequestTests extends AbstractWireSerializingTestCas
indicesOptions = randomValueOtherThan(indicesOptions, () -> IndicesOptions.fromOptions(randomBoolean(), randomBoolean(),
randomBoolean(), randomBoolean(), randomBoolean(), randomBoolean(), randomBoolean(), randomBoolean()));
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;
}

View File

@ -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<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));
@ -957,15 +998,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;
}

View File

@ -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

View File

@ -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.
*

View File

@ -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);

View File

@ -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,6 +104,9 @@ public class TransportExplainLifecycleAction
}
final IndexLifecycleExplainResponse indexResponse;
if (Strings.hasLength(policyName)) {
// 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(),
@ -112,11 +119,19 @@ public class TransportExplainLifecycleAction
stepInfoBytes,
phaseExecutionInfo);
} else {
indexResponse = null;
}
} else if (request.onlyManaged() == false && request.onlyErrors() == false) {
indexResponse = IndexLifecycleExplainResponse.newUnmanagedIndexResponse(index);
} else {
indexResponse = null;
}
indexReponses.put(indexResponse.getIndex(), indexResponse);
if (indexResponse != null) {
indexResponses.put(indexResponse.getIndex(), indexResponse);
}
listener.onResponse(new ExplainLifecycleResponse(indexReponses));
}
listener.onResponse(new ExplainLifecycleResponse(indexResponses));
}
}

View File

@ -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
}