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:
parent
97177a3b4f
commit
d4b2d21339
|
@ -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
|
||||
|
|
|
@ -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() + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue